summaryrefslogtreecommitdiffstats
path: root/src/tools/cargo/tests
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/cargo/tests')
-rw-r--r--src/tools/cargo/tests/build-std/main.rs229
-rw-r--r--src/tools/cargo/tests/internal.rs107
-rw-r--r--src/tools/cargo/tests/testsuite/advanced_env.rs35
-rw-r--r--src/tools/cargo/tests/testsuite/alt_registry.rs1496
-rw-r--r--src/tools/cargo/tests/testsuite/artifact_dep.rs2901
-rw-r--r--src/tools/cargo/tests/testsuite/bad_config.rs1514
-rw-r--r--src/tools/cargo/tests/testsuite/bad_manifest_path.rs386
-rw-r--r--src/tools/cargo/tests/testsuite/bench.rs1673
-rw-r--r--src/tools/cargo/tests/testsuite/binary_name.rs301
-rw-r--r--src/tools/cargo/tests/testsuite/build.rs6409
-rw-r--r--src/tools/cargo/tests/testsuite/build_plan.rs222
-rw-r--r--src/tools/cargo/tests/testsuite/build_script.rs5168
-rw-r--r--src/tools/cargo/tests/testsuite/build_script_env.rs303
-rw-r--r--src/tools/cargo/tests/testsuite/build_script_extra_link_arg.rs376
-rw-r--r--src/tools/cargo/tests/testsuite/cache_messages.rs488
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/add-basic.in/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/add-basic.in/src/lib.rs0
l---------src/tools/cargo/tests/testsuite/cargo_add/add_basic/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/add_basic/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/add_basic/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/add_basic/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/add_basic/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/add_multiple/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/add_multiple/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/add_multiple/out/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/add_multiple/stderr.log3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/add_multiple/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/add_normalized_name_external/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/add_normalized_name_external/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/add_normalized_name_external/out/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/add_normalized_name_external/stderr.log18
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/add_normalized_name_external/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/build/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/build/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/build/out/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/build/stderr.log3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/build/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/in/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/in/dependency/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/in/dependency/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/in/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/mod.rs28
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/out/Cargo.toml12
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/out/dependency/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/stderr.log4
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/change_rename_target/in/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/change_rename_target/in/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/change_rename_target/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/change_rename_target/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/change_rename_target/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/change_rename_target/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/cyclic_features/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/cyclic_features/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/cyclic_features/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/cyclic_features/stderr.log5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/cyclic_features/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/default_features/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/default_features/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/default_features/out/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/default_features/stderr.log3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/default_features/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/deprecated_default_features/in/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/deprecated_default_features/in/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/deprecated_default_features/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/deprecated_default_features/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/deprecated_default_features/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/deprecated_default_features/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/deprecated_section/in/Cargo.toml11
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/deprecated_section/in/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/deprecated_section/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/deprecated_section/out/Cargo.toml11
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/deprecated_section/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/deprecated_section/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/in/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/in/dependency/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/in/dependency/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/in/primary/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/in/primary/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/out/dependency/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/out/primary/Cargo.toml6
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/in/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/in/dependency/Cargo.toml14
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/in/dependency/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/in/primary/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/in/primary/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/out/dependency/Cargo.toml14
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/out/primary/Cargo.toml6
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/stderr.log10
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/in/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/in/dependency/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/in/dependency/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/in/primary/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/in/primary/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/out/dependency/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/out/primary/Cargo.toml6
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/dev/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/dev/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/dev/out/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/dev/stderr.log3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/dev/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/dev_build_conflict/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/dev_build_conflict/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/dev_build_conflict/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/dev_build_conflict/stderr.log7
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/dev_build_conflict/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/in/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/in/dependency/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/in/dependency/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/in/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/out/Cargo.toml12
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/out/dependency/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/stderr.log4
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/dry_run/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/dry_run/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/dry_run/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/dry_run/stderr.log3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/dry_run/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/features/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/features/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/features/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/features/stderr.log7
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/features/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/features_empty/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/features_empty/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/features_empty/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/features_empty/stderr.log7
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/features_empty/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/features_multiple_occurrences/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/features_multiple_occurrences/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/features_multiple_occurrences/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/features_multiple_occurrences/stderr.log7
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/features_multiple_occurrences/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/features_preserve/in/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/features_preserve/in/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/features_preserve/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/features_preserve/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/features_preserve/stderr.log7
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/features_preserve/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/features_spaced_values/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/features_spaced_values/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/features_spaced_values/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/features_spaced_values/stderr.log7
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/features_spaced_values/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/features_unknown/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/features_unknown/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/features_unknown/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/features_unknown/stderr.log5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/features_unknown/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/features_unknown_no_features/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/features_unknown_no_features/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/features_unknown_no_features/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/features_unknown_no_features/stderr.log4
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/features_unknown_no_features/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/git/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git/mod.rs34
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git/stderr.log3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/git_branch/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_branch/mod.rs37
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_branch/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_branch/stderr.log3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_branch/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/git_conflicts_namever/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_conflicts_namever/mod.rs29
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_conflicts_namever/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_conflicts_namever/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_conflicts_namever/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/git_dev/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_dev/mod.rs34
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_dev/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_dev/stderr.log3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_dev/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name/mod.rs34
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name/stderr.log4
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name_multiple/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name_multiple/mod.rs74
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name_multiple/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name_multiple/stderr.log5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name_multiple/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/git_multiple_names/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_multiple_names/mod.rs39
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_multiple_names/out/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_multiple_names/stderr.log4
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_multiple_names/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/git_normalized_name/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_normalized_name/mod.rs34
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_normalized_name/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_normalized_name/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_normalized_name/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_registry/in/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_registry/in/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_registry/mod.rs40
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_registry/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_registry/stderr.log6
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_registry/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/git_rev/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_rev/mod.rs36
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_rev/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_rev/stderr.log3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_rev/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/git_tag/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_tag/mod.rs36
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_tag/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_tag/stderr.log3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/git_tag/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/infer_prerelease/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/infer_prerelease/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/infer_prerelease/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/infer_prerelease/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/infer_prerelease/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/invalid_arg/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_arg/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_arg/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_arg/stderr.log9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_arg/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/invalid_git_external/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_git_external/mod.rs28
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_git_external/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_git_external/stderr.log12
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_git_external/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/invalid_git_name/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_git_name/mod.rs34
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_git_name/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_git_name/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_git_name/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/in/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/in/dependency/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/in/dependency/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/in/primary/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/in/primary/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/mod.rs23
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/out/dependency/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/out/primary/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/in/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/in/dependency/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/in/dependency/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/in/primary/Cargo.toml6
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/in/primary/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/mod.rs23
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/out/dependency/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/out/primary/Cargo.toml6
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/in/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/in/dependency-alt/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/in/dependency-alt/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/in/dependency/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/in/dependency/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/in/primary/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/in/primary/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/mod.rs23
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/out/dependency-alt/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/out/dependency/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/out/primary/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_manifest/in/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_manifest/in/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_manifest/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_manifest/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_manifest/stderr.log12
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_manifest/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/invalid_name_external/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_name_external/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_name_external/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_name_external/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_name_external/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/invalid_path/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_path/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_path/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_path/stderr.log10
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_path/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/in/dependency/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/in/dependency/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/in/primary/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/in/primary/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/out/dependency/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/out/primary/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/invalid_path_self/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_path_self/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_path_self/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_path_self/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_path_self/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/invalid_target_empty/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_target_empty/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_target_empty/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_target_empty/stderr.log3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_target_empty/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/invalid_vers/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_vers/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_vers/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_vers/stderr.log4
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/invalid_vers/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/list_features/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/list_features/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/list_features/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/list_features/stderr.log7
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/list_features/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/list_features_path/in/Cargo.toml2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/list_features_path/in/dependency/Cargo.toml13
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/list_features_path/in/dependency/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/list_features_path/in/optional/Cargo.toml6
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/list_features_path/in/optional/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/list_features_path/in/primary/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/list_features_path/in/primary/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/list_features_path/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/list_features_path/out/Cargo.toml2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/list_features_path/out/dependency/Cargo.toml13
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/list_features_path/out/primary/Cargo.toml6
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/list_features_path/stderr.log7
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/list_features_path/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/in/Cargo.toml2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/in/dependency/Cargo.toml13
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/in/dependency/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/in/optional/Cargo.toml6
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/in/optional/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/in/primary/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/in/primary/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/mod.rs30
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/out/Cargo.toml2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/out/dependency/Cargo.toml13
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/out/primary/Cargo.toml6
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/stderr.log7
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/locked_changed/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/locked_changed/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/locked_changed/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/locked_changed/stderr.log3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/locked_changed/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/locked_unchanged/in/Cargo.lock16
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/locked_unchanged/in/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/locked_unchanged/in/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/locked_unchanged/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/locked_unchanged/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/locked_unchanged/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/locked_unchanged/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/lockfile_updated/in/Cargo.lock17
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/lockfile_updated/in/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/lockfile_updated/in/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/lockfile_updated/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/lockfile_updated/out/Cargo.lock23
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/lockfile_updated/out/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/lockfile_updated/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/lockfile_updated/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/in/Cargo.toml2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/in/dependency/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/in/dependency/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/in/primary/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/in/primary/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/mod.rs31
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/out/Cargo.toml2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/out/dependency/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/out/primary/Cargo.toml6
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/in/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/in/dependency/Cargo.toml14
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/in/dependency/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/in/primary/Cargo.toml6
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/in/primary/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/mod.rs23
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/out/dependency/Cargo.toml14
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/out/primary/Cargo.toml6
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/stderr.log10
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/mod.rs203
l---------src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_features/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_features/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_features/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_features/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_features/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_rename/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_rename/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_rename/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_rename/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_rename/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/namever/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/namever/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/namever/out/Cargo.toml10
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/namever/stderr.log4
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/namever/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/no_args/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/no_args/mod.rs24
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/no_args/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/no_args/stderr.log8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/no_args/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/no_default_features/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/no_default_features/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/no_default_features/out/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/no_default_features/stderr.log3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/no_default_features/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/no_optional/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/no_optional/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/no_optional/out/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/no_optional/stderr.log3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/no_optional/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/offline_empty_cache/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/offline_empty_cache/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/offline_empty_cache/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/offline_empty_cache/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/offline_empty_cache/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/optional/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/optional/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/optional/out/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/optional/stderr.log3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/optional/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features/in/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features/in/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features/out/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features/stderr.log3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features_with_no_default_features/in/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features_with_no_default_features/in/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features_with_no_default_features/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features_with_no_default_features/out/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features_with_no_default_features/stderr.log3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features_with_no_default_features/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_features/in/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_features/in/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_features/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_features/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_features/stderr.log7
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_features/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/in/dependency/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/in/dependency/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/in/primary/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/in/primary/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/out/dependency/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/out/primary/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/in/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/in/dependency/Cargo.toml6
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/in/dependency/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/in/primary/Cargo.toml6
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/in/primary/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/mod.rs23
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/out/dependency/Cargo.toml6
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/out/primary/Cargo.toml6
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/stderr.log3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/in/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/in/dependency/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/in/dependency/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/in/primary/Cargo.toml6
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/in/primary/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/out/dependency/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/out/primary/Cargo.toml6
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/in/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/in/dependency/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/in/dependency/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/in/primary/Cargo.toml6
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/in/primary/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/out/dependency/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/out/primary/Cargo.toml6
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inline_features/in/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inline_features/in/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inline_features/mod.rs27
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inline_features/out/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inline_features/stderr.log8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_inline_features/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/in/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/in/dependency/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/in/dependency/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/in/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/out/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/out/dependency/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/stderr.log4
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/in/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/in/dependency/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/in/dependency/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/in/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/out/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/out/dependency/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/stderr.log4
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features/in/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features/in/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features/out/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features/stderr.log3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features_with_default_features/in/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features_with_default_features/in/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features_with_default_features/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features_with_default_features/out/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features_with_default_features/stderr.log3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features_with_default_features/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional/in/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional/in/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional/out/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional/stderr.log3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional_with_optional/in/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional_with_optional/in/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional_with_optional/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional_with_optional/out/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional_with_optional/stderr.log3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional_with_optional/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional/in/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional/in/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional/out/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional/stderr.log3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional_with_no_optional/in/Cargo.toml13
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional_with_no_optional/in/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional_with_no_optional/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional_with_no_optional/out/Cargo.toml13
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional_with_no_optional/stderr.log8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional_with_no_optional/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/in/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/in/dependency/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/in/dependency/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/in/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/out/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/out/dependency/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/stderr.log4
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/in/dependency/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/in/dependency/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/in/primary/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/in/primary/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/out/dependency/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/out/primary/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_preserves_inline_table/in/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_preserves_inline_table/in/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_preserves_inline_table/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_preserves_inline_table/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_preserves_inline_table/stderr.log7
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_preserves_inline_table/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_no_rename/in/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_no_rename/in/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_no_rename/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_no_rename/out/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_no_rename/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_no_rename/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename/in/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename/in/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename/out/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename_noop/in/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename_noop/in/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename_noop/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename_noop/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename_noop/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename_noop/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_git/in/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_git/in/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_git/mod.rs34
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_git/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_git/stderr.log3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_git/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/in/dependency/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/in/dependency/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/in/primary/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/in/primary/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/out/dependency/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/out/primary/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_with_rename/in/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_with_rename/in/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_with_rename/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_with_rename/out/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_with_rename/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_with_rename/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/in/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/in/dependency/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/in/dependency/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/in/primary/Cargo.toml6
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/in/primary/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/out/dependency/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/out/primary/Cargo.toml6
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/in/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/in/dependency/Cargo.toml14
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/in/dependency/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/in/primary/Cargo.toml6
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/in/primary/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/out/dependency/Cargo.toml14
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/out/primary/Cargo.toml6
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/stderr.log10
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path/in/dependency/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path/in/dependency/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path/in/primary/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path/in/primary/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path/out/dependency/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path/out/primary/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_dev/in/dependency/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_dev/in/dependency/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_dev/in/primary/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_dev/in/primary/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_dev/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_dev/out/dependency/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_dev/out/primary/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_dev/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_dev/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/in/dependency/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/in/dependency/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/in/primary/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/in/primary/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/out/dependency/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/out/primary/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/in/Cargo.toml2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/in/dependency/Cargo.toml14
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/in/dependency/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/in/optional/Cargo.toml7
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/in/optional/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/in/primary/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/in/primary/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/out/Cargo.toml2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/out/dependency/Cargo.toml14
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/out/primary/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/in/dependency/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/in/dependency/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/in/primary/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/in/primary/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/out/dependency/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/out/primary/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/preserve_sorted/in/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/preserve_sorted/in/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/preserve_sorted/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/preserve_sorted/out/Cargo.toml10
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/preserve_sorted/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/preserve_sorted/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/preserve_unsorted/in/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/preserve_unsorted/in/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/preserve_unsorted/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/preserve_unsorted/out/Cargo.toml10
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/preserve_unsorted/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/preserve_unsorted/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/quiet/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/quiet/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/quiet/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/quiet/stderr.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/quiet/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/registry/in/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/registry/in/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/registry/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/registry/out/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/registry/stderr.log3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/registry/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/rename/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/rename/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/rename/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/rename/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/rename/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/require_weak/in/Cargo.toml11
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/require_weak/in/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/require_weak/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/require_weak/out/Cargo.toml11
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/require_weak/stderr.log7
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/require_weak/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/sorted_table_with_dotted_item/in/Cargo.toml13
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/sorted_table_with_dotted_item/in/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/sorted_table_with_dotted_item/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/sorted_table_with_dotted_item/out/Cargo.toml14
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/sorted_table_with_dotted_item/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/sorted_table_with_dotted_item/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/target/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/target/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/target/out/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/target/stderr.log3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/target/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/target_cfg/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/target_cfg/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/target_cfg/out/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/target_cfg/stderr.log3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/target_cfg/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/in/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/in/dependency/Cargo.toml20
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/in/dependency/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/in/primary/Cargo.toml6
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/in/primary/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/mod.rs23
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/out/dependency/Cargo.toml20
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/out/primary/Cargo.toml6
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/stderr.log7
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_add/vers/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/vers/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/vers/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/vers/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/vers/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/workspace_name/in/Cargo.toml2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/workspace_name/in/dependency/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/workspace_name/in/dependency/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/workspace_name/in/primary/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/workspace_name/in/primary/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/workspace_name/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/workspace_name/out/Cargo.toml2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/workspace_name/out/dependency/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/workspace_name/out/primary/Cargo.toml6
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/workspace_name/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/workspace_name/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/workspace_path/in/Cargo.toml2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/workspace_path/in/dependency/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/workspace_path/in/dependency/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/workspace_path/in/primary/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/workspace_path/in/primary/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/workspace_path/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/workspace_path/out/Cargo.toml2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/workspace_path/out/dependency/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/workspace_path/out/primary/Cargo.toml6
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/workspace_path/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/workspace_path/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/in/Cargo.toml2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/in/dependency/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/in/dependency/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/in/primary/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/in/primary/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/out/Cargo.toml2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/out/dependency/Cargo.toml3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/out/primary/Cargo.toml6
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_alias_config.rs434
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_command.rs535
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_config.rs520
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_env_config.rs181
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_features.rs714
l---------src/tools/cargo/tests/testsuite/cargo_remove/avoid_empty_tables/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/avoid_empty_tables/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/avoid_empty_tables/out/Cargo.toml23
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/avoid_empty_tables/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/avoid_empty_tables/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_remove/build/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/build/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/build/out/Cargo.toml21
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/build/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/build/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_remove/dev/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/dev/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/dev/out/Cargo.toml23
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/dev/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/dev/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_remove/dry_run/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/dry_run/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/dry_run/out/Cargo.toml24
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/dry_run/out/src/lib.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/dry_run/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/dry_run/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/gc_patch/mod.rs72
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/gc_patch/out/Cargo.toml9
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/gc_patch/out/src/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/gc_patch/stderr.log3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/gc_patch/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/gc_profile/in/Cargo.toml36
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/gc_profile/in/src/lib.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/gc_profile/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/gc_profile/out/Cargo.toml32
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/gc_profile/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/gc_profile/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/in/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/in/my-package/Cargo.toml26
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/in/my-package/src/main.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/out/Cargo.toml2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/out/my-package/Cargo.toml25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/out/my-package/src/main.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_remove/invalid_arg/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_arg/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_arg/out/Cargo.toml24
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_arg/stderr.log7
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_arg/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_remove/invalid_dep/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_dep/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_dep/out/Cargo.toml24
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_dep/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_dep/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/out/dep-a/Cargo.toml23
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/out/dep-a/src/lib.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/out/dep-b/Cargo.toml23
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/out/dep-b/src/lib.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/out/dep-a/Cargo.toml23
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/out/dep-a/src/lib.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/out/dep-b/Cargo.toml23
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/out/dep-b/src/lib.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_remove/invalid_section/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_section/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_section/out/Cargo.toml24
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_section/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_section/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_remove/invalid_section_dep/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_section_dep/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_section_dep/out/Cargo.toml24
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_section_dep/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_section_dep/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_remove/invalid_target/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_target/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_target/out/Cargo.toml33
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_target/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_target/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_remove/invalid_target_dep/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_target_dep/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_target_dep/out/Cargo.toml33
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_target_dep/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/invalid_target_dep/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/mod.rs88
l---------src/tools/cargo/tests/testsuite/cargo_remove/multiple_deps/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/multiple_deps/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/multiple_deps/out/Cargo.toml22
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/multiple_deps/stderr.log3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/multiple_deps/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_remove/multiple_dev/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/multiple_dev/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/multiple_dev/out/Cargo.toml20
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/multiple_dev/stderr.log3
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/multiple_dev/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_remove/no_arg/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/no_arg/mod.rs24
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/no_arg/out/Cargo.toml24
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/no_arg/stderr.log6
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/no_arg/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_remove/offline/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/offline/mod.rs32
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/offline/out/Cargo.toml23
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/offline/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/offline/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature/out/Cargo.toml23
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_remove/optional_feature/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/optional_feature/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/optional_feature/out/Cargo.toml23
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/optional_feature/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/optional_feature/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_remove/package/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/package/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/package/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/package/out/dep-a/Cargo.toml22
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/package/out/dep-a/src/lib.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/package/out/dep-b/Cargo.toml23
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/package/out/dep-b/src/lib.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/package/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/package/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/remove-basic.in/Cargo.toml24
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/remove-basic.in/src/lib.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/remove-package.in/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/remove-package.in/dep-a/Cargo.toml23
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/remove-package.in/dep-a/src/lib.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/remove-package.in/dep-b/Cargo.toml23
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/remove-package.in/dep-b/src/lib.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/remove-target.in/Cargo.toml33
l---------src/tools/cargo/tests/testsuite/cargo_remove/remove_basic/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/remove_basic/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/remove_basic/out/Cargo.toml23
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/remove_basic/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/remove_basic/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_remove/target/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/target/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/target/out/Cargo.toml30
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/target/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/target/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_remove/target_build/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/target_build/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/target_build/out/Cargo.toml30
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/target_build/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/target_build/stdout.log0
l---------src/tools/cargo/tests/testsuite/cargo_remove/target_dev/in1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/target_dev/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/target_dev/out/Cargo.toml30
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/target_dev/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/target_dev/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/in/Cargo.lock58
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/in/Cargo.toml24
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/in/src/main.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/out/Cargo.lock51
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/out/Cargo.toml23
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/out/src/main.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/workspace/in/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/workspace/in/my-package/Cargo.toml24
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/workspace/in/my-package/src/main.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/workspace/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/workspace/out/Cargo.toml2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/workspace/out/my-package/Cargo.toml21
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/workspace/out/my-package/src/main.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/workspace/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/workspace/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/in/Cargo.toml30
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/in/my-member/Cargo.toml6
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/in/my-member/src/main.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/out/Cargo.toml24
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/out/my-member/Cargo.toml6
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/out/my-member/src/main.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/in/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/in/my-other-package/Cargo.toml22
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/in/my-other-package/src/main.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/in/my-package/Cargo.toml24
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/in/my-package/src/main.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/mod.rs25
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/out/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/out/my-other-package/Cargo.toml22
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/out/my-other-package/src/main.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/out/my-package/Cargo.toml21
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/out/my-package/src/main.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_targets.rs68
-rw-r--r--src/tools/cargo/tests/testsuite/cfg.rs515
-rw-r--r--src/tools/cargo/tests/testsuite/check.rs1521
-rw-r--r--src/tools/cargo/tests/testsuite/check_cfg.rs588
-rw-r--r--src/tools/cargo/tests/testsuite/clean.rs675
-rw-r--r--src/tools/cargo/tests/testsuite/collisions.rs550
-rw-r--r--src/tools/cargo/tests/testsuite/concurrent.rs507
-rw-r--r--src/tools/cargo/tests/testsuite/config.rs1596
-rw-r--r--src/tools/cargo/tests/testsuite/config_cli.rs564
-rw-r--r--src/tools/cargo/tests/testsuite/config_include.rs285
-rw-r--r--src/tools/cargo/tests/testsuite/corrupt_git.rs159
-rw-r--r--src/tools/cargo/tests/testsuite/credential_process.rs504
-rw-r--r--src/tools/cargo/tests/testsuite/cross_compile.rs1342
-rw-r--r--src/tools/cargo/tests/testsuite/cross_publish.rs122
-rw-r--r--src/tools/cargo/tests/testsuite/custom_target.rs250
-rw-r--r--src/tools/cargo/tests/testsuite/death.rs101
-rw-r--r--src/tools/cargo/tests/testsuite/dep_info.rs600
-rw-r--r--src/tools/cargo/tests/testsuite/direct_minimal_versions.rs236
-rw-r--r--src/tools/cargo/tests/testsuite/directory.rs774
-rw-r--r--src/tools/cargo/tests/testsuite/doc.rs2503
-rw-r--r--src/tools/cargo/tests/testsuite/docscrape.rs637
-rw-r--r--src/tools/cargo/tests/testsuite/edition.rs124
-rw-r--r--src/tools/cargo/tests/testsuite/error.rs19
-rw-r--r--src/tools/cargo/tests/testsuite/features.rs2084
-rw-r--r--src/tools/cargo/tests/testsuite/features2.rs2553
-rw-r--r--src/tools/cargo/tests/testsuite/features_namespaced.rs1215
-rw-r--r--src/tools/cargo/tests/testsuite/fetch.rs135
-rw-r--r--src/tools/cargo/tests/testsuite/fix.rs1855
-rw-r--r--src/tools/cargo/tests/testsuite/freshness.rs2816
-rw-r--r--src/tools/cargo/tests/testsuite/future_incompat_report.rs391
-rw-r--r--src/tools/cargo/tests/testsuite/generate_lockfile.rs230
-rw-r--r--src/tools/cargo/tests/testsuite/git.rs3702
-rw-r--r--src/tools/cargo/tests/testsuite/git_auth.rs437
-rw-r--r--src/tools/cargo/tests/testsuite/git_gc.rs117
-rw-r--r--src/tools/cargo/tests/testsuite/glob_targets.rs539
-rw-r--r--src/tools/cargo/tests/testsuite/help.rs219
-rw-r--r--src/tools/cargo/tests/testsuite/https.rs152
-rw-r--r--src/tools/cargo/tests/testsuite/inheritable_workspace_fields.rs1717
l---------src/tools/cargo/tests/testsuite/init/auto_git/in1
-rw-r--r--src/tools/cargo/tests/testsuite/init/auto_git/mod.rs22
-rw-r--r--src/tools/cargo/tests/testsuite/init/auto_git/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/init/auto_git/out/src/lib.rs14
-rw-r--r--src/tools/cargo/tests/testsuite/init/auto_git/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/init/auto_git/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit/in/src/main.rs4
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit/mod.rs21
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit/out/src/main.rs4
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit_nosrc/in/main.rs4
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit_nosrc/mod.rs22
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit_nosrc/out/Cargo.toml12
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit_nosrc/out/main.rs4
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit_nosrc/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit_nosrc/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit/in/src/main.rs4
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit/mod.rs21
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit/out/src/main.rs4
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namenosrc/in/case.rs4
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namenosrc/mod.rs22
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namenosrc/out/Cargo.toml12
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namenosrc/out/case.rs4
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namenosrc/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namenosrc/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namesrc/in/src/case.rs4
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namesrc/mod.rs22
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namesrc/out/Cargo.toml12
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namesrc/out/src/case.rs4
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namesrc/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namesrc/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_nosrc/in/main.rs4
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_nosrc/mod.rs22
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_nosrc/out/Cargo.toml12
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_nosrc/out/main.rs4
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_nosrc/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_nosrc/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/init/both_lib_and_bin/mod.rs19
-rw-r--r--src/tools/cargo/tests/testsuite/init/both_lib_and_bin/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/init/both_lib_and_bin/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/init/cant_create_library_when_both_binlib_present/in/case.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/init/cant_create_library_when_both_binlib_present/in/lib.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/init/cant_create_library_when_both_binlib_present/mod.rs18
-rw-r--r--src/tools/cargo/tests/testsuite/init/cant_create_library_when_both_binlib_present/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/init/cant_create_library_when_both_binlib_present/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/init/confused_by_multiple_lib_files/in/lib.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/init/confused_by_multiple_lib_files/in/src/lib.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/init/confused_by_multiple_lib_files/mod.rs22
-rw-r--r--src/tools/cargo/tests/testsuite/init/confused_by_multiple_lib_files/out/lib.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/init/confused_by_multiple_lib_files/out/src/lib.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/init/confused_by_multiple_lib_files/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/init/confused_by_multiple_lib_files/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/init/creates_binary_when_both_binlib_present/in/case.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/init/creates_binary_when_both_binlib_present/in/lib.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/init/creates_binary_when_both_binlib_present/mod.rs21
-rw-r--r--src/tools/cargo/tests/testsuite/init/creates_binary_when_both_binlib_present/out/Cargo.toml16
-rw-r--r--src/tools/cargo/tests/testsuite/init/creates_binary_when_both_binlib_present/out/case.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/init/creates_binary_when_both_binlib_present/out/lib.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/init/creates_binary_when_both_binlib_present/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/init/creates_binary_when_both_binlib_present/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/init/creates_binary_when_instructed_and_has_lib_file/in/case.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/init/creates_binary_when_instructed_and_has_lib_file/mod.rs21
-rw-r--r--src/tools/cargo/tests/testsuite/init/creates_binary_when_instructed_and_has_lib_file/out/Cargo.toml12
-rw-r--r--src/tools/cargo/tests/testsuite/init/creates_binary_when_instructed_and_has_lib_file/out/case.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/init/creates_binary_when_instructed_and_has_lib_file/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/init/creates_binary_when_instructed_and_has_lib_file/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/init/creates_library_when_instructed_and_has_bin_file/in/case.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/init/creates_library_when_instructed_and_has_bin_file/mod.rs21
-rw-r--r--src/tools/cargo/tests/testsuite/init/creates_library_when_instructed_and_has_bin_file/out/Cargo.toml12
-rw-r--r--src/tools/cargo/tests/testsuite/init/creates_library_when_instructed_and_has_bin_file/out/case.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/init/creates_library_when_instructed_and_has_bin_file/stderr.log2
-rw-r--r--src/tools/cargo/tests/testsuite/init/creates_library_when_instructed_and_has_bin_file/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/init/empty_dir/.keep0
-rw-r--r--src/tools/cargo/tests/testsuite/init/empty_dir/mod.rs7
l---------src/tools/cargo/tests/testsuite/init/explicit_bin_with_git/in1
-rw-r--r--src/tools/cargo/tests/testsuite/init/explicit_bin_with_git/mod.rs21
-rw-r--r--src/tools/cargo/tests/testsuite/init/explicit_bin_with_git/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/init/explicit_bin_with_git/out/src/main.rs3
-rw-r--r--src/tools/cargo/tests/testsuite/init/explicit_bin_with_git/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/init/explicit_bin_with_git/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/init/formats_source/in/rustfmt.toml1
-rw-r--r--src/tools/cargo/tests/testsuite/init/formats_source/mod.rs29
-rw-r--r--src/tools/cargo/tests/testsuite/init/formats_source/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/init/formats_source/out/rustfmt.toml1
-rw-r--r--src/tools/cargo/tests/testsuite/init/formats_source/out/src/lib.rs14
-rw-r--r--src/tools/cargo/tests/testsuite/init/formats_source/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/init/formats_source/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/init/fossil_autodetect/in/.fossil/.keep0
-rw-r--r--src/tools/cargo/tests/testsuite/init/fossil_autodetect/mod.rs22
-rw-r--r--src/tools/cargo/tests/testsuite/init/fossil_autodetect/out/.fossil-settings/clean-glob2
-rw-r--r--src/tools/cargo/tests/testsuite/init/fossil_autodetect/out/.fossil-settings/ignore-glob2
-rw-r--r--src/tools/cargo/tests/testsuite/init/fossil_autodetect/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/init/fossil_autodetect/out/src/lib.rs14
-rw-r--r--src/tools/cargo/tests/testsuite/init/fossil_autodetect/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/init/fossil_autodetect/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/init/git_autodetect/mod.rs24
-rw-r--r--src/tools/cargo/tests/testsuite/init/git_autodetect/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/init/git_autodetect/out/src/lib.rs14
-rw-r--r--src/tools/cargo/tests/testsuite/init/git_autodetect/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/init/git_autodetect/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/init/git_ignore_exists_no_conflicting_entries/mod.rs22
-rw-r--r--src/tools/cargo/tests/testsuite/init/git_ignore_exists_no_conflicting_entries/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/init/git_ignore_exists_no_conflicting_entries/out/src/lib.rs14
-rw-r--r--src/tools/cargo/tests/testsuite/init/git_ignore_exists_no_conflicting_entries/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/init/git_ignore_exists_no_conflicting_entries/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/init/ignores_failure_to_format_source/in/rustfmt.toml1
-rw-r--r--src/tools/cargo/tests/testsuite/init/ignores_failure_to_format_source/mod.rs22
-rw-r--r--src/tools/cargo/tests/testsuite/init/ignores_failure_to_format_source/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/init/ignores_failure_to_format_source/out/rustfmt.toml1
-rw-r--r--src/tools/cargo/tests/testsuite/init/ignores_failure_to_format_source/out/src/lib.rs14
-rw-r--r--src/tools/cargo/tests/testsuite/init/ignores_failure_to_format_source/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/init/ignores_failure_to_format_source/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/init/inferred_bin_with_git/in/main.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/init/inferred_bin_with_git/mod.rs21
-rw-r--r--src/tools/cargo/tests/testsuite/init/inferred_bin_with_git/out/Cargo.toml12
-rw-r--r--src/tools/cargo/tests/testsuite/init/inferred_bin_with_git/out/main.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/init/inferred_bin_with_git/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/init/inferred_bin_with_git/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/init/inferred_lib_with_git/in/lib.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/init/inferred_lib_with_git/mod.rs21
-rw-r--r--src/tools/cargo/tests/testsuite/init/inferred_lib_with_git/out/Cargo.toml12
-rw-r--r--src/tools/cargo/tests/testsuite/init/inferred_lib_with_git/out/lib.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/init/inferred_lib_with_git/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/init/inferred_lib_with_git/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/init/invalid_dir_name/mod.rs21
-rw-r--r--src/tools/cargo/tests/testsuite/init/invalid_dir_name/stderr.log8
-rw-r--r--src/tools/cargo/tests/testsuite/init/invalid_dir_name/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/init/lib_already_exists_nosrc/in/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/init/lib_already_exists_nosrc/mod.rs22
-rw-r--r--src/tools/cargo/tests/testsuite/init/lib_already_exists_nosrc/out/Cargo.toml12
-rw-r--r--src/tools/cargo/tests/testsuite/init/lib_already_exists_nosrc/out/lib.rs0
-rw-r--r--src/tools/cargo/tests/testsuite/init/lib_already_exists_nosrc/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/init/lib_already_exists_nosrc/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/init/lib_already_exists_src/in/src/lib.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/init/lib_already_exists_src/mod.rs22
-rw-r--r--src/tools/cargo/tests/testsuite/init/lib_already_exists_src/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/init/lib_already_exists_src/out/src/lib.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/init/lib_already_exists_src/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/init/lib_already_exists_src/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/init/mercurial_autodetect/mod.rs22
-rw-r--r--src/tools/cargo/tests/testsuite/init/mercurial_autodetect/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/init/mercurial_autodetect/out/src/lib.rs14
-rw-r--r--src/tools/cargo/tests/testsuite/init/mercurial_autodetect/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/init/mercurial_autodetect/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/init/mod.rs42
-rw-r--r--src/tools/cargo/tests/testsuite/init/multibin_project_name_clash/in/case.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/init/multibin_project_name_clash/in/main.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/init/multibin_project_name_clash/mod.rs22
-rw-r--r--src/tools/cargo/tests/testsuite/init/multibin_project_name_clash/out/case.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/init/multibin_project_name_clash/out/main.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/init/multibin_project_name_clash/stderr.log4
-rw-r--r--src/tools/cargo/tests/testsuite/init/multibin_project_name_clash/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/init/no_filename/mod.rs16
-rw-r--r--src/tools/cargo/tests/testsuite/init/no_filename/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/init/no_filename/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/init/path_contains_separator/in/.keep0
-rw-r--r--src/tools/cargo/tests/testsuite/init/path_contains_separator/mod.rs26
-rw-r--r--src/tools/cargo/tests/testsuite/init/path_contains_separator/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/init/path_contains_separator/out/src/main.rs3
-rw-r--r--src/tools/cargo/tests/testsuite/init/path_contains_separator/stderr.log3
-rw-r--r--src/tools/cargo/tests/testsuite/init/path_contains_separator/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/init/pijul_autodetect/in/.pijul/.keep0
-rw-r--r--src/tools/cargo/tests/testsuite/init/pijul_autodetect/mod.rs22
-rw-r--r--src/tools/cargo/tests/testsuite/init/pijul_autodetect/out/.ignore2
-rw-r--r--src/tools/cargo/tests/testsuite/init/pijul_autodetect/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/init/pijul_autodetect/out/src/lib.rs14
-rw-r--r--src/tools/cargo/tests/testsuite/init/pijul_autodetect/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/init/pijul_autodetect/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/init/reserved_name/mod.rs21
-rw-r--r--src/tools/cargo/tests/testsuite/init/reserved_name/stderr.log8
-rw-r--r--src/tools/cargo/tests/testsuite/init/reserved_name/stdout.log0
l---------src/tools/cargo/tests/testsuite/init/simple_bin/in1
-rw-r--r--src/tools/cargo/tests/testsuite/init/simple_bin/mod.rs29
-rw-r--r--src/tools/cargo/tests/testsuite/init/simple_bin/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/init/simple_bin/out/src/main.rs3
-rw-r--r--src/tools/cargo/tests/testsuite/init/simple_bin/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/init/simple_bin/stdout.log0
l---------src/tools/cargo/tests/testsuite/init/simple_git/in1
-rw-r--r--src/tools/cargo/tests/testsuite/init/simple_git/mod.rs22
-rw-r--r--src/tools/cargo/tests/testsuite/init/simple_git/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/init/simple_git/out/src/lib.rs14
-rw-r--r--src/tools/cargo/tests/testsuite/init/simple_git/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/init/simple_git/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/init/simple_git_ignore_exists/mod.rs28
-rw-r--r--src/tools/cargo/tests/testsuite/init/simple_git_ignore_exists/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/init/simple_git_ignore_exists/out/src/lib.rs14
-rw-r--r--src/tools/cargo/tests/testsuite/init/simple_git_ignore_exists/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/init/simple_git_ignore_exists/stdout.log0
l---------src/tools/cargo/tests/testsuite/init/simple_hg/in1
-rw-r--r--src/tools/cargo/tests/testsuite/init/simple_hg/mod.rs22
-rw-r--r--src/tools/cargo/tests/testsuite/init/simple_hg/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/init/simple_hg/out/src/lib.rs14
-rw-r--r--src/tools/cargo/tests/testsuite/init/simple_hg/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/init/simple_hg/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/init/simple_hg_ignore_exists/mod.rs22
-rw-r--r--src/tools/cargo/tests/testsuite/init/simple_hg_ignore_exists/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/init/simple_hg_ignore_exists/out/src/lib.rs14
-rw-r--r--src/tools/cargo/tests/testsuite/init/simple_hg_ignore_exists/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/init/simple_hg_ignore_exists/stdout.log0
l---------src/tools/cargo/tests/testsuite/init/simple_lib/in1
-rw-r--r--src/tools/cargo/tests/testsuite/init/simple_lib/mod.rs29
-rw-r--r--src/tools/cargo/tests/testsuite/init/simple_lib/out/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/init/simple_lib/out/src/lib.rs14
-rw-r--r--src/tools/cargo/tests/testsuite/init/simple_lib/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/init/simple_lib/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/init/unknown_flags/mod.rs15
-rw-r--r--src/tools/cargo/tests/testsuite/init/unknown_flags/stderr.log7
-rw-r--r--src/tools/cargo/tests/testsuite/init/unknown_flags/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/init/with_argument/in/foo/.keep0
-rw-r--r--src/tools/cargo/tests/testsuite/init/with_argument/mod.rs21
-rw-r--r--src/tools/cargo/tests/testsuite/init/with_argument/out/foo/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/init/with_argument/out/foo/src/main.rs3
-rw-r--r--src/tools/cargo/tests/testsuite/init/with_argument/stderr.log1
-rw-r--r--src/tools/cargo/tests/testsuite/init/with_argument/stdout.log0
-rw-r--r--src/tools/cargo/tests/testsuite/install.rs2289
-rw-r--r--src/tools/cargo/tests/testsuite/install_upgrade.rs862
-rw-r--r--src/tools/cargo/tests/testsuite/jobserver.rs250
-rw-r--r--src/tools/cargo/tests/testsuite/list_availables.rs232
-rw-r--r--src/tools/cargo/tests/testsuite/local_registry.rs528
-rw-r--r--src/tools/cargo/tests/testsuite/locate_project.rs76
-rw-r--r--src/tools/cargo/tests/testsuite/lockfile_compat.rs890
-rw-r--r--src/tools/cargo/tests/testsuite/login.rs404
-rw-r--r--src/tools/cargo/tests/testsuite/logout.rs104
-rw-r--r--src/tools/cargo/tests/testsuite/lto.rs850
-rw-r--r--src/tools/cargo/tests/testsuite/main.rs146
-rw-r--r--src/tools/cargo/tests/testsuite/member_discovery.rs44
-rw-r--r--src/tools/cargo/tests/testsuite/member_errors.rs164
-rw-r--r--src/tools/cargo/tests/testsuite/message_format.rs133
-rw-r--r--src/tools/cargo/tests/testsuite/messages.rs144
-rw-r--r--src/tools/cargo/tests/testsuite/metabuild.rs771
-rw-r--r--src/tools/cargo/tests/testsuite/metadata.rs4192
-rw-r--r--src/tools/cargo/tests/testsuite/minimal_versions.rs38
-rw-r--r--src/tools/cargo/tests/testsuite/mock-std/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/mock-std/library/alloc/Cargo.toml8
-rw-r--r--src/tools/cargo/tests/testsuite/mock-std/library/alloc/src/lib.rs11
-rw-r--r--src/tools/cargo/tests/testsuite/mock-std/library/compiler_builtins/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/mock-std/library/compiler_builtins/src/lib.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/mock-std/library/core/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/mock-std/library/core/src/lib.rs9
-rw-r--r--src/tools/cargo/tests/testsuite/mock-std/library/panic_unwind/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/mock-std/library/panic_unwind/src/lib.rs5
-rw-r--r--src/tools/cargo/tests/testsuite/mock-std/library/proc_macro/Cargo.toml5
-rw-r--r--src/tools/cargo/tests/testsuite/mock-std/library/proc_macro/src/lib.rs11
-rw-r--r--src/tools/cargo/tests/testsuite/mock-std/library/rustc-std-workspace-alloc/Cargo.toml11
-rw-r--r--src/tools/cargo/tests/testsuite/mock-std/library/rustc-std-workspace-alloc/lib.rs3
-rw-r--r--src/tools/cargo/tests/testsuite/mock-std/library/rustc-std-workspace-core/Cargo.toml11
-rw-r--r--src/tools/cargo/tests/testsuite/mock-std/library/rustc-std-workspace-core/lib.rs3
-rw-r--r--src/tools/cargo/tests/testsuite/mock-std/library/rustc-std-workspace-std/Cargo.toml11
-rw-r--r--src/tools/cargo/tests/testsuite/mock-std/library/rustc-std-workspace-std/lib.rs1
-rw-r--r--src/tools/cargo/tests/testsuite/mock-std/library/std/Cargo.toml11
-rw-r--r--src/tools/cargo/tests/testsuite/mock-std/library/std/src/lib.rs12
-rw-r--r--src/tools/cargo/tests/testsuite/mock-std/library/test/Cargo.toml18
-rw-r--r--src/tools/cargo/tests/testsuite/mock-std/library/test/src/lib.rs10
-rw-r--r--src/tools/cargo/tests/testsuite/multitarget.rs231
-rw-r--r--src/tools/cargo/tests/testsuite/net_config.rs74
-rw-r--r--src/tools/cargo/tests/testsuite/new.rs560
-rw-r--r--src/tools/cargo/tests/testsuite/offline.rs728
-rw-r--r--src/tools/cargo/tests/testsuite/old_cargos.rs679
-rw-r--r--src/tools/cargo/tests/testsuite/out_dir.rs317
-rw-r--r--src/tools/cargo/tests/testsuite/owner.rs192
-rw-r--r--src/tools/cargo/tests/testsuite/package.rs2764
-rw-r--r--src/tools/cargo/tests/testsuite/package_features.rs704
-rw-r--r--src/tools/cargo/tests/testsuite/patch.rs2645
-rw-r--r--src/tools/cargo/tests/testsuite/path.rs1139
-rw-r--r--src/tools/cargo/tests/testsuite/paths.rs226
-rw-r--r--src/tools/cargo/tests/testsuite/pkgid.rs128
-rw-r--r--src/tools/cargo/tests/testsuite/plugins.rs421
-rw-r--r--src/tools/cargo/tests/testsuite/proc_macro.rs560
-rw-r--r--src/tools/cargo/tests/testsuite/profile_config.rs519
-rw-r--r--src/tools/cargo/tests/testsuite/profile_custom.rs731
-rw-r--r--src/tools/cargo/tests/testsuite/profile_overrides.rs515
-rw-r--r--src/tools/cargo/tests/testsuite/profile_targets.rs674
-rw-r--r--src/tools/cargo/tests/testsuite/profiles.rs744
-rw-r--r--src/tools/cargo/tests/testsuite/progress.rs159
-rw-r--r--src/tools/cargo/tests/testsuite/pub_priv.rs199
-rw-r--r--src/tools/cargo/tests/testsuite/publish.rs2951
-rw-r--r--src/tools/cargo/tests/testsuite/publish_lockfile.rs592
-rw-r--r--src/tools/cargo/tests/testsuite/read_manifest.rs206
-rw-r--r--src/tools/cargo/tests/testsuite/registry.rs3406
-rw-r--r--src/tools/cargo/tests/testsuite/registry_auth.rs519
-rw-r--r--src/tools/cargo/tests/testsuite/rename_deps.rs391
-rw-r--r--src/tools/cargo/tests/testsuite/replace.rs1300
-rw-r--r--src/tools/cargo/tests/testsuite/required_features.rs1452
-rw-r--r--src/tools/cargo/tests/testsuite/run.rs1509
-rw-r--r--src/tools/cargo/tests/testsuite/rust_version.rs194
-rw-r--r--src/tools/cargo/tests/testsuite/rustc.rs794
-rw-r--r--src/tools/cargo/tests/testsuite/rustc_info_cache.rs186
-rw-r--r--src/tools/cargo/tests/testsuite/rustdoc.rs252
-rw-r--r--src/tools/cargo/tests/testsuite/rustdoc_extern_html.rs426
-rw-r--r--src/tools/cargo/tests/testsuite/rustdocflags.rs155
-rw-r--r--src/tools/cargo/tests/testsuite/rustflags.rs1673
-rw-r--r--src/tools/cargo/tests/testsuite/search.rs192
-rw-r--r--src/tools/cargo/tests/testsuite/shell_quoting.rs37
-rw-r--r--src/tools/cargo/tests/testsuite/source_replacement.rs250
-rw-r--r--src/tools/cargo/tests/testsuite/ssh.rs592
-rw-r--r--src/tools/cargo/tests/testsuite/standard_lib.rs657
-rw-r--r--src/tools/cargo/tests/testsuite/test.rs4820
-rw-r--r--src/tools/cargo/tests/testsuite/timings.rs53
-rw-r--r--src/tools/cargo/tests/testsuite/tool_paths.rs402
-rw-r--r--src/tools/cargo/tests/testsuite/tree.rs2150
-rw-r--r--src/tools/cargo/tests/testsuite/tree_graph_features.rs362
-rw-r--r--src/tools/cargo/tests/testsuite/unit_graph.rs233
-rw-r--r--src/tools/cargo/tests/testsuite/update.rs832
-rw-r--r--src/tools/cargo/tests/testsuite/vendor.rs1152
-rw-r--r--src/tools/cargo/tests/testsuite/verify_project.rs73
-rw-r--r--src/tools/cargo/tests/testsuite/version.rs54
-rw-r--r--src/tools/cargo/tests/testsuite/warn_on_failure.rs111
-rw-r--r--src/tools/cargo/tests/testsuite/weak_dep_features.rs632
-rw-r--r--src/tools/cargo/tests/testsuite/workspaces.rs2531
-rw-r--r--src/tools/cargo/tests/testsuite/yank.rs202
1341 files changed, 124294 insertions, 0 deletions
diff --git a/src/tools/cargo/tests/build-std/main.rs b/src/tools/cargo/tests/build-std/main.rs
new file mode 100644
index 000000000..47a4bb671
--- /dev/null
+++ b/src/tools/cargo/tests/build-std/main.rs
@@ -0,0 +1,229 @@
+//! A test suite for `-Zbuild-std` which is much more expensive than the
+//! standard test suite.
+//!
+//! This test suite attempts to perform a full integration test where we
+//! actually compile the standard library from source (like the real one) and
+//! the various tests associated with that.
+//!
+//! YOU SHOULD IDEALLY NOT WRITE TESTS HERE.
+//!
+//! If possible, use `tests/testsuite/standard_lib.rs` instead. That uses a
+//! 'mock' sysroot which is much faster to compile. The tests here are
+//! extremely intensive and are only intended to run on CI and are theoretically
+//! not catching any regressions that `tests/testsuite/standard_lib.rs` isn't
+//! already catching.
+//!
+//! All tests here should use `#[cargo_test(build_std_real)]` to indicate that
+//! boilerplate should be generated to require the nightly toolchain and the
+//! `CARGO_RUN_BUILD_STD_TESTS` env var to be set to actually run these tests.
+//! Otherwise the tests are skipped.
+
+use cargo_test_support::*;
+use std::env;
+use std::path::Path;
+
+fn enable_build_std(e: &mut Execs, arg: Option<&str>) {
+ e.env_remove("CARGO_HOME");
+ e.env_remove("HOME");
+
+ // And finally actually enable `build-std` for now
+ let arg = match arg {
+ Some(s) => format!("-Zbuild-std={}", s),
+ None => "-Zbuild-std".to_string(),
+ };
+ e.arg(arg);
+ e.masquerade_as_nightly_cargo(&["build-std"]);
+}
+
+// Helper methods used in the tests below
+trait BuildStd: Sized {
+ fn build_std(&mut self) -> &mut Self;
+ fn build_std_arg(&mut self, arg: &str) -> &mut Self;
+ fn target_host(&mut self) -> &mut Self;
+}
+
+impl BuildStd for Execs {
+ fn build_std(&mut self) -> &mut Self {
+ enable_build_std(self, None);
+ self
+ }
+
+ fn build_std_arg(&mut self, arg: &str) -> &mut Self {
+ enable_build_std(self, Some(arg));
+ self
+ }
+
+ fn target_host(&mut self) -> &mut Self {
+ self.arg("--target").arg(rustc_host());
+ self
+ }
+}
+
+#[cargo_test(build_std_real)]
+fn basic() {
+ let p = project()
+ .file(
+ "src/main.rs",
+ "
+ fn main() {
+ foo::f();
+ }
+
+ #[test]
+ fn smoke_bin_unit() {
+ foo::f();
+ }
+ ",
+ )
+ .file(
+ "src/lib.rs",
+ "
+ extern crate alloc;
+ extern crate proc_macro;
+
+ /// ```
+ /// foo::f();
+ /// ```
+ pub fn f() {
+ }
+
+ #[test]
+ fn smoke_lib_unit() {
+ f();
+ }
+ ",
+ )
+ .file(
+ "tests/smoke.rs",
+ "
+ #[test]
+ fn smoke_integration() {
+ foo::f();
+ }
+ ",
+ )
+ .build();
+
+ p.cargo("check").build_std().target_host().run();
+ p.cargo("build")
+ .build_std()
+ .target_host()
+ // Importantly, this should not say [UPDATING]
+ // There have been multiple bugs where every build triggers and update.
+ .with_stderr(
+ "[COMPILING] foo v0.0.1 [..]\n\
+ [FINISHED] dev [..]",
+ )
+ .run();
+ p.cargo("run").build_std().target_host().run();
+ p.cargo("test").build_std().target_host().run();
+
+ // Check for hack that removes dylibs.
+ let deps_dir = Path::new("target")
+ .join(rustc_host())
+ .join("debug")
+ .join("deps");
+ assert!(p.glob(deps_dir.join("*.rlib")).count() > 0);
+ assert_eq!(p.glob(deps_dir.join("*.dylib")).count(), 0);
+}
+
+#[cargo_test(build_std_real)]
+fn cross_custom() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [target.custom-target.dependencies]
+ dep = { path = "dep" }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "#![no_std] pub fn f() -> u32 { dep::answer() }",
+ )
+ .file("dep/Cargo.toml", &basic_manifest("dep", "0.1.0"))
+ .file("dep/src/lib.rs", "#![no_std] pub fn answer() -> u32 { 42 }")
+ .file(
+ "custom-target.json",
+ r#"
+ {
+ "llvm-target": "x86_64-unknown-none-gnu",
+ "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
+ "arch": "x86_64",
+ "target-endian": "little",
+ "target-pointer-width": "64",
+ "target-c-int-width": "32",
+ "os": "none",
+ "linker-flavor": "ld.lld"
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build --target custom-target.json -v")
+ .build_std_arg("core")
+ .run();
+}
+
+#[cargo_test(build_std_real)]
+fn custom_test_framework() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ #![no_std]
+ #![cfg_attr(test, no_main)]
+ #![feature(custom_test_frameworks)]
+ #![test_runner(crate::test_runner)]
+
+ pub fn test_runner(_tests: &[&dyn Fn()]) {}
+
+ #[panic_handler]
+ fn panic(_info: &core::panic::PanicInfo) -> ! {
+ loop {}
+ }
+ "#,
+ )
+ .file(
+ "target.json",
+ r#"
+ {
+ "llvm-target": "x86_64-unknown-none-gnu",
+ "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
+ "arch": "x86_64",
+ "target-endian": "little",
+ "target-pointer-width": "64",
+ "target-c-int-width": "32",
+ "os": "none",
+ "linker-flavor": "ld.lld",
+ "linker": "rust-lld",
+ "executables": true,
+ "panic-strategy": "abort"
+ }
+ "#,
+ )
+ .build();
+
+ // This is a bit of a hack to use the rust-lld that ships with most toolchains.
+ let sysroot = paths::sysroot();
+ let sysroot = Path::new(&sysroot);
+ let sysroot_bin = sysroot
+ .join("lib")
+ .join("rustlib")
+ .join(rustc_host())
+ .join("bin");
+ let path = env::var_os("PATH").unwrap_or_default();
+ let mut paths = env::split_paths(&path).collect::<Vec<_>>();
+ paths.insert(0, sysroot_bin);
+ let new_path = env::join_paths(paths).unwrap();
+
+ p.cargo("test --target target.json --no-run -v")
+ .env("PATH", new_path)
+ .build_std_arg("core")
+ .run();
+}
diff --git a/src/tools/cargo/tests/internal.rs b/src/tools/cargo/tests/internal.rs
new file mode 100644
index 000000000..c42cfa8f0
--- /dev/null
+++ b/src/tools/cargo/tests/internal.rs
@@ -0,0 +1,107 @@
+//! Tests for internal code checks.
+
+#![allow(clippy::all)]
+
+use std::fs;
+
+#[test]
+fn check_forbidden_code() {
+ // Do not use certain macros, functions, etc.
+ if !cargo_util::is_ci() {
+ // Only check these on CI, otherwise it could be annoying.
+ use std::io::Write;
+ writeln!(
+ std::io::stderr(),
+ "\nSkipping check_forbidden_code test, set CI=1 to enable"
+ )
+ .unwrap();
+ return;
+ }
+ let root_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
+ for entry in walkdir::WalkDir::new(&root_path)
+ .into_iter()
+ .filter_entry(|e| e.path() != root_path.join("doc"))
+ .filter_map(|e| e.ok())
+ {
+ let path = entry.path();
+ if !entry
+ .file_name()
+ .to_str()
+ .map(|s| s.ends_with(".rs"))
+ .unwrap_or(false)
+ {
+ continue;
+ }
+ eprintln!("checking {}", path.display());
+ let c = fs::read_to_string(path).unwrap();
+ for (line_index, line) in c.lines().enumerate() {
+ if line.trim().starts_with("//") {
+ continue;
+ }
+ if line_has_print(line) {
+ if entry.file_name().to_str().unwrap() == "cargo_new.rs" && line.contains("Hello") {
+ // An exception.
+ continue;
+ }
+ panic!(
+ "found print macro in {}:{}\n\n{}\n\n\
+ print! macros should not be used in Cargo because they can panic.\n\
+ Use one of the drop_print macros instead.\n\
+ ",
+ path.display(),
+ line_index,
+ line
+ );
+ }
+ if line_has_macro(line, "dbg") {
+ panic!(
+ "found dbg! macro in {}:{}\n\n{}\n\n\
+ dbg! should not be used outside of debugging.",
+ path.display(),
+ line_index,
+ line
+ );
+ }
+ }
+ }
+}
+
+fn line_has_print(line: &str) -> bool {
+ line_has_macro(line, "print")
+ || line_has_macro(line, "eprint")
+ || line_has_macro(line, "println")
+ || line_has_macro(line, "eprintln")
+}
+
+#[test]
+fn line_has_print_works() {
+ assert!(line_has_print("print!"));
+ assert!(line_has_print("println!"));
+ assert!(line_has_print("eprint!"));
+ assert!(line_has_print("eprintln!"));
+ assert!(line_has_print("(print!(\"hi!\"))"));
+ assert!(!line_has_print("print"));
+ assert!(!line_has_print("i like to print things"));
+ assert!(!line_has_print("drop_print!"));
+ assert!(!line_has_print("drop_println!"));
+ assert!(!line_has_print("drop_eprint!"));
+ assert!(!line_has_print("drop_eprintln!"));
+}
+
+fn line_has_macro(line: &str, mac: &str) -> bool {
+ for (i, _) in line.match_indices(mac) {
+ if line.get(i + mac.len()..i + mac.len() + 1) != Some("!") {
+ continue;
+ }
+ if i == 0 {
+ return true;
+ }
+ // Check for identifier boundary start.
+ let prev1 = line.get(i - 1..i).unwrap().chars().next().unwrap();
+ if prev1.is_alphanumeric() || prev1 == '_' {
+ continue;
+ }
+ return true;
+ }
+ false
+}
diff --git a/src/tools/cargo/tests/testsuite/advanced_env.rs b/src/tools/cargo/tests/testsuite/advanced_env.rs
new file mode 100644
index 000000000..8aab528ea
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/advanced_env.rs
@@ -0,0 +1,35 @@
+//! -Zadvanced-env tests
+
+use cargo_test_support::{paths, project, registry::Package};
+
+#[cargo_test]
+fn source_config_env() {
+ // Try to define [source] with environment variables.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ somedep = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ Package::new("somedep", "1.0.0")
+ .local(true)
+ .file("src/lib.rs", "")
+ .publish();
+
+ let path = paths::root().join("registry");
+
+ p.cargo("check -Zadvanced-env")
+ .masquerade_as_nightly_cargo(&["advanced-env"])
+ .env("CARGO_SOURCE_crates-io_REPLACE_WITH", "my-local-source")
+ .env("CARGO_SOURCE_my-local-source_LOCAL_REGISTRY", path)
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/alt_registry.rs b/src/tools/cargo/tests/testsuite/alt_registry.rs
new file mode 100644
index 000000000..97da909b8
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/alt_registry.rs
@@ -0,0 +1,1496 @@
+//! Tests for alternative registries.
+
+use cargo_test_support::compare::assert_match_exact;
+use cargo_test_support::publish::validate_alt_upload;
+use cargo_test_support::registry::{self, Package, RegistryBuilder};
+use cargo_test_support::{basic_manifest, paths, project};
+use std::fs;
+
+#[cargo_test]
+fn depend_on_alt_registry() {
+ registry::alt_init();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ version = "0.0.1"
+ registry = "alternative"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("bar", "0.0.1").alternative(true).publish();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `alternative` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.0.1 (registry `alternative`)
+[CHECKING] bar v0.0.1 (registry `alternative`)
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s
+",
+ )
+ .run();
+
+ p.cargo("clean").run();
+
+ // Don't download a second time
+ p.cargo("check")
+ .with_stderr(
+ "\
+[CHECKING] bar v0.0.1 (registry `alternative`)
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn depend_on_alt_registry_depends_on_same_registry_no_index() {
+ registry::alt_init();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ version = "0.0.1"
+ registry = "alternative"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("baz", "0.0.1").alternative(true).publish();
+ Package::new("bar", "0.0.1")
+ .registry_dep("baz", "0.0.1")
+ .alternative(true)
+ .publish();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `alternative` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] [..] v0.0.1 (registry `alternative`)
+[DOWNLOADED] [..] v0.0.1 (registry `alternative`)
+[CHECKING] baz v0.0.1 (registry `alternative`)
+[CHECKING] bar v0.0.1 (registry `alternative`)
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn depend_on_alt_registry_depends_on_same_registry() {
+ registry::alt_init();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ version = "0.0.1"
+ registry = "alternative"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("baz", "0.0.1").alternative(true).publish();
+ Package::new("bar", "0.0.1")
+ .registry_dep("baz", "0.0.1")
+ .alternative(true)
+ .publish();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `alternative` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] [..] v0.0.1 (registry `alternative`)
+[DOWNLOADED] [..] v0.0.1 (registry `alternative`)
+[CHECKING] baz v0.0.1 (registry `alternative`)
+[CHECKING] bar v0.0.1 (registry `alternative`)
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn depend_on_alt_registry_depends_on_crates_io() {
+ registry::alt_init();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ version = "0.0.1"
+ registry = "alternative"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("baz", "0.0.1").publish();
+ Package::new("bar", "0.0.1")
+ .dep("baz", "0.0.1")
+ .alternative(true)
+ .publish();
+
+ p.cargo("check")
+ .with_stderr_unordered(
+ "\
+[UPDATING] `alternative` index
+[UPDATING] `dummy-registry` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] baz v0.0.1 (registry `dummy-registry`)
+[DOWNLOADED] bar v0.0.1 (registry `alternative`)
+[CHECKING] baz v0.0.1
+[CHECKING] bar v0.0.1 (registry `alternative`)
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn registry_and_path_dep_works() {
+ registry::alt_init();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "bar"
+ registry = "alternative"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[CHECKING] bar v0.0.1 ([CWD]/bar)
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn registry_incompatible_with_git() {
+ registry::alt_init();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ git = ""
+ registry = "alternative"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr_contains(
+ " dependency (bar) specification is ambiguous. \
+ Only one of `git` or `registry` is allowed.",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cannot_publish_to_crates_io_with_registry_dependency() {
+ let crates_io = registry::init();
+ let _alternative = RegistryBuilder::new().alternative().build();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ [dependencies.bar]
+ version = "0.0.1"
+ registry = "alternative"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("bar", "0.0.1").alternative(true).publish();
+
+ p.cargo("publish")
+ .replace_crates_io(crates_io.index_url())
+ .with_status(101)
+ .with_stderr_contains("[ERROR] crates cannot be published to crates.io[..]")
+ .run();
+
+ p.cargo("publish")
+ .replace_crates_io(crates_io.index_url())
+ .arg("--token")
+ .arg(crates_io.token())
+ .arg("--index")
+ .arg(crates_io.index_url().as_str())
+ .with_status(101)
+ .with_stderr_contains("[ERROR] crates cannot be published to crates.io[..]")
+ .run();
+}
+
+#[cargo_test]
+fn publish_with_registry_dependency() {
+ let _reg = RegistryBuilder::new()
+ .http_api()
+ .http_index()
+ .alternative()
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ version = "0.0.1"
+ registry = "alternative"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("bar", "0.0.1").alternative(true).publish();
+
+ p.cargo("publish --registry alternative")
+ .with_stderr(
+ "\
+[UPDATING] `alternative` index
+[WARNING] [..]
+[..]
+[PACKAGING] foo v0.0.1 [..]
+[UPDATING] `alternative` index
+[VERIFYING] foo v0.0.1 [..]
+[DOWNLOADING] [..]
+[DOWNLOADED] bar v0.0.1 (registry `alternative`)
+[COMPILING] bar v0.0.1 (registry `alternative`)
+[COMPILING] foo v0.0.1 [..]
+[FINISHED] [..]
+[PACKAGED] [..]
+[UPLOADING] foo v0.0.1 [..]
+[UPLOADED] foo v0.0.1 to registry `alternative`
+note: Waiting for `foo v0.0.1` to be available at registry `alternative`.
+You may press ctrl-c to skip waiting; the crate should be available shortly.
+[PUBLISHED] foo v0.0.1 at registry `alternative`
+",
+ )
+ .run();
+
+ validate_alt_upload(
+ r#"{
+ "authors": [],
+ "badges": {},
+ "categories": [],
+ "deps": [
+ {
+ "default_features": true,
+ "features": [],
+ "kind": "normal",
+ "name": "bar",
+ "optional": false,
+ "target": null,
+ "version_req": "^0.0.1"
+ }
+ ],
+ "description": null,
+ "documentation": null,
+ "features": {},
+ "homepage": null,
+ "keywords": [],
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "name": "foo",
+ "readme": null,
+ "readme_file": null,
+ "repository": null,
+ "homepage": null,
+ "documentation": null,
+ "vers": "0.0.1"
+ }"#,
+ "foo-0.0.1.crate",
+ &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"],
+ );
+}
+
+#[cargo_test]
+fn alt_registry_and_crates_io_deps() {
+ registry::alt_init();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ crates_io_dep = "0.0.1"
+
+ [dependencies.alt_reg_dep]
+ version = "0.1.0"
+ registry = "alternative"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("crates_io_dep", "0.0.1").publish();
+ Package::new("alt_reg_dep", "0.1.0")
+ .alternative(true)
+ .publish();
+
+ p.cargo("check")
+ .with_stderr_unordered(
+ "\
+[UPDATING] `alternative` index
+[UPDATING] `dummy-registry` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] crates_io_dep v0.0.1 (registry `dummy-registry`)
+[DOWNLOADED] alt_reg_dep v0.1.0 (registry `alternative`)
+[CHECKING] alt_reg_dep v0.1.0 (registry `alternative`)
+[CHECKING] crates_io_dep v0.0.1
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn block_publish_due_to_no_token() {
+ registry::alt_init();
+ let p = project().file("src/lib.rs", "").build();
+
+ fs::remove_file(paths::home().join(".cargo/credentials.toml")).unwrap();
+
+ // Now perform the actual publish
+ p.cargo("publish --registry alternative")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] `alternative` index
+error: no token found for `alternative`, please run `cargo login --registry alternative`
+or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_registries_crates_io_protocol() {
+ let _ = RegistryBuilder::new()
+ .no_configure_token()
+ .alternative()
+ .build();
+ // Should not produce a warning due to the registries.crates-io.protocol = 'sparse' configuration
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config.toml",
+ "[registries.crates-io]
+ protocol = 'sparse'",
+ )
+ .build();
+
+ p.cargo("publish --registry alternative")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] `alternative` index
+error: no token found for `alternative`, please run `cargo login --registry alternative`
+or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn publish_to_alt_registry() {
+ let _reg = RegistryBuilder::new()
+ .http_api()
+ .http_index()
+ .alternative()
+ .build();
+
+ let p = project().file("src/main.rs", "fn main() {}").build();
+
+ // Now perform the actual publish
+ p.cargo("publish --registry alternative")
+ .with_stderr(
+ "\
+[UPDATING] `alternative` index
+[WARNING] [..]
+[..]
+[PACKAGING] foo v0.0.1 [..]
+[VERIFYING] foo v0.0.1 [..]
+[COMPILING] foo v0.0.1 [..]
+[FINISHED] [..]
+[PACKAGED] [..]
+[UPLOADING] foo v0.0.1 [..]
+[UPLOADED] foo v0.0.1 to registry `alternative`
+note: Waiting for `foo v0.0.1` to be available at registry `alternative`.
+You may press ctrl-c to skip waiting; the crate should be available shortly.
+[PUBLISHED] foo v0.0.1 at registry `alternative`
+",
+ )
+ .run();
+
+ validate_alt_upload(
+ r#"{
+ "authors": [],
+ "badges": {},
+ "categories": [],
+ "deps": [],
+ "description": null,
+ "documentation": null,
+ "features": {},
+ "homepage": null,
+ "keywords": [],
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "name": "foo",
+ "readme": null,
+ "readme_file": null,
+ "repository": null,
+ "homepage": null,
+ "documentation": null,
+ "vers": "0.0.1"
+ }"#,
+ "foo-0.0.1.crate",
+ &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"],
+ );
+}
+
+#[cargo_test]
+fn publish_with_crates_io_dep() {
+ // crates.io registry.
+ let _dummy_reg = registry::init();
+ // Alternative registry.
+ let _alt_reg = RegistryBuilder::new()
+ .http_api()
+ .http_index()
+ .alternative()
+ .build();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = ["me"]
+ license = "MIT"
+ description = "foo"
+
+ [dependencies.bar]
+ version = "0.0.1"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("bar", "0.0.1").publish();
+
+ p.cargo("publish --registry alternative")
+ .with_stderr(
+ "\
+[UPDATING] `alternative` index
+[WARNING] [..]
+[..]
+[PACKAGING] foo v0.0.1 [..]
+[UPDATING] `dummy-registry` index
+[VERIFYING] foo v0.0.1 [..]
+[DOWNLOADING] [..]
+[DOWNLOADED] bar v0.0.1 (registry `dummy-registry`)
+[COMPILING] bar v0.0.1
+[COMPILING] foo v0.0.1 [..]
+[FINISHED] [..]
+[PACKAGED] [..]
+[UPLOADING] foo v0.0.1 [..]
+[UPLOADED] foo v0.0.1 to registry `alternative`
+note: Waiting for `foo v0.0.1` to be available at registry `alternative`.
+You may press ctrl-c to skip waiting; the crate should be available shortly.
+[PUBLISHED] foo v0.0.1 at registry `alternative`
+",
+ )
+ .run();
+
+ validate_alt_upload(
+ r#"{
+ "authors": ["me"],
+ "badges": {},
+ "categories": [],
+ "deps": [
+ {
+ "default_features": true,
+ "features": [],
+ "kind": "normal",
+ "name": "bar",
+ "optional": false,
+ "registry": "https://github.com/rust-lang/crates.io-index",
+ "target": null,
+ "version_req": "^0.0.1"
+ }
+ ],
+ "description": "foo",
+ "documentation": null,
+ "features": {},
+ "homepage": null,
+ "keywords": [],
+ "license": "MIT",
+ "license_file": null,
+ "links": null,
+ "name": "foo",
+ "readme": null,
+ "readme_file": null,
+ "repository": null,
+ "homepage": null,
+ "documentation": null,
+ "vers": "0.0.1"
+ }"#,
+ "foo-0.0.1.crate",
+ &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"],
+ );
+}
+
+#[cargo_test]
+fn passwords_in_registries_index_url_forbidden() {
+ registry::alt_init();
+
+ let config = paths::home().join(".cargo/config");
+
+ fs::write(
+ config,
+ r#"
+ [registries.alternative]
+ index = "ssh://git:secret@foobar.com"
+ "#,
+ )
+ .unwrap();
+
+ let p = project().file("src/main.rs", "fn main() {}").build();
+
+ p.cargo("publish --registry alternative")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: invalid index URL for registry `alternative` defined in [..]/home/.cargo/config
+
+Caused by:
+ registry URLs may not contain passwords
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn patch_alt_reg() {
+ registry::alt_init();
+ Package::new("bar", "0.1.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies]
+ bar = { version = "0.1.0", registry = "alternative" }
+
+ [patch.alternative]
+ bar = { path = "bar" }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "
+ extern crate bar;
+ pub fn f() { bar::bar(); }
+ ",
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `alternative` index
+[CHECKING] bar v0.1.0 ([CWD]/bar)
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bad_registry_name() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ version = "0.0.1"
+ registry = "bad name"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[CWD]/Cargo.toml`
+
+Caused by:
+ invalid character ` ` in registry name: `bad name`, [..]",
+ )
+ .run();
+
+ for cmd in &[
+ "init",
+ "install foo",
+ "login",
+ "owner",
+ "publish",
+ "search",
+ "yank --version 0.0.1",
+ ] {
+ p.cargo(cmd)
+ .arg("--registry")
+ .arg("bad name")
+ .with_status(101)
+ .with_stderr("[ERROR] invalid character ` ` in registry name: `bad name`, [..]")
+ .run();
+ }
+}
+
+#[cargo_test]
+fn no_api() {
+ let _registry = RegistryBuilder::new().alternative().no_api().build();
+ Package::new("bar", "0.0.1").alternative(true).publish();
+
+ // First check that a dependency works.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies.bar]
+ version = "0.0.1"
+ registry = "alternative"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `alternative` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.0.1 (registry `alternative`)
+[CHECKING] bar v0.0.1 (registry `alternative`)
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s
+",
+ )
+ .run();
+
+ // Check all of the API commands.
+ let err = "[ERROR] registry `alternative` does not support API commands";
+
+ p.cargo("login --registry alternative TOKEN")
+ .with_status(101)
+ .with_stderr_contains(&err)
+ .run();
+
+ p.cargo("publish --registry alternative")
+ .with_status(101)
+ .with_stderr_contains(&err)
+ .run();
+
+ p.cargo("search --registry alternative")
+ .with_status(101)
+ .with_stderr_contains(&err)
+ .run();
+
+ p.cargo("owner --registry alternative --list")
+ .with_status(101)
+ .with_stderr_contains(&err)
+ .run();
+
+ p.cargo("yank --registry alternative --version=0.0.1 bar")
+ .with_status(101)
+ .with_stderr_contains(&err)
+ .run();
+
+ p.cargo("yank --registry alternative --version=0.0.1 bar")
+ .with_stderr_contains(&err)
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn alt_reg_metadata() {
+ // Check for "registry" entries in `cargo metadata` with alternative registries.
+ registry::alt_init();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies]
+ altdep = { version = "0.0.1", registry = "alternative" }
+ iodep = { version = "0.0.1" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ Package::new("bar", "0.0.1").publish();
+ Package::new("altdep", "0.0.1")
+ .dep("bar", "0.0.1")
+ .alternative(true)
+ .publish();
+ Package::new("altdep2", "0.0.1").alternative(true).publish();
+ Package::new("iodep", "0.0.1")
+ .registry_dep("altdep2", "0.0.1")
+ .publish();
+
+ // The important thing to check here is the "registry" value in `deps`.
+ // They should be:
+ // foo -> altdep: alternative-registry
+ // foo -> iodep: null (because it is in crates.io)
+ // altdep -> bar: null (because it is in crates.io)
+ // iodep -> altdep2: alternative-registry
+ p.cargo("metadata --format-version=1 --no-deps")
+ .with_json(
+ r#"
+ {
+ "packages": [
+ {
+ "name": "foo",
+ "version": "0.0.1",
+ "id": "foo 0.0.1 (path+file:[..]/foo)",
+ "license": null,
+ "license_file": null,
+ "description": null,
+ "source": null,
+ "dependencies": [
+ {
+ "name": "altdep",
+ "source": "registry+file:[..]/alternative-registry",
+ "req": "^0.0.1",
+ "kind": null,
+ "rename": null,
+ "optional": false,
+ "uses_default_features": true,
+ "features": [],
+ "target": null,
+ "registry": "file:[..]/alternative-registry"
+ },
+ {
+ "name": "iodep",
+ "source": "registry+https://github.com/rust-lang/crates.io-index",
+ "req": "^0.0.1",
+ "kind": null,
+ "rename": null,
+ "optional": false,
+ "uses_default_features": true,
+ "features": [],
+ "target": null,
+ "registry": null
+ }
+ ],
+ "targets": "{...}",
+ "features": {},
+ "manifest_path": "[..]/foo/Cargo.toml",
+ "metadata": null,
+ "publish": null,
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "keywords": [],
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "edition": "2015",
+ "links": null
+ }
+ ],
+ "workspace_members": [
+ "foo 0.0.1 (path+file:[..]/foo)"
+ ],
+ "resolve": null,
+ "target_directory": "[..]/foo/target",
+ "version": 1,
+ "workspace_root": "[..]/foo",
+ "metadata": null
+ }"#,
+ )
+ .run();
+
+ // --no-deps uses a different code path, make sure both work.
+ p.cargo("metadata --format-version=1")
+ .with_json(
+ r#"
+ {
+ "packages": [
+ {
+ "name": "altdep",
+ "version": "0.0.1",
+ "id": "altdep 0.0.1 (registry+file:[..]/alternative-registry)",
+ "license": null,
+ "license_file": null,
+ "description": null,
+ "source": "registry+file:[..]/alternative-registry",
+ "dependencies": [
+ {
+ "name": "bar",
+ "source": "registry+https://github.com/rust-lang/crates.io-index",
+ "req": "^0.0.1",
+ "kind": null,
+ "rename": null,
+ "optional": false,
+ "uses_default_features": true,
+ "features": [],
+ "target": null,
+ "registry": null
+ }
+ ],
+ "targets": "{...}",
+ "features": {},
+ "manifest_path": "[..]/altdep-0.0.1/Cargo.toml",
+ "metadata": null,
+ "publish": null,
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "keywords": [],
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "edition": "2015",
+ "links": null
+ },
+ {
+ "name": "altdep2",
+ "version": "0.0.1",
+ "id": "altdep2 0.0.1 (registry+file:[..]/alternative-registry)",
+ "license": null,
+ "license_file": null,
+ "description": null,
+ "source": "registry+file:[..]/alternative-registry",
+ "dependencies": [],
+ "targets": "{...}",
+ "features": {},
+ "manifest_path": "[..]/altdep2-0.0.1/Cargo.toml",
+ "metadata": null,
+ "publish": null,
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "keywords": [],
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "edition": "2015",
+ "links": null
+ },
+ {
+ "name": "bar",
+ "version": "0.0.1",
+ "id": "bar 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "license": null,
+ "license_file": null,
+ "description": null,
+ "source": "registry+https://github.com/rust-lang/crates.io-index",
+ "dependencies": [],
+ "targets": "{...}",
+ "features": {},
+ "manifest_path": "[..]/bar-0.0.1/Cargo.toml",
+ "metadata": null,
+ "publish": null,
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "keywords": [],
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "edition": "2015",
+ "links": null
+ },
+ {
+ "name": "foo",
+ "version": "0.0.1",
+ "id": "foo 0.0.1 (path+file:[..]/foo)",
+ "license": null,
+ "license_file": null,
+ "description": null,
+ "source": null,
+ "dependencies": [
+ {
+ "name": "altdep",
+ "source": "registry+file:[..]/alternative-registry",
+ "req": "^0.0.1",
+ "kind": null,
+ "rename": null,
+ "optional": false,
+ "uses_default_features": true,
+ "features": [],
+ "target": null,
+ "registry": "file:[..]/alternative-registry"
+ },
+ {
+ "name": "iodep",
+ "source": "registry+https://github.com/rust-lang/crates.io-index",
+ "req": "^0.0.1",
+ "kind": null,
+ "rename": null,
+ "optional": false,
+ "uses_default_features": true,
+ "features": [],
+ "target": null,
+ "registry": null
+ }
+ ],
+ "targets": "{...}",
+ "features": {},
+ "manifest_path": "[..]/foo/Cargo.toml",
+ "metadata": null,
+ "publish": null,
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "keywords": [],
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "edition": "2015",
+ "links": null
+ },
+ {
+ "name": "iodep",
+ "version": "0.0.1",
+ "id": "iodep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "license": null,
+ "license_file": null,
+ "description": null,
+ "source": "registry+https://github.com/rust-lang/crates.io-index",
+ "dependencies": [
+ {
+ "name": "altdep2",
+ "source": "registry+file:[..]/alternative-registry",
+ "req": "^0.0.1",
+ "kind": null,
+ "rename": null,
+ "optional": false,
+ "uses_default_features": true,
+ "features": [],
+ "target": null,
+ "registry": "file:[..]/alternative-registry"
+ }
+ ],
+ "targets": "{...}",
+ "features": {},
+ "manifest_path": "[..]/iodep-0.0.1/Cargo.toml",
+ "metadata": null,
+ "publish": null,
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "keywords": [],
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "edition": "2015",
+ "links": null
+ }
+ ],
+ "workspace_members": [
+ "foo 0.0.1 (path+file:[..]/foo)"
+ ],
+ "resolve": "{...}",
+ "target_directory": "[..]/foo/target",
+ "version": 1,
+ "workspace_root": "[..]/foo",
+ "metadata": null
+ }"#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn unknown_registry() {
+ // A known registry refers to an unknown registry.
+ // foo -> bar(crates.io) -> baz(alt)
+ registry::alt_init();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ version = "0.0.1"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("baz", "0.0.1").alternative(true).publish();
+ Package::new("bar", "0.0.1")
+ .registry_dep("baz", "0.0.1")
+ .publish();
+
+ // Remove "alternative" from config.
+ let cfg_path = paths::home().join(".cargo/config");
+ let mut config = fs::read_to_string(&cfg_path).unwrap();
+ let start = config.find("[registries.alternative]").unwrap();
+ config.insert(start, '#');
+ let start_index = &config[start..].find("index =").unwrap();
+ config.insert(start + start_index, '#');
+ fs::write(&cfg_path, config).unwrap();
+
+ p.cargo("check").run();
+
+ // Important parts:
+ // foo -> bar registry = null
+ // bar -> baz registry = alternate
+ p.cargo("metadata --format-version=1")
+ .with_json(
+ r#"
+ {
+ "packages": [
+ {
+ "name": "bar",
+ "version": "0.0.1",
+ "id": "bar 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "license": null,
+ "license_file": null,
+ "description": null,
+ "source": "registry+https://github.com/rust-lang/crates.io-index",
+ "dependencies": [
+ {
+ "name": "baz",
+ "source": "registry+file://[..]/alternative-registry",
+ "req": "^0.0.1",
+ "kind": null,
+ "rename": null,
+ "optional": false,
+ "uses_default_features": true,
+ "features": [],
+ "target": null,
+ "registry": "file:[..]/alternative-registry"
+ }
+ ],
+ "targets": "{...}",
+ "features": {},
+ "manifest_path": "[..]",
+ "metadata": null,
+ "publish": null,
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "keywords": [],
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "edition": "2015",
+ "links": null
+ },
+ {
+ "name": "baz",
+ "version": "0.0.1",
+ "id": "baz 0.0.1 (registry+file://[..]/alternative-registry)",
+ "license": null,
+ "license_file": null,
+ "description": null,
+ "source": "registry+file://[..]/alternative-registry",
+ "dependencies": [],
+ "targets": "{...}",
+ "features": {},
+ "manifest_path": "[..]",
+ "metadata": null,
+ "publish": null,
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "keywords": [],
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "edition": "2015",
+ "links": null
+ },
+ {
+ "name": "foo",
+ "version": "0.0.1",
+ "id": "foo 0.0.1 (path+file://[..]/foo)",
+ "license": null,
+ "license_file": null,
+ "description": null,
+ "source": null,
+ "dependencies": [
+ {
+ "name": "bar",
+ "source": "registry+https://github.com/rust-lang/crates.io-index",
+ "req": "^0.0.1",
+ "kind": null,
+ "rename": null,
+ "optional": false,
+ "uses_default_features": true,
+ "features": [],
+ "target": null,
+ "registry": null
+ }
+ ],
+ "targets": "{...}",
+ "features": {},
+ "manifest_path": "[..]/foo/Cargo.toml",
+ "metadata": null,
+ "publish": null,
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "keywords": [],
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "edition": "2015",
+ "links": null
+ }
+ ],
+ "workspace_members": [
+ "foo 0.0.1 (path+file://[..]/foo)"
+ ],
+ "resolve": "{...}",
+ "target_directory": "[..]/foo/target",
+ "version": 1,
+ "workspace_root": "[..]/foo",
+ "metadata": null
+ }
+ "#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn registries_index_relative_url() {
+ registry::alt_init();
+ let config = paths::root().join(".cargo/config");
+ fs::create_dir_all(config.parent().unwrap()).unwrap();
+ fs::write(
+ &config,
+ r#"
+ [registries.relative]
+ index = "file:alternative-registry"
+ "#,
+ )
+ .unwrap();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ version = "0.0.1"
+ registry = "relative"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("bar", "0.0.1").alternative(true).publish();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `relative` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.0.1 (registry `relative`)
+[CHECKING] bar v0.0.1 (registry `relative`)
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn registries_index_relative_path_not_allowed() {
+ registry::alt_init();
+ let config = paths::root().join(".cargo/config");
+ fs::create_dir_all(config.parent().unwrap()).unwrap();
+ fs::write(
+ &config,
+ r#"
+ [registries.relative]
+ index = "alternative-registry"
+ "#,
+ )
+ .unwrap();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ version = "0.0.1"
+ registry = "relative"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("bar", "0.0.1").alternative(true).publish();
+
+ p.cargo("check")
+ .with_stderr(&format!(
+ "\
+error: failed to parse manifest at `{root}/foo/Cargo.toml`
+
+Caused by:
+ invalid index URL for registry `relative` defined in [..]/.cargo/config
+
+Caused by:
+ invalid url `alternative-registry`: relative URL without a base
+",
+ root = paths::root().to_str().unwrap()
+ ))
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn both_index_and_registry() {
+ let p = project().file("src/lib.rs", "").build();
+ for cmd in &["publish", "owner", "search", "yank --version 1.0.0"] {
+ p.cargo(cmd)
+ .arg("--registry=foo")
+ .arg("--index=foo")
+ .with_status(101)
+ .with_stderr(
+ "[ERROR] both `--index` and `--registry` \
+ should not be set at the same time",
+ )
+ .run();
+ }
+}
+
+#[cargo_test]
+fn both_index_and_default() {
+ let p = project().file("src/lib.rs", "").build();
+ for cmd in &[
+ "publish",
+ "owner",
+ "search",
+ "yank --version 1.0.0",
+ "install foo",
+ ] {
+ p.cargo(cmd)
+ .env("CARGO_REGISTRY_DEFAULT", "undefined")
+ .arg(format!("--index=index_url"))
+ .with_status(101)
+ .with_stderr("[ERROR] invalid url `index_url`: relative URL without a base")
+ .run();
+ }
+}
+
+#[cargo_test]
+fn sparse_lockfile() {
+ let _registry = registry::RegistryBuilder::new()
+ .http_index()
+ .alternative()
+ .build();
+ Package::new("foo", "0.1.0").alternative(true).publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [project]
+ name = "a"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ foo = { registry = 'alternative', version = '0.1.0'}
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("generate-lockfile").run();
+ assert_match_exact(
+ &p.read_lockfile(),
+ r#"# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "a"
+version = "0.5.0"
+dependencies = [
+ "foo",
+]
+
+[[package]]
+name = "foo"
+version = "0.1.0"
+source = "sparse+http://[..]/"
+checksum = "f6a200a9339fef960979d94d5c99cbbfd899b6f5a396a55d9775089119050203""#,
+ );
+}
+
+#[cargo_test]
+fn publish_with_transitive_dep() {
+ let _alt1 = RegistryBuilder::new()
+ .http_api()
+ .http_index()
+ .alternative_named("Alt-1")
+ .build();
+ let _alt2 = RegistryBuilder::new()
+ .http_api()
+ .http_index()
+ .alternative_named("Alt-2")
+ .build();
+
+ let p1 = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.5.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p1.cargo("publish --registry Alt-1").run();
+
+ let p2 = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.6.0"
+ publish = ["Alt-2"]
+
+ [dependencies]
+ a = { version = "0.5.0", registry = "Alt-1" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p2.cargo("publish").run();
+}
diff --git a/src/tools/cargo/tests/testsuite/artifact_dep.rs b/src/tools/cargo/tests/testsuite/artifact_dep.rs
new file mode 100644
index 000000000..ec6bb7103
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/artifact_dep.rs
@@ -0,0 +1,2901 @@
+//! Tests specific to artifact dependencies, designated using
+//! the new `dep = { artifact = "bin", … }` syntax in manifests.
+
+use cargo_test_support::compare::match_exact;
+use cargo_test_support::registry::{Package, RegistryBuilder};
+use cargo_test_support::{
+ basic_bin_manifest, basic_manifest, cross_compile, project, publish, registry, rustc_host,
+ Project,
+};
+
+#[cargo_test]
+fn check_with_invalid_artifact_dependency() {
+ // invalid name
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ resolver = "2"
+
+ [dependencies]
+ bar = { path = "bar/", artifact = "unknown" }
+ "#,
+ )
+ .file("src/lib.rs", "extern crate bar;") // this would fail but we don't get there, artifacts are no libs
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "")
+ .build();
+ p.cargo("check -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]/Cargo.toml`
+
+Caused by:
+ 'unknown' is not a valid artifact specifier
+",
+ )
+ .with_status(101)
+ .run();
+
+ fn run_cargo_with_and_without_bindeps_feature(
+ p: &Project,
+ cmd: &str,
+ assert: &dyn Fn(&mut cargo_test_support::Execs),
+ ) {
+ assert(
+ p.cargo(&format!("{} -Z bindeps", cmd))
+ .masquerade_as_nightly_cargo(&["bindeps"]),
+ );
+ assert(&mut p.cargo(cmd));
+ }
+
+ // lib specified without artifact
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+
+ [dependencies]
+ bar = { path = "bar/", lib = true }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "")
+ .build();
+ run_cargo_with_and_without_bindeps_feature(&p, "check", &|cargo| {
+ cargo
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]/Cargo.toml`
+
+Caused by:
+ 'lib' specifier cannot be used without an 'artifact = …' value (bar)
+",
+ )
+ .with_status(101)
+ .run();
+ });
+
+ // target specified without artifact
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+
+ [dependencies]
+ bar = { path = "bar/", target = "target" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "")
+ .build();
+ run_cargo_with_and_without_bindeps_feature(&p, "check", &|cargo| {
+ cargo
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]/Cargo.toml`
+
+Caused by:
+ 'target' specifier cannot be used without an 'artifact = …' value (bar)
+",
+ )
+ .with_status(101)
+ .run();
+ })
+}
+
+#[cargo_test]
+fn check_with_invalid_target_triple() {
+ // invalid name
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ resolver = "2"
+
+ [dependencies]
+ bar = { path = "bar/", artifact = "bin", target = "unknown-target-triple" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/main.rs", "fn main() {}")
+ .build();
+ p.cargo("check -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_stderr_contains(
+ r#"[..]Could not find specification for target "unknown-target-triple"[..]"#,
+ )
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn build_without_nightly_aborts_with_error() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ resolver = "2"
+
+ [dependencies]
+ bar = { path = "bar/", artifact = "bin" }
+ "#,
+ )
+ .file("src/lib.rs", "extern crate bar;")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "")
+ .build();
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at [..]
+
+Caused by:
+ `artifact = …` requires `-Z bindeps` (bar)
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn disallow_artifact_and_no_artifact_dep_to_same_package_within_the_same_dep_category() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ resolver = "2"
+
+ [dependencies]
+ bar = { path = "bar/", artifact = "bin" }
+ bar_stable = { path = "bar/", package = "bar" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_bin_manifest("bar"))
+ .file("bar/src/main.rs", "fn main() {}")
+ .build();
+ p.cargo("check -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_status(101)
+ .with_stderr("\
+[WARNING] foo v0.0.0 ([CWD]) ignoring invalid dependency `bar_stable` which is missing a lib target
+[ERROR] the crate `foo v0.0.0 ([CWD])` depends on crate `bar v0.5.0 ([CWD]/bar)` multiple times with different names",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn features_are_unified_among_lib_and_bin_dep_of_same_target() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ resolver = "2"
+
+ [dependencies.d1]
+ path = "d1"
+ features = ["d1f1"]
+ artifact = "bin"
+ lib = true
+
+ [dependencies.d2]
+ path = "d2"
+ features = ["d2f2"]
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ d1::f1();
+ d1::f2();
+ d2::f1();
+ d2::f2();
+ }
+ "#,
+ )
+ .file(
+ "d1/Cargo.toml",
+ r#"
+ [package]
+ name = "d1"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ d1f1 = ["d2"]
+
+ [dependencies.d2]
+ path = "../d2"
+ features = ["d2f1"]
+ optional = true
+ "#,
+ )
+ .file(
+ "d1/src/main.rs",
+ r#"fn main() {
+ #[cfg(feature = "d1f1")]
+ d2::f1();
+
+ // Using f2 is only possible as features are unififed across the same target.
+ // Our own manifest would only enable f1, and f2 comes in because a parent crate
+ // enables the feature in its manifest.
+ #[cfg(feature = "d1f1")]
+ d2::f2();
+ }"#,
+ )
+ .file(
+ "d1/src/lib.rs",
+ r#"
+ #[cfg(feature = "d2")]
+ extern crate d2;
+ /// Importing f2 here shouldn't be possible as unless features are unified.
+ #[cfg(feature = "d1f1")]
+ pub use d2::{f1, f2};
+ "#,
+ )
+ .file(
+ "d2/Cargo.toml",
+ r#"
+ [package]
+ name = "d2"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ d2f1 = []
+ d2f2 = []
+ "#,
+ )
+ .file(
+ "d2/src/lib.rs",
+ r#"
+ #[cfg(feature = "d2f1")] pub fn f1() {}
+ #[cfg(feature = "d2f2")] pub fn f2() {}
+ "#,
+ )
+ .build();
+
+ p.cargo("build -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_stderr(
+ "\
+[COMPILING] d2 v0.0.1 ([CWD]/d2)
+[COMPILING] d1 v0.0.1 ([CWD]/d1)
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn features_are_not_unified_among_lib_and_bin_dep_of_different_target() {
+ if cross_compile::disabled() {
+ return;
+ }
+ let target = cross_compile::alternate();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ resolver = "2"
+
+ [dependencies.d1]
+ path = "d1"
+ features = ["d1f1"]
+ artifact = "bin"
+ lib = true
+ target = "$TARGET"
+
+ [dependencies.d2]
+ path = "d2"
+ features = ["d2f2"]
+ "#
+ .replace("$TARGET", target),
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ // the lib = true part always builds for our current target, unifying dependencies
+ d1::d2::f1();
+ d1::d2::f2();
+ d2::f1();
+ d2::f2();
+ }
+ "#,
+ )
+ .file(
+ "d1/Cargo.toml",
+ r#"
+ [package]
+ name = "d1"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ d1f1 = ["d2"]
+
+ [dependencies.d2]
+ path = "../d2"
+ features = ["d2f1"]
+ optional = true
+ "#,
+ )
+ .file("d1/src/main.rs", r#"fn main() {
+ // f1 we set ourselves
+ d2::f1();
+ // As 'main' is only compiled as part of the artifact dependency and since that is not unified
+ // if the target differs, trying to access f2 is a compile time error as the feature isn't enabled in our dependency tree.
+ d2::f2();
+ }"#)
+ .file(
+ "d1/src/lib.rs",
+ r#"
+ #[cfg(feature = "d2")]
+ pub extern crate d2;
+ "#,
+ )
+ .file(
+ "d2/Cargo.toml",
+ r#"
+ [package]
+ name = "d2"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ d2f1 = []
+ d2f2 = []
+ "#,
+ )
+ .file(
+ "d2/src/lib.rs",
+ r#"
+ #[cfg(feature = "d2f1")] pub fn f1() {}
+ #[cfg(feature = "d2f2")] pub fn f2() {}
+ "#,
+ )
+ .build();
+
+ p.cargo("build -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_status(101)
+ .with_stderr_contains(
+ "error[E0425]: cannot find function `f2` in crate `d2`\n --> d1/src/main.rs:6:17",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn feature_resolution_works_for_cfg_target_specification() {
+ if cross_compile::disabled() {
+ return;
+ }
+ let target = cross_compile::alternate();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ resolver = "2"
+
+ [dependencies.d1]
+ path = "d1"
+ artifact = "bin"
+ target = "$TARGET"
+ "#
+ .replace("$TARGET", target),
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ let _b = include_bytes!(env!("CARGO_BIN_FILE_D1"));
+ }
+ "#,
+ )
+ .file(
+ "d1/Cargo.toml",
+ &r#"
+ [package]
+ name = "d1"
+ version = "0.0.1"
+ authors = []
+
+ [target.'$TARGET'.dependencies]
+ d2 = { path = "../d2" }
+ "#
+ .replace("$TARGET", target),
+ )
+ .file(
+ "d1/src/main.rs",
+ r#"fn main() {
+ d1::f();
+ }"#,
+ )
+ .file("d1/build.rs", r#"fn main() { }"#)
+ .file(
+ "d1/src/lib.rs",
+ &r#"pub fn f() {
+ #[cfg(target = "$TARGET")]
+ d2::f();
+ }
+ "#
+ .replace("$TARGET", target),
+ )
+ .file(
+ "d2/Cargo.toml",
+ r#"
+ [package]
+ name = "d2"
+ version = "0.0.1"
+ authors = []
+ "#,
+ )
+ .file("d2/build.rs", r#"fn main() { }"#)
+ .file("d2/src/lib.rs", "pub fn f() {}")
+ .build();
+
+ p.cargo("test -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .run();
+}
+
+#[cargo_test]
+fn build_script_with_bin_artifacts() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ resolver = "2"
+
+ [build-dependencies]
+ bar = { path = "bar/", artifact = ["bin", "staticlib", "cdylib"] }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("build.rs", r#"
+ fn main() {
+ let baz: std::path::PathBuf = std::env::var("CARGO_BIN_FILE_BAR_baz").expect("CARGO_BIN_FILE_BAR_baz").into();
+ println!("{}", baz.display());
+ assert!(&baz.is_file());
+
+ let lib: std::path::PathBuf = std::env::var("CARGO_STATICLIB_FILE_BAR_bar").expect("CARGO_STATICLIB_FILE_BAR_bar").into();
+ println!("{}", lib.display());
+ assert!(&lib.is_file());
+
+ let lib: std::path::PathBuf = std::env::var("CARGO_CDYLIB_FILE_BAR_bar").expect("CARGO_CDYLIB_FILE_BAR_bar").into();
+ println!("{}", lib.display());
+ assert!(&lib.is_file());
+
+ let dir: std::path::PathBuf = std::env::var("CARGO_BIN_DIR_BAR").expect("CARGO_BIN_DIR_BAR").into();
+ println!("{}", dir.display());
+ assert!(dir.is_dir());
+
+ let bar: std::path::PathBuf = std::env::var("CARGO_BIN_FILE_BAR").expect("CARGO_BIN_FILE_BAR").into();
+ println!("{}", bar.display());
+ assert!(&bar.is_file());
+
+ let bar2: std::path::PathBuf = std::env::var("CARGO_BIN_FILE_BAR_bar").expect("CARGO_BIN_FILE_BAR_bar").into();
+ println!("{}", bar2.display());
+ assert_eq!(bar, bar2);
+ }
+ "#)
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.5.0"
+ authors = []
+
+ [lib]
+ crate-type = ["staticlib", "cdylib"]
+ "#,
+ )
+ // compilation target is native for build scripts unless overridden
+ .file("bar/src/bin/bar.rs", &format!(r#"fn main() {{ assert_eq!(std::env::var("TARGET").unwrap(), "{}"); }}"#, cross_compile::native()))
+ .file("bar/src/bin/baz.rs", "fn main() {}")
+ .file("bar/src/lib.rs", "")
+ .build();
+ p.cargo("build -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_stderr_contains("[COMPILING] foo [..]")
+ .with_stderr_contains("[COMPILING] bar v0.5.0 ([CWD]/bar)")
+ .with_stderr_contains("[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]")
+ .run();
+
+ let build_script_output = build_script_output_string(&p, "foo");
+ let msg = "we need the binary directory for this artifact along with all binary paths";
+ if cfg!(target_env = "msvc") {
+ match_exact(
+ "[..]/artifact/bar-[..]/bin/baz.exe\n\
+ [..]/artifact/bar-[..]/staticlib/bar-[..].lib\n\
+ [..]/artifact/bar-[..]/cdylib/bar.dll\n\
+ [..]/artifact/bar-[..]/bin\n\
+ [..]/artifact/bar-[..]/bin/bar.exe\n\
+ [..]/artifact/bar-[..]/bin/bar.exe",
+ &build_script_output,
+ msg,
+ "",
+ None,
+ )
+ .unwrap();
+ } else {
+ match_exact(
+ "[..]/artifact/bar-[..]/bin/baz-[..]\n\
+ [..]/artifact/bar-[..]/staticlib/libbar-[..].a\n\
+ [..]/artifact/bar-[..]/cdylib/[..]bar.[..]\n\
+ [..]/artifact/bar-[..]/bin\n\
+ [..]/artifact/bar-[..]/bin/bar-[..]\n\
+ [..]/artifact/bar-[..]/bin/bar-[..]",
+ &build_script_output,
+ msg,
+ "",
+ None,
+ )
+ .unwrap();
+ }
+
+ assert!(
+ !p.bin("bar").is_file(),
+ "artifacts are located in their own directory, exclusively, and won't be lifted up"
+ );
+ assert!(!p.bin("baz").is_file(),);
+ assert_artifact_executable_output(&p, "debug", "bar", "bar");
+}
+
+#[cargo_test]
+fn build_script_with_bin_artifact_and_lib_false() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ resolver = "2"
+
+ [build-dependencies]
+ bar = { path = "bar/", artifact = "bin" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ bar::doit()
+ }
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_bin_manifest("bar"))
+ .file("bar/src/main.rs", "fn main() { bar::doit(); }")
+ .file(
+ "bar/src/lib.rs",
+ r#"
+ pub fn doit() {
+ panic!("sentinel");
+ }
+ "#,
+ )
+ .build();
+ p.cargo("build -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_status(101)
+ .with_stderr_does_not_contain("[..]sentinel[..]")
+ .run();
+}
+
+#[cargo_test]
+fn lib_with_bin_artifact_and_lib_false() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ resolver = "2"
+
+ [dependencies]
+ bar = { path = "bar/", artifact = "bin" }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn foo() {
+ bar::doit()
+ }"#,
+ )
+ .file("bar/Cargo.toml", &basic_bin_manifest("bar"))
+ .file("bar/src/main.rs", "fn main() { bar::doit(); }")
+ .file(
+ "bar/src/lib.rs",
+ r#"
+ pub fn doit() {
+ panic!("sentinel");
+ }
+ "#,
+ )
+ .build();
+ p.cargo("build -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_status(101)
+ .with_stderr_does_not_contain("[..]sentinel[..]")
+ .run();
+}
+
+#[cargo_test]
+fn build_script_with_selected_dashed_bin_artifact_and_lib_true() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ resolver = "2"
+
+ [build-dependencies]
+ bar-baz = { path = "bar/", artifact = "bin:baz-suffix", lib = true }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("build.rs", r#"
+ fn main() {
+ bar_baz::print_env()
+ }
+ "#)
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar-baz"
+ version = "0.5.0"
+ authors = []
+
+ [[bin]]
+ name = "bar"
+
+ [[bin]]
+ name = "baz-suffix"
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}")
+ .file("bar/src/lib.rs", r#"
+ pub fn print_env() {
+ let dir: std::path::PathBuf = std::env::var("CARGO_BIN_DIR_BAR_BAZ").expect("CARGO_BIN_DIR_BAR_BAZ").into();
+ let bin: std::path::PathBuf = std::env::var("CARGO_BIN_FILE_BAR_BAZ_baz-suffix").expect("CARGO_BIN_FILE_BAR_BAZ_baz-suffix").into();
+ println!("{}", dir.display());
+ println!("{}", bin.display());
+ assert!(dir.is_dir());
+ assert!(&bin.is_file());
+ assert!(std::env::var("CARGO_BIN_FILE_BAR_BAZ").is_err(), "CARGO_BIN_FILE_BAR_BAZ isn't set due to name mismatch");
+ assert!(std::env::var("CARGO_BIN_FILE_BAR_BAZ_bar").is_err(), "CARGO_BIN_FILE_BAR_BAZ_bar isn't set as binary isn't selected");
+ }
+ "#)
+ .build();
+ p.cargo("build -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_stderr(
+ "\
+[COMPILING] bar-baz v0.5.0 ([CWD]/bar)
+[COMPILING] foo [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ )
+ .run();
+
+ let build_script_output = build_script_output_string(&p, "foo");
+ let msg = "we need the binary directory for this artifact and the binary itself";
+
+ if cfg!(target_env = "msvc") {
+ cargo_test_support::compare::match_exact(
+ &format!(
+ "[..]/artifact/bar-baz-[..]/bin\n\
+ [..]/artifact/bar-baz-[..]/bin/baz_suffix{}",
+ std::env::consts::EXE_SUFFIX,
+ ),
+ &build_script_output,
+ msg,
+ "",
+ None,
+ )
+ .unwrap();
+ } else {
+ cargo_test_support::compare::match_exact(
+ "[..]/artifact/bar-baz-[..]/bin\n\
+ [..]/artifact/bar-baz-[..]/bin/baz_suffix-[..]",
+ &build_script_output,
+ msg,
+ "",
+ None,
+ )
+ .unwrap();
+ }
+
+ assert!(
+ !p.bin("bar").is_file(),
+ "artifacts are located in their own directory, exclusively, and won't be lifted up"
+ );
+ assert_artifact_executable_output(&p, "debug", "bar", "baz_suffix");
+}
+
+#[cargo_test]
+fn lib_with_selected_dashed_bin_artifact_and_lib_true() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ resolver = "2"
+
+ [dependencies]
+ bar-baz = { path = "bar/", artifact = ["bin:baz-suffix", "staticlib", "cdylib"], lib = true }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn foo() {
+ bar_baz::exists();
+
+ env!("CARGO_BIN_DIR_BAR_BAZ");
+ let _b = include_bytes!(env!("CARGO_BIN_FILE_BAR_BAZ_baz-suffix"));
+ let _b = include_bytes!(env!("CARGO_STATICLIB_FILE_BAR_BAZ"));
+ let _b = include_bytes!(env!("CARGO_STATICLIB_FILE_BAR_BAZ_bar-baz"));
+ let _b = include_bytes!(env!("CARGO_CDYLIB_FILE_BAR_BAZ"));
+ let _b = include_bytes!(env!("CARGO_CDYLIB_FILE_BAR_BAZ_bar-baz"));
+ }
+ "#,
+ )
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar-baz"
+ version = "0.5.0"
+ authors = []
+
+ [lib]
+ crate-type = ["rlib", "staticlib", "cdylib"]
+
+ [[bin]]
+ name = "bar"
+
+ [[bin]]
+ name = "baz-suffix"
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}")
+ .file("bar/src/lib.rs", "pub fn exists() {}")
+ .build();
+ p.cargo("build -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_stderr(
+ "\
+[COMPILING] bar-baz v0.5.0 ([CWD]/bar)
+[COMPILING] foo [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ )
+ .run();
+
+ assert!(
+ !p.bin("bar").is_file(),
+ "artifacts are located in their own directory, exclusively, and won't be lifted up"
+ );
+ assert_artifact_executable_output(&p, "debug", "bar", "baz_suffix");
+}
+
+#[cargo_test]
+fn allow_artifact_and_no_artifact_dep_to_same_package_within_different_dep_categories() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ resolver = "2"
+
+ [dependencies]
+ bar = { path = "bar/", artifact = "bin" }
+
+ [dev-dependencies]
+ bar = { path = "bar/", package = "bar" }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ #[cfg(test)] extern crate bar;
+ pub fn foo() {
+ env!("CARGO_BIN_DIR_BAR");
+ let _b = include_bytes!(env!("CARGO_BIN_FILE_BAR"));
+ }"#,
+ )
+ .file("bar/Cargo.toml", &basic_bin_manifest("bar"))
+ .file("bar/src/main.rs", "fn main() {}")
+ .file("bar/src/lib.rs", "")
+ .build();
+ p.cargo("test -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_stderr_contains("[COMPILING] bar v0.5.0 ([CWD]/bar)")
+ .with_stderr_contains("[FINISHED] test [unoptimized + debuginfo] target(s) in [..]")
+ .run();
+}
+
+#[cargo_test]
+fn normal_build_deps_are_picked_up_in_presence_of_an_artifact_build_dep_to_the_same_package() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ resolver = "2"
+
+ [dependencies]
+ bar = { path = "bar", artifact = "bin:bar" }
+
+ [build-dependencies]
+ bar = { path = "bar" }
+ "#,
+ )
+ .file("build.rs", "fn main() { bar::f(); }")
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn foo() {
+ env!("CARGO_BIN_DIR_BAR");
+ let _b = include_bytes!(env!("CARGO_BIN_FILE_BAR"));
+ }"#,
+ )
+ .file("bar/Cargo.toml", &basic_bin_manifest("bar"))
+ .file("bar/src/main.rs", "fn main() {}")
+ .file("bar/src/lib.rs", "pub fn f() {}")
+ .build();
+ p.cargo("check -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .run();
+}
+
+#[cargo_test]
+fn disallow_using_example_binaries_as_artifacts() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ resolver = "2"
+
+ [dependencies]
+ bar = { path = "bar/", artifact = "bin:one-example" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_bin_manifest("bar"))
+ .file("bar/src/main.rs", "fn main() {}")
+ .file("bar/examples/one-example.rs", "fn main() {}")
+ .build();
+ p.cargo("build -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_status(101)
+ .with_stderr(r#"[ERROR] dependency `bar` in package `foo` requires a `bin:one-example` artifact to be present."#)
+ .run();
+}
+
+/// From RFC 3028
+///
+/// > You may also specify separate dependencies with different artifact values, as well as
+/// dependencies on the same crate without artifact specified; for instance, you may have a
+/// build dependency on the binary of a crate and a normal dependency on the Rust library of the same crate.
+#[cargo_test]
+fn allow_artifact_and_non_artifact_dependency_to_same_crate() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ resolver = "2"
+
+ [build-dependencies]
+ bar = { path = "bar/", artifact = "bin" }
+
+ [dependencies]
+ bar = { path = "bar/" }
+ "#,
+ )
+ .file("src/lib.rs", r#"
+ pub fn foo() {
+ bar::doit();
+ assert!(option_env!("CARGO_BIN_FILE_BAR").is_none());
+ }"#)
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ assert!(option_env!("CARGO_BIN_FILE_BAR").is_none(), "no environment variables at build time");
+ std::process::Command::new(std::env::var("CARGO_BIN_FILE_BAR").expect("BAR present")).status().unwrap();
+ }"#,
+ )
+ .file("bar/Cargo.toml", &basic_bin_manifest("bar"))
+ .file("bar/src/main.rs", "fn main() {}")
+ .file("bar/src/lib.rs", "pub fn doit() {}")
+ .build();
+
+ p.cargo("check -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_stderr_contains("[COMPILING] bar [..]")
+ .with_stderr_contains("[COMPILING] foo [..]")
+ .run();
+}
+
+#[cargo_test]
+fn build_script_deps_adopt_specified_target_unconditionally() {
+ if cross_compile::disabled() {
+ return;
+ }
+
+ let target = cross_compile::alternate();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ resolver = "2"
+
+ [build-dependencies.bar]
+ path = "bar/"
+ artifact = "bin"
+ target = "{}"
+ "#,
+ target
+ ),
+ )
+ .file("src/lib.rs", "")
+ .file("build.rs", r#"
+ fn main() {
+ let bar: std::path::PathBuf = std::env::var("CARGO_BIN_FILE_BAR").expect("CARGO_BIN_FILE_BAR").into();
+ assert!(&bar.is_file());
+ }"#)
+ .file("bar/Cargo.toml", &basic_bin_manifest("bar"))
+ .file("bar/src/main.rs", "fn main() {}")
+ .file("bar/src/lib.rs", "pub fn doit() {}")
+ .build();
+
+ p.cargo("check -v -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_stderr_does_not_contain(format!(
+ "[RUNNING] `rustc --crate-name build_script_build build.rs [..]--target {} [..]",
+ target
+ ))
+ .with_stderr_contains("[RUNNING] `rustc --crate-name build_script_build build.rs [..]")
+ .with_stderr_contains(format!(
+ "[RUNNING] `rustc --crate-name bar bar/src/lib.rs [..]--target {} [..]",
+ target
+ ))
+ .with_stderr_contains(format!(
+ "[RUNNING] `rustc --crate-name bar bar/src/main.rs [..]--target {} [..]",
+ target
+ ))
+ .with_stderr_does_not_contain(format!(
+ "[RUNNING] `rustc --crate-name foo [..]--target {} [..]",
+ target
+ ))
+ .with_stderr_contains("[RUNNING] `rustc --crate-name foo [..]")
+ .run();
+}
+
+/// inverse RFC-3176
+#[cargo_test]
+fn build_script_deps_adopt_do_not_allow_multiple_targets_under_different_name_and_same_version() {
+ if cross_compile::disabled() {
+ return;
+ }
+
+ let alternate = cross_compile::alternate();
+ let native = cross_compile::native();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ resolver = "2"
+
+ [build-dependencies.bar]
+ path = "bar/"
+ artifact = "bin"
+ target = "{}"
+
+ [build-dependencies.bar-native]
+ package = "bar"
+ path = "bar/"
+ artifact = "bin"
+ target = "{}"
+ "#,
+ alternate,
+ native
+ ),
+ )
+ .file("src/lib.rs", "")
+ .file("build.rs", r#"
+ fn main() {
+ let bar: std::path::PathBuf = std::env::var("CARGO_BIN_FILE_BAR").expect("CARGO_BIN_FILE_BAR").into();
+ assert!(&bar.is_file());
+ let bar_native: std::path::PathBuf = std::env::var("CARGO_BIN_FILE_BAR_NATIVE_bar").expect("CARGO_BIN_FILE_BAR_NATIVE_bar").into();
+ assert!(&bar_native.is_file());
+ assert_ne!(bar_native, bar, "should build different binaries due to different targets");
+ }"#)
+ .file("bar/Cargo.toml", &basic_bin_manifest("bar"))
+ .file("bar/src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check -v -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_status(101)
+ .with_stderr(format!(
+ "error: the crate `foo v0.0.0 ([CWD])` depends on crate `bar v0.5.0 ([CWD]/bar)` multiple times with different names",
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn non_build_script_deps_adopt_specified_target_unconditionally() {
+ if cross_compile::disabled() {
+ return;
+ }
+
+ let target = cross_compile::alternate();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ resolver = "2"
+
+ [dependencies.bar]
+ path = "bar/"
+ artifact = "bin"
+ target = "{}"
+ "#,
+ target
+ ),
+ )
+ .file(
+ "src/lib.rs",
+ r#"pub fn foo() { let _b = include_bytes!(env!("CARGO_BIN_FILE_BAR")); }"#,
+ )
+ .file("bar/Cargo.toml", &basic_bin_manifest("bar"))
+ .file("bar/src/main.rs", "fn main() {}")
+ .file("bar/src/lib.rs", "pub fn doit() {}")
+ .build();
+
+ p.cargo("check -v -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_stderr_contains(format!(
+ "[RUNNING] `rustc --crate-name bar bar/src/lib.rs [..]--target {} [..]",
+ target
+ ))
+ .with_stderr_contains(format!(
+ "[RUNNING] `rustc --crate-name bar bar/src/main.rs [..]--target {} [..]",
+ target
+ ))
+ .with_stderr_does_not_contain(format!(
+ "[RUNNING] `rustc --crate-name foo [..]--target {} [..]",
+ target
+ ))
+ .with_stderr_contains("[RUNNING] `rustc --crate-name foo [..]")
+ .run();
+}
+
+#[cargo_test]
+fn no_cross_doctests_works_with_artifacts() {
+ if cross_compile::disabled() {
+ return;
+ }
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ resolver = "2"
+
+ [dependencies]
+ bar = { path = "bar/", artifact = "bin", lib = true }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ //! ```
+ //! env!("CARGO_BIN_DIR_BAR");
+ //! let _b = include_bytes!(env!("CARGO_BIN_FILE_BAR"));
+ //! ```
+ pub fn foo() {
+ env!("CARGO_BIN_DIR_BAR");
+ let _b = include_bytes!(env!("CARGO_BIN_FILE_BAR"));
+ }
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_bin_manifest("bar"))
+ .file("bar/src/lib.rs", r#"pub extern "C" fn c() {}"#)
+ .file("bar/src/main.rs", "fn main() {}")
+ .build();
+
+ let target = rustc_host();
+ p.cargo("test -Z bindeps --target")
+ .arg(&target)
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_stderr(&format!(
+ "\
+[COMPILING] bar v0.5.0 ([CWD]/bar)
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/{triple}/debug/deps/foo-[..][EXE])
+[DOCTEST] foo
+",
+ triple = target
+ ))
+ .run();
+
+ println!("c");
+ let target = cross_compile::alternate();
+
+ // This will build the library, but does not build or run doc tests.
+ // This should probably be a warning or error.
+ p.cargo("test -Z bindeps -v --doc --target")
+ .arg(&target)
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_stderr_contains(format!(
+ "[COMPILING] bar v0.5.0 ([CWD]/bar)
+[RUNNING] `rustc --crate-name bar bar/src/lib.rs [..]--target {triple} [..]
+[RUNNING] `rustc --crate-name bar bar/src/main.rs [..]--target {triple} [..]
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name foo [..]
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]",
+ triple = target
+ ))
+ .run();
+
+ if !cross_compile::can_run_on_host() {
+ return;
+ }
+
+ // This tests the library, but does not run the doc tests.
+ p.cargo("test -Z bindeps -v --target")
+ .arg(&target)
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_stderr_contains(&format!(
+ "[FRESH] bar v0.5.0 ([CWD]/bar)
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name foo [..]--test[..]
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `[CWD]/target/{triple}/debug/deps/foo-[..][EXE]`",
+ triple = target
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn build_script_deps_adopts_target_platform_if_target_equals_target() {
+ if cross_compile::disabled() {
+ return;
+ }
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ resolver = "2"
+
+ [build-dependencies]
+ bar = { path = "bar/", artifact = "bin", target = "target" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("build.rs", r#"
+ fn main() {
+ let bar: std::path::PathBuf = std::env::var("CARGO_BIN_FILE_BAR").expect("CARGO_BIN_FILE_BAR").into();
+ assert!(&bar.is_file());
+ }"#)
+ .file("bar/Cargo.toml", &basic_bin_manifest("bar"))
+ .file("bar/src/main.rs", "fn main() {}")
+ .file("bar/src/lib.rs", "pub fn doit() {}")
+ .build();
+
+ let alternate_target = cross_compile::alternate();
+ p.cargo("check -v -Z bindeps --target")
+ .arg(alternate_target)
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_stderr_does_not_contain(format!(
+ "[RUNNING] `rustc --crate-name build_script_build build.rs [..]--target {} [..]",
+ alternate_target
+ ))
+ .with_stderr_contains("[RUNNING] `rustc --crate-name build_script_build build.rs [..]")
+ .with_stderr_contains(format!(
+ "[RUNNING] `rustc --crate-name bar bar/src/lib.rs [..]--target {} [..]",
+ alternate_target
+ ))
+ .with_stderr_contains(format!(
+ "[RUNNING] `rustc --crate-name bar bar/src/main.rs [..]--target {} [..]",
+ alternate_target
+ ))
+ .with_stderr_contains(format!(
+ "[RUNNING] `rustc --crate-name foo [..]--target {} [..]",
+ alternate_target
+ ))
+ .run();
+}
+
+#[cargo_test]
+// TODO(ST): rename bar (dependency) to something else and un-ignore this with RFC-3176
+#[cfg_attr(target_env = "msvc", ignore = "msvc not working")]
+fn profile_override_basic() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [build-dependencies]
+ bar = { path = "bar", artifact = "bin" }
+
+ [dependencies]
+ bar = { path = "bar", artifact = "bin" }
+
+ [profile.dev.build-override]
+ opt-level = 1
+
+ [profile.dev]
+ opt-level = 3
+ "#,
+ )
+ .file("build.rs", "fn main() {}")
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_bin_manifest("bar"))
+ .file("bar/src/main.rs", "fn main() {}")
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ p.cargo("build -v -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_stderr_contains(
+ "[RUNNING] `rustc --crate-name build_script_build [..] -C opt-level=1 [..]`",
+ )
+ .with_stderr_contains(
+ "[RUNNING] `rustc --crate-name bar bar/src/main.rs [..] -C opt-level=3 [..]`",
+ )
+ .with_stderr_contains(
+ "[RUNNING] `rustc --crate-name bar bar/src/main.rs [..] -C opt-level=1 [..]`",
+ )
+ .with_stderr_contains(
+ "[RUNNING] `rustc --crate-name bar bar/src/lib.rs [..] -C opt-level=1 [..]`",
+ )
+ .with_stderr_contains(
+ "[RUNNING] `rustc --crate-name bar bar/src/lib.rs [..] -C opt-level=3 [..]`",
+ )
+ .with_stderr_contains("[RUNNING] `rustc --crate-name foo [..] -C opt-level=3 [..]`")
+ .run();
+}
+
+#[cargo_test]
+fn dependencies_of_dependencies_work_in_artifacts() {
+ Package::new("baz", "1.0.0")
+ .file("src/lib.rs", "pub fn baz() {}")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ resolver = "2"
+
+ [build-dependencies]
+ bar = { path = "bar/", artifact = "bin" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ std::process::Command::new(std::env::var("CARGO_BIN_FILE_BAR").expect("BAR present")).status().unwrap();
+ }
+ "#,
+ )
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ baz = "1.0.0"
+ "#,
+ )
+ .file("bar/src/lib.rs", r#"pub fn bar() {baz::baz()}"#)
+ .file("bar/src/main.rs", r#"fn main() {bar::bar()}"#)
+ .build();
+ p.cargo("build -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .run();
+
+ // cargo tree sees artifacts as the dependency kind they are in and doesn't do anything special with it.
+ p.cargo("tree -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_stdout(
+ "\
+foo v0.0.0 ([CWD])
+[build-dependencies]
+└── bar v0.5.0 ([CWD]/bar)
+ └── baz v1.0.0
+",
+ )
+ .run();
+}
+
+// TODO: Fix this potentially by reverting 887562bfeb8c540594d7d08e6e9a4ab7eb255865 which adds artifact information to the registry
+// followed by 0ff93733626f7cbecaf9dce9ab62b4ced0be088e which picks it up.
+// For reference, see comments by ehuss https://github.com/rust-lang/cargo/pull/9992#discussion_r801086315 and
+// joshtriplett https://github.com/rust-lang/cargo/pull/9992#issuecomment-1033394197 .
+#[cargo_test]
+#[ignore = "broken, need artifact info in index"]
+fn targets_are_picked_up_from_non_workspace_artifact_deps() {
+ if cross_compile::disabled() {
+ return;
+ }
+ let target = cross_compile::alternate();
+ Package::new("artifact", "1.0.0")
+ .file("src/main.rs", r#"fn main() {}"#)
+ .file("src/lib.rs", r#"pub fn lib() {}"#)
+ .publish();
+
+ let mut dep = registry::Dependency::new("artifact", "1.0.0");
+ Package::new("uses-artifact", "1.0.0")
+ .file(
+ "src/lib.rs",
+ r#"pub fn uses_artifact() { let _b = include_bytes!(env!("CARGO_BIN_FILE_ARTIFACT")); }"#,
+ )
+ .add_dep(dep.artifact("bin", Some(target.to_string())))
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+
+ [dependencies]
+ uses-artifact = { version = "1.0.0" }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"pub fn foo() { uses_artifact::uses_artifact(); }"#,
+ )
+ .build();
+
+ p.cargo("build -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .run();
+}
+
+#[cargo_test]
+fn allow_dep_renames_with_multiple_versions() {
+ Package::new("bar", "1.0.0")
+ .file("src/main.rs", r#"fn main() {println!("1.0.0")}"#)
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ resolver = "2"
+
+ [build-dependencies]
+ bar = { path = "bar/", artifact = "bin" }
+ bar_stable = { package = "bar", version = "1.0.0", artifact = "bin" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ std::process::Command::new(std::env::var("CARGO_BIN_FILE_BAR").expect("BAR present")).status().unwrap();
+ std::process::Command::new(std::env::var("CARGO_BIN_FILE_BAR_STABLE_bar").expect("BAR STABLE present")).status().unwrap();
+ }
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_bin_manifest("bar"))
+ .file("bar/src/main.rs", r#"fn main() {println!("0.5.0")}"#)
+ .build();
+ p.cargo("check -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_stderr_contains("[COMPILING] bar [..]")
+ .with_stderr_contains("[COMPILING] foo [..]")
+ .run();
+ let build_script_output = build_script_output_string(&p, "foo");
+ match_exact(
+ "0.5.0\n1.0.0",
+ &build_script_output,
+ "build script output",
+ "",
+ None,
+ )
+ .unwrap();
+}
+
+#[cargo_test]
+fn allow_artifact_and_non_artifact_dependency_to_same_crate_if_these_are_not_the_same_dep_kind() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ resolver = "2"
+
+ [build-dependencies]
+ bar = { path = "bar/", artifact = "bin", lib = false }
+
+ [dependencies]
+ bar = { path = "bar/" }
+ "#,
+ )
+ .file("src/lib.rs", r#"
+ pub fn foo() {
+ bar::doit();
+ assert!(option_env!("CARGO_BIN_FILE_BAR").is_none());
+ }"#)
+ .file(
+ "build.rs",
+ r#"fn main() {
+ println!("{}", std::env::var("CARGO_BIN_FILE_BAR").expect("CARGO_BIN_FILE_BAR"));
+ println!("{}", std::env::var("CARGO_BIN_FILE_BAR_bar").expect("CARGO_BIN_FILE_BAR_bar"));
+ }"#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "pub fn doit() {}")
+ .file("bar/src/main.rs", "fn main() {}")
+ .build();
+ p.cargo("build -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_stderr(
+ "\
+[COMPILING] bar [..]
+[COMPILING] foo [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn prevent_no_lib_warning_with_artifact_dependencies() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ resolver = "2"
+
+ [dependencies]
+ bar = { path = "bar/", artifact = "bin" }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"pub fn foo() { let _b = include_bytes!(env!("CARGO_BIN_FILE_BAR")); }"#,
+ )
+ .file("bar/Cargo.toml", &basic_bin_manifest("bar"))
+ .file("bar/src/main.rs", "fn main() {}")
+ .build();
+ p.cargo("check -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_stderr(
+ "\
+ [COMPILING] bar v0.5.0 ([CWD]/bar)\n\
+ [CHECKING] foo v0.0.0 ([CWD])\n\
+ [FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn show_no_lib_warning_with_artifact_dependencies_that_have_no_lib_but_lib_true() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ resolver = "2"
+
+ [build-dependencies]
+ bar = { path = "bar/", artifact = "bin" }
+
+ [dependencies]
+ bar = { path = "bar/", artifact = "bin", lib = true }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("src/build.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_bin_manifest("bar"))
+ .file("bar/src/main.rs", "fn main() {}")
+ .build();
+ p.cargo("check -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_stderr_contains("[WARNING] foo v0.0.0 ([CWD]) ignoring invalid dependency `bar` which is missing a lib target")
+ .with_stderr_contains("[COMPILING] bar v0.5.0 ([CWD]/bar)")
+ .with_stderr_contains("[CHECKING] foo [..]")
+ .with_stderr_contains("[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]")
+ .run();
+}
+
+#[cargo_test]
+fn resolver_2_build_dep_without_lib() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ edition = "2021"
+
+ [build-dependencies]
+ bar = { path = "bar/", artifact = "bin" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("build.rs", r#"
+ fn main() {
+ let bar: std::path::PathBuf = std::env::var("CARGO_BIN_FILE_BAR").expect("CARGO_BIN_FILE_BAR").into();
+ assert!(&bar.is_file());
+ }"#)
+ .file("bar/Cargo.toml", &basic_bin_manifest("bar"))
+ .file("bar/src/main.rs", "fn main() {}")
+ .build();
+ p.cargo("check -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .run();
+}
+
+#[cargo_test]
+fn check_missing_crate_type_in_package_fails() {
+ for crate_type in &["cdylib", "staticlib", "bin"] {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+
+ [dependencies]
+ bar = {{ path = "bar/", artifact = "{}" }}
+ "#,
+ crate_type
+ ),
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1")) //no bin, just rlib
+ .file("bar/src/lib.rs", "")
+ .build();
+ p.cargo("check -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_status(101)
+ .with_stderr(
+ "[ERROR] dependency `bar` in package `foo` requires a `[..]` artifact to be present.",
+ )
+ .run();
+ }
+}
+
+#[cargo_test]
+fn check_target_equals_target_in_non_build_dependency_errors() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ resolver = "2"
+
+ [dependencies]
+ bar = { path = "bar/", artifact = "bin", target = "target" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/main.rs", "fn main() {}")
+ .build();
+ p.cargo("check -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_status(101)
+ .with_stderr_contains(
+ " `target = \"target\"` in normal- or dev-dependencies has no effect (bar)",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn env_vars_and_build_products_for_various_build_targets() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ resolver = "2"
+
+ [lib]
+ doctest = true
+
+ [build-dependencies]
+ bar = { path = "bar/", artifact = ["cdylib", "staticlib"] }
+
+ [dependencies]
+ bar = { path = "bar/", artifact = "bin", lib = true }
+
+ [dev-dependencies]
+ bar = { path = "bar/", artifact = "bin:baz" }
+ "#,
+ )
+ .file("build.rs", r#"
+ fn main() {
+ let file: std::path::PathBuf = std::env::var("CARGO_CDYLIB_FILE_BAR").expect("CARGO_CDYLIB_FILE_BAR").into();
+ assert!(&file.is_file());
+
+ let file: std::path::PathBuf = std::env::var("CARGO_STATICLIB_FILE_BAR").expect("CARGO_STATICLIB_FILE_BAR").into();
+ assert!(&file.is_file());
+
+ assert!(std::env::var("CARGO_BIN_FILE_BAR").is_err());
+ assert!(std::env::var("CARGO_BIN_FILE_BAR_baz").is_err());
+ }
+ "#)
+ .file(
+ "src/lib.rs",
+ r#"
+ //! ```
+ //! bar::c();
+ //! env!("CARGO_BIN_DIR_BAR");
+ //! let _b = include_bytes!(env!("CARGO_BIN_FILE_BAR"));
+ //! let _b = include_bytes!(env!("CARGO_BIN_FILE_BAR_bar"));
+ //! let _b = include_bytes!(env!("CARGO_BIN_FILE_BAR_baz"));
+ //! assert!(option_env!("CARGO_STATICLIB_FILE_BAR").is_none());
+ //! assert!(option_env!("CARGO_CDYLIB_FILE_BAR").is_none());
+ //! ```
+ pub fn foo() {
+ bar::c();
+ env!("CARGO_BIN_DIR_BAR");
+ let _b = include_bytes!(env!("CARGO_BIN_FILE_BAR"));
+ let _b = include_bytes!(env!("CARGO_BIN_FILE_BAR_bar"));
+ let _b = include_bytes!(env!("CARGO_BIN_FILE_BAR_baz"));
+ assert!(option_env!("CARGO_STATICLIB_FILE_BAR").is_none());
+ assert!(option_env!("CARGO_CDYLIB_FILE_BAR").is_none());
+ }
+
+ #[cfg(test)]
+ #[test]
+ fn env_unit() {
+ env!("CARGO_BIN_DIR_BAR");
+ let _b = include_bytes!(env!("CARGO_BIN_FILE_BAR"));
+ let _b = include_bytes!(env!("CARGO_BIN_FILE_BAR_bar"));
+ let _b = include_bytes!(env!("CARGO_BIN_FILE_BAR_baz"));
+ assert!(option_env!("CARGO_STATICLIB_FILE_BAR").is_none());
+ assert!(option_env!("CARGO_CDYLIB_FILE_BAR").is_none());
+ }
+ "#,
+ )
+ .file(
+ "tests/main.rs",
+ r#"
+ #[test]
+ fn env_integration() {
+ env!("CARGO_BIN_DIR_BAR");
+ let _b = include_bytes!(env!("CARGO_BIN_FILE_BAR"));
+ let _b = include_bytes!(env!("CARGO_BIN_FILE_BAR_bar"));
+ let _b = include_bytes!(env!("CARGO_BIN_FILE_BAR_baz"));
+ }"#,
+ )
+ .file("build.rs", "fn main() {}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.5.0"
+ authors = []
+
+ [lib]
+ crate-type = ["staticlib", "cdylib", "rlib"]
+
+ [[bin]]
+ name = "bar"
+
+ [[bin]]
+ name = "baz"
+ "#,
+ )
+ .file("bar/src/lib.rs", r#"pub extern "C" fn c() {}"#)
+ .file("bar/src/main.rs", "fn main() {}")
+ .build();
+ p.cargo("test -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_stderr(
+ "\
+[COMPILING] bar [..]
+[COMPILING] foo [..]
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] unittests [..]
+[RUNNING] tests/main.rs [..]
+[DOCTEST] foo
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn publish_artifact_dep() {
+ let registry = RegistryBuilder::new().http_api().http_index().build();
+
+ Package::new("bar", "1.0.0").publish();
+ Package::new("baz", "1.0.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ documentation = "foo"
+ homepage = "foo"
+ repository = "foo"
+ resolver = "2"
+
+ [dependencies]
+ bar = { version = "1.0", artifact = "bin", lib = true }
+
+ [build-dependencies]
+ baz = { version = "1.0", artifact = ["bin:a", "cdylib", "staticlib"], target = "target" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("publish -Z bindeps --no-verify")
+ .replace_crates_io(registry.index_url())
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[PACKAGING] foo v0.1.0 [..]
+[PACKAGED] [..]
+[UPLOADING] foo v0.1.0 [..]
+[UPLOADED] foo v0.1.0 [..]
+note: Waiting [..]
+You may press ctrl-c [..]
+[PUBLISHED] foo v0.1.0 [..]
+",
+ )
+ .run();
+
+ publish::validate_upload_with_contents(
+ r#"
+ {
+ "authors": [],
+ "badges": {},
+ "categories": [],
+ "deps": [{
+ "default_features": true,
+ "features": [],
+ "kind": "normal",
+ "name": "bar",
+ "optional": false,
+ "target": null,
+ "version_req": "^1.0"
+ },
+ {
+ "default_features": true,
+ "features": [],
+ "kind": "build",
+ "name": "baz",
+ "optional": false,
+ "target": null,
+ "version_req": "^1.0"
+ }
+ ],
+ "description": "foo",
+ "documentation": "foo",
+ "features": {},
+ "homepage": "foo",
+ "keywords": [],
+ "license": "MIT",
+ "license_file": null,
+ "links": null,
+ "name": "foo",
+ "readme": null,
+ "readme_file": null,
+ "repository": "foo",
+ "vers": "0.1.0"
+ }
+ "#,
+ "foo-0.1.0.crate",
+ &["Cargo.toml", "Cargo.toml.orig", "src/lib.rs"],
+ &[(
+ "Cargo.toml",
+ &format!(
+ r#"{}
+[package]
+name = "foo"
+version = "0.1.0"
+authors = []
+description = "foo"
+homepage = "foo"
+documentation = "foo"
+license = "MIT"
+repository = "foo"
+resolver = "2"
+
+[dependencies.bar]
+version = "1.0"
+artifact = ["bin"]
+lib = true
+
+[build-dependencies.baz]
+version = "1.0"
+artifact = [
+ "bin:a",
+ "cdylib",
+ "staticlib",
+]
+target = "target""#,
+ cargo::core::package::MANIFEST_PREAMBLE
+ ),
+ )],
+ );
+}
+
+#[cargo_test]
+fn doc_lib_true() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ resolver = "2"
+
+ [dependencies.bar]
+ path = "bar"
+ artifact = "bin"
+ lib = true
+ "#,
+ )
+ .file("src/lib.rs", "extern crate bar; pub fn foo() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .file("bar/src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("doc -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_stderr(
+ "\
+[COMPILING] bar v0.0.1 ([CWD]/bar)
+[DOCUMENTING] bar v0.0.1 ([CWD]/bar)
+[DOCUMENTING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ assert!(p.root().join("target/doc").is_dir());
+ assert!(p.root().join("target/doc/foo/index.html").is_file());
+ assert!(p.root().join("target/doc/bar/index.html").is_file());
+
+ // Verify that it emits rmeta for the bin and lib dependency.
+ assert_eq!(p.glob("target/debug/artifact/*.rlib").count(), 0);
+ assert_eq!(p.glob("target/debug/deps/libbar-*.rmeta").count(), 2);
+
+ p.cargo("doc -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .env("CARGO_LOG", "cargo::ops::cargo_rustc::fingerprint")
+ .with_stdout("")
+ .run();
+
+ assert!(p.root().join("target/doc").is_dir());
+ assert!(p.root().join("target/doc/foo/index.html").is_file());
+ assert!(p.root().join("target/doc/bar/index.html").is_file());
+}
+
+#[cargo_test]
+fn rustdoc_works_on_libs_with_artifacts_and_lib_false() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ resolver = "2"
+
+ [dependencies.bar]
+ path = "bar"
+ artifact = ["bin", "staticlib", "cdylib"]
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn foo() {
+ env!("CARGO_BIN_DIR_BAR");
+ let _b = include_bytes!(env!("CARGO_BIN_FILE_BAR"));
+ let _b = include_bytes!(env!("CARGO_CDYLIB_FILE_BAR"));
+ let _b = include_bytes!(env!("CARGO_CDYLIB_FILE_BAR_bar"));
+ let _b = include_bytes!(env!("CARGO_STATICLIB_FILE_BAR"));
+ let _b = include_bytes!(env!("CARGO_STATICLIB_FILE_BAR_bar"));
+ }"#,
+ )
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.5.0"
+ authors = []
+
+ [lib]
+ crate-type = ["staticlib", "cdylib"]
+ "#,
+ )
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .file("bar/src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("doc -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_stderr(
+ "\
+[COMPILING] bar v0.5.0 ([CWD]/bar)
+[DOCUMENTING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ assert!(p.root().join("target/doc").is_dir());
+ assert!(p.root().join("target/doc/foo/index.html").is_file());
+ assert!(
+ !p.root().join("target/doc/bar/index.html").is_file(),
+ "bar is not a lib dependency and thus remains undocumented"
+ );
+}
+
+fn assert_artifact_executable_output(
+ p: &Project,
+ target_name: &str,
+ dep_name: &str,
+ bin_name: &str,
+) {
+ if cfg!(target_env = "msvc") {
+ assert_eq!(
+ p.glob(format!(
+ "target/{}/deps/artifact/{}-*/bin/{}{}",
+ target_name,
+ dep_name,
+ bin_name,
+ std::env::consts::EXE_SUFFIX
+ ))
+ .count(),
+ 1,
+ "artifacts are placed into their own output directory to not possibly clash"
+ );
+ } else {
+ assert_eq!(
+ p.glob(format!(
+ "target/{}/deps/artifact/{}-*/bin/{}-*{}",
+ target_name,
+ dep_name,
+ bin_name,
+ std::env::consts::EXE_SUFFIX
+ ))
+ .filter_map(Result::ok)
+ .filter(|f| f.extension().map_or(true, |ext| ext != "o" && ext != "d"))
+ .count(),
+ 1,
+ "artifacts are placed into their own output directory to not possibly clash"
+ );
+ }
+}
+
+fn build_script_output_string(p: &Project, package_name: &str) -> String {
+ let paths = p
+ .glob(format!("target/debug/build/{}-*/output", package_name))
+ .collect::<Result<Vec<_>, _>>()
+ .unwrap();
+ assert_eq!(paths.len(), 1);
+ std::fs::read_to_string(&paths[0]).unwrap()
+}
+
+#[cargo_test]
+fn build_script_features_for_shared_dependency() {
+ // When a build script is built and run, its features should match. Here:
+ //
+ // foo
+ // -> artifact on d1 with target
+ // -> common with features f1
+ //
+ // d1
+ // -> common with features f2
+ //
+ // common has features f1 and f2, with a build script.
+ //
+ // When common is built as a dependency of d1, it should have features
+ // `f2` (for the library and the build script).
+ //
+ // When common is built as a dependency of foo, it should have features
+ // `f1` (for the library and the build script).
+ if cross_compile::disabled() {
+ return;
+ }
+ let target = cross_compile::alternate();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ resolver = "2"
+
+ [dependencies]
+ d1 = { path = "d1", artifact = "bin", target = "$TARGET" }
+ common = { path = "common", features = ["f1"] }
+ "#
+ .replace("$TARGET", target),
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ let _b = include_bytes!(env!("CARGO_BIN_FILE_D1"));
+ common::f1();
+ }
+ "#,
+ )
+ .file(
+ "d1/Cargo.toml",
+ r#"
+ [package]
+ name = "d1"
+ version = "0.0.1"
+
+ [dependencies]
+ common = { path = "../common", features = ["f2"] }
+ "#,
+ )
+ .file(
+ "d1/src/main.rs",
+ r#"fn main() {
+ common::f2();
+ }"#,
+ )
+ .file(
+ "common/Cargo.toml",
+ r#"
+ [package]
+ name = "common"
+ version = "0.0.1"
+
+ [features]
+ f1 = []
+ f2 = []
+ "#,
+ )
+ .file(
+ "common/src/lib.rs",
+ r#"
+ #[cfg(feature = "f1")]
+ pub fn f1() {}
+
+ #[cfg(feature = "f2")]
+ pub fn f2() {}
+ "#,
+ )
+ .file(
+ "common/build.rs",
+ &r#"
+ use std::env::var_os;
+ fn main() {
+ assert_eq!(var_os("CARGO_FEATURE_F1").is_some(), cfg!(feature="f1"));
+ assert_eq!(var_os("CARGO_FEATURE_F2").is_some(), cfg!(feature="f2"));
+ if std::env::var("TARGET").unwrap() == "$TARGET" {
+ assert!(var_os("CARGO_FEATURE_F1").is_none());
+ assert!(var_os("CARGO_FEATURE_F2").is_some());
+ } else {
+ assert!(var_os("CARGO_FEATURE_F1").is_some());
+ assert!(var_os("CARGO_FEATURE_F2").is_none());
+ }
+ }
+ "#
+ .replace("$TARGET", target),
+ )
+ .build();
+
+ p.cargo("build -Z bindeps -v")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .run();
+}
+
+#[cargo_test]
+fn calc_bin_artifact_fingerprint() {
+ // See rust-lang/cargo#10527
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ resolver = "2"
+
+ [dependencies]
+ bar = { path = "bar/", artifact = "bin" }
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ let _b = include_bytes!(env!("CARGO_BIN_FILE_BAR"));
+ }
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_bin_manifest("bar"))
+ .file("bar/src/main.rs", r#"fn main() { println!("foo") }"#)
+ .build();
+ p.cargo("check -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_stderr(
+ "\
+[COMPILING] bar v0.5.0 ([CWD]/bar)
+[CHECKING] foo v0.1.0 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.change_file("bar/src/main.rs", r#"fn main() { println!("bar") }"#);
+ // Change in artifact bin dep `bar` propagates to `foo`, triggering recompile.
+ p.cargo("check -v -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_stderr(
+ "\
+[DIRTY] bar v0.5.0 ([CWD]/bar): the file `bar/src/main.rs` has changed ([..])
+[COMPILING] bar v0.5.0 ([CWD]/bar)
+[RUNNING] `rustc --crate-name bar [..]`
+[DIRTY] foo v0.1.0 ([CWD]): the dependency bar was rebuilt
+[CHECKING] foo v0.1.0 ([CWD])
+[RUNNING] `rustc --crate-name foo [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ // All units are fresh. No recompile.
+ p.cargo("check -v -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_stderr(
+ "\
+[FRESH] bar v0.5.0 ([CWD]/bar)
+[FRESH] foo v0.1.0 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn with_target_and_optional() {
+ // See rust-lang/cargo#10526
+ if cross_compile::disabled() {
+ return;
+ }
+ let target = cross_compile::alternate();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ edition = "2021"
+ [dependencies]
+ d1 = { path = "d1", artifact = "bin", optional = true, target = "$TARGET" }
+ "#
+ .replace("$TARGET", target),
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ let _b = include_bytes!(env!("CARGO_BIN_FILE_D1"));
+ }
+ "#,
+ )
+ .file(
+ "d1/Cargo.toml",
+ r#"
+ [package]
+ name = "d1"
+ version = "0.0.1"
+ edition = "2021"
+ "#,
+ )
+ .file("d1/src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check -Z bindeps -F d1 -v")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_stderr(
+ "\
+[COMPILING] d1 v0.0.1 [..]
+[RUNNING] `rustc --crate-name d1 [..]--crate-type bin[..]
+[CHECKING] foo v0.0.1 [..]
+[RUNNING] `rustc --crate-name foo [..]--cfg[..]d1[..]
+[FINISHED] dev [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn with_assumed_host_target_and_optional_build_dep() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ edition = "2021"
+ [build-dependencies]
+ d1 = { path = "d1", artifact = "bin", optional = true, target = "target" }
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ std::env::var("CARGO_BIN_FILE_D1").unwrap();
+ }
+ "#,
+ )
+ .file(
+ "d1/Cargo.toml",
+ r#"
+ [package]
+ name = "d1"
+ version = "0.0.1"
+ edition = "2021"
+ "#,
+ )
+ .file("d1/src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check -Z bindeps -F d1 -v")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_stderr_unordered(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[COMPILING] d1 v0.0.1 ([CWD]/d1)
+[RUNNING] `rustc --crate-name build_script_build [..]--crate-type bin[..]
+[RUNNING] `rustc --crate-name d1 [..]--crate-type bin[..]
+[RUNNING] `[CWD]/target/debug/build/foo-[..]/build-script-build`
+[RUNNING] `rustc --crate-name foo [..]--cfg[..]d1[..]
+[FINISHED] dev [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn decouple_same_target_transitive_dep_from_artifact_dep() {
+ // See https://github.com/rust-lang/cargo/issues/11463
+ let target = rustc_host();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2021"
+
+ [dependencies]
+ a = {{ path = "a" }}
+ bar = {{ path = "bar", artifact = "bin", target = "{target}" }}
+ "#
+ ),
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {}
+ "#,
+ )
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+
+ [dependencies]
+ a = { path = "../a", features = ["feature"] }
+ "#,
+ )
+ .file(
+ "bar/src/main.rs",
+ r#"
+ fn main() {}
+ "#,
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+ edition = "2021"
+
+ [dependencies]
+ b = { path = "../b" }
+ c = { path = "../c" }
+
+ [features]
+ feature = ["c/feature"]
+ "#,
+ )
+ .file(
+ "a/src/lib.rs",
+ r#"
+ use b::Trait as _;
+
+ pub fn use_b_trait(x: &impl c::Trait) {
+ x.b();
+ }
+ "#,
+ )
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.1.0"
+
+ [dependencies]
+ c = { path = "../c" }
+ "#,
+ )
+ .file(
+ "b/src/lib.rs",
+ r#"
+ pub trait Trait {
+ fn b(&self) {}
+ }
+
+ impl<T: c::Trait> Trait for T {}
+ "#,
+ )
+ .file(
+ "c/Cargo.toml",
+ r#"
+ [package]
+ name = "c"
+ version = "0.1.0"
+
+ [features]
+ feature = []
+ "#,
+ )
+ .file(
+ "c/src/lib.rs",
+ r#"
+ pub trait Trait {}
+ "#,
+ )
+ .build();
+ p.cargo("build -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_stderr(
+ "\
+[COMPILING] c v0.1.0 ([CWD]/c)
+[COMPILING] b v0.1.0 ([CWD]/b)
+[COMPILING] a v0.1.0 ([CWD]/a)
+[COMPILING] bar v0.1.0 ([CWD]/bar)
+[COMPILING] foo v0.1.0 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn decouple_same_target_transitive_dep_from_artifact_dep_lib() {
+ // See https://github.com/rust-lang/cargo/issues/10837
+ let target = rustc_host();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2021"
+
+ [dependencies]
+ a = {{ path = "a" }}
+ b = {{ path = "b", features = ["feature"] }}
+ bar = {{ path = "bar", artifact = "bin", lib = true, target = "{target}" }}
+ "#
+ ),
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ edition = "2021"
+
+ [dependencies]
+ a = { path = "../a", features = ["b"] }
+ b = { path = "../b" }
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .file(
+ "bar/src/main.rs",
+ r#"
+ use b::Trait;
+
+ fn main() {
+ a::A.b()
+ }
+ "#,
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+
+ [dependencies]
+ b = { path = "../b", optional = true }
+ "#,
+ )
+ .file(
+ "a/src/lib.rs",
+ r#"
+ pub struct A;
+
+ #[cfg(feature = "b")]
+ impl b::Trait for A {}
+ "#,
+ )
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.1.0"
+
+ [features]
+ feature = []
+ "#,
+ )
+ .file(
+ "b/src/lib.rs",
+ r#"
+ pub trait Trait {
+ fn b(&self) {}
+ }
+ "#,
+ )
+ .build();
+ p.cargo("build -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_stderr(
+ "\
+[COMPILING] b v0.1.0 ([CWD]/b)
+[COMPILING] a v0.1.0 ([CWD]/a)
+[COMPILING] bar v0.1.0 ([CWD]/bar)
+[COMPILING] foo v0.1.0 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn decouple_same_target_transitive_dep_from_artifact_dep_and_proc_macro() {
+ let target = rustc_host();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2021"
+
+ [dependencies]
+ c = {{ path = "c" }}
+ bar = {{ path = "bar", artifact = "bin", target = "{target}" }}
+ "#
+ ),
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+
+ [dependencies]
+ b = { path = "../b" }
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}")
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.1.0"
+ edition = "2021"
+
+ [dependencies]
+ a = { path = "../a" }
+
+ [lib]
+ proc-macro = true
+ "#,
+ )
+ .file("b/src/lib.rs", "")
+ .file(
+ "c/Cargo.toml",
+ r#"
+ [package]
+ name = "c"
+ version = "0.1.0"
+ edition = "2021"
+
+ [dependencies]
+ d = { path = "../d", features = ["feature"] }
+ a = { path = "../a" }
+
+ [lib]
+ proc-macro = true
+ "#,
+ )
+ .file(
+ "c/src/lib.rs",
+ r#"
+ use a::Trait;
+
+ fn _c() {
+ d::D.a()
+ }
+ "#,
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+
+ [dependencies]
+ d = { path = "../d" }
+ "#,
+ )
+ .file(
+ "a/src/lib.rs",
+ r#"
+ pub trait Trait {
+ fn a(&self) {}
+ }
+
+ impl Trait for d::D {}
+ "#,
+ )
+ .file(
+ "d/Cargo.toml",
+ r#"
+ [package]
+ name = "d"
+ version = "0.1.0"
+
+ [features]
+ feature = []
+ "#,
+ )
+ .file("d/src/lib.rs", "pub struct D;")
+ .build();
+
+ p.cargo("build -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_stderr_unordered(
+ "\
+[COMPILING] d v0.1.0 ([CWD]/d)
+[COMPILING] a v0.1.0 ([CWD]/a)
+[COMPILING] b v0.1.0 ([CWD]/b)
+[COMPILING] c v0.1.0 ([CWD]/c)
+[COMPILING] bar v0.1.0 ([CWD]/bar)
+[COMPILING] foo v0.1.0 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn same_target_artifact_dep_sharing() {
+ let target = rustc_host();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ a = {{ path = "a" }}
+ bar = {{ path = "bar", artifact = "bin", target = "{target}" }}
+ "#
+ ),
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+
+ [dependencies]
+ a = { path = "../a" }
+ "#,
+ )
+ .file(
+ "bar/src/main.rs",
+ r#"
+ fn main() {}
+ "#,
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .build();
+ p.cargo(&format!("build -Z bindeps --target {target}"))
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_stderr(
+ "\
+[COMPILING] a v0.1.0 ([CWD]/a)
+[COMPILING] bar v0.1.0 ([CWD]/bar)
+[COMPILING] foo v0.1.0 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn check_transitive_artifact_dependency_with_different_target() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+
+ [dependencies]
+ bar = { path = "bar/" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.0"
+
+ [dependencies]
+ baz = { path = "baz/", artifact = "bin", target = "custom-target" }
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .file(
+ "bar/baz/Cargo.toml",
+ r#"
+ [package]
+ name = "baz"
+ version = "0.0.0"
+
+ [dependencies]
+ "#,
+ )
+ .file("bar/baz/src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_stderr_contains(
+ "error: could not find specification for target `custom-target`.\n \
+ Dependency `baz v0.0.0 [..]` requires to build for target `custom-target`.",
+ )
+ .with_status(101)
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/bad_config.rs b/src/tools/cargo/tests/testsuite/bad_config.rs
new file mode 100644
index 000000000..ca51b101e
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/bad_config.rs
@@ -0,0 +1,1514 @@
+//! Tests for some invalid .cargo/config files.
+
+use cargo_test_support::git::cargo_uses_gitoxide;
+use cargo_test_support::registry::{self, Package};
+use cargo_test_support::{basic_manifest, project, rustc_host};
+
+#[cargo_test]
+fn bad1() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ r#"
+ [target]
+ nonexistent-target = "foo"
+ "#,
+ )
+ .build();
+ p.cargo("check -v --target=nonexistent-target")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] expected table for configuration key `target.nonexistent-target`, \
+but found string in [..]/config
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bad2() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ r#"
+ [http]
+ proxy = 3.0
+ "#,
+ )
+ .build();
+ p.cargo("publish -v")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] could not load Cargo configuration
+
+Caused by:
+ failed to load TOML configuration from `[..]config`
+
+Caused by:
+ failed to parse key `http`
+
+Caused by:
+ failed to parse key `proxy`
+
+Caused by:
+ found TOML configuration value of unknown type `float`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bad3() {
+ let registry = registry::init();
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ r#"
+ [http]
+ proxy = true
+ "#,
+ )
+ .build();
+ Package::new("foo", "1.0.0").publish();
+
+ p.cargo("publish -v")
+ .replace_crates_io(registry.index_url())
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to update registry [..]
+
+Caused by:
+ error in [..]config: `http.proxy` expected a string, but found a boolean
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bad4() {
+ let p = project()
+ .file(
+ ".cargo/config",
+ r#"
+ [cargo-new]
+ vcs = false
+ "#,
+ )
+ .build();
+ p.cargo("new -v foo")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] Failed to create package `foo` at `[..]`
+
+Caused by:
+ error in [..]config: `cargo-new.vcs` expected a string, but found a boolean
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bad6() {
+ let registry = registry::init();
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ r#"
+ [http]
+ user-agent = true
+ "#,
+ )
+ .build();
+ Package::new("foo", "1.0.0").publish();
+
+ p.cargo("publish -v")
+ .replace_crates_io(registry.index_url())
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to update registry [..]
+
+Caused by:
+ error in [..]config: `http.user-agent` expected a string, but found a boolean
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn invalid_global_config() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+
+ [dependencies]
+ foo = "0.1.0"
+ "#,
+ )
+ .file(".cargo/config", "4")
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check -v")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] could not load Cargo configuration
+
+Caused by:
+ could not parse TOML configuration in `[..]`
+
+Caused by:
+ could not parse input as TOML
+
+Caused by:
+ TOML parse error at line 1, column 2
+ |
+ 1 | 4
+ | ^
+ expected `.`, `=`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bad_cargo_lock() {
+ let p = project()
+ .file("Cargo.lock", "[[package]]\nfoo = 92")
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check -v")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse lock file at: [..]Cargo.lock
+
+Caused by:
+ missing field `name`
+ in `package`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn duplicate_packages_in_cargo_lock() {
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "Cargo.lock",
+ r#"
+ [[package]]
+ name = "foo"
+ version = "0.0.1"
+ dependencies = [
+ "bar 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ ]
+
+ [[package]]
+ name = "bar"
+ version = "0.1.0"
+ source = "registry+https://github.com/rust-lang/crates.io-index"
+
+ [[package]]
+ name = "bar"
+ version = "0.1.0"
+ source = "registry+https://github.com/rust-lang/crates.io-index"
+ "#,
+ )
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse lock file at: [..]
+
+Caused by:
+ package `bar` is specified twice in the lockfile
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bad_source_in_cargo_lock() {
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "Cargo.lock",
+ r#"
+ [[package]]
+ name = "foo"
+ version = "0.0.1"
+ dependencies = [
+ "bar 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ ]
+
+ [[package]]
+ name = "bar"
+ version = "0.1.0"
+ source = "You shall not parse"
+ "#,
+ )
+ .build();
+
+ p.cargo("check --verbose")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse lock file at: [..]
+
+Caused by:
+ invalid source `You shall not parse`
+ in `package.source`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bad_dependency_in_lockfile() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(
+ "Cargo.lock",
+ r#"
+ [[package]]
+ name = "foo"
+ version = "0.0.1"
+ dependencies = [
+ "bar 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ ]
+ "#,
+ )
+ .build();
+
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn bad_git_dependency() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+
+ [dependencies]
+ foo = {{ git = "{url}" }}
+ "#,
+ url = if cargo_uses_gitoxide() {
+ "git://host.xz"
+ } else {
+ "file:.."
+ }
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ let expected_stderr = if cargo_uses_gitoxide() {
+ "\
+[UPDATING] git repository `git://host.xz`
+[ERROR] failed to get `foo` as a dependency of package `foo v0.0.0 [..]`
+
+Caused by:
+ failed to load source for dependency `foo`
+
+Caused by:
+ Unable to update git://host.xz
+
+Caused by:
+ failed to clone into: [..]
+
+Caused by:
+ URLs need to specify the path to the repository
+"
+ } else {
+ "\
+[UPDATING] git repository `file:///`
+[ERROR] failed to get `foo` as a dependency of package `foo v0.0.0 [..]`
+
+Caused by:
+ failed to load source for dependency `foo`
+
+Caused by:
+ Unable to update file:///
+
+Caused by:
+ failed to clone into: [..]
+
+Caused by:
+ [..]'file:///' is not a valid local file URI[..]
+"
+ };
+ p.cargo("check -v")
+ .with_status(101)
+ .with_stderr(expected_stderr)
+ .run();
+}
+
+#[cargo_test]
+fn bad_crate_type() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+
+ [lib]
+ crate-type = ["bad_type", "rlib"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build -v")
+ .with_status(101)
+ .with_stderr_contains(
+ "error: failed to run `rustc` to learn about crate-type bad_type information",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn malformed_override() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+
+ [target.x86_64-apple-darwin.freetype]
+ native = {
+ foo: "bar"
+ }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ could not parse input as TOML
+
+Caused by:
+ TOML parse error at line 8, column 27
+ |
+ 8 | native = {
+ | ^
+ invalid inline table
+ expected `}`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn duplicate_binary_names() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "qqq"
+ version = "0.1.0"
+ authors = ["A <a@a.a>"]
+
+ [[bin]]
+ name = "e"
+ path = "a.rs"
+
+ [[bin]]
+ name = "e"
+ path = "b.rs"
+ "#,
+ )
+ .file("a.rs", r#"fn main() -> () {}"#)
+ .file("b.rs", r#"fn main() -> () {}"#)
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ found duplicate binary name e, but all binary targets must have a unique name
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn duplicate_example_names() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "qqq"
+ version = "0.1.0"
+ authors = ["A <a@a.a>"]
+
+ [[example]]
+ name = "ex"
+ path = "examples/ex.rs"
+
+ [[example]]
+ name = "ex"
+ path = "examples/ex2.rs"
+ "#,
+ )
+ .file("examples/ex.rs", r#"fn main () -> () {}"#)
+ .file("examples/ex2.rs", r#"fn main () -> () {}"#)
+ .build();
+
+ p.cargo("check --example ex")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ found duplicate example name ex, but all example targets must have a unique name
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn duplicate_bench_names() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "qqq"
+ version = "0.1.0"
+ authors = ["A <a@a.a>"]
+
+ [[bench]]
+ name = "ex"
+ path = "benches/ex.rs"
+
+ [[bench]]
+ name = "ex"
+ path = "benches/ex2.rs"
+ "#,
+ )
+ .file("benches/ex.rs", r#"fn main () {}"#)
+ .file("benches/ex2.rs", r#"fn main () {}"#)
+ .build();
+
+ p.cargo("bench")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ found duplicate bench name ex, but all bench targets must have a unique name
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn duplicate_deps() {
+ let p = project()
+ .file("shim-bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("shim-bar/src/lib.rs", "pub fn a() {}")
+ .file("linux-bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("linux-bar/src/lib.rs", "pub fn a() {}")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "qqq"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = { path = "shim-bar" }
+
+ [target.x86_64-unknown-linux-gnu.dependencies]
+ bar = { path = "linux-bar" }
+ "#,
+ )
+ .file("src/main.rs", r#"fn main () {}"#)
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ Dependency 'bar' has different source paths depending on the build target. Each dependency must \
+have a single canonical source path irrespective of build target.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn duplicate_deps_diff_sources() {
+ let p = project()
+ .file("shim-bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("shim-bar/src/lib.rs", "pub fn a() {}")
+ .file("linux-bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("linux-bar/src/lib.rs", "pub fn a() {}")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "qqq"
+ version = "0.0.1"
+ authors = []
+
+ [target.i686-unknown-linux-gnu.dependencies]
+ bar = { path = "shim-bar" }
+
+ [target.x86_64-unknown-linux-gnu.dependencies]
+ bar = { path = "linux-bar" }
+ "#,
+ )
+ .file("src/main.rs", r#"fn main () {}"#)
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ Dependency 'bar' has different source paths depending on the build target. Each dependency must \
+have a single canonical source path irrespective of build target.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn unused_keys() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [target.foo]
+ bar = "3"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+warning: unused manifest key: target.foo.bar
+[CHECKING] foo v0.1.0 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+ bulid = "foo"
+ "#,
+ )
+ .file("src/lib.rs", "pub fn foo() {}")
+ .build();
+ p.cargo("check")
+ .with_stderr(
+ "\
+warning: unused manifest key: package.bulid
+[CHECKING] foo [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ let p = project()
+ .at("bar")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [lib]
+ build = "foo"
+ "#,
+ )
+ .file("src/lib.rs", "pub fn foo() {}")
+ .build();
+ p.cargo("check")
+ .with_stderr(
+ "\
+warning: unused manifest key: lib.build
+[CHECKING] foo [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn unused_keys_in_virtual_manifest() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar"]
+ bulid = "foo"
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "")
+ .build();
+ p.cargo("check --workspace")
+ .with_stderr(
+ "\
+[WARNING] [..]/foo/Cargo.toml: unused manifest key: workspace.bulid
+[CHECKING] bar [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn empty_dependencies() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+
+ [dependencies]
+ bar = {}
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("bar", "0.0.1").publish();
+
+ p.cargo("check")
+ .with_stderr_contains(
+ "\
+warning: dependency (bar) specified without providing a local path, Git repository, or version \
+to use. This will be considered an error in future versions
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn invalid_toml_historically_allowed_fails() {
+ let p = project()
+ .file(".cargo/config", "[bar] baz = 2")
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: could not load Cargo configuration
+
+Caused by:
+ could not parse TOML configuration in `[..]`
+
+Caused by:
+ could not parse input as TOML
+
+Caused by:
+ TOML parse error at line 1, column 7
+ |
+ 1 | [bar] baz = 2
+ | ^
+ invalid table header
+ expected newline, `#`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn ambiguous_git_reference() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+
+ [dependencies.bar]
+ git = "http://127.0.0.1"
+ branch = "master"
+ tag = "some-tag"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check -v")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ dependency (bar) specification is ambiguous. Only one of `branch`, `tag` or `rev` is allowed.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn fragment_in_git_url() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+
+ [dependencies.bar]
+ git = "http://127.0.0.1#foo"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check -v")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+[WARNING] URL fragment `#foo` in git URL is ignored for dependency (bar). \
+If you were trying to specify a specific git revision, \
+use `rev = \"foo\"` in the dependency declaration.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bad_source_config1() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(".cargo/config", "[source.foo]")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr("error: no source location specified for `source.foo`, need [..]")
+ .run();
+}
+
+#[cargo_test]
+fn bad_source_config2() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+
+ [dependencies]
+ bar = "*"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ r#"
+ [source.crates-io]
+ registry = 'http://example.com'
+ replace-with = 'bar'
+ "#,
+ )
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to get `bar` as a dependency of package `foo v0.0.0 [..]`
+
+Caused by:
+ failed to load source for dependency `bar`
+
+Caused by:
+ Unable to update registry `crates-io`
+
+Caused by:
+ could not find a configured source with the name `bar` \
+ when attempting to lookup `crates-io` (configuration in [..])
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bad_source_config3() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+
+ [dependencies]
+ bar = "*"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ r#"
+ [source.crates-io]
+ registry = 'https://example.com'
+ replace-with = 'crates-io'
+ "#,
+ )
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to get `bar` as a dependency of package `foo v0.0.0 [..]`
+
+Caused by:
+ failed to load source for dependency `bar`
+
+Caused by:
+ Unable to update registry `crates-io`
+
+Caused by:
+ detected a cycle of `replace-with` sources, [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bad_source_config4() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+
+ [dependencies]
+ bar = "*"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ r#"
+ [source.crates-io]
+ replace-with = 'bar'
+
+ [source.bar]
+ registry = 'https://example.com'
+ replace-with = 'crates-io'
+ "#,
+ )
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to get `bar` as a dependency of package `foo v0.0.0 ([..])`
+
+Caused by:
+ failed to load source for dependency `bar`
+
+Caused by:
+ Unable to update registry `crates-io`
+
+Caused by:
+ detected a cycle of `replace-with` sources, the source `crates-io` is \
+ eventually replaced with itself (configuration in [..])
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bad_source_config5() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+
+ [dependencies]
+ bar = "*"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ r#"
+ [source.crates-io]
+ registry = 'https://example.com'
+ replace-with = 'bar'
+
+ [source.bar]
+ registry = 'not a url'
+ "#,
+ )
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: configuration key `source.bar.registry` specified an invalid URL (in [..])
+
+Caused by:
+ invalid url `not a url`: [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn both_git_and_path_specified() {
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+
+ [dependencies.bar]
+ git = "http://127.0.0.1"
+ path = "bar"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ foo.cargo("check -v")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[..]`
+
+Caused by:
+ dependency (bar) specification is ambiguous. Only one of `git` or `path` is allowed.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bad_source_config6() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+
+ [dependencies]
+ bar = "*"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ r#"
+ [source.crates-io]
+ registry = 'https://example.com'
+ replace-with = ['not', 'a', 'string']
+ "#,
+ )
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] error in [..]/foo/.cargo/config: could not load config key `source.crates-io.replace-with`
+
+Caused by:
+ error in [..]/foo/.cargo/config: `source.crates-io.replace-with` expected a string, but found a array
+"
+ )
+ .run();
+}
+
+#[cargo_test]
+fn ignored_git_revision() {
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+
+ [dependencies.bar]
+ path = "bar"
+ branch = "spam"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ let err_msg = "\
+error: failed to parse manifest at `[..]`
+
+Caused by:
+ key `branch` is ignored for dependency (bar).
+";
+ foo.cargo("check -v")
+ .with_status(101)
+ .with_stderr(err_msg)
+ .run();
+
+ // #11540, check that [target] dependencies fail the same way.
+ foo.change_file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+
+ [target.some-target.dependencies]
+ bar = { path = "bar", branch = "spam" }
+ "#,
+ );
+ foo.cargo("check")
+ .with_status(101)
+ .with_stderr(err_msg)
+ .run();
+}
+
+#[cargo_test]
+fn bad_source_config7() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+
+ [dependencies]
+ bar = "*"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ r#"
+ [source.foo]
+ registry = 'https://example.com'
+ local-registry = 'file:///another/file'
+ "#,
+ )
+ .build();
+
+ Package::new("bar", "0.1.0").publish();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr("error: more than one source location specified for `source.foo`")
+ .run();
+}
+
+#[cargo_test]
+fn bad_source_config8() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+
+ [dependencies]
+ bar = "*"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ r#"
+ [source.foo]
+ branch = "somebranch"
+ "#,
+ )
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "[ERROR] source definition `source.foo` specifies `branch`, \
+ but that requires a `git` key to be specified (in [..]/foo/.cargo/config)",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bad_dependency() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+
+ [dependencies]
+ bar = 3
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[..]`
+
+Caused by:
+ invalid type: integer `3`, expected a version string like [..]
+ in `dependencies.bar`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bad_debuginfo() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+
+ [profile.dev]
+ debug = 'a'
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[..]`
+
+Caused by:
+ expected a boolean or an integer
+ in `profile.dev.debug`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bad_opt_level() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ build = 3
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[..]`
+
+Caused by:
+ expected a boolean or a string
+ in `package.build`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn warn_semver_metadata() {
+ Package::new("bar", "1.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "1.0.0"
+
+ [dependencies]
+ bar = "1.0.0+1234"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("check")
+ .with_stderr_contains("[WARNING] version requirement `1.0.0+1234` for dependency `bar`[..]")
+ .run();
+}
+
+#[cargo_test]
+fn bad_target_cfg() {
+ // Invalid type in a StringList.
+ //
+ // The error message is a bit unfortunate here. The type here ends up
+ // being essentially Value<Value<StringList>>, and each layer of "Value"
+ // adds some context to the error message. Also, untagged enums provide
+ // strange error messages. Hopefully most users will be able to untangle
+ // the message.
+ let p = project()
+ .file(
+ ".cargo/config",
+ r#"
+ [target.'cfg(not(target_os = "none"))']
+ runner = false
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] error in [..]/foo/.cargo/config: \
+could not load config key `target.\"cfg(not(target_os = \\\"none\\\"))\".runner`
+
+Caused by:
+ error in [..]/foo/.cargo/config: \
+ could not load config key `target.\"cfg(not(target_os = \\\"none\\\"))\".runner`
+
+Caused by:
+ invalid configuration for key `target.\"cfg(not(target_os = \\\"none\\\"))\".runner`
+ expected a string or array of strings, but found a boolean for \
+ `target.\"cfg(not(target_os = \\\"none\\\"))\".runner` in [..]/foo/.cargo/config
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bad_target_links_overrides() {
+ // Invalid parsing of links overrides.
+ //
+ // This error message is terrible. Nothing in the deserialization path is
+ // using config::Value<>, so nothing is able to report the location. I
+ // think this illustrates how the way things break down with how it
+ // currently is designed with serde.
+ let p = project()
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [target.{}.somelib]
+ rustc-flags = 'foo'
+ "#,
+ rustc_host()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "[ERROR] Only `-l` and `-L` flags are allowed in target config \
+ `target.[..].rustc-flags` (in [..]foo/.cargo/config): `foo`",
+ )
+ .run();
+
+ p.change_file(
+ ".cargo/config",
+ &format!(
+ "[target.{}.somelib]
+ warning = \"foo\"
+ ",
+ rustc_host(),
+ ),
+ );
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr("[ERROR] `warning` is not supported in build script overrides")
+ .run();
+}
+
+#[cargo_test]
+fn redefined_sources() {
+ // Cannot define a source multiple times.
+ let p = project()
+ .file(
+ ".cargo/config",
+ r#"
+ [source.foo]
+ registry = "https://github.com/rust-lang/crates.io-index"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] source `foo` defines source registry `crates-io`, \
+ but that source is already defined by `crates-io`
+note: Sources are not allowed to be defined multiple times.
+",
+ )
+ .run();
+
+ p.change_file(
+ ".cargo/config",
+ r#"
+ [source.one]
+ directory = "index"
+
+ [source.two]
+ directory = "index"
+ "#,
+ );
+
+ // Name is `[..]` because we can't guarantee the order.
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] source `[..]` defines source dir [..]/foo/index, \
+ but that source is already defined by `[..]`
+note: Sources are not allowed to be defined multiple times.
+",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/bad_manifest_path.rs b/src/tools/cargo/tests/testsuite/bad_manifest_path.rs
new file mode 100644
index 000000000..fb214e321
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/bad_manifest_path.rs
@@ -0,0 +1,386 @@
+//! Tests for invalid --manifest-path arguments.
+
+use cargo_test_support::{basic_bin_manifest, main_file, project};
+
+#[track_caller]
+fn assert_not_a_cargo_toml(command: &str, manifest_path_argument: &str) {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .build();
+
+ p.cargo(command)
+ .arg("--manifest-path")
+ .arg(manifest_path_argument)
+ .cwd(p.root().parent().unwrap())
+ .with_status(101)
+ .with_stderr(
+ "[ERROR] the manifest-path must be a path \
+ to a Cargo.toml file",
+ )
+ .run();
+}
+
+#[track_caller]
+fn assert_cargo_toml_doesnt_exist(command: &str, manifest_path_argument: &str) {
+ let p = project().build();
+ let expected_path = manifest_path_argument
+ .split('/')
+ .collect::<Vec<_>>()
+ .join("[..]");
+
+ p.cargo(command)
+ .arg("--manifest-path")
+ .arg(manifest_path_argument)
+ .cwd(p.root().parent().unwrap())
+ .with_status(101)
+ .with_stderr(format!(
+ "[ERROR] manifest path `{}` does not exist",
+ expected_path
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn bench_dir_containing_cargo_toml() {
+ assert_not_a_cargo_toml("bench", "foo");
+}
+
+#[cargo_test]
+fn bench_dir_plus_file() {
+ assert_not_a_cargo_toml("bench", "foo/bar");
+}
+
+#[cargo_test]
+fn bench_dir_plus_path() {
+ assert_not_a_cargo_toml("bench", "foo/bar/baz");
+}
+
+#[cargo_test]
+fn bench_dir_to_nonexistent_cargo_toml() {
+ assert_cargo_toml_doesnt_exist("bench", "foo/bar/baz/Cargo.toml");
+}
+
+#[cargo_test]
+fn build_dir_containing_cargo_toml() {
+ assert_not_a_cargo_toml("check", "foo");
+}
+
+#[cargo_test]
+fn build_dir_plus_file() {
+ assert_not_a_cargo_toml("bench", "foo/bar");
+}
+
+#[cargo_test]
+fn build_dir_plus_path() {
+ assert_not_a_cargo_toml("bench", "foo/bar/baz");
+}
+
+#[cargo_test]
+fn build_dir_to_nonexistent_cargo_toml() {
+ assert_cargo_toml_doesnt_exist("check", "foo/bar/baz/Cargo.toml");
+}
+
+#[cargo_test]
+fn clean_dir_containing_cargo_toml() {
+ assert_not_a_cargo_toml("clean", "foo");
+}
+
+#[cargo_test]
+fn clean_dir_plus_file() {
+ assert_not_a_cargo_toml("clean", "foo/bar");
+}
+
+#[cargo_test]
+fn clean_dir_plus_path() {
+ assert_not_a_cargo_toml("clean", "foo/bar/baz");
+}
+
+#[cargo_test]
+fn clean_dir_to_nonexistent_cargo_toml() {
+ assert_cargo_toml_doesnt_exist("clean", "foo/bar/baz/Cargo.toml");
+}
+
+#[cargo_test]
+fn doc_dir_containing_cargo_toml() {
+ assert_not_a_cargo_toml("doc", "foo");
+}
+
+#[cargo_test]
+fn doc_dir_plus_file() {
+ assert_not_a_cargo_toml("doc", "foo/bar");
+}
+
+#[cargo_test]
+fn doc_dir_plus_path() {
+ assert_not_a_cargo_toml("doc", "foo/bar/baz");
+}
+
+#[cargo_test]
+fn doc_dir_to_nonexistent_cargo_toml() {
+ assert_cargo_toml_doesnt_exist("doc", "foo/bar/baz/Cargo.toml");
+}
+
+#[cargo_test]
+fn fetch_dir_containing_cargo_toml() {
+ assert_not_a_cargo_toml("fetch", "foo");
+}
+
+#[cargo_test]
+fn fetch_dir_plus_file() {
+ assert_not_a_cargo_toml("fetch", "foo/bar");
+}
+
+#[cargo_test]
+fn fetch_dir_plus_path() {
+ assert_not_a_cargo_toml("fetch", "foo/bar/baz");
+}
+
+#[cargo_test]
+fn fetch_dir_to_nonexistent_cargo_toml() {
+ assert_cargo_toml_doesnt_exist("fetch", "foo/bar/baz/Cargo.toml");
+}
+
+#[cargo_test]
+fn generate_lockfile_dir_containing_cargo_toml() {
+ assert_not_a_cargo_toml("generate-lockfile", "foo");
+}
+
+#[cargo_test]
+fn generate_lockfile_dir_plus_file() {
+ assert_not_a_cargo_toml("generate-lockfile", "foo/bar");
+}
+
+#[cargo_test]
+fn generate_lockfile_dir_plus_path() {
+ assert_not_a_cargo_toml("generate-lockfile", "foo/bar/baz");
+}
+
+#[cargo_test]
+fn generate_lockfile_dir_to_nonexistent_cargo_toml() {
+ assert_cargo_toml_doesnt_exist("generate-lockfile", "foo/bar/baz/Cargo.toml");
+}
+
+#[cargo_test]
+fn package_dir_containing_cargo_toml() {
+ assert_not_a_cargo_toml("package", "foo");
+}
+
+#[cargo_test]
+fn package_dir_plus_file() {
+ assert_not_a_cargo_toml("package", "foo/bar");
+}
+
+#[cargo_test]
+fn package_dir_plus_path() {
+ assert_not_a_cargo_toml("package", "foo/bar/baz");
+}
+
+#[cargo_test]
+fn package_dir_to_nonexistent_cargo_toml() {
+ assert_cargo_toml_doesnt_exist("package", "foo/bar/baz/Cargo.toml");
+}
+
+#[cargo_test]
+fn pkgid_dir_containing_cargo_toml() {
+ assert_not_a_cargo_toml("pkgid", "foo");
+}
+
+#[cargo_test]
+fn pkgid_dir_plus_file() {
+ assert_not_a_cargo_toml("pkgid", "foo/bar");
+}
+
+#[cargo_test]
+fn pkgid_dir_plus_path() {
+ assert_not_a_cargo_toml("pkgid", "foo/bar/baz");
+}
+
+#[cargo_test]
+fn pkgid_dir_to_nonexistent_cargo_toml() {
+ assert_cargo_toml_doesnt_exist("pkgid", "foo/bar/baz/Cargo.toml");
+}
+
+#[cargo_test]
+fn publish_dir_containing_cargo_toml() {
+ assert_not_a_cargo_toml("publish", "foo");
+}
+
+#[cargo_test]
+fn publish_dir_plus_file() {
+ assert_not_a_cargo_toml("publish", "foo/bar");
+}
+
+#[cargo_test]
+fn publish_dir_plus_path() {
+ assert_not_a_cargo_toml("publish", "foo/bar/baz");
+}
+
+#[cargo_test]
+fn publish_dir_to_nonexistent_cargo_toml() {
+ assert_cargo_toml_doesnt_exist("publish", "foo/bar/baz/Cargo.toml");
+}
+
+#[cargo_test]
+fn read_manifest_dir_containing_cargo_toml() {
+ assert_not_a_cargo_toml("read-manifest", "foo");
+}
+
+#[cargo_test]
+fn read_manifest_dir_plus_file() {
+ assert_not_a_cargo_toml("read-manifest", "foo/bar");
+}
+
+#[cargo_test]
+fn read_manifest_dir_plus_path() {
+ assert_not_a_cargo_toml("read-manifest", "foo/bar/baz");
+}
+
+#[cargo_test]
+fn read_manifest_dir_to_nonexistent_cargo_toml() {
+ assert_cargo_toml_doesnt_exist("read-manifest", "foo/bar/baz/Cargo.toml");
+}
+
+#[cargo_test]
+fn run_dir_containing_cargo_toml() {
+ assert_not_a_cargo_toml("run", "foo");
+}
+
+#[cargo_test]
+fn run_dir_plus_file() {
+ assert_not_a_cargo_toml("run", "foo/bar");
+}
+
+#[cargo_test]
+fn run_dir_plus_path() {
+ assert_not_a_cargo_toml("run", "foo/bar/baz");
+}
+
+#[cargo_test]
+fn run_dir_to_nonexistent_cargo_toml() {
+ assert_cargo_toml_doesnt_exist("run", "foo/bar/baz/Cargo.toml");
+}
+
+#[cargo_test]
+fn rustc_dir_containing_cargo_toml() {
+ assert_not_a_cargo_toml("rustc", "foo");
+}
+
+#[cargo_test]
+fn rustc_dir_plus_file() {
+ assert_not_a_cargo_toml("rustc", "foo/bar");
+}
+
+#[cargo_test]
+fn rustc_dir_plus_path() {
+ assert_not_a_cargo_toml("rustc", "foo/bar/baz");
+}
+
+#[cargo_test]
+fn rustc_dir_to_nonexistent_cargo_toml() {
+ assert_cargo_toml_doesnt_exist("rustc", "foo/bar/baz/Cargo.toml");
+}
+
+#[cargo_test]
+fn test_dir_containing_cargo_toml() {
+ assert_not_a_cargo_toml("test", "foo");
+}
+
+#[cargo_test]
+fn test_dir_plus_file() {
+ assert_not_a_cargo_toml("test", "foo/bar");
+}
+
+#[cargo_test]
+fn test_dir_plus_path() {
+ assert_not_a_cargo_toml("test", "foo/bar/baz");
+}
+
+#[cargo_test]
+fn test_dir_to_nonexistent_cargo_toml() {
+ assert_cargo_toml_doesnt_exist("test", "foo/bar/baz/Cargo.toml");
+}
+
+#[cargo_test]
+fn update_dir_containing_cargo_toml() {
+ assert_not_a_cargo_toml("update", "foo");
+}
+
+#[cargo_test]
+fn update_dir_plus_file() {
+ assert_not_a_cargo_toml("update", "foo/bar");
+}
+
+#[cargo_test]
+fn update_dir_plus_path() {
+ assert_not_a_cargo_toml("update", "foo/bar/baz");
+}
+
+#[cargo_test]
+fn update_dir_to_nonexistent_cargo_toml() {
+ assert_cargo_toml_doesnt_exist("update", "foo/bar/baz/Cargo.toml");
+}
+
+#[cargo_test]
+fn verify_project_dir_containing_cargo_toml() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .build();
+
+ p.cargo("verify-project --manifest-path foo")
+ .cwd(p.root().parent().unwrap())
+ .with_status(1)
+ .with_stdout(
+ "{\"invalid\":\"the manifest-path must be a path to a Cargo.toml file\"}\
+ ",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn verify_project_dir_plus_file() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .build();
+
+ p.cargo("verify-project --manifest-path foo/bar")
+ .cwd(p.root().parent().unwrap())
+ .with_status(1)
+ .with_stdout(
+ "{\"invalid\":\"the manifest-path must be a path to a Cargo.toml file\"}\
+ ",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn verify_project_dir_plus_path() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .build();
+
+ p.cargo("verify-project --manifest-path foo/bar/baz")
+ .cwd(p.root().parent().unwrap())
+ .with_status(1)
+ .with_stdout(
+ "{\"invalid\":\"the manifest-path must be a path to a Cargo.toml file\"}\
+ ",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn verify_project_dir_to_nonexistent_cargo_toml() {
+ let p = project().build();
+ p.cargo("verify-project --manifest-path foo/bar/baz/Cargo.toml")
+ .cwd(p.root().parent().unwrap())
+ .with_status(1)
+ .with_stdout(
+ "{\"invalid\":\"manifest path `foo[..]bar[..]baz[..]Cargo.toml` does not exist\"}\
+ ",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/bench.rs b/src/tools/cargo/tests/testsuite/bench.rs
new file mode 100644
index 000000000..60ad2b60d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/bench.rs
@@ -0,0 +1,1673 @@
+//! Tests for the `cargo bench` command.
+
+use cargo_test_support::paths::CargoPathExt;
+use cargo_test_support::{basic_bin_manifest, basic_lib_manifest, basic_manifest, project};
+
+#[cargo_test(nightly, reason = "bench")]
+fn cargo_bench_simple() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file(
+ "src/main.rs",
+ r#"
+ #![feature(test)]
+ #[cfg(test)]
+ extern crate test;
+
+ fn hello() -> &'static str {
+ "hello"
+ }
+
+ pub fn main() {
+ println!("{}", hello())
+ }
+
+ #[bench]
+ fn bench_hello(_b: &mut test::Bencher) {
+ assert_eq!(hello(), "hello")
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build").run();
+ assert!(p.bin("foo").is_file());
+
+ p.process(&p.bin("foo")).with_stdout("hello\n").run();
+
+ p.cargo("bench")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.5.0 ([CWD])
+[FINISHED] bench [optimized] target(s) in [..]
+[RUNNING] [..] (target/release/deps/foo-[..][EXE])",
+ )
+ .with_stdout_contains("test bench_hello ... bench: [..]")
+ .run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn bench_bench_implicit() {
+ let p = project()
+ .file(
+ "src/main.rs",
+ r#"
+ #![feature(test)]
+ #[cfg(test)]
+ extern crate test;
+ #[bench] fn run1(_ben: &mut test::Bencher) { }
+ fn main() { println!("Hello main!"); }
+ "#,
+ )
+ .file(
+ "tests/other.rs",
+ r#"
+ #![feature(test)]
+ extern crate test;
+ #[bench] fn run3(_ben: &mut test::Bencher) { }
+ "#,
+ )
+ .file(
+ "benches/mybench.rs",
+ r#"
+ #![feature(test)]
+ extern crate test;
+ #[bench] fn run2(_ben: &mut test::Bencher) { }
+ "#,
+ )
+ .build();
+
+ p.cargo("bench --benches")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] bench [optimized] target(s) in [..]
+[RUNNING] [..] (target/release/deps/foo-[..][EXE])
+[RUNNING] [..] (target/release/deps/mybench-[..][EXE])
+",
+ )
+ .with_stdout_contains("test run2 ... bench: [..]")
+ .run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn bench_bin_implicit() {
+ let p = project()
+ .file(
+ "src/main.rs",
+ r#"
+ #![feature(test)]
+ #[cfg(test)]
+ extern crate test;
+ #[bench] fn run1(_ben: &mut test::Bencher) { }
+ fn main() { println!("Hello main!"); }
+ "#,
+ )
+ .file(
+ "tests/other.rs",
+ r#"
+ #![feature(test)]
+ extern crate test;
+ #[bench] fn run3(_ben: &mut test::Bencher) { }
+ "#,
+ )
+ .file(
+ "benches/mybench.rs",
+ r#"
+ #![feature(test)]
+ extern crate test;
+ #[bench] fn run2(_ben: &mut test::Bencher) { }
+ "#,
+ )
+ .build();
+
+ p.cargo("bench --bins")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] bench [optimized] target(s) in [..]
+[RUNNING] [..] (target/release/deps/foo-[..][EXE])
+",
+ )
+ .with_stdout_contains("test run1 ... bench: [..]")
+ .run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn bench_tarname() {
+ let p = project()
+ .file(
+ "benches/bin1.rs",
+ r#"
+ #![feature(test)]
+ extern crate test;
+ #[bench] fn run1(_ben: &mut test::Bencher) { }
+ "#,
+ )
+ .file(
+ "benches/bin2.rs",
+ r#"
+ #![feature(test)]
+ extern crate test;
+ #[bench] fn run2(_ben: &mut test::Bencher) { }
+ "#,
+ )
+ .build();
+
+ p.cargo("bench --bench bin2")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] bench [optimized] target(s) in [..]
+[RUNNING] [..] (target/release/deps/bin2-[..][EXE])
+",
+ )
+ .with_stdout_contains("test run2 ... bench: [..]")
+ .run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn bench_multiple_targets() {
+ let p = project()
+ .file(
+ "benches/bin1.rs",
+ r#"
+ #![feature(test)]
+ extern crate test;
+ #[bench] fn run1(_ben: &mut test::Bencher) { }
+ "#,
+ )
+ .file(
+ "benches/bin2.rs",
+ r#"
+ #![feature(test)]
+ extern crate test;
+ #[bench] fn run2(_ben: &mut test::Bencher) { }
+ "#,
+ )
+ .file(
+ "benches/bin3.rs",
+ r#"
+ #![feature(test)]
+ extern crate test;
+ #[bench] fn run3(_ben: &mut test::Bencher) { }
+ "#,
+ )
+ .build();
+
+ p.cargo("bench --bench bin1 --bench bin2")
+ .with_stdout_contains("test run1 ... bench: [..]")
+ .with_stdout_contains("test run2 ... bench: [..]")
+ .with_stdout_does_not_contain("[..]run3[..]")
+ .run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn cargo_bench_verbose() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file(
+ "src/main.rs",
+ r#"
+ #![feature(test)]
+ #[cfg(test)]
+ extern crate test;
+ fn main() {}
+ #[bench] fn bench_hello(_b: &mut test::Bencher) {}
+ "#,
+ )
+ .build();
+
+ p.cargo("bench -v hello")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.5.0 ([CWD])
+[RUNNING] `rustc [..] src/main.rs [..]`
+[FINISHED] bench [optimized] target(s) in [..]
+[RUNNING] `[..]target/release/deps/foo-[..][EXE] hello --bench`",
+ )
+ .with_stdout_contains("test bench_hello ... bench: [..]")
+ .run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn many_similar_names() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ "
+ #![feature(test)]
+ #[cfg(test)]
+ extern crate test;
+ pub fn foo() {}
+ #[bench] fn lib_bench(_b: &mut test::Bencher) {}
+ ",
+ )
+ .file(
+ "src/main.rs",
+ "
+ #![feature(test)]
+ #[cfg(test)]
+ extern crate foo;
+ #[cfg(test)]
+ extern crate test;
+ fn main() {}
+ #[bench] fn bin_bench(_b: &mut test::Bencher) { foo::foo() }
+ ",
+ )
+ .file(
+ "benches/foo.rs",
+ r#"
+ #![feature(test)]
+ extern crate foo;
+ extern crate test;
+ #[bench] fn bench_bench(_b: &mut test::Bencher) { foo::foo() }
+ "#,
+ )
+ .build();
+
+ p.cargo("bench")
+ .with_stdout_contains("test bin_bench ... bench: 0 ns/iter (+/- 0)")
+ .with_stdout_contains("test lib_bench ... bench: 0 ns/iter (+/- 0)")
+ .with_stdout_contains("test bench_bench ... bench: 0 ns/iter (+/- 0)")
+ .run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn cargo_bench_failing_test() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file(
+ "src/main.rs",
+ r#"
+ #![feature(test)]
+ #[cfg(test)]
+ extern crate test;
+ fn hello() -> &'static str {
+ "hello"
+ }
+
+ pub fn main() {
+ println!("{}", hello())
+ }
+
+ #[bench]
+ fn bench_hello(_b: &mut test::Bencher) {
+ assert_eq!(hello(), "nope")
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build").run();
+ assert!(p.bin("foo").is_file());
+
+ p.process(&p.bin("foo")).with_stdout("hello\n").run();
+
+ // Force libtest into serial execution so that the test header will be printed.
+ p.cargo("bench -- --test-threads=1")
+ .with_stdout_contains("test bench_hello ...[..]")
+ .with_stderr_contains(
+ "\
+[COMPILING] foo v0.5.0 ([CWD])[..]
+[FINISHED] bench [optimized] target(s) in [..]
+[RUNNING] [..] (target/release/deps/foo-[..][EXE])",
+ )
+ .with_stdout_contains(
+ "[..]thread '[..]' panicked at 'assertion failed: `(left == right)`[..]",
+ )
+ .with_stdout_contains("[..]left: `\"hello\"`[..]")
+ .with_stdout_contains("[..]right: `\"nope\"`[..]")
+ .with_stdout_contains("[..]src/main.rs:15[..]")
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn bench_with_lib_dep() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [[bin]]
+ name = "baz"
+ path = "src/main.rs"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ #![feature(test)]
+ #[cfg(test)]
+ extern crate test;
+ ///
+ /// ```rust
+ /// extern crate foo;
+ /// fn main() {
+ /// println!("{}", foo::foo());
+ /// }
+ /// ```
+ ///
+ pub fn foo(){}
+ #[bench] fn lib_bench(_b: &mut test::Bencher) {}
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ "
+ #![feature(test)]
+ #[allow(unused_extern_crates)]
+ extern crate foo;
+ #[cfg(test)]
+ extern crate test;
+
+ fn main() {}
+
+ #[bench]
+ fn bin_bench(_b: &mut test::Bencher) {}
+ ",
+ )
+ .build();
+
+ p.cargo("bench")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] bench [optimized] target(s) in [..]
+[RUNNING] [..] (target/release/deps/foo-[..][EXE])
+[RUNNING] [..] (target/release/deps/baz-[..][EXE])",
+ )
+ .with_stdout_contains("test lib_bench ... bench: [..]")
+ .with_stdout_contains("test bin_bench ... bench: [..]")
+ .run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn bench_with_deep_lib_dep() {
+ let p = project()
+ .at("bar")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.foo]
+ path = "../foo"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "
+ #![feature(test)]
+ #[cfg(test)]
+ extern crate foo;
+ #[cfg(test)]
+ extern crate test;
+ #[bench]
+ fn bar_bench(_b: &mut test::Bencher) {
+ foo::foo();
+ }
+ ",
+ )
+ .build();
+ let _p2 = project()
+ .file(
+ "src/lib.rs",
+ "
+ #![feature(test)]
+ #[cfg(test)]
+ extern crate test;
+
+ pub fn foo() {}
+
+ #[bench]
+ fn foo_bench(_b: &mut test::Bencher) {}
+ ",
+ )
+ .build();
+
+ p.cargo("bench")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[COMPILING] bar v0.0.1 ([CWD])
+[FINISHED] bench [optimized] target(s) in [..]
+[RUNNING] [..] (target/release/deps/bar-[..][EXE])",
+ )
+ .with_stdout_contains("test bar_bench ... bench: [..]")
+ .run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn external_bench_explicit() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [[bench]]
+ name = "bench"
+ path = "src/bench.rs"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ #![feature(test)]
+ #[cfg(test)]
+ extern crate test;
+ pub fn get_hello() -> &'static str { "Hello" }
+
+ #[bench]
+ fn internal_bench(_b: &mut test::Bencher) {}
+ "#,
+ )
+ .file(
+ "src/bench.rs",
+ r#"
+ #![feature(test)]
+ #[allow(unused_extern_crates)]
+ extern crate foo;
+ extern crate test;
+
+ #[bench]
+ fn external_bench(_b: &mut test::Bencher) {}
+ "#,
+ )
+ .build();
+
+ p.cargo("bench")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] bench [optimized] target(s) in [..]
+[RUNNING] [..] (target/release/deps/foo-[..][EXE])
+[RUNNING] [..] (target/release/deps/bench-[..][EXE])",
+ )
+ .with_stdout_contains("test internal_bench ... bench: [..]")
+ .with_stdout_contains("test external_bench ... bench: [..]")
+ .run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn external_bench_implicit() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ #![feature(test)]
+ #[cfg(test)]
+ extern crate test;
+
+ pub fn get_hello() -> &'static str { "Hello" }
+
+ #[bench]
+ fn internal_bench(_b: &mut test::Bencher) {}
+ "#,
+ )
+ .file(
+ "benches/external.rs",
+ r#"
+ #![feature(test)]
+ #[allow(unused_extern_crates)]
+ extern crate foo;
+ extern crate test;
+
+ #[bench]
+ fn external_bench(_b: &mut test::Bencher) {}
+ "#,
+ )
+ .build();
+
+ p.cargo("bench")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] bench [optimized] target(s) in [..]
+[RUNNING] [..] (target/release/deps/foo-[..][EXE])
+[RUNNING] [..] (target/release/deps/external-[..][EXE])",
+ )
+ .with_stdout_contains("test internal_bench ... bench: [..]")
+ .with_stdout_contains("test external_bench ... bench: [..]")
+ .run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn bench_autodiscover_2015() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ edition = "2015"
+
+ [features]
+ magic = []
+
+ [[bench]]
+ name = "bench_magic"
+ required-features = ["magic"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "benches/bench_basic.rs",
+ r#"
+ #![feature(test)]
+ #[allow(unused_extern_crates)]
+ extern crate foo;
+ extern crate test;
+
+ #[bench]
+ fn bench_basic(_b: &mut test::Bencher) {}
+ "#,
+ )
+ .file(
+ "benches/bench_magic.rs",
+ r#"
+ #![feature(test)]
+ #[allow(unused_extern_crates)]
+ extern crate foo;
+ extern crate test;
+
+ #[bench]
+ fn bench_magic(_b: &mut test::Bencher) {}
+ "#,
+ )
+ .build();
+
+ p.cargo("bench bench_basic")
+ .with_stderr(
+ "warning: \
+An explicit [[bench]] section is specified in Cargo.toml which currently
+disables Cargo from automatically inferring other benchmark targets.
+This inference behavior will change in the Rust 2018 edition and the following
+files will be included as a benchmark target:
+
+* [..]bench_basic.rs
+
+This is likely to break cargo build or cargo test as these files may not be
+ready to be compiled as a benchmark target today. You can future-proof yourself
+and disable this warning by adding `autobenches = false` to your [package]
+section. You may also move the files to a location where Cargo would not
+automatically infer them to be a target, such as in subfolders.
+
+For more information on this warning you can consult
+https://github.com/rust-lang/cargo/issues/5330
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] bench [optimized] target(s) in [..]
+[RUNNING] [..] (target/release/deps/foo-[..][EXE])
+",
+ )
+ .run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn dont_run_examples() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(
+ "examples/dont-run-me-i-will-fail.rs",
+ r#"fn main() { panic!("Examples should not be run by 'cargo test'"); }"#,
+ )
+ .build();
+ p.cargo("bench").run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn pass_through_command_line() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ "
+ #![feature(test)]
+ #[cfg(test)]
+ extern crate test;
+
+ #[bench] fn foo(_b: &mut test::Bencher) {}
+ #[bench] fn bar(_b: &mut test::Bencher) {}
+ ",
+ )
+ .build();
+
+ p.cargo("bench bar")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] bench [optimized] target(s) in [..]
+[RUNNING] [..] (target/release/deps/foo-[..][EXE])",
+ )
+ .with_stdout_contains("test bar ... bench: [..]")
+ .run();
+
+ p.cargo("bench foo")
+ .with_stderr(
+ "[FINISHED] bench [optimized] target(s) in [..]
+[RUNNING] [..] (target/release/deps/foo-[..][EXE])",
+ )
+ .with_stdout_contains("test foo ... bench: [..]")
+ .run();
+}
+
+// Regression test for running cargo-bench twice with
+// tests in an rlib
+#[cargo_test(nightly, reason = "bench")]
+fn cargo_bench_twice() {
+ let p = project()
+ .file("Cargo.toml", &basic_lib_manifest("foo"))
+ .file(
+ "src/foo.rs",
+ r#"
+ #![crate_type = "rlib"]
+ #![feature(test)]
+ #[cfg(test)]
+ extern crate test;
+
+ #[bench]
+ fn dummy_bench(b: &mut test::Bencher) { }
+ "#,
+ )
+ .build();
+
+ for _ in 0..2 {
+ p.cargo("bench").run();
+ }
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn lib_bin_same_name() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ name = "foo"
+ [[bin]]
+ name = "foo"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "
+ #![feature(test)]
+ #[cfg(test)]
+ extern crate test;
+ #[bench] fn lib_bench(_b: &mut test::Bencher) {}
+ ",
+ )
+ .file(
+ "src/main.rs",
+ "
+ #![feature(test)]
+ #[allow(unused_extern_crates)]
+ extern crate foo;
+ #[cfg(test)]
+ extern crate test;
+
+ #[bench]
+ fn bin_bench(_b: &mut test::Bencher) {}
+ ",
+ )
+ .build();
+
+ p.cargo("bench")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] bench [optimized] target(s) in [..]
+[RUNNING] [..] (target/release/deps/foo-[..][EXE])
+[RUNNING] [..] (target/release/deps/foo-[..][EXE])",
+ )
+ .with_stdout_contains_n("test [..] ... bench: [..]", 2)
+ .run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn lib_with_standard_name() {
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("syntax", "0.0.1"))
+ .file(
+ "src/lib.rs",
+ "
+ #![feature(test)]
+ #[cfg(test)]
+ extern crate test;
+
+ /// ```
+ /// syntax::foo();
+ /// ```
+ pub fn foo() {}
+
+ #[bench]
+ fn foo_bench(_b: &mut test::Bencher) {}
+ ",
+ )
+ .file(
+ "benches/bench.rs",
+ "
+ #![feature(test)]
+ extern crate syntax;
+ extern crate test;
+
+ #[bench]
+ fn bench(_b: &mut test::Bencher) { syntax::foo() }
+ ",
+ )
+ .build();
+
+ p.cargo("bench")
+ .with_stderr(
+ "\
+[COMPILING] syntax v0.0.1 ([CWD])
+[FINISHED] bench [optimized] target(s) in [..]
+[RUNNING] [..] (target/release/deps/syntax-[..][EXE])
+[RUNNING] [..] (target/release/deps/bench-[..][EXE])",
+ )
+ .with_stdout_contains("test foo_bench ... bench: [..]")
+ .with_stdout_contains("test bench ... bench: [..]")
+ .run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn lib_with_standard_name2() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "syntax"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ name = "syntax"
+ bench = false
+ doctest = false
+ "#,
+ )
+ .file("src/lib.rs", "pub fn foo() {}")
+ .file(
+ "src/main.rs",
+ "
+ #![feature(test)]
+ #[cfg(test)]
+ extern crate syntax;
+ #[cfg(test)]
+ extern crate test;
+
+ fn main() {}
+
+ #[bench]
+ fn bench(_b: &mut test::Bencher) { syntax::foo() }
+ ",
+ )
+ .build();
+
+ p.cargo("bench")
+ .with_stderr(
+ "\
+[COMPILING] syntax v0.0.1 ([CWD])
+[FINISHED] bench [optimized] target(s) in [..]
+[RUNNING] [..] (target/release/deps/syntax-[..][EXE])",
+ )
+ .with_stdout_contains("test bench ... bench: [..]")
+ .run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn bench_dylib() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ name = "foo"
+ crate_type = ["dylib"]
+
+ [dependencies.bar]
+ path = "bar"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ #![feature(test)]
+ extern crate bar as the_bar;
+ #[cfg(test)]
+ extern crate test;
+
+ pub fn bar() { the_bar::baz(); }
+
+ #[bench]
+ fn foo(_b: &mut test::Bencher) {}
+ "#,
+ )
+ .file(
+ "benches/bench.rs",
+ r#"
+ #![feature(test)]
+ extern crate foo as the_foo;
+ extern crate test;
+
+ #[bench]
+ fn foo(_b: &mut test::Bencher) { the_foo::bar(); }
+ "#,
+ )
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ name = "bar"
+ crate_type = ["dylib"]
+ "#,
+ )
+ .file("bar/src/lib.rs", "pub fn baz() {}")
+ .build();
+
+ p.cargo("bench -v")
+ .with_stderr(
+ "\
+[COMPILING] bar v0.0.1 ([CWD]/bar)
+[RUNNING] [..] -C opt-level=3 [..]
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] [..] -C opt-level=3 [..]
+[RUNNING] [..] -C opt-level=3 [..]
+[RUNNING] [..] -C opt-level=3 [..]
+[FINISHED] bench [optimized] target(s) in [..]
+[RUNNING] `[..]target/release/deps/foo-[..][EXE] --bench`
+[RUNNING] `[..]target/release/deps/bench-[..][EXE] --bench`",
+ )
+ .with_stdout_contains_n("test foo ... bench: [..]", 2)
+ .run();
+
+ p.root().move_into_the_past();
+ p.cargo("bench -v")
+ .with_stderr(
+ "\
+[FRESH] bar v0.0.1 ([CWD]/bar)
+[FRESH] foo v0.0.1 ([CWD])
+[FINISHED] bench [optimized] target(s) in [..]
+[RUNNING] `[..]target/release/deps/foo-[..][EXE] --bench`
+[RUNNING] `[..]target/release/deps/bench-[..][EXE] --bench`",
+ )
+ .with_stdout_contains_n("test foo ... bench: [..]", 2)
+ .run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn bench_twice_with_build_cmd() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file("build.rs", "fn main() {}")
+ .file(
+ "src/lib.rs",
+ "
+ #![feature(test)]
+ #[cfg(test)]
+ extern crate test;
+ #[bench]
+ fn foo(_b: &mut test::Bencher) {}
+ ",
+ )
+ .build();
+
+ p.cargo("bench")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] bench [optimized] target(s) in [..]
+[RUNNING] [..] (target/release/deps/foo-[..][EXE])",
+ )
+ .with_stdout_contains("test foo ... bench: [..]")
+ .run();
+
+ p.cargo("bench")
+ .with_stderr(
+ "[FINISHED] bench [optimized] target(s) in [..]
+[RUNNING] [..] (target/release/deps/foo-[..][EXE])",
+ )
+ .with_stdout_contains("test foo ... bench: [..]")
+ .run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn bench_with_examples() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "6.6.6"
+ authors = []
+
+ [[example]]
+ name = "teste1"
+
+ [[bench]]
+ name = "testb1"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ #![feature(test)]
+ #[cfg(test)]
+ extern crate test;
+ #[cfg(test)]
+ use test::Bencher;
+
+ pub fn f1() {
+ println!("f1");
+ }
+
+ pub fn f2() {}
+
+ #[bench]
+ fn bench_bench1(_b: &mut Bencher) {
+ f2();
+ }
+ "#,
+ )
+ .file(
+ "benches/testb1.rs",
+ "
+ #![feature(test)]
+ extern crate foo;
+ extern crate test;
+
+ use test::Bencher;
+
+ #[bench]
+ fn bench_bench2(_b: &mut Bencher) {
+ foo::f2();
+ }
+ ",
+ )
+ .file(
+ "examples/teste1.rs",
+ r#"
+ extern crate foo;
+
+ fn main() {
+ println!("example1");
+ foo::f1();
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("bench -v")
+ .with_stderr(
+ "\
+[COMPILING] foo v6.6.6 ([CWD])
+[RUNNING] `rustc [..]`
+[RUNNING] `rustc [..]`
+[RUNNING] `rustc [..]`
+[FINISHED] bench [optimized] target(s) in [..]
+[RUNNING] `[CWD]/target/release/deps/foo-[..][EXE] --bench`
+[RUNNING] `[CWD]/target/release/deps/testb1-[..][EXE] --bench`",
+ )
+ .with_stdout_contains("test bench_bench1 ... bench: [..]")
+ .with_stdout_contains("test bench_bench2 ... bench: [..]")
+ .run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn test_a_bench() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.1.0"
+
+ [lib]
+ name = "foo"
+ test = false
+ doctest = false
+
+ [[bench]]
+ name = "b"
+ test = true
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("benches/b.rs", "#[test] fn foo() {}")
+ .build();
+
+ p.cargo("test")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.1.0 ([..])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/b-[..][EXE])",
+ )
+ .with_stdout_contains("test foo ... ok")
+ .run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn test_bench_no_run() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(
+ "benches/bbaz.rs",
+ r#"
+ #![feature(test)]
+
+ extern crate test;
+
+ use test::Bencher;
+
+ #[bench]
+ fn bench_baz(_: &mut Bencher) {}
+ "#,
+ )
+ .build();
+
+ p.cargo("bench --no-run")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] bench [optimized] target(s) in [..]
+[EXECUTABLE] benches src/lib.rs (target/release/deps/foo-[..][EXE])
+[EXECUTABLE] benches/bbaz.rs (target/release/deps/bbaz-[..][EXE])
+",
+ )
+ .run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn test_bench_no_run_emit_json() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(
+ "benches/bbaz.rs",
+ r#"
+ #![feature(test)]
+
+ extern crate test;
+
+ use test::Bencher;
+
+ #[bench]
+ fn bench_baz(_: &mut Bencher) {}
+ "#,
+ )
+ .build();
+
+ p.cargo("bench --no-run --message-format json")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] bench [optimized] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn test_bench_no_fail_fast() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file(
+ "src/main.rs",
+ r#"
+ #![feature(test)]
+ #[cfg(test)]
+ extern crate test;
+ fn hello() -> &'static str {
+ "hello"
+ }
+
+ pub fn main() {
+ println!("{}", hello())
+ }
+
+ #[bench]
+ fn bench_hello(_b: &mut test::Bencher) {
+ assert_eq!(hello(), "hello")
+ }
+
+ #[bench]
+ fn bench_nope(_b: &mut test::Bencher) {
+ assert_eq!("nope", hello())
+ }
+ "#,
+ )
+ .file(
+ "benches/b1.rs",
+ r#"
+ #![feature(test)]
+ extern crate test;
+ #[bench]
+ fn b1_fail(_b: &mut test::Bencher) { assert_eq!(1, 2); }
+ "#,
+ )
+ .build();
+
+ p.cargo("bench --no-fail-fast -- --test-threads=1")
+ .with_status(101)
+ .with_stderr(
+ "\
+[COMPILING] foo v0.5.0 [..]
+[FINISHED] bench [..]
+[RUNNING] unittests src/main.rs (target/release/deps/foo[..])
+[ERROR] bench failed, to rerun pass `--bin foo`
+[RUNNING] benches/b1.rs (target/release/deps/b1[..])
+[ERROR] bench failed, to rerun pass `--bench b1`
+[ERROR] 2 targets failed:
+ `--bin foo`
+ `--bench b1`
+",
+ )
+ .with_stdout_contains("running 2 tests")
+ .with_stdout_contains("test bench_hello [..]")
+ .with_stdout_contains("test bench_nope [..]")
+ .with_stdout_contains("test b1_fail [..]")
+ .run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn test_bench_multiple_packages() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.1.0"
+
+ [dependencies.bar]
+ path = "../bar"
+
+ [dependencies.baz]
+ path = "../baz"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ let _bar = project()
+ .at("bar")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ authors = []
+ version = "0.1.0"
+
+ [[bench]]
+ name = "bbar"
+ test = true
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "benches/bbar.rs",
+ r#"
+ #![feature(test)]
+ extern crate test;
+
+ use test::Bencher;
+
+ #[bench]
+ fn bench_bar(_b: &mut Bencher) {}
+ "#,
+ )
+ .build();
+
+ let _baz = project()
+ .at("baz")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "baz"
+ authors = []
+ version = "0.1.0"
+
+ [[bench]]
+ name = "bbaz"
+ test = true
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "benches/bbaz.rs",
+ r#"
+ #![feature(test)]
+ extern crate test;
+
+ use test::Bencher;
+
+ #[bench]
+ fn bench_baz(_b: &mut Bencher) {}
+ "#,
+ )
+ .build();
+
+ p.cargo("bench -p bar -p baz")
+ .with_stderr_contains("[RUNNING] [..] (target/release/deps/bbaz-[..][EXE])")
+ .with_stdout_contains("test bench_baz ... bench: [..]")
+ .with_stderr_contains("[RUNNING] [..] (target/release/deps/bbar-[..][EXE])")
+ .with_stdout_contains("test bench_bar ... bench: [..]")
+ .run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn bench_all_workspace() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { path = "bar" }
+
+ [workspace]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "benches/foo.rs",
+ r#"
+ #![feature(test)]
+ extern crate test;
+
+ use test::Bencher;
+
+ #[bench]
+ fn bench_foo(_: &mut Bencher) -> () { () }
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .file(
+ "bar/benches/bar.rs",
+ r#"
+ #![feature(test)]
+ extern crate test;
+
+ use test::Bencher;
+
+ #[bench]
+ fn bench_bar(_: &mut Bencher) -> () { () }
+ "#,
+ )
+ .build();
+
+ p.cargo("bench --workspace")
+ .with_stderr_contains("[RUNNING] [..] (target/release/deps/bar-[..][EXE])")
+ .with_stdout_contains("test bench_bar ... bench: [..]")
+ .with_stderr_contains("[RUNNING] [..] (target/release/deps/foo-[..][EXE])")
+ .with_stdout_contains("test bench_foo ... bench: [..]")
+ .run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn bench_all_exclude() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [workspace]
+ members = ["bar", "baz"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file(
+ "bar/src/lib.rs",
+ r#"
+ #![feature(test)]
+ #[cfg(test)]
+ extern crate test;
+
+ #[bench]
+ pub fn bar(b: &mut test::Bencher) {
+ b.iter(|| {});
+ }
+ "#,
+ )
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file(
+ "baz/src/lib.rs",
+ "#[test] pub fn baz() { break_the_build(); }",
+ )
+ .build();
+
+ p.cargo("bench --workspace --exclude baz")
+ .with_stdout_contains(
+ "\
+running 1 test
+test bar ... bench: [..] ns/iter (+/- [..])",
+ )
+ .run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn bench_all_exclude_glob() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [workspace]
+ members = ["bar", "baz"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file(
+ "bar/src/lib.rs",
+ r#"
+ #![feature(test)]
+ #[cfg(test)]
+ extern crate test;
+
+ #[bench]
+ pub fn bar(b: &mut test::Bencher) {
+ b.iter(|| {});
+ }
+ "#,
+ )
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file(
+ "baz/src/lib.rs",
+ "#[test] pub fn baz() { break_the_build(); }",
+ )
+ .build();
+
+ p.cargo("bench --workspace --exclude '*z'")
+ .with_stdout_contains(
+ "\
+running 1 test
+test bar ... bench: [..] ns/iter (+/- [..])",
+ )
+ .run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn bench_all_virtual_manifest() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar", "baz"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .file(
+ "bar/benches/bar.rs",
+ r#"
+ #![feature(test)]
+ extern crate test;
+
+ use test::Bencher;
+
+ #[bench]
+ fn bench_bar(_: &mut Bencher) -> () { () }
+ "#,
+ )
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("baz/src/lib.rs", "pub fn baz() {}")
+ .file(
+ "baz/benches/baz.rs",
+ r#"
+ #![feature(test)]
+ extern crate test;
+
+ use test::Bencher;
+
+ #[bench]
+ fn bench_baz(_: &mut Bencher) -> () { () }
+ "#,
+ )
+ .build();
+
+ // The order in which bar and baz are built is not guaranteed
+ p.cargo("bench --workspace")
+ .with_stderr_contains("[RUNNING] [..] (target/release/deps/baz-[..][EXE])")
+ .with_stdout_contains("test bench_baz ... bench: [..]")
+ .with_stderr_contains("[RUNNING] [..] (target/release/deps/bar-[..][EXE])")
+ .with_stdout_contains("test bench_bar ... bench: [..]")
+ .run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn bench_virtual_manifest_glob() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar", "baz"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() { break_the_build(); }")
+ .file(
+ "bar/benches/bar.rs",
+ r#"
+ #![feature(test)]
+ extern crate test;
+
+ use test::Bencher;
+
+ #[bench]
+ fn bench_bar(_: &mut Bencher) -> () { break_the_build(); }
+ "#,
+ )
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("baz/src/lib.rs", "pub fn baz() {}")
+ .file(
+ "baz/benches/baz.rs",
+ r#"
+ #![feature(test)]
+ extern crate test;
+
+ use test::Bencher;
+
+ #[bench]
+ fn bench_baz(_: &mut Bencher) -> () { () }
+ "#,
+ )
+ .build();
+
+ // The order in which bar and baz are built is not guaranteed
+ p.cargo("bench -p '*z'")
+ .with_stderr_contains("[RUNNING] [..] (target/release/deps/baz-[..][EXE])")
+ .with_stdout_contains("test bench_baz ... bench: [..]")
+ .with_stderr_does_not_contain("[RUNNING] [..] (target/release/deps/bar-[..][EXE])")
+ .with_stdout_does_not_contain("test bench_bar ... bench: [..]")
+ .run();
+}
+
+// https://github.com/rust-lang/cargo/issues/4287
+#[cargo_test(nightly, reason = "bench")]
+fn legacy_bench_name() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [[bench]]
+ name = "bench"
+ "#,
+ )
+ .file("src/lib.rs", "pub fn foo() {}")
+ .file(
+ "src/bench.rs",
+ r#"
+ #![feature(test)]
+ extern crate test;
+
+ use test::Bencher;
+
+ #[bench]
+ fn bench_foo(_: &mut Bencher) -> () { () }
+ "#,
+ )
+ .build();
+
+ p.cargo("bench")
+ .with_stderr_contains(
+ "\
+[WARNING] path `[..]src/bench.rs` was erroneously implicitly accepted for benchmark `bench`,
+please set bench.path in Cargo.toml",
+ )
+ .run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn bench_virtual_manifest_all_implied() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar", "baz"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn foo() {}")
+ .file(
+ "bar/benches/bar.rs",
+ r#"
+ #![feature(test)]
+ extern crate test;
+ use test::Bencher;
+ #[bench]
+ fn bench_bar(_: &mut Bencher) -> () { () }
+ "#,
+ )
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("baz/src/lib.rs", "pub fn baz() {}")
+ .file(
+ "baz/benches/baz.rs",
+ r#"
+ #![feature(test)]
+ extern crate test;
+ use test::Bencher;
+ #[bench]
+ fn bench_baz(_: &mut Bencher) -> () { () }
+ "#,
+ )
+ .build();
+
+ // The order in which bar and baz are built is not guaranteed
+
+ p.cargo("bench")
+ .with_stderr_contains("[RUNNING] [..] (target/release/deps/baz-[..][EXE])")
+ .with_stdout_contains("test bench_baz ... bench: [..]")
+ .with_stderr_contains("[RUNNING] [..] (target/release/deps/bar-[..][EXE])")
+ .with_stdout_contains("test bench_bar ... bench: [..]")
+ .run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn json_artifact_includes_executable_for_benchmark() {
+ let p = project()
+ .file(
+ "benches/benchmark.rs",
+ r#"
+ #![feature(test)]
+ extern crate test;
+
+ use test::Bencher;
+
+ #[bench]
+ fn bench_foo(_: &mut Bencher) -> () { () }
+ "#,
+ )
+ .build();
+
+ p.cargo("bench --no-run --message-format=json")
+ .with_json(
+ r#"
+ {
+ "executable": "[..]/foo/target/release/deps/benchmark-[..][EXE]",
+ "features": [],
+ "filenames": "{...}",
+ "fresh": false,
+ "package_id": "foo 0.0.1 ([..])",
+ "manifest_path": "[..]",
+ "profile": "{...}",
+ "reason": "compiler-artifact",
+ "target": {
+ "crate_types": [ "bin" ],
+ "kind": [ "bench" ],
+ "doc": false,
+ "doctest": false,
+ "edition": "2015",
+ "name": "benchmark",
+ "src_path": "[..]/foo/benches/benchmark.rs",
+ "test": false
+ }
+ }
+
+ {"reason": "build-finished", "success": true}
+ "#,
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/binary_name.rs b/src/tools/cargo/tests/testsuite/binary_name.rs
new file mode 100644
index 000000000..7735d6054
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/binary_name.rs
@@ -0,0 +1,301 @@
+use cargo_test_support::install::{
+ assert_has_installed_exe, assert_has_not_installed_exe, cargo_home,
+};
+use cargo_test_support::project;
+
+#[cargo_test]
+fn gated() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [[bin]]
+ name = "foo"
+ filename = "007bar"
+ path = "src/main.rs"
+ "#,
+ )
+ .file("src/main.rs", "fn main() { assert!(true) }")
+ .build();
+
+ // Run cargo build.
+ p.cargo("build")
+ .masquerade_as_nightly_cargo(&["different-binary-name"])
+ .with_status(101)
+ .with_stderr_contains("[..]feature `different-binary-name` is required")
+ .run();
+}
+
+#[cargo_test]
+// This test checks if:
+// 1. The correct binary is produced
+// 2. The deps file has the correct content
+// 3. Fingerprinting works
+// 4. `cargo clean` command works
+fn binary_name1() {
+ // Create the project.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ cargo-features = ["different-binary-name"]
+
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [[bin]]
+ name = "foo"
+ filename = "007bar"
+ path = "src/main.rs"
+ "#,
+ )
+ .file("src/main.rs", "fn main() { assert!(true) }")
+ .build();
+
+ // Run cargo build.
+ p.cargo("build")
+ .masquerade_as_nightly_cargo(&["different-binary-name"])
+ .run();
+
+ // Check the name of the binary that cargo has generated.
+ // A binary with the name of the crate should NOT be created.
+ let foo_path = p.bin("foo");
+ assert!(!foo_path.is_file());
+ // A binary with the name provided in `filename` parameter should be created.
+ let bar_path = p.bin("007bar");
+ assert!(bar_path.is_file());
+
+ // Check if deps file exists.
+ let deps_path = p.bin("007bar").with_extension("d");
+ assert!(deps_path.is_file(), "{:?}", bar_path);
+
+ let depinfo = p.read_file(deps_path.to_str().unwrap());
+
+ // Prepare what content we expect to be present in deps file.
+ let deps_exp = format!(
+ "{}: {}",
+ p.bin("007bar").to_str().unwrap(),
+ p.root().join("src").join("main.rs").to_str().unwrap()
+ );
+
+ // Compare actual deps content with expected deps content.
+ assert!(
+ depinfo.lines().any(|line| line == deps_exp),
+ "Content of `{}` is incorrect",
+ deps_path.to_string_lossy()
+ );
+
+ // Run cargo second time, to verify fingerprint.
+ p.cargo("build -p foo -v")
+ .masquerade_as_nightly_cargo(&["different-binary-name"])
+ .with_stderr(
+ "\
+[FRESH] foo [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ // Run cargo clean.
+ p.cargo("clean -p foo")
+ .masquerade_as_nightly_cargo(&["different-binary-name"])
+ .run();
+
+ // Check if the appropriate file was removed.
+ assert!(
+ !bar_path.is_file(),
+ "`cargo clean` did not remove the correct files"
+ );
+}
+
+#[cargo_test]
+// This test checks if:
+// 1. Check `cargo run`
+// 2. Check `cargo test`
+// 3. Check `cargo install/uninstall`
+fn binary_name2() {
+ // Create the project.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ cargo-features = ["different-binary-name"]
+
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [[bin]]
+ name = "foo"
+ filename = "007bar"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn hello(name: &str) -> String {
+ format!("Hello, {}!", name)
+ }
+
+ fn main() {
+ println!("{}", hello("crabs"));
+ }
+
+ #[cfg(test)]
+ mod tests {
+ use super::*;
+
+ #[test]
+ fn check_crabs() {
+ assert_eq!(hello("crabs"), "Hello, crabs!");
+ }
+ }
+ "#,
+ )
+ .build();
+
+ // Run cargo build.
+ p.cargo("build")
+ .masquerade_as_nightly_cargo(&["different-binary-name"])
+ .run();
+
+ // Check the name of the binary that cargo has generated.
+ // A binary with the name of the crate should NOT be created.
+ let foo_path = p.bin("foo");
+ assert!(!foo_path.is_file());
+ // A binary with the name provided in `filename` parameter should be created.
+ let bar_path = p.bin("007bar");
+ assert!(bar_path.is_file());
+
+ // Check if `cargo test` works
+ p.cargo("test")
+ .masquerade_as_nightly_cargo(&["different-binary-name"])
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo-[..][EXE])",
+ )
+ .with_stdout_contains("test tests::check_crabs ... ok")
+ .run();
+
+ // Check if `cargo run` is able to execute the binary
+ p.cargo("run")
+ .masquerade_as_nightly_cargo(&["different-binary-name"])
+ .with_stdout("Hello, crabs!")
+ .run();
+
+ p.cargo("install")
+ .masquerade_as_nightly_cargo(&["different-binary-name"])
+ .run();
+
+ assert_has_installed_exe(cargo_home(), "007bar");
+
+ p.cargo("uninstall")
+ .with_stderr("[REMOVING] [ROOT]/home/.cargo/bin/007bar[EXE]")
+ .masquerade_as_nightly_cargo(&["different-binary-name"])
+ .run();
+
+ assert_has_not_installed_exe(cargo_home(), "007bar");
+}
+
+#[cargo_test]
+fn check_env_vars() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ cargo-features = ["different-binary-name"]
+
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [[bin]]
+ name = "foo"
+ filename = "007bar"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ println!("{}", option_env!("CARGO_BIN_NAME").unwrap());
+ }
+ "#,
+ )
+ .file(
+ "tests/integration.rs",
+ r#"
+ #[test]
+ fn check_env_vars2() {
+ let value = option_env!("CARGO_BIN_EXE_007bar").expect("Could not find environment variable.");
+ assert!(value.contains("007bar"));
+ }
+ "#
+ )
+ .build();
+
+ // Run cargo build.
+ p.cargo("build")
+ .masquerade_as_nightly_cargo(&["different-binary-name"])
+ .run();
+ p.cargo("run")
+ .masquerade_as_nightly_cargo(&["different-binary-name"])
+ .with_stdout("007bar")
+ .run();
+ p.cargo("test")
+ .masquerade_as_nightly_cargo(&["different-binary-name"])
+ .with_status(0)
+ .run();
+}
+
+#[cargo_test]
+fn check_msg_format_json() {
+ // Create the project.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ cargo-features = ["different-binary-name"]
+
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [[bin]]
+ name = "foo"
+ filename = "007bar"
+ path = "src/main.rs"
+ "#,
+ )
+ .file("src/main.rs", "fn main() { assert!(true) }")
+ .build();
+
+ let output = r#"
+{
+ "reason": "compiler-artifact",
+ "package_id": "foo 0.0.1 [..]",
+ "manifest_path": "[CWD]/Cargo.toml",
+ "target": "{...}",
+ "profile": "{...}",
+ "features": [],
+ "filenames": "{...}",
+ "executable": "[ROOT]/foo/target/debug/007bar[EXE]",
+ "fresh": false
+}
+
+{"reason":"build-finished", "success":true}
+"#;
+
+ // Run cargo build.
+ p.cargo("build --message-format=json")
+ .masquerade_as_nightly_cargo(&["different-binary-name"])
+ .with_json(output)
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/build.rs b/src/tools/cargo/tests/testsuite/build.rs
new file mode 100644
index 000000000..8a1b6ca86
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/build.rs
@@ -0,0 +1,6409 @@
+//! Tests for the `cargo build` command.
+
+use cargo::{
+ core::compiler::CompileMode,
+ core::{Shell, Workspace},
+ ops::CompileOptions,
+ Config,
+};
+use cargo_test_support::compare;
+use cargo_test_support::paths::{root, CargoPathExt};
+use cargo_test_support::registry::Package;
+use cargo_test_support::tools;
+use cargo_test_support::{
+ basic_bin_manifest, basic_lib_manifest, basic_manifest, cargo_exe, git, is_nightly, main_file,
+ paths, process, project, rustc_host, sleep_ms, symlink_supported, t, Execs, ProjectBuilder,
+};
+use cargo_util::paths::dylib_path_envvar;
+use std::env;
+use std::fs;
+use std::io::Read;
+use std::process::Stdio;
+
+#[cargo_test]
+fn cargo_compile_simple() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .build();
+
+ p.cargo("build").run();
+ assert!(p.bin("foo").is_file());
+
+ p.process(&p.bin("foo")).with_stdout("i am foo\n").run();
+}
+
+#[cargo_test]
+fn cargo_fail_with_no_stderr() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", &String::from("refusal"))
+ .build();
+ p.cargo("build --message-format=json")
+ .with_status(101)
+ .with_stderr_does_not_contain("--- stderr")
+ .run();
+}
+
+/// Checks that the `CARGO_INCREMENTAL` environment variable results in
+/// `rustc` getting `-C incremental` passed to it.
+#[cargo_test]
+fn cargo_compile_incremental() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .build();
+
+ p.cargo("build -v")
+ .env("CARGO_INCREMENTAL", "1")
+ .with_stderr_contains(
+ "[RUNNING] `rustc [..] -C incremental=[..]/target/debug/incremental[..]`\n",
+ )
+ .run();
+
+ p.cargo("test -v")
+ .env("CARGO_INCREMENTAL", "1")
+ .with_stderr_contains(
+ "[RUNNING] `rustc [..] -C incremental=[..]/target/debug/incremental[..]`\n",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn incremental_profile() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [profile.dev]
+ incremental = false
+
+ [profile.release]
+ incremental = true
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build -v")
+ .env_remove("CARGO_INCREMENTAL")
+ .with_stderr_does_not_contain("[..]C incremental=[..]")
+ .run();
+
+ p.cargo("build -v")
+ .env("CARGO_INCREMENTAL", "1")
+ .with_stderr_contains("[..]C incremental=[..]")
+ .run();
+
+ p.cargo("build --release -v")
+ .env_remove("CARGO_INCREMENTAL")
+ .with_stderr_contains("[..]C incremental=[..]")
+ .run();
+
+ p.cargo("build --release -v")
+ .env("CARGO_INCREMENTAL", "0")
+ .with_stderr_does_not_contain("[..]C incremental=[..]")
+ .run();
+}
+
+#[cargo_test]
+fn incremental_config() {
+ let p = project()
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ ".cargo/config",
+ r#"
+ [build]
+ incremental = false
+ "#,
+ )
+ .build();
+
+ p.cargo("build -v")
+ .env_remove("CARGO_INCREMENTAL")
+ .with_stderr_does_not_contain("[..]C incremental=[..]")
+ .run();
+
+ p.cargo("build -v")
+ .env("CARGO_INCREMENTAL", "1")
+ .with_stderr_contains("[..]C incremental=[..]")
+ .run();
+}
+
+#[cargo_test]
+fn cargo_compile_with_workspace_excluded() {
+ let p = project().file("src/main.rs", "fn main() {}").build();
+
+ p.cargo("build --workspace --exclude foo")
+ .with_stderr_does_not_contain("[..]virtual[..]")
+ .with_stderr_contains("[..]no packages to compile")
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn cargo_compile_manifest_path() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .build();
+
+ p.cargo("build --manifest-path foo/Cargo.toml")
+ .cwd(p.root().parent().unwrap())
+ .run();
+ assert!(p.bin("foo").is_file());
+}
+
+#[cargo_test]
+fn chdir_gated() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .build();
+ p.cargo("-C foo build")
+ .cwd(p.root().parent().unwrap())
+ .with_stderr(
+ "error: the `-C` flag is unstable, \
+ pass `-Z unstable-options` on the nightly channel to enable it",
+ )
+ .with_status(101)
+ .run();
+ // No masquerade should also fail.
+ p.cargo("-C foo -Z unstable-options build")
+ .cwd(p.root().parent().unwrap())
+ .with_stderr(
+ "error: the `-C` flag is unstable, \
+ pass `-Z unstable-options` on the nightly channel to enable it",
+ )
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn cargo_compile_directory_not_cwd() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .file(".cargo/config.toml", &"")
+ .build();
+
+ p.cargo("-Zunstable-options -C foo build")
+ .masquerade_as_nightly_cargo(&["chdir"])
+ .cwd(p.root().parent().unwrap())
+ .run();
+ assert!(p.bin("foo").is_file());
+}
+
+#[cargo_test]
+fn cargo_compile_directory_not_cwd_with_invalid_config() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .file(".cargo/config.toml", &"!")
+ .build();
+
+ p.cargo("-Zunstable-options -C foo build")
+ .masquerade_as_nightly_cargo(&["chdir"])
+ .cwd(p.root().parent().unwrap())
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+Caused by:
+ TOML parse error at line 1, column 1
+ |
+ 1 | !
+ | ^
+ invalid key
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_compile_with_invalid_manifest() {
+ let p = project().file("Cargo.toml", "").build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ virtual manifests must be configured with [workspace]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_compile_with_invalid_manifest2() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ "
+ [package]
+ foo = bar
+ ",
+ )
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ could not parse input as TOML
+
+Caused by:
+ TOML parse error at line 3, column 23
+ |
+ 3 | foo = bar
+ | ^
+ invalid string
+ expected `\"`, `'`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_compile_with_invalid_manifest3() {
+ let p = project().file("src/Cargo.toml", "a = bar").build();
+
+ p.cargo("build --manifest-path src/Cargo.toml")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ could not parse input as TOML
+
+Caused by:
+ TOML parse error at line 1, column 5
+ |
+ 1 | a = bar
+ | ^
+ invalid string
+ expected `\"`, `'`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_compile_duplicate_build_targets() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ name = "main"
+ path = "src/main.rs"
+ crate-type = ["dylib"]
+
+ [dependencies]
+ "#,
+ )
+ .file("src/main.rs", "#![allow(warnings)] fn main() {}")
+ .build();
+
+ p.cargo("build")
+ .with_stderr(
+ "\
+warning: file `[..]main.rs` found to be present in multiple build targets:
+ * `lib` target `main`
+ * `bin` target `foo`
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_compile_with_invalid_version() {
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo", "1.0"))
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ unexpected end of input while parsing minor version number
+ in `package.version`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_compile_with_empty_package_name() {
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("", "0.0.0"))
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ package name cannot be an empty string
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_compile_with_invalid_package_name() {
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo::bar", "0.0.0"))
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ invalid character `:` in package name: `foo::bar`, [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_compile_with_invalid_bin_target_name() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.0"
+
+ [[bin]]
+ name = ""
+ "#,
+ )
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ binary target names cannot be empty
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_compile_with_forbidden_bin_target_name() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.0"
+
+ [[bin]]
+ name = "build"
+ "#,
+ )
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ the binary target name `build` is forbidden, it conflicts with with cargo's build directory names
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_compile_with_bin_and_crate_type() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.0"
+
+ [[bin]]
+ name = "the_foo_bin"
+ path = "src/foo.rs"
+ crate-type = ["cdylib", "rlib"]
+ "#,
+ )
+ .file("src/foo.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ the target `the_foo_bin` is a binary and can't have any crate-types set \
+(currently \"cdylib, rlib\")",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_compile_api_exposes_artifact_paths() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.0"
+
+ [[bin]]
+ name = "the_foo_bin"
+ path = "src/bin.rs"
+
+ [lib]
+ name = "the_foo_lib"
+ path = "src/foo.rs"
+ crate-type = ["cdylib", "rlib"]
+ "#,
+ )
+ .file("src/foo.rs", "pub fn bar() {}")
+ .file("src/bin.rs", "pub fn main() {}")
+ .build();
+
+ let shell = Shell::from_write(Box::new(Vec::new()));
+ let config = Config::new(shell, env::current_dir().unwrap(), paths::home());
+ let ws = Workspace::new(&p.root().join("Cargo.toml"), &config).unwrap();
+ let compile_options = CompileOptions::new(ws.config(), CompileMode::Build).unwrap();
+
+ let result = cargo::ops::compile(&ws, &compile_options).unwrap();
+
+ assert_eq!(1, result.binaries.len());
+ assert!(result.binaries[0].path.exists());
+ assert!(result.binaries[0]
+ .path
+ .to_str()
+ .unwrap()
+ .contains("the_foo_bin"));
+
+ assert_eq!(1, result.cdylibs.len());
+ // The exact library path varies by platform, but should certainly exist at least
+ assert!(result.cdylibs[0].path.exists());
+ assert!(result.cdylibs[0]
+ .path
+ .to_str()
+ .unwrap()
+ .contains("the_foo_lib"));
+}
+
+#[cargo_test]
+fn cargo_compile_with_bin_and_proc() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.0"
+
+ [[bin]]
+ name = "the_foo_bin"
+ path = "src/foo.rs"
+ proc-macro = true
+ "#,
+ )
+ .file("src/foo.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ the target `the_foo_bin` is a binary and can't have `proc-macro` set `true`",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_compile_with_invalid_lib_target_name() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.0"
+
+ [lib]
+ name = ""
+ "#,
+ )
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ library target names cannot be empty
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_compile_with_invalid_non_numeric_dep_version() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies]
+ crossbeam = "y"
+ "#,
+ )
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[CWD]/Cargo.toml`
+
+Caused by:
+ failed to parse the version requirement `y` for dependency `crossbeam`
+
+Caused by:
+ unexpected character 'y' while parsing major version number
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_compile_without_manifest() {
+ let p = project().no_manifest().build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr("[ERROR] could not find `Cargo.toml` in `[..]` or any parent directory")
+ .run();
+}
+
+#[cargo_test]
+#[cfg(target_os = "linux")]
+fn cargo_compile_with_lowercase_cargo_toml() {
+ let p = project()
+ .no_manifest()
+ .file("cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("src/lib.rs", &main_file(r#""i am foo""#, &[]))
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "[ERROR] could not find `Cargo.toml` in `[..]` or any parent directory, \
+ but found cargo.toml please try to rename it to Cargo.toml",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_compile_with_invalid_code() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", "invalid rust code!")
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr_contains(
+ "[ERROR] could not compile `foo` (bin \"foo\") due to previous error\n",
+ )
+ .run();
+ assert!(p.root().join("Cargo.lock").is_file());
+}
+
+#[cargo_test]
+fn cargo_compile_with_invalid_code_in_deps() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "../bar"
+ [dependencies.baz]
+ path = "../baz"
+ "#,
+ )
+ .file("src/main.rs", "invalid rust code!")
+ .build();
+ let _bar = project()
+ .at("bar")
+ .file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("src/lib.rs", "invalid rust code!")
+ .build();
+ let _baz = project()
+ .at("baz")
+ .file("Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("src/lib.rs", "invalid rust code!")
+ .build();
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr_contains("[..]invalid rust code[..]")
+ .with_stderr_contains("[ERROR] could not compile [..]")
+ .run();
+}
+
+#[cargo_test]
+fn cargo_compile_with_warnings_in_the_root_package() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", "fn main() {} fn dead() {}")
+ .build();
+
+ p.cargo("build")
+ .with_stderr_contains("[WARNING] [..]dead[..]")
+ .run();
+}
+
+#[cargo_test]
+fn cargo_compile_with_warnings_in_a_dep_package() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.bar]
+ path = "bar"
+
+ [[bin]]
+
+ name = "foo"
+ "#,
+ )
+ .file("src/foo.rs", &main_file(r#""{}", bar::gimme()"#, &["bar"]))
+ .file("bar/Cargo.toml", &basic_lib_manifest("bar"))
+ .file(
+ "bar/src/bar.rs",
+ r#"
+ pub fn gimme() -> &'static str {
+ "test passed"
+ }
+
+ fn dead() {}
+ "#,
+ )
+ .build();
+
+ p.cargo("build")
+ .with_stderr_contains("[WARNING] [..]dead[..]")
+ .run();
+
+ assert!(p.bin("foo").is_file());
+
+ p.process(&p.bin("foo")).with_stdout("test passed\n").run();
+}
+
+#[cargo_test]
+fn cargo_compile_with_nested_deps_inferred() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.bar]
+ path = 'bar'
+
+ [[bin]]
+ name = "foo"
+ "#,
+ )
+ .file("src/foo.rs", &main_file(r#""{}", bar::gimme()"#, &["bar"]))
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+
+ name = "bar"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.baz]
+ path = "../baz"
+ "#,
+ )
+ .file(
+ "bar/src/lib.rs",
+ r#"
+ extern crate baz;
+
+ pub fn gimme() -> String {
+ baz::gimme()
+ }
+ "#,
+ )
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.5.0"))
+ .file(
+ "baz/src/lib.rs",
+ r#"
+ pub fn gimme() -> String {
+ "test passed".to_string()
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build").run();
+
+ assert!(p.bin("foo").is_file());
+ assert!(!p.bin("libbar.rlib").is_file());
+ assert!(!p.bin("libbaz.rlib").is_file());
+
+ p.process(&p.bin("foo")).with_stdout("test passed\n").run();
+}
+
+#[cargo_test]
+fn cargo_compile_with_nested_deps_correct_bin() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.bar]
+ path = "bar"
+
+ [[bin]]
+ name = "foo"
+ "#,
+ )
+ .file("src/main.rs", &main_file(r#""{}", bar::gimme()"#, &["bar"]))
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+
+ name = "bar"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.baz]
+ path = "../baz"
+ "#,
+ )
+ .file(
+ "bar/src/lib.rs",
+ r#"
+ extern crate baz;
+
+ pub fn gimme() -> String {
+ baz::gimme()
+ }
+ "#,
+ )
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.5.0"))
+ .file(
+ "baz/src/lib.rs",
+ r#"
+ pub fn gimme() -> String {
+ "test passed".to_string()
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build").run();
+
+ assert!(p.bin("foo").is_file());
+ assert!(!p.bin("libbar.rlib").is_file());
+ assert!(!p.bin("libbaz.rlib").is_file());
+
+ p.process(&p.bin("foo")).with_stdout("test passed\n").run();
+}
+
+#[cargo_test]
+fn cargo_compile_with_nested_deps_shorthand() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.bar]
+ path = "bar"
+ "#,
+ )
+ .file("src/main.rs", &main_file(r#""{}", bar::gimme()"#, &["bar"]))
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+
+ name = "bar"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.baz]
+ path = "../baz"
+
+ [lib]
+
+ name = "bar"
+ "#,
+ )
+ .file(
+ "bar/src/bar.rs",
+ r#"
+ extern crate baz;
+
+ pub fn gimme() -> String {
+ baz::gimme()
+ }
+ "#,
+ )
+ .file("baz/Cargo.toml", &basic_lib_manifest("baz"))
+ .file(
+ "baz/src/baz.rs",
+ r#"
+ pub fn gimme() -> String {
+ "test passed".to_string()
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build").run();
+
+ assert!(p.bin("foo").is_file());
+ assert!(!p.bin("libbar.rlib").is_file());
+ assert!(!p.bin("libbaz.rlib").is_file());
+
+ p.process(&p.bin("foo")).with_stdout("test passed\n").run();
+}
+
+#[cargo_test]
+fn cargo_compile_with_nested_deps_longhand() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.bar]
+ path = "bar"
+ version = "0.5.0"
+
+ [[bin]]
+
+ name = "foo"
+ "#,
+ )
+ .file("src/foo.rs", &main_file(r#""{}", bar::gimme()"#, &["bar"]))
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+
+ name = "bar"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.baz]
+ path = "../baz"
+ version = "0.5.0"
+
+ [lib]
+
+ name = "bar"
+ "#,
+ )
+ .file(
+ "bar/src/bar.rs",
+ r#"
+ extern crate baz;
+
+ pub fn gimme() -> String {
+ baz::gimme()
+ }
+ "#,
+ )
+ .file("baz/Cargo.toml", &basic_lib_manifest("baz"))
+ .file(
+ "baz/src/baz.rs",
+ r#"
+ pub fn gimme() -> String {
+ "test passed".to_string()
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build").run();
+
+ assert!(p.bin("foo").is_file());
+ assert!(!p.bin("libbar.rlib").is_file());
+ assert!(!p.bin("libbaz.rlib").is_file());
+
+ p.process(&p.bin("foo")).with_stdout("test passed\n").run();
+}
+
+// Check that Cargo gives a sensible error if a dependency can't be found
+// because of a name mismatch.
+#[cargo_test]
+fn cargo_compile_with_dep_name_mismatch() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.0.1"
+ authors = ["wycats@example.com"]
+
+ [[bin]]
+
+ name = "foo"
+
+ [dependencies.notquitebar]
+
+ path = "bar"
+ "#,
+ )
+ .file("src/bin/foo.rs", &main_file(r#""i am foo""#, &["bar"]))
+ .file("bar/Cargo.toml", &basic_bin_manifest("bar"))
+ .file("bar/src/bar.rs", &main_file(r#""i am bar""#, &[]))
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: no matching package named `notquitebar` found
+location searched: [CWD]/bar
+required by package `foo v0.0.1 ([CWD])`
+",
+ )
+ .run();
+}
+
+// Ensure that renamed deps have a valid name
+#[cargo_test]
+fn cargo_compile_with_invalid_dep_rename() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "buggin"
+ version = "0.1.0"
+
+ [dependencies]
+ "haha this isn't a valid name 🐛" = { package = "libc", version = "0.1" }
+ "#,
+ )
+ .file("src/main.rs", &main_file(r#""What's good?""#, &[]))
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[..]`
+
+Caused by:
+ invalid character ` ` in dependency name: `haha this isn't a valid name 🐛`, characters must be Unicode XID characters (numbers, `-`, `_`, or most letters)
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_compile_with_filename() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(
+ "src/bin/a.rs",
+ r#"
+ extern crate foo;
+ fn main() { println!("hello a.rs"); }
+ "#,
+ )
+ .file("examples/a.rs", r#"fn main() { println!("example"); }"#)
+ .build();
+
+ p.cargo("build --bin bin.rs")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] no bin target named `bin.rs`.
+Available bin targets:
+ a
+
+",
+ )
+ .run();
+
+ p.cargo("build --bin a.rs")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] no bin target named `a.rs`
+
+<tab>Did you mean `a`?",
+ )
+ .run();
+
+ p.cargo("build --example example.rs")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] no example target named `example.rs`.
+Available example targets:
+ a
+
+",
+ )
+ .run();
+
+ p.cargo("build --example a.rs")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] no example target named `a.rs`
+
+<tab>Did you mean `a`?",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn incompatible_dependencies() {
+ Package::new("bad", "0.1.0").publish();
+ Package::new("bad", "1.0.0").publish();
+ Package::new("bad", "1.0.1").publish();
+ Package::new("bad", "1.0.2").publish();
+ Package::new("bar", "0.1.0").dep("bad", "0.1.0").publish();
+ Package::new("baz", "0.1.1").dep("bad", "=1.0.0").publish();
+ Package::new("baz", "0.1.0").dep("bad", "=1.0.0").publish();
+ Package::new("qux", "0.1.2").dep("bad", ">=1.0.1").publish();
+ Package::new("qux", "0.1.1").dep("bad", ">=1.0.1").publish();
+ Package::new("qux", "0.1.0").dep("bad", ">=1.0.1").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies]
+ bar = "0.1.0"
+ baz = "0.1.0"
+ qux = "0.1.0"
+ "#,
+ )
+ .file("src/main.rs", "fn main(){}")
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+error: failed to select a version for `bad`.
+ ... required by package `qux v0.1.0`
+ ... which satisfies dependency `qux = \"^0.1.0\"` of package `foo v0.0.1 ([..])`
+versions that meet the requirements `>=1.0.1` are: 1.0.2, 1.0.1
+
+all possible versions conflict with previously selected packages.
+
+ previously selected package `bad v1.0.0`
+ ... which satisfies dependency `bad = \"=1.0.0\"` of package `baz v0.1.0`
+ ... which satisfies dependency `baz = \"^0.1.0\"` of package `foo v0.0.1 ([..])`
+
+failed to select a version for `bad` which could resolve this conflict",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn incompatible_dependencies_with_multi_semver() {
+ Package::new("bad", "1.0.0").publish();
+ Package::new("bad", "1.0.1").publish();
+ Package::new("bad", "2.0.0").publish();
+ Package::new("bad", "2.0.1").publish();
+ Package::new("bar", "0.1.0").dep("bad", "=1.0.0").publish();
+ Package::new("baz", "0.1.0").dep("bad", ">=2.0.1").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies]
+ bar = "0.1.0"
+ baz = "0.1.0"
+ bad = ">=1.0.1, <=2.0.0"
+ "#,
+ )
+ .file("src/main.rs", "fn main(){}")
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+error: failed to select a version for `bad`.
+ ... required by package `foo v0.0.1 ([..])`
+versions that meet the requirements `>=1.0.1, <=2.0.0` are: 2.0.0, 1.0.1
+
+all possible versions conflict with previously selected packages.
+
+ previously selected package `bad v2.0.1`
+ ... which satisfies dependency `bad = \">=2.0.1\"` of package `baz v0.1.0`
+ ... which satisfies dependency `baz = \"^0.1.0\"` of package `foo v0.0.1 ([..])`
+
+ previously selected package `bad v1.0.0`
+ ... which satisfies dependency `bad = \"=1.0.0\"` of package `bar v0.1.0`
+ ... which satisfies dependency `bar = \"^0.1.0\"` of package `foo v0.0.1 ([..])`
+
+failed to select a version for `bad` which could resolve this conflict",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn compile_path_dep_then_change_version() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "bar"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+
+ p.change_file("bar/Cargo.toml", &basic_manifest("bar", "0.0.2"));
+
+ p.cargo("build").run();
+}
+
+#[cargo_test]
+fn ignores_carriage_return_in_lockfile() {
+ let p = project()
+ .file("src/main.rs", "mod a; fn main() {}")
+ .file("src/a.rs", "")
+ .build();
+
+ p.cargo("build").run();
+
+ let lock = p.read_lockfile();
+ p.change_file("Cargo.lock", &lock.replace("\n", "\r\n"));
+ p.cargo("build").run();
+}
+
+#[cargo_test]
+fn cargo_default_env_metadata_env_var() {
+ // Ensure that path dep + dylib + env_var get metadata
+ // (even though path_dep + dylib should not)
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "bar"
+ "#,
+ )
+ .file("src/lib.rs", "// hi")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ name = "bar"
+ crate_type = ["dylib"]
+ "#,
+ )
+ .file("bar/src/lib.rs", "// hello")
+ .build();
+
+ // No metadata on libbar since it's a dylib path dependency
+ p.cargo("build -v")
+ .with_stderr(&format!(
+ "\
+[COMPILING] bar v0.0.1 ([CWD]/bar)
+[RUNNING] `rustc --crate-name bar bar/src/lib.rs [..]--crate-type dylib \
+ --emit=[..]link \
+ -C prefer-dynamic[..]-C debuginfo=2 \
+ -C metadata=[..] \
+ --out-dir [..] \
+ -L dependency=[CWD]/target/debug/deps`
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name foo src/lib.rs [..]--crate-type lib \
+ --emit=[..]link[..]-C debuginfo=2 \
+ -C metadata=[..] \
+ -C extra-filename=[..] \
+ --out-dir [..] \
+ -L dependency=[CWD]/target/debug/deps \
+ --extern bar=[CWD]/target/debug/deps/{prefix}bar{suffix}`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ prefix = env::consts::DLL_PREFIX,
+ suffix = env::consts::DLL_SUFFIX,
+ ))
+ .run();
+
+ p.cargo("clean").run();
+
+ // If you set the env-var, then we expect metadata on libbar
+ p.cargo("build -v")
+ .env("__CARGO_DEFAULT_LIB_METADATA", "stable")
+ .with_stderr(&format!(
+ "\
+[COMPILING] bar v0.0.1 ([CWD]/bar)
+[RUNNING] `rustc --crate-name bar bar/src/lib.rs [..]--crate-type dylib \
+ --emit=[..]link \
+ -C prefer-dynamic[..]-C debuginfo=2 \
+ -C metadata=[..] \
+ --out-dir [..] \
+ -L dependency=[CWD]/target/debug/deps`
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name foo src/lib.rs [..]--crate-type lib \
+ --emit=[..]link[..]-C debuginfo=2 \
+ -C metadata=[..] \
+ -C extra-filename=[..] \
+ --out-dir [..] \
+ -L dependency=[CWD]/target/debug/deps \
+ --extern bar=[CWD]/target/debug/deps/{prefix}bar-[..]{suffix}`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ prefix = env::consts::DLL_PREFIX,
+ suffix = env::consts::DLL_SUFFIX,
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn crate_env_vars() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.1-alpha.1"
+ description = "This is foo"
+ homepage = "https://example.com"
+ repository = "https://example.com/repo.git"
+ authors = ["wycats@example.com"]
+ license = "MIT OR Apache-2.0"
+ license-file = "license.txt"
+ rust-version = "1.61.0"
+ readme = "../../README.md"
+
+ [[bin]]
+ name = "foo-bar"
+ path = "src/main.rs"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ extern crate foo;
+
+
+ static VERSION_MAJOR: &'static str = env!("CARGO_PKG_VERSION_MAJOR");
+ static VERSION_MINOR: &'static str = env!("CARGO_PKG_VERSION_MINOR");
+ static VERSION_PATCH: &'static str = env!("CARGO_PKG_VERSION_PATCH");
+ static VERSION_PRE: &'static str = env!("CARGO_PKG_VERSION_PRE");
+ static VERSION: &'static str = env!("CARGO_PKG_VERSION");
+ static CARGO_MANIFEST_DIR: &'static str = env!("CARGO_MANIFEST_DIR");
+ static PKG_NAME: &'static str = env!("CARGO_PKG_NAME");
+ static HOMEPAGE: &'static str = env!("CARGO_PKG_HOMEPAGE");
+ static REPOSITORY: &'static str = env!("CARGO_PKG_REPOSITORY");
+ static LICENSE: &'static str = env!("CARGO_PKG_LICENSE");
+ static LICENSE_FILE: &'static str = env!("CARGO_PKG_LICENSE_FILE");
+ static DESCRIPTION: &'static str = env!("CARGO_PKG_DESCRIPTION");
+ static RUST_VERSION: &'static str = env!("CARGO_PKG_RUST_VERSION");
+ static README: &'static str = env!("CARGO_PKG_README");
+ static BIN_NAME: &'static str = env!("CARGO_BIN_NAME");
+ static CRATE_NAME: &'static str = env!("CARGO_CRATE_NAME");
+
+
+ fn main() {
+ let s = format!("{}-{}-{} @ {} in {}", VERSION_MAJOR,
+ VERSION_MINOR, VERSION_PATCH, VERSION_PRE,
+ CARGO_MANIFEST_DIR);
+ assert_eq!(s, foo::version());
+ println!("{}", s);
+ assert_eq!("foo", PKG_NAME);
+ assert_eq!("foo-bar", BIN_NAME);
+ assert_eq!("foo_bar", CRATE_NAME);
+ assert_eq!("https://example.com", HOMEPAGE);
+ assert_eq!("https://example.com/repo.git", REPOSITORY);
+ assert_eq!("MIT OR Apache-2.0", LICENSE);
+ assert_eq!("license.txt", LICENSE_FILE);
+ assert_eq!("This is foo", DESCRIPTION);
+ assert_eq!("1.61.0", RUST_VERSION);
+ assert_eq!("../../README.md", README);
+ let s = format!("{}.{}.{}-{}", VERSION_MAJOR,
+ VERSION_MINOR, VERSION_PATCH, VERSION_PRE);
+ assert_eq!(s, VERSION);
+
+ // Verify CARGO_TARGET_TMPDIR isn't set for bins
+ assert!(option_env!("CARGO_TARGET_TMPDIR").is_none());
+ }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ use std::env;
+ use std::path::PathBuf;
+
+ pub fn version() -> String {
+ format!("{}-{}-{} @ {} in {}",
+ env!("CARGO_PKG_VERSION_MAJOR"),
+ env!("CARGO_PKG_VERSION_MINOR"),
+ env!("CARGO_PKG_VERSION_PATCH"),
+ env!("CARGO_PKG_VERSION_PRE"),
+ env!("CARGO_MANIFEST_DIR"))
+ }
+
+ pub fn check_no_int_test_env() {
+ env::var("CARGO_TARGET_DIR").unwrap_err();
+ }
+
+ pub fn check_tmpdir(tmp: Option<&'static str>) {
+ let tmpdir: PathBuf = tmp.unwrap().into();
+
+ let exe: PathBuf = env::current_exe().unwrap().into();
+ let mut expected: PathBuf = exe.parent().unwrap()
+ .parent().unwrap()
+ .parent().unwrap()
+ .into();
+ expected.push("tmp");
+ assert_eq!(tmpdir, expected);
+
+ // Check that CARGO_TARGET_TMPDIR isn't set for lib code
+ assert!(option_env!("CARGO_TARGET_TMPDIR").is_none());
+ env::var("CARGO_TARGET_TMPDIR").unwrap_err();
+ }
+
+ #[test]
+ fn env() {
+ // Check that CARGO_TARGET_TMPDIR isn't set for unit tests
+ assert!(option_env!("CARGO_TARGET_TMPDIR").is_none());
+ env::var("CARGO_TARGET_TMPDIR").unwrap_err();
+ }
+ "#,
+ )
+ .file(
+ "examples/ex-env-vars.rs",
+ r#"
+ static PKG_NAME: &'static str = env!("CARGO_PKG_NAME");
+ static BIN_NAME: &'static str = env!("CARGO_BIN_NAME");
+ static CRATE_NAME: &'static str = env!("CARGO_CRATE_NAME");
+
+ fn main() {
+ assert_eq!("foo", PKG_NAME);
+ assert_eq!("ex-env-vars", BIN_NAME);
+ assert_eq!("ex_env_vars", CRATE_NAME);
+
+ // Verify CARGO_TARGET_TMPDIR isn't set for examples
+ assert!(option_env!("CARGO_TARGET_TMPDIR").is_none());
+ }
+ "#,
+ )
+ .file(
+ "tests/env.rs",
+ r#"
+ #[test]
+ fn env() {
+ foo::check_tmpdir(option_env!("CARGO_TARGET_TMPDIR"));
+ }
+ "#,
+ );
+
+ let p = if is_nightly() {
+ p.file(
+ "benches/env.rs",
+ r#"
+ #![feature(test)]
+ extern crate test;
+ use test::Bencher;
+
+ #[bench]
+ fn env(_: &mut Bencher) {
+ foo::check_tmpdir(option_env!("CARGO_TARGET_TMPDIR"));
+ }
+ "#,
+ )
+ .build()
+ } else {
+ p.build()
+ };
+
+ println!("build");
+ p.cargo("build -v").run();
+
+ println!("bin");
+ p.process(&p.bin("foo-bar"))
+ .with_stdout("0-5-1 @ alpha.1 in [CWD]")
+ .run();
+
+ println!("example");
+ p.cargo("run --example ex-env-vars -v").run();
+
+ println!("test");
+ p.cargo("test -v").run();
+
+ if is_nightly() {
+ println!("bench");
+ p.cargo("bench -v").run();
+ }
+}
+
+#[cargo_test]
+fn crate_authors_env_vars() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.1-alpha.1"
+ authors = ["wycats@example.com", "neikos@example.com"]
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ extern crate foo;
+
+ static AUTHORS: &'static str = env!("CARGO_PKG_AUTHORS");
+
+ fn main() {
+ let s = "wycats@example.com:neikos@example.com";
+ assert_eq!(AUTHORS, foo::authors());
+ println!("{}", AUTHORS);
+ assert_eq!(s, AUTHORS);
+ }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn authors() -> String {
+ format!("{}", env!("CARGO_PKG_AUTHORS"))
+ }
+ "#,
+ )
+ .build();
+
+ println!("build");
+ p.cargo("build -v").run();
+
+ println!("bin");
+ p.process(&p.bin("foo"))
+ .with_stdout("wycats@example.com:neikos@example.com")
+ .run();
+
+ println!("test");
+ p.cargo("test -v").run();
+}
+
+#[cargo_test]
+fn vv_prints_rustc_env_vars() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = ["escape='\"@example.com"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ let mut b = p.cargo("build -vv");
+
+ if cfg!(windows) {
+ b.with_stderr_contains(
+ "[RUNNING] `[..]set CARGO_PKG_NAME=foo&& [..]rustc [..]`"
+ ).with_stderr_contains(
+ r#"[RUNNING] `[..]set CARGO_PKG_AUTHORS="escape='\"@example.com"&& [..]rustc [..]`"#
+ )
+ } else {
+ b.with_stderr_contains("[RUNNING] `[..]CARGO_PKG_NAME=foo [..]rustc [..]`")
+ .with_stderr_contains(
+ r#"[RUNNING] `[..]CARGO_PKG_AUTHORS='escape='\''"@example.com' [..]rustc [..]`"#,
+ )
+ };
+
+ b.run();
+}
+
+// The tester may already have LD_LIBRARY_PATH=::/foo/bar which leads to a false positive error
+fn setenv_for_removing_empty_component(mut execs: Execs) -> Execs {
+ let v = dylib_path_envvar();
+ if let Ok(search_path) = env::var(v) {
+ let new_search_path =
+ env::join_paths(env::split_paths(&search_path).filter(|e| !e.as_os_str().is_empty()))
+ .expect("join_paths");
+ execs.env(v, new_search_path); // build_command() will override LD_LIBRARY_PATH accordingly
+ }
+ execs
+}
+
+// Regression test for #4277
+#[cargo_test]
+fn crate_library_path_env_var() {
+ let p = project()
+ .file(
+ "src/main.rs",
+ &format!(
+ r#"
+ fn main() {{
+ let search_path = env!("{}");
+ let paths = std::env::split_paths(&search_path).collect::<Vec<_>>();
+ assert!(!paths.contains(&"".into()));
+ }}
+ "#,
+ dylib_path_envvar()
+ ),
+ )
+ .build();
+
+ setenv_for_removing_empty_component(p.cargo("run")).run();
+}
+
+// Regression test for #4277
+#[cargo_test]
+fn build_with_fake_libc_not_loading() {
+ let p = project()
+ .file("src/main.rs", "fn main() {}")
+ .file("src/lib.rs", r#" "#)
+ .file("libc.so.6", r#""#)
+ .build();
+
+ setenv_for_removing_empty_component(p.cargo("build")).run();
+}
+
+// this is testing that src/<pkg-name>.rs still works (for now)
+#[cargo_test]
+fn many_crate_types_old_style_lib_location() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [lib]
+
+ name = "foo"
+ crate_type = ["rlib", "dylib"]
+ "#,
+ )
+ .file("src/foo.rs", "pub fn foo() {}")
+ .build();
+ p.cargo("build")
+ .with_stderr_contains(
+ "\
+[WARNING] path `[..]src/foo.rs` was erroneously implicitly accepted for library `foo`,
+please rename the file to `src/lib.rs` or set lib.path in Cargo.toml",
+ )
+ .run();
+
+ assert!(p.root().join("target/debug/libfoo.rlib").is_file());
+ let fname = format!("{}foo{}", env::consts::DLL_PREFIX, env::consts::DLL_SUFFIX);
+ assert!(p.root().join("target/debug").join(&fname).is_file());
+}
+
+#[cargo_test]
+fn many_crate_types_correct() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [lib]
+
+ name = "foo"
+ crate_type = ["rlib", "dylib"]
+ "#,
+ )
+ .file("src/lib.rs", "pub fn foo() {}")
+ .build();
+ p.cargo("build").run();
+
+ assert!(p.root().join("target/debug/libfoo.rlib").is_file());
+ let fname = format!("{}foo{}", env::consts::DLL_PREFIX, env::consts::DLL_SUFFIX);
+ assert!(p.root().join("target/debug").join(&fname).is_file());
+}
+
+#[cargo_test]
+fn set_both_dylib_and_cdylib_crate_types() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [lib]
+
+ name = "foo"
+ crate_type = ["cdylib", "dylib"]
+ "#,
+ )
+ .file("src/lib.rs", "pub fn foo() {}")
+ .build();
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[..]`
+
+Caused by:
+ library `foo` cannot set the crate type of both `dylib` and `cdylib`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn dev_dependencies_conflicting_warning() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [dev-dependencies]
+ a = {path = "a"}
+ [dev_dependencies]
+ a = {path = "a"}
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .build();
+ p.cargo("build")
+ .with_stderr_contains(
+"[WARNING] conflicting between `dev-dependencies` and `dev_dependencies` in the `foo` package.\n
+ `dev_dependencies` is ignored and not recommended for use in the future"
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_dependencies_conflicting_warning() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [build-dependencies]
+ a = {path = "a"}
+ [build_dependencies]
+ a = {path = "a"}
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .build();
+ p.cargo("build")
+ .with_stderr_contains(
+"[WARNING] conflicting between `build-dependencies` and `build_dependencies` in the `foo` package.\n
+ `build_dependencies` is ignored and not recommended for use in the future"
+ )
+ .run();
+}
+
+#[cargo_test]
+fn lib_crate_types_conflicting_warning() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [lib]
+ name = "foo"
+ crate-type = ["rlib", "dylib"]
+ crate_type = ["staticlib", "dylib"]
+ "#,
+ )
+ .file("src/lib.rs", "pub fn foo() {}")
+ .build();
+ p.cargo("build")
+ .with_stderr_contains(
+"[WARNING] conflicting between `crate-type` and `crate_type` in the `foo` library target.\n
+ `crate_type` is ignored and not recommended for use in the future",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn examples_crate_types_conflicting_warning() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [[example]]
+ name = "ex"
+ path = "examples/ex.rs"
+ crate-type = ["rlib", "dylib"]
+ crate_type = ["proc_macro"]
+ [[example]]
+ name = "goodbye"
+ path = "examples/ex-goodbye.rs"
+ crate-type = ["rlib", "dylib"]
+ crate_type = ["rlib", "staticlib"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "examples/ex.rs",
+ r#"
+ fn main() { println!("ex"); }
+ "#,
+ )
+ .file(
+ "examples/ex-goodbye.rs",
+ r#"
+ fn main() { println!("goodbye"); }
+ "#,
+ )
+ .build();
+ p.cargo("build")
+ .with_stderr_contains(
+ "\
+[WARNING] conflicting between `crate-type` and `crate_type` in the `ex` example target.\n
+ `crate_type` is ignored and not recommended for use in the future
+[WARNING] conflicting between `crate-type` and `crate_type` in the `goodbye` example target.\n
+ `crate_type` is ignored and not recommended for use in the future",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn self_dependency() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "test"
+ version = "0.0.0"
+ authors = []
+
+ [dependencies.test]
+
+ path = "."
+
+ [lib]
+ name = "test"
+ path = "src/test.rs"
+ "#,
+ )
+ .file("src/test.rs", "fn main() {}")
+ .build();
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] cyclic package dependency: package `test v0.0.0 ([CWD])` depends on itself. Cycle:
+package `test v0.0.0 ([CWD])`
+ ... which satisfies path dependency `test` of package `test v0.0.0 ([..])`",
+ )
+ .run();
+}
+
+#[cargo_test]
+/// Make sure broken and loop symlinks don't break the build
+///
+/// This test requires you to be able to make symlinks.
+/// For windows, this may require you to enable developer mode.
+fn ignore_broken_symlinks() {
+ if !symlink_supported() {
+ return;
+ }
+
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .symlink("Notafile", "bar")
+ // To hit the symlink directory, we need a build script
+ // to trigger a full scan of package files.
+ .file("build.rs", &main_file(r#""build script""#, &[]))
+ .symlink_dir("a/b", "a/b/c/d/foo")
+ .build();
+
+ p.cargo("build")
+ .with_stderr_contains(
+ "[WARNING] File system loop found: [..]/a/b/c/d/foo points to an ancestor [..]/a/b",
+ )
+ .run();
+ assert!(p.bin("foo").is_file());
+
+ p.process(&p.bin("foo")).with_stdout("i am foo\n").run();
+}
+
+#[cargo_test]
+fn missing_lib_and_bin() {
+ let p = project().build();
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]Cargo.toml`
+
+Caused by:
+ no targets specified in the manifest
+ either src/lib.rs, src/main.rs, a [lib] section, or [[bin]] section must be present\n",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn lto_build() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "test"
+ version = "0.0.0"
+ authors = []
+
+ [profile.release]
+ lto = true
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+ p.cargo("build -v --release")
+ .with_stderr(
+ "\
+[COMPILING] test v0.0.0 ([CWD])
+[RUNNING] `rustc --crate-name test src/main.rs [..]--crate-type bin \
+ --emit=[..]link \
+ -C opt-level=3 \
+ -C lto \
+ [..]
+[FINISHED] release [optimized] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn verbose_build() {
+ let p = project().file("src/lib.rs", "").build();
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name foo src/lib.rs [..]--crate-type lib \
+ --emit=[..]link[..]-C debuginfo=2 \
+ -C metadata=[..] \
+ --out-dir [..] \
+ -L dependency=[CWD]/target/debug/deps`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn verbose_release_build() {
+ let p = project().file("src/lib.rs", "").build();
+ p.cargo("build -v --release")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name foo src/lib.rs [..]--crate-type lib \
+ --emit=[..]link[..]\
+ -C opt-level=3[..]\
+ -C metadata=[..] \
+ --out-dir [..] \
+ -L dependency=[CWD]/target/release/deps`
+[FINISHED] release [optimized] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn verbose_release_build_short() {
+ let p = project().file("src/lib.rs", "").build();
+ p.cargo("build -v -r")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name foo src/lib.rs [..]--crate-type lib \
+ --emit=[..]link[..]\
+ -C opt-level=3[..]\
+ -C metadata=[..] \
+ --out-dir [..] \
+ -L dependency=[CWD]/target/release/deps`
+[FINISHED] release [optimized] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn verbose_release_build_deps() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "test"
+ version = "0.0.0"
+ authors = []
+
+ [dependencies.foo]
+ path = "foo"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+
+ [lib]
+ name = "foo"
+ crate_type = ["dylib", "rlib"]
+ "#,
+ )
+ .file("foo/src/lib.rs", "")
+ .build();
+ p.cargo("build -v --release")
+ .with_stderr(&format!(
+ "\
+[COMPILING] foo v0.0.0 ([CWD]/foo)
+[RUNNING] `rustc --crate-name foo foo/src/lib.rs [..]\
+ --crate-type dylib --crate-type rlib \
+ --emit=[..]link \
+ -C prefer-dynamic[..]\
+ -C opt-level=3[..]\
+ -C metadata=[..] \
+ --out-dir [..] \
+ -L dependency=[CWD]/target/release/deps`
+[COMPILING] test v0.0.0 ([CWD])
+[RUNNING] `rustc --crate-name test src/lib.rs [..]--crate-type lib \
+ --emit=[..]link[..]\
+ -C opt-level=3[..]\
+ -C metadata=[..] \
+ --out-dir [..] \
+ -L dependency=[CWD]/target/release/deps \
+ --extern foo=[CWD]/target/release/deps/{prefix}foo{suffix} \
+ --extern foo=[CWD]/target/release/deps/libfoo.rlib`
+[FINISHED] release [optimized] target(s) in [..]
+",
+ prefix = env::consts::DLL_PREFIX,
+ suffix = env::consts::DLL_SUFFIX
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn explicit_examples() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "1.0.0"
+ authors = []
+
+ [lib]
+ name = "foo"
+ path = "src/lib.rs"
+
+ [[example]]
+ name = "hello"
+ path = "examples/ex-hello.rs"
+
+ [[example]]
+ name = "goodbye"
+ path = "examples/ex-goodbye.rs"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn get_hello() -> &'static str { "Hello" }
+ pub fn get_goodbye() -> &'static str { "Goodbye" }
+ pub fn get_world() -> &'static str { "World" }
+ "#,
+ )
+ .file(
+ "examples/ex-hello.rs",
+ r#"
+ extern crate foo;
+ fn main() { println!("{}, {}!", foo::get_hello(), foo::get_world()); }
+ "#,
+ )
+ .file(
+ "examples/ex-goodbye.rs",
+ r#"
+ extern crate foo;
+ fn main() { println!("{}, {}!", foo::get_goodbye(), foo::get_world()); }
+ "#,
+ )
+ .build();
+
+ p.cargo("build --examples").run();
+ p.process(&p.bin("examples/hello"))
+ .with_stdout("Hello, World!\n")
+ .run();
+ p.process(&p.bin("examples/goodbye"))
+ .with_stdout("Goodbye, World!\n")
+ .run();
+}
+
+#[cargo_test]
+fn non_existing_test() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "1.0.0"
+
+ [lib]
+ name = "foo"
+ path = "src/lib.rs"
+
+ [[test]]
+ name = "hello"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build --tests -v")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ can't find `hello` test at `tests/hello.rs` or `tests/hello/main.rs`. \
+ Please specify test.path if you want to use a non-default path.",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn non_existing_example() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "1.0.0"
+
+ [lib]
+ name = "foo"
+ path = "src/lib.rs"
+
+ [[example]]
+ name = "hello"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build --examples -v")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ can't find `hello` example at `examples/hello.rs` or `examples/hello/main.rs`. \
+ Please specify example.path if you want to use a non-default path.",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn non_existing_benchmark() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "1.0.0"
+
+ [lib]
+ name = "foo"
+ path = "src/lib.rs"
+
+ [[bench]]
+ name = "hello"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build --benches -v")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ can't find `hello` bench at `benches/hello.rs` or `benches/hello/main.rs`. \
+ Please specify bench.path if you want to use a non-default path.",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn non_existing_binary() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/lib.rs", "")
+ .file("src/bin/ehlo.rs", "")
+ .build();
+
+ p.cargo("build -v")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ can't find `foo` bin at `src/bin/foo.rs` or `src/bin/foo/main.rs`. \
+ Please specify bin.path if you want to use a non-default path.",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn commonly_wrong_path_of_test() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "1.0.0"
+
+ [lib]
+ name = "foo"
+ path = "src/lib.rs"
+
+ [[test]]
+ name = "foo"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("test/foo.rs", "")
+ .build();
+
+ p.cargo("build --tests -v")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ can't find `foo` test at default paths, but found a file at `test/foo.rs`.
+ Perhaps rename the file to `tests/foo.rs` for target auto-discovery, \
+ or specify test.path if you want to use a non-default path.",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn commonly_wrong_path_of_example() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "1.0.0"
+
+ [lib]
+ name = "foo"
+ path = "src/lib.rs"
+
+ [[example]]
+ name = "foo"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("example/foo.rs", "")
+ .build();
+
+ p.cargo("build --examples -v")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ can't find `foo` example at default paths, but found a file at `example/foo.rs`.
+ Perhaps rename the file to `examples/foo.rs` for target auto-discovery, \
+ or specify example.path if you want to use a non-default path.",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn commonly_wrong_path_of_benchmark() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "1.0.0"
+
+ [lib]
+ name = "foo"
+ path = "src/lib.rs"
+
+ [[bench]]
+ name = "foo"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bench/foo.rs", "")
+ .build();
+
+ p.cargo("build --benches -v")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ can't find `foo` bench at default paths, but found a file at `bench/foo.rs`.
+ Perhaps rename the file to `benches/foo.rs` for target auto-discovery, \
+ or specify bench.path if you want to use a non-default path.",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn commonly_wrong_path_binary() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/lib.rs", "")
+ .file("src/bins/foo.rs", "")
+ .build();
+
+ p.cargo("build -v")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ can't find `foo` bin at default paths, but found a file at `src/bins/foo.rs`.
+ Perhaps rename the file to `src/bin/foo.rs` for target auto-discovery, \
+ or specify bin.path if you want to use a non-default path.",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn commonly_wrong_path_subdir_binary() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/lib.rs", "")
+ .file("src/bins/foo/main.rs", "")
+ .build();
+
+ p.cargo("build -v")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ can't find `foo` bin at default paths, but found a file at `src/bins/foo/main.rs`.
+ Perhaps rename the file to `src/bin/foo/main.rs` for target auto-discovery, \
+ or specify bin.path if you want to use a non-default path.",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn found_multiple_target_files() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/lib.rs", "")
+ .file("src/bin/foo.rs", "")
+ .file("src/bin/foo/main.rs", "")
+ .build();
+
+ p.cargo("build -v")
+ .with_status(101)
+ // Don't assert the inferred paths since the order is non-deterministic.
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ cannot infer path for `foo` bin
+ Cargo doesn't know which to use because multiple target files found \
+ at `src/bin/foo[..].rs` and `src/bin/foo[..].rs`.",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn legacy_binary_paths_warnings() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "1.0.0"
+ authors = []
+
+ [[bin]]
+ name = "bar"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build -v")
+ .with_stderr_contains(
+ "\
+[WARNING] path `[..]src/main.rs` was erroneously implicitly accepted for binary `bar`,
+please set bin.path in Cargo.toml",
+ )
+ .run();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "1.0.0"
+ authors = []
+
+ [[bin]]
+ name = "bar"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("src/bin/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build -v")
+ .with_stderr_contains(
+ "\
+[WARNING] path `[..]src/bin/main.rs` was erroneously implicitly accepted for binary `bar`,
+please set bin.path in Cargo.toml",
+ )
+ .run();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "1.0.0"
+ authors = []
+
+ [[bin]]
+ name = "bar"
+ "#,
+ )
+ .file("src/bar.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build -v")
+ .with_stderr_contains(
+ "\
+[WARNING] path `[..]src/bar.rs` was erroneously implicitly accepted for binary `bar`,
+please set bin.path in Cargo.toml",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn implicit_examples() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn get_hello() -> &'static str { "Hello" }
+ pub fn get_goodbye() -> &'static str { "Goodbye" }
+ pub fn get_world() -> &'static str { "World" }
+ "#,
+ )
+ .file(
+ "examples/hello.rs",
+ r#"
+ extern crate foo;
+ fn main() {
+ println!("{}, {}!", foo::get_hello(), foo::get_world());
+ }
+ "#,
+ )
+ .file(
+ "examples/goodbye.rs",
+ r#"
+ extern crate foo;
+ fn main() {
+ println!("{}, {}!", foo::get_goodbye(), foo::get_world());
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build --examples").run();
+ p.process(&p.bin("examples/hello"))
+ .with_stdout("Hello, World!\n")
+ .run();
+ p.process(&p.bin("examples/goodbye"))
+ .with_stdout("Goodbye, World!\n")
+ .run();
+}
+
+#[cargo_test]
+fn standard_build_no_ndebug() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file(
+ "src/foo.rs",
+ r#"
+ fn main() {
+ if cfg!(debug_assertions) {
+ println!("slow")
+ } else {
+ println!("fast")
+ }
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build").run();
+ p.process(&p.bin("foo")).with_stdout("slow\n").run();
+}
+
+#[cargo_test]
+fn release_build_ndebug() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file(
+ "src/foo.rs",
+ r#"
+ fn main() {
+ if cfg!(debug_assertions) {
+ println!("slow")
+ } else {
+ println!("fast")
+ }
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build --release").run();
+ p.process(&p.release_bin("foo")).with_stdout("fast\n").run();
+}
+
+#[cargo_test]
+fn inferred_main_bin() {
+ let p = project().file("src/main.rs", "fn main() {}").build();
+
+ p.cargo("build").run();
+ p.process(&p.bin("foo")).run();
+}
+
+#[cargo_test]
+fn deletion_causes_failure() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "bar"
+ "#,
+ )
+ .file("src/main.rs", "extern crate bar; fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+ p.change_file("Cargo.toml", &basic_manifest("foo", "0.0.1"));
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr_contains("[..]can't find crate for `bar`")
+ .run();
+}
+
+#[cargo_test]
+fn bad_cargo_toml_in_target_dir() {
+ let p = project()
+ .file("src/main.rs", "fn main() {}")
+ .file("target/Cargo.toml", "bad-toml")
+ .build();
+
+ p.cargo("build").run();
+ p.process(&p.bin("foo")).run();
+}
+
+#[cargo_test]
+fn lib_with_standard_name() {
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("syntax", "0.0.1"))
+ .file("src/lib.rs", "pub fn foo() {}")
+ .file(
+ "src/main.rs",
+ "extern crate syntax; fn main() { syntax::foo() }",
+ )
+ .build();
+
+ p.cargo("build")
+ .with_stderr(
+ "\
+[COMPILING] syntax v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn simple_staticlib() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.1"
+
+ [lib]
+ name = "foo"
+ crate-type = ["staticlib"]
+ "#,
+ )
+ .file("src/lib.rs", "pub fn foo() {}")
+ .build();
+
+ // env var is a test for #1381
+ p.cargo("build").env("CARGO_LOG", "nekoneko=trace").run();
+}
+
+#[cargo_test]
+fn staticlib_rlib_and_bin() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.1"
+
+ [lib]
+ name = "foo"
+ crate-type = ["staticlib", "rlib"]
+ "#,
+ )
+ .file("src/lib.rs", "pub fn foo() {}")
+ .file("src/main.rs", "extern crate foo; fn main() { foo::foo(); }")
+ .build();
+
+ p.cargo("build -v").run();
+}
+
+#[cargo_test]
+fn opt_out_of_bin() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ bin = []
+
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.1"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("src/main.rs", "bad syntax")
+ .build();
+ p.cargo("build").run();
+}
+
+#[cargo_test]
+fn single_lib() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.1"
+
+ [lib]
+ name = "foo"
+ path = "src/bar.rs"
+ "#,
+ )
+ .file("src/bar.rs", "")
+ .build();
+ p.cargo("build").run();
+}
+
+#[cargo_test]
+fn freshness_ignores_excluded() {
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ build = "build.rs"
+ exclude = ["src/b*.rs"]
+ "#,
+ )
+ .file("build.rs", "fn main() {}")
+ .file("src/lib.rs", "pub fn bar() -> i32 { 1 }")
+ .build();
+ foo.root().move_into_the_past();
+
+ foo.cargo("build")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.0 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ // Smoke test to make sure it doesn't compile again
+ println!("first pass");
+ foo.cargo("build").with_stdout("").run();
+
+ // Modify an ignored file and make sure we don't rebuild
+ println!("second pass");
+ foo.change_file("src/bar.rs", "");
+ foo.cargo("build").with_stdout("").run();
+}
+
+#[cargo_test]
+fn rebuild_preserves_out_dir() {
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ build = 'build.rs'
+ "#,
+ )
+ .file(
+ "build.rs",
+ r#"
+ use std::env;
+ use std::fs::File;
+ use std::path::Path;
+
+ fn main() {
+ let path = Path::new(&env::var("OUT_DIR").unwrap()).join("foo");
+ if env::var_os("FIRST").is_some() {
+ File::create(&path).unwrap();
+ } else {
+ File::create(&path).unwrap();
+ }
+ }
+ "#,
+ )
+ .file("src/lib.rs", "pub fn bar() -> i32 { 1 }")
+ .build();
+ foo.root().move_into_the_past();
+
+ foo.cargo("build")
+ .env("FIRST", "1")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.0 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ foo.change_file("src/bar.rs", "");
+ foo.cargo("build")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.0 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn dep_no_libs() {
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+
+ [dependencies.bar]
+ path = "bar"
+ "#,
+ )
+ .file("src/lib.rs", "pub fn bar() -> i32 { 1 }")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.0"))
+ .file("bar/src/main.rs", "")
+ .build();
+ foo.cargo("build").run();
+}
+
+#[cargo_test]
+fn recompile_space_in_name() {
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+
+ [lib]
+ name = "foo"
+ path = "src/my lib.rs"
+ "#,
+ )
+ .file("src/my lib.rs", "")
+ .build();
+ foo.cargo("build").run();
+ foo.root().move_into_the_past();
+ foo.cargo("build").with_stdout("").run();
+}
+
+#[cfg(unix)]
+#[cargo_test]
+fn credentials_is_unreadable() {
+ use cargo_test_support::paths::home;
+ use std::os::unix::prelude::*;
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("src/lib.rs", "")
+ .build();
+
+ let credentials = home().join(".cargo/credentials.toml");
+ t!(fs::create_dir_all(credentials.parent().unwrap()));
+ t!(fs::write(
+ &credentials,
+ r#"
+ [registry]
+ token = "api-token"
+ "#
+ ));
+ let stat = fs::metadata(credentials.as_path()).unwrap();
+ let mut perms = stat.permissions();
+ perms.set_mode(0o000);
+ fs::set_permissions(credentials, perms).unwrap();
+
+ p.cargo("build").run();
+}
+
+#[cfg(unix)]
+#[cargo_test]
+fn ignore_bad_directories() {
+ use std::os::unix::prelude::*;
+ let foo = project()
+ .file("Cargo.toml", &basic_manifest("foo", "0.0.0"))
+ .file("src/lib.rs", "")
+ .build();
+ let dir = foo.root().join("tmp");
+ fs::create_dir(&dir).unwrap();
+ let stat = fs::metadata(&dir).unwrap();
+ let mut perms = stat.permissions();
+ perms.set_mode(0o644);
+ fs::set_permissions(&dir, perms.clone()).unwrap();
+ foo.cargo("build").run();
+ perms.set_mode(0o755);
+ fs::set_permissions(&dir, perms).unwrap();
+}
+
+#[cargo_test]
+fn bad_cargo_config() {
+ let foo = project()
+ .file("Cargo.toml", &basic_manifest("foo", "0.0.0"))
+ .file("src/lib.rs", "")
+ .file(".cargo/config", "this is not valid toml")
+ .build();
+ foo.cargo("build -v")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] could not load Cargo configuration
+
+Caused by:
+ could not parse TOML configuration in `[..]`
+
+Caused by:
+ could not parse input as TOML
+
+Caused by:
+ TOML parse error at line 1, column 6
+ |
+ 1 | this is not valid toml
+ | ^
+ expected `.`, `=`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_platform_specific_dependency() {
+ let host = rustc_host();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+ build = "build.rs"
+
+ [target.{host}.dependencies]
+ dep = {{ path = "dep" }}
+ [target.{host}.build-dependencies]
+ build = {{ path = "build" }}
+ [target.{host}.dev-dependencies]
+ dev = {{ path = "dev" }}
+ "#,
+ host = host
+ ),
+ )
+ .file("src/main.rs", "extern crate dep; fn main() { dep::dep() }")
+ .file(
+ "tests/foo.rs",
+ "extern crate dev; #[test] fn foo() { dev::dev() }",
+ )
+ .file(
+ "build.rs",
+ "extern crate build; fn main() { build::build(); }",
+ )
+ .file("dep/Cargo.toml", &basic_manifest("dep", "0.5.0"))
+ .file("dep/src/lib.rs", "pub fn dep() {}")
+ .file("build/Cargo.toml", &basic_manifest("build", "0.5.0"))
+ .file("build/src/lib.rs", "pub fn build() {}")
+ .file("dev/Cargo.toml", &basic_manifest("dev", "0.5.0"))
+ .file("dev/src/lib.rs", "pub fn dev() {}")
+ .build();
+
+ p.cargo("build").run();
+
+ assert!(p.bin("foo").is_file());
+ p.cargo("test").run();
+}
+
+#[cargo_test]
+fn cargo_platform_specific_dependency_build_dependencies_conflicting_warning() {
+ let host = rustc_host();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+ build = "build.rs"
+
+ [target.{host}.build-dependencies]
+ build = {{ path = "build" }}
+ [target.{host}.build_dependencies]
+ build = {{ path = "build" }}
+ "#,
+ host = host
+ ),
+ )
+ .file("src/main.rs", "fn main() { }")
+ .file(
+ "build.rs",
+ "extern crate build; fn main() { build::build(); }",
+ )
+ .file("build/Cargo.toml", &basic_manifest("build", "0.5.0"))
+ .file("build/src/lib.rs", "pub fn build() {}")
+ .build();
+
+ p.cargo("build")
+ .with_stderr_contains(
+ format!("[WARNING] conflicting between `build-dependencies` and `build_dependencies` in the `{}` platform target.\n
+ `build_dependencies` is ignored and not recommended for use in the future", host)
+ )
+ .run();
+
+ assert!(p.bin("foo").is_file());
+}
+
+#[cargo_test]
+fn cargo_platform_specific_dependency_dev_dependencies_conflicting_warning() {
+ let host = rustc_host();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [target.{host}.dev-dependencies]
+ dev = {{ path = "dev" }}
+ [target.{host}.dev_dependencies]
+ dev = {{ path = "dev" }}
+ "#,
+ host = host
+ ),
+ )
+ .file("src/main.rs", "fn main() { }")
+ .file(
+ "tests/foo.rs",
+ "extern crate dev; #[test] fn foo() { dev::dev() }",
+ )
+ .file("dev/Cargo.toml", &basic_manifest("dev", "0.5.0"))
+ .file("dev/src/lib.rs", "pub fn dev() {}")
+ .build();
+
+ p.cargo("build")
+ .with_stderr_contains(
+ format!("[WARNING] conflicting between `dev-dependencies` and `dev_dependencies` in the `{}` platform target.\n
+ `dev_dependencies` is ignored and not recommended for use in the future", host)
+ )
+ .run();
+
+ assert!(p.bin("foo").is_file());
+ p.cargo("test").run();
+}
+
+#[cargo_test]
+fn bad_platform_specific_dependency() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [target.wrong-target.dependencies.bar]
+ path = "bar"
+ "#,
+ )
+ .file("src/main.rs", &main_file(r#""{}", bar::gimme()"#, &["bar"]))
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.5.0"))
+ .file(
+ "bar/src/lib.rs",
+ r#"pub fn gimme() -> String { format!("") }"#,
+ )
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr_contains("[..]can't find crate for `bar`")
+ .run();
+}
+
+#[cargo_test]
+fn cargo_platform_specific_dependency_wrong_platform() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [target.non-existing-triplet.dependencies.bar]
+ path = "bar"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.5.0"))
+ .file(
+ "bar/src/lib.rs",
+ "invalid rust file, should not be compiled",
+ )
+ .build();
+
+ p.cargo("build").run();
+
+ assert!(p.bin("foo").is_file());
+ p.process(&p.bin("foo")).run();
+
+ let lockfile = p.read_lockfile();
+ assert!(lockfile.contains("bar"));
+}
+
+#[cargo_test]
+fn example_as_lib() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [[example]]
+ name = "ex"
+ crate-type = ["lib"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("examples/ex.rs", "")
+ .build();
+
+ p.cargo("build --example=ex").run();
+ assert!(p.example_lib("ex", "lib").is_file());
+}
+
+#[cargo_test]
+fn example_as_rlib() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [[example]]
+ name = "ex"
+ crate-type = ["rlib"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("examples/ex.rs", "")
+ .build();
+
+ p.cargo("build --example=ex").run();
+ assert!(p.example_lib("ex", "rlib").is_file());
+}
+
+#[cargo_test]
+fn example_as_dylib() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [[example]]
+ name = "ex"
+ crate-type = ["dylib"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("examples/ex.rs", "")
+ .build();
+
+ p.cargo("build --example=ex").run();
+ assert!(p.example_lib("ex", "dylib").is_file());
+}
+
+#[cargo_test]
+fn example_as_proc_macro() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [[example]]
+ name = "ex"
+ crate-type = ["proc-macro"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "examples/ex.rs",
+ r#"
+ extern crate proc_macro;
+ use proc_macro::TokenStream;
+
+ #[proc_macro]
+ pub fn eat(_item: TokenStream) -> TokenStream {
+ "".parse().unwrap()
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build --example=ex").run();
+ assert!(p.example_lib("ex", "proc-macro").is_file());
+}
+
+#[cargo_test]
+fn example_bin_same_name() {
+ let p = project()
+ .file("src/main.rs", "fn main() {}")
+ .file("examples/foo.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build --examples").run();
+
+ assert!(!p.bin("foo").is_file());
+ // We expect a file of the form bin/foo-{metadata_hash}
+ assert!(p.bin("examples/foo").is_file());
+
+ p.cargo("build --examples").run();
+
+ assert!(!p.bin("foo").is_file());
+ // We expect a file of the form bin/foo-{metadata_hash}
+ assert!(p.bin("examples/foo").is_file());
+}
+
+#[cargo_test]
+fn compile_then_delete() {
+ let p = project().file("src/main.rs", "fn main() {}").build();
+
+ p.cargo("run -v").run();
+ assert!(p.bin("foo").is_file());
+ if cfg!(windows) {
+ // On windows unlinking immediately after running often fails, so sleep
+ sleep_ms(100);
+ }
+ fs::remove_file(&p.bin("foo")).unwrap();
+ p.cargo("run -v").run();
+}
+
+#[cargo_test]
+fn transitive_dependencies_not_available() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.aaaaa]
+ path = "a"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ "extern crate bbbbb; extern crate aaaaa; fn main() {}",
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "aaaaa"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bbbbb]
+ path = "../b"
+ "#,
+ )
+ .file("a/src/lib.rs", "extern crate bbbbb;")
+ .file("b/Cargo.toml", &basic_manifest("bbbbb", "0.0.1"))
+ .file("b/src/lib.rs", "")
+ .build();
+
+ p.cargo("build -v")
+ .with_status(101)
+ .with_stderr_contains("[..] can't find crate for `bbbbb`[..]")
+ .run();
+}
+
+#[cargo_test]
+fn cyclic_deps_rejected() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.a]
+ path = "a"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.foo]
+ path = ".."
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .build();
+
+ p.cargo("build -v")
+ .with_status(101)
+ .with_stderr(
+"[ERROR] cyclic package dependency: package `a v0.0.1 ([CWD]/a)` depends on itself. Cycle:
+package `a v0.0.1 ([CWD]/a)`
+ ... which satisfies path dependency `a` of package `foo v0.0.1 ([CWD])`
+ ... which satisfies path dependency `foo` of package `a v0.0.1 ([..])`",
+ ).run();
+}
+
+#[cargo_test]
+fn predictable_filenames() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ name = "foo"
+ crate-type = ["dylib", "rlib"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build -v").run();
+ assert!(p.root().join("target/debug/libfoo.rlib").is_file());
+ let dylib_name = format!("{}foo{}", env::consts::DLL_PREFIX, env::consts::DLL_SUFFIX);
+ assert!(p.root().join("target/debug").join(dylib_name).is_file());
+}
+
+#[cargo_test]
+fn dashes_to_underscores() {
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo-bar", "0.0.1"))
+ .file("src/lib.rs", "")
+ .file("src/main.rs", "extern crate foo_bar; fn main() {}")
+ .build();
+
+ p.cargo("build -v").run();
+ assert!(p.bin("foo-bar").is_file());
+}
+
+#[cargo_test]
+fn dashes_in_crate_name_bad() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ name = "foo-bar"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("src/main.rs", "extern crate foo_bar; fn main() {}")
+ .build();
+
+ p.cargo("build -v")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]/foo/Cargo.toml`
+
+Caused by:
+ library target names cannot contain hyphens: foo-bar
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rustc_env_var() {
+ let p = project().file("src/lib.rs", "").build();
+
+ p.cargo("build -v")
+ .env("RUSTC", "rustc-that-does-not-exist")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] could not execute process `rustc-that-does-not-exist -vV` ([..])
+
+Caused by:
+[..]
+",
+ )
+ .run();
+ assert!(!p.bin("a").is_file());
+}
+
+#[cargo_test]
+fn filtering() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file("src/bin/a.rs", "fn main() {}")
+ .file("src/bin/b.rs", "fn main() {}")
+ .file("examples/a.rs", "fn main() {}")
+ .file("examples/b.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build --lib").run();
+ assert!(!p.bin("a").is_file());
+
+ p.cargo("build --bin=a --example=a").run();
+ assert!(p.bin("a").is_file());
+ assert!(!p.bin("b").is_file());
+ assert!(p.bin("examples/a").is_file());
+ assert!(!p.bin("examples/b").is_file());
+}
+
+#[cargo_test]
+fn filtering_implicit_bins() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file("src/bin/a.rs", "fn main() {}")
+ .file("src/bin/b.rs", "fn main() {}")
+ .file("examples/a.rs", "fn main() {}")
+ .file("examples/b.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build --bins").run();
+ assert!(p.bin("a").is_file());
+ assert!(p.bin("b").is_file());
+ assert!(!p.bin("examples/a").is_file());
+ assert!(!p.bin("examples/b").is_file());
+}
+
+#[cargo_test]
+fn filtering_implicit_examples() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file("src/bin/a.rs", "fn main() {}")
+ .file("src/bin/b.rs", "fn main() {}")
+ .file("examples/a.rs", "fn main() {}")
+ .file("examples/b.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build --examples").run();
+ assert!(!p.bin("a").is_file());
+ assert!(!p.bin("b").is_file());
+ assert!(p.bin("examples/a").is_file());
+ assert!(p.bin("examples/b").is_file());
+}
+
+#[cargo_test]
+fn ignore_dotfile() {
+ let p = project()
+ .file("src/bin/.a.rs", "")
+ .file("src/bin/a.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build").run();
+}
+
+#[cargo_test]
+fn ignore_dotdirs() {
+ let p = project()
+ .file("src/bin/a.rs", "fn main() {}")
+ .file(".git/Cargo.toml", "")
+ .file(".pc/dummy-fix.patch/Cargo.toml", "")
+ .build();
+
+ p.cargo("build").run();
+}
+
+#[cargo_test]
+fn dotdir_root() {
+ let p = ProjectBuilder::new(root().join(".foo"))
+ .file("src/bin/a.rs", "fn main() {}")
+ .build();
+ p.cargo("build").run();
+}
+
+#[cargo_test]
+fn custom_target_dir_env() {
+ let p = project().file("src/main.rs", "fn main() {}").build();
+
+ let exe_name = format!("foo{}", env::consts::EXE_SUFFIX);
+
+ p.cargo("build").env("CARGO_TARGET_DIR", "foo/target").run();
+ assert!(p.root().join("foo/target/debug").join(&exe_name).is_file());
+ assert!(!p.root().join("target/debug").join(&exe_name).is_file());
+
+ p.cargo("build").run();
+ assert!(p.root().join("foo/target/debug").join(&exe_name).is_file());
+ assert!(p.root().join("target/debug").join(&exe_name).is_file());
+
+ p.cargo("build")
+ .env("CARGO_BUILD_TARGET_DIR", "foo2/target")
+ .run();
+ assert!(p.root().join("foo2/target/debug").join(&exe_name).is_file());
+
+ p.change_file(
+ ".cargo/config",
+ r#"
+ [build]
+ target-dir = "foo/target"
+ "#,
+ );
+ p.cargo("build").env("CARGO_TARGET_DIR", "bar/target").run();
+ assert!(p.root().join("bar/target/debug").join(&exe_name).is_file());
+ assert!(p.root().join("foo/target/debug").join(&exe_name).is_file());
+ assert!(p.root().join("target/debug").join(&exe_name).is_file());
+}
+
+#[cargo_test]
+fn custom_target_dir_line_parameter() {
+ let p = project().file("src/main.rs", "fn main() {}").build();
+
+ let exe_name = format!("foo{}", env::consts::EXE_SUFFIX);
+
+ p.cargo("build --target-dir foo/target").run();
+ assert!(p.root().join("foo/target/debug").join(&exe_name).is_file());
+ assert!(!p.root().join("target/debug").join(&exe_name).is_file());
+
+ p.cargo("build").run();
+ assert!(p.root().join("foo/target/debug").join(&exe_name).is_file());
+ assert!(p.root().join("target/debug").join(&exe_name).is_file());
+
+ p.change_file(
+ ".cargo/config",
+ r#"
+ [build]
+ target-dir = "foo/target"
+ "#,
+ );
+ p.cargo("build --target-dir bar/target").run();
+ assert!(p.root().join("bar/target/debug").join(&exe_name).is_file());
+ assert!(p.root().join("foo/target/debug").join(&exe_name).is_file());
+ assert!(p.root().join("target/debug").join(&exe_name).is_file());
+
+ p.cargo("build --target-dir foobar/target")
+ .env("CARGO_TARGET_DIR", "bar/target")
+ .run();
+ assert!(p
+ .root()
+ .join("foobar/target/debug")
+ .join(&exe_name)
+ .is_file());
+ assert!(p.root().join("bar/target/debug").join(&exe_name).is_file());
+ assert!(p.root().join("foo/target/debug").join(&exe_name).is_file());
+ assert!(p.root().join("target/debug").join(&exe_name).is_file());
+}
+
+#[cargo_test]
+fn build_multiple_packages() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.d1]
+ path = "d1"
+ [dependencies.d2]
+ path = "d2"
+
+ [[bin]]
+ name = "foo"
+ "#,
+ )
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .file("d1/Cargo.toml", &basic_bin_manifest("d1"))
+ .file("d1/src/lib.rs", "")
+ .file("d1/src/main.rs", "fn main() { println!(\"d1\"); }")
+ .file(
+ "d2/Cargo.toml",
+ r#"
+ [package]
+ name = "d2"
+ version = "0.0.1"
+ authors = []
+
+ [[bin]]
+ name = "d2"
+ doctest = false
+ "#,
+ )
+ .file("d2/src/main.rs", "fn main() { println!(\"d2\"); }")
+ .build();
+
+ p.cargo("build -p d1 -p d2 -p foo").run();
+
+ assert!(p.bin("foo").is_file());
+ p.process(&p.bin("foo")).with_stdout("i am foo\n").run();
+
+ let d1_path = &p
+ .build_dir()
+ .join("debug")
+ .join(format!("d1{}", env::consts::EXE_SUFFIX));
+ let d2_path = &p
+ .build_dir()
+ .join("debug")
+ .join(format!("d2{}", env::consts::EXE_SUFFIX));
+
+ assert!(d1_path.is_file());
+ p.process(d1_path).with_stdout("d1").run();
+
+ assert!(d2_path.is_file());
+ p.process(d2_path).with_stdout("d2").run();
+}
+
+#[cargo_test]
+fn invalid_spec() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.d1]
+ path = "d1"
+
+ [[bin]]
+ name = "foo"
+ "#,
+ )
+ .file("src/bin/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .file("d1/Cargo.toml", &basic_bin_manifest("d1"))
+ .file("d1/src/lib.rs", "")
+ .file("d1/src/main.rs", "fn main() { println!(\"d1\"); }")
+ .build();
+
+ p.cargo("build -p notAValidDep")
+ .with_status(101)
+ .with_stderr("[ERROR] package ID specification `notAValidDep` did not match any packages")
+ .run();
+
+ p.cargo("build -p d1 -p notAValidDep")
+ .with_status(101)
+ .with_stderr("[ERROR] package ID specification `notAValidDep` did not match any packages")
+ .run();
+}
+
+#[cargo_test]
+fn manifest_with_bom_is_ok() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ "\u{FEFF}
+ [package]
+ name = \"foo\"
+ version = \"0.0.1\"
+ authors = []
+ ",
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("build -v").run();
+}
+
+#[cargo_test]
+fn panic_abort_compiles_with_panic_abort() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [profile.dev]
+ panic = 'abort'
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("build -v")
+ .with_stderr_contains("[..] -C panic=abort [..]")
+ .run();
+}
+
+#[cargo_test]
+fn compiler_json_error_format() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.bar]
+ path = "bar"
+ "#,
+ )
+ .file(
+ "build.rs",
+ "fn main() { println!(\"cargo:rustc-cfg=xyz\") }",
+ )
+ .file("src/main.rs", "fn main() { let unused = 92; }")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.5.0"))
+ .file("bar/src/lib.rs", r#"fn dead() {}"#)
+ .build();
+
+ let output = |fresh| {
+ r#"
+ {
+ "reason":"compiler-artifact",
+ "package_id":"foo 0.5.0 ([..])",
+ "manifest_path": "[..]",
+ "target":{
+ "kind":["custom-build"],
+ "crate_types":["bin"],
+ "doc": false,
+ "doctest": false,
+ "edition": "2015",
+ "name":"build-script-build",
+ "src_path":"[..]build.rs",
+ "test": false
+ },
+ "profile": {
+ "debug_assertions": true,
+ "debuginfo": null,
+ "opt_level": "0",
+ "overflow_checks": true,
+ "test": false
+ },
+ "executable": null,
+ "features": [],
+ "filenames": "{...}",
+ "fresh": $FRESH
+ }
+
+ {
+ "reason":"compiler-message",
+ "package_id":"bar 0.5.0 ([..])",
+ "manifest_path": "[..]",
+ "target":{
+ "kind":["lib"],
+ "crate_types":["lib"],
+ "doc": true,
+ "doctest": true,
+ "edition": "2015",
+ "name":"bar",
+ "src_path":"[..]lib.rs",
+ "test": true
+ },
+ "message":"{...}"
+ }
+
+ {
+ "reason":"compiler-artifact",
+ "profile": {
+ "debug_assertions": true,
+ "debuginfo": 2,
+ "opt_level": "0",
+ "overflow_checks": true,
+ "test": false
+ },
+ "executable": null,
+ "features": [],
+ "package_id":"bar 0.5.0 ([..])",
+ "manifest_path": "[..]",
+ "target":{
+ "kind":["lib"],
+ "crate_types":["lib"],
+ "doc": true,
+ "doctest": true,
+ "edition": "2015",
+ "name":"bar",
+ "src_path":"[..]lib.rs",
+ "test": true
+ },
+ "filenames":[
+ "[..].rlib",
+ "[..].rmeta"
+ ],
+ "fresh": $FRESH
+ }
+
+ {
+ "reason":"build-script-executed",
+ "package_id":"foo 0.5.0 ([..])",
+ "linked_libs":[],
+ "linked_paths":[],
+ "env":[],
+ "cfgs":["xyz"],
+ "out_dir": "[..]target/debug/build/foo-[..]/out"
+ }
+
+ {
+ "reason":"compiler-message",
+ "package_id":"foo 0.5.0 ([..])",
+ "manifest_path": "[..]",
+ "target":{
+ "kind":["bin"],
+ "crate_types":["bin"],
+ "doc": true,
+ "doctest": false,
+ "edition": "2015",
+ "name":"foo",
+ "src_path":"[..]main.rs",
+ "test": true
+ },
+ "message":"{...}"
+ }
+
+ {
+ "reason":"compiler-artifact",
+ "package_id":"foo 0.5.0 ([..])",
+ "manifest_path": "[..]",
+ "target":{
+ "kind":["bin"],
+ "crate_types":["bin"],
+ "doc": true,
+ "doctest": false,
+ "edition": "2015",
+ "name":"foo",
+ "src_path":"[..]main.rs",
+ "test": true
+ },
+ "profile": {
+ "debug_assertions": true,
+ "debuginfo": 2,
+ "opt_level": "0",
+ "overflow_checks": true,
+ "test": false
+ },
+ "executable": "[..]/foo/target/debug/foo[EXE]",
+ "features": [],
+ "filenames": "{...}",
+ "fresh": $FRESH
+ }
+
+ {"reason": "build-finished", "success": true}
+ "#
+ .replace("$FRESH", fresh)
+ };
+
+ // Use `jobs=1` to ensure that the order of messages is consistent.
+ p.cargo("build -v --message-format=json --jobs=1")
+ .with_json_contains_unordered(&output("false"))
+ .run();
+
+ // With fresh build, we should repeat the artifacts,
+ // and replay the cached compiler warnings.
+ p.cargo("build -v --message-format=json --jobs=1")
+ .with_json_contains_unordered(&output("true"))
+ .run();
+}
+
+#[cargo_test]
+fn wrong_message_format_option() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build --message-format XML")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+error: invalid message format specifier: `xml`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn message_format_json_forward_stderr() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/main.rs", "fn main() { let unused = 0; }")
+ .build();
+
+ p.cargo("rustc --release --bin foo --message-format JSON")
+ .with_json_contains_unordered(
+ r#"
+ {
+ "reason":"compiler-message",
+ "package_id":"foo 0.5.0 ([..])",
+ "manifest_path": "[..]",
+ "target":{
+ "kind":["bin"],
+ "crate_types":["bin"],
+ "doc": true,
+ "doctest": false,
+ "edition": "2015",
+ "name":"foo",
+ "src_path":"[..]",
+ "test": true
+ },
+ "message":"{...}"
+ }
+
+ {
+ "reason":"compiler-artifact",
+ "package_id":"foo 0.5.0 ([..])",
+ "manifest_path": "[..]",
+ "target":{
+ "kind":["bin"],
+ "crate_types":["bin"],
+ "doc": true,
+ "doctest": false,
+ "edition": "2015",
+ "name":"foo",
+ "src_path":"[..]",
+ "test": true
+ },
+ "profile":{
+ "debug_assertions":false,
+ "debuginfo":null,
+ "opt_level":"3",
+ "overflow_checks": false,
+ "test":false
+ },
+ "executable": "{...}",
+ "features":[],
+ "filenames": "{...}",
+ "fresh": false
+ }
+
+ {"reason": "build-finished", "success": true}
+ "#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn no_warn_about_package_metadata() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [package.metadata]
+ foo = "bar"
+ a = true
+ b = 3
+
+ [package.metadata.another]
+ bar = 3
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("build")
+ .with_stderr(
+ "[..] foo v0.0.1 ([..])\n\
+ [FINISHED] dev [unoptimized + debuginfo] target(s) in [..]\n",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn no_warn_about_workspace_metadata() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["foo"]
+
+ [workspace.metadata]
+ something = "something_else"
+ x = 1
+ y = 2
+
+ [workspace.metadata.another]
+ bar = 12
+ "#,
+ )
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ "#,
+ )
+ .file("foo/src/lib.rs", "")
+ .build();
+
+ p.cargo("build")
+ .with_stderr(
+ "[..] foo v0.0.1 ([..])\n\
+ [FINISHED] dev [unoptimized + debuginfo] target(s) in [..]\n",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_build_empty_target() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build --target")
+ .arg("")
+ .with_status(101)
+ .with_stderr_contains("[..] target was empty")
+ .run();
+}
+
+#[cargo_test]
+fn build_all_workspace() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { path = "bar" }
+
+ [workspace]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ p.cargo("build --workspace")
+ .with_stderr(
+ "\
+[COMPILING] bar v0.1.0 ([..])
+[COMPILING] foo v0.1.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_all_exclude() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [workspace]
+ members = ["bar", "baz"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("baz/src/lib.rs", "pub fn baz() { break_the_build(); }")
+ .build();
+
+ p.cargo("build --workspace --exclude baz")
+ .with_stderr_does_not_contain("[COMPILING] baz v0.1.0 [..]")
+ .with_stderr_unordered(
+ "\
+[COMPILING] foo v0.1.0 ([..])
+[COMPILING] bar v0.1.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_all_exclude_not_found() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [workspace]
+ members = ["bar"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ p.cargo("build --workspace --exclude baz")
+ .with_stderr_does_not_contain("[COMPILING] baz v0.1.0 [..]")
+ .with_stderr_unordered(
+ "\
+[WARNING] excluded package(s) `baz` not found in workspace [..]
+[COMPILING] foo v0.1.0 ([..])
+[COMPILING] bar v0.1.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_all_exclude_glob() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [workspace]
+ members = ["bar", "baz"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("baz/src/lib.rs", "pub fn baz() { break_the_build(); }")
+ .build();
+
+ p.cargo("build --workspace --exclude '*z'")
+ .with_stderr_does_not_contain("[COMPILING] baz v0.1.0 [..]")
+ .with_stderr_unordered(
+ "\
+[COMPILING] foo v0.1.0 ([..])
+[COMPILING] bar v0.1.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_all_exclude_glob_not_found() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [workspace]
+ members = ["bar"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ p.cargo("build --workspace --exclude '*z'")
+ .with_stderr_does_not_contain("[COMPILING] baz v0.1.0 [..]")
+ .with_stderr(
+ "\
+[WARNING] excluded package pattern(s) `*z` not found in workspace [..]
+[COMPILING] [..] v0.1.0 ([..])
+[COMPILING] [..] v0.1.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_all_exclude_broken_glob() {
+ let p = project().file("src/main.rs", "fn main() {}").build();
+
+ p.cargo("build --workspace --exclude '[*z'")
+ .with_status(101)
+ .with_stderr_contains("[ERROR] cannot build glob pattern from `[*z`")
+ .run();
+}
+
+#[cargo_test]
+fn build_all_workspace_implicit_examples() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { path = "bar" }
+
+ [workspace]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("src/bin/a.rs", "fn main() {}")
+ .file("src/bin/b.rs", "fn main() {}")
+ .file("examples/c.rs", "fn main() {}")
+ .file("examples/d.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "")
+ .file("bar/src/bin/e.rs", "fn main() {}")
+ .file("bar/src/bin/f.rs", "fn main() {}")
+ .file("bar/examples/g.rs", "fn main() {}")
+ .file("bar/examples/h.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build --workspace --examples")
+ .with_stderr(
+ "[..] Compiling bar v0.1.0 ([..])\n\
+ [..] Compiling foo v0.1.0 ([..])\n\
+ [..] Finished dev [unoptimized + debuginfo] target(s) in [..]\n",
+ )
+ .run();
+ assert!(!p.bin("a").is_file());
+ assert!(!p.bin("b").is_file());
+ assert!(p.bin("examples/c").is_file());
+ assert!(p.bin("examples/d").is_file());
+ assert!(!p.bin("e").is_file());
+ assert!(!p.bin("f").is_file());
+ assert!(p.bin("examples/g").is_file());
+ assert!(p.bin("examples/h").is_file());
+}
+
+#[cargo_test]
+fn build_all_virtual_manifest() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar", "baz"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("baz/src/lib.rs", "pub fn baz() {}")
+ .build();
+
+ // The order in which bar and baz are built is not guaranteed
+ p.cargo("build --workspace")
+ .with_stderr_unordered(
+ "\
+[COMPILING] baz v0.1.0 ([..])
+[COMPILING] bar v0.1.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_virtual_manifest_all_implied() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar", "baz"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("baz/src/lib.rs", "pub fn baz() {}")
+ .build();
+
+ // The order in which `bar` and `baz` are built is not guaranteed.
+ p.cargo("build")
+ .with_stderr_unordered(
+ "\
+[COMPILING] baz v0.1.0 ([..])
+[COMPILING] bar v0.1.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_virtual_manifest_one_project() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar", "baz"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("baz/src/lib.rs", "pub fn baz() { break_the_build(); }")
+ .build();
+
+ p.cargo("build -p bar")
+ .with_stderr_does_not_contain("[..]baz[..]")
+ .with_stderr(
+ "\
+[COMPILING] bar v0.1.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_virtual_manifest_glob() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar", "baz"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() { break_the_build(); }")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("baz/src/lib.rs", "pub fn baz() {}")
+ .build();
+
+ p.cargo("build -p '*z'")
+ .with_stderr_does_not_contain("[..]bar[..]")
+ .with_stderr(
+ "\
+[COMPILING] baz v0.1.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_virtual_manifest_glob_not_found() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ p.cargo("build -p bar -p '*z'")
+ .with_status(101)
+ .with_stderr("[ERROR] package pattern(s) `*z` not found in workspace [..]")
+ .run();
+}
+
+#[cargo_test]
+fn build_virtual_manifest_broken_glob() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ p.cargo("build -p '[*z'")
+ .with_status(101)
+ .with_stderr_contains("[ERROR] cannot build glob pattern from `[*z`")
+ .run();
+}
+
+#[cargo_test]
+fn build_all_virtual_manifest_implicit_examples() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar", "baz"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "")
+ .file("bar/src/bin/a.rs", "fn main() {}")
+ .file("bar/src/bin/b.rs", "fn main() {}")
+ .file("bar/examples/c.rs", "fn main() {}")
+ .file("bar/examples/d.rs", "fn main() {}")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("baz/src/lib.rs", "")
+ .file("baz/src/bin/e.rs", "fn main() {}")
+ .file("baz/src/bin/f.rs", "fn main() {}")
+ .file("baz/examples/g.rs", "fn main() {}")
+ .file("baz/examples/h.rs", "fn main() {}")
+ .build();
+
+ // The order in which bar and baz are built is not guaranteed
+ p.cargo("build --workspace --examples")
+ .with_stderr_unordered(
+ "\
+[COMPILING] baz v0.1.0 ([..])
+[COMPILING] bar v0.1.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ assert!(!p.bin("a").is_file());
+ assert!(!p.bin("b").is_file());
+ assert!(p.bin("examples/c").is_file());
+ assert!(p.bin("examples/d").is_file());
+ assert!(!p.bin("e").is_file());
+ assert!(!p.bin("f").is_file());
+ assert!(p.bin("examples/g").is_file());
+ assert!(p.bin("examples/h").is_file());
+}
+
+#[cargo_test]
+fn build_all_member_dependency_same_name() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["a"]
+ "#,
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+
+ [dependencies]
+ a = "0.1.0"
+ "#,
+ )
+ .file("a/src/lib.rs", "pub fn a() {}")
+ .build();
+
+ Package::new("a", "0.1.0").publish();
+
+ p.cargo("build --workspace")
+ .with_stderr(
+ "[UPDATING] `[..]` index\n\
+ [DOWNLOADING] crates ...\n\
+ [DOWNLOADED] a v0.1.0 ([..])\n\
+ [COMPILING] a v0.1.0\n\
+ [COMPILING] a v0.1.0 ([..])\n\
+ [FINISHED] dev [unoptimized + debuginfo] target(s) in [..]\n",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn run_proper_binary() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.0"
+ [[bin]]
+ name = "main"
+ [[bin]]
+ name = "other"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "src/bin/main.rs",
+ r#"fn main() { panic!("This should never be run."); }"#,
+ )
+ .file("src/bin/other.rs", "fn main() {}")
+ .build();
+
+ p.cargo("run --bin other").run();
+}
+
+#[cargo_test]
+fn run_proper_binary_main_rs() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/lib.rs", "")
+ .file("src/bin/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("run --bin foo").run();
+}
+
+#[cargo_test]
+fn run_proper_alias_binary_from_src() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.0"
+ [[bin]]
+ name = "foo"
+ [[bin]]
+ name = "bar"
+ "#,
+ )
+ .file("src/foo.rs", r#"fn main() { println!("foo"); }"#)
+ .file("src/bar.rs", r#"fn main() { println!("bar"); }"#)
+ .build();
+
+ p.cargo("build --workspace").run();
+ p.process(&p.bin("foo")).with_stdout("foo\n").run();
+ p.process(&p.bin("bar")).with_stdout("bar\n").run();
+}
+
+#[cargo_test]
+fn run_proper_alias_binary_main_rs() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.0"
+ [[bin]]
+ name = "foo"
+ [[bin]]
+ name = "bar"
+ "#,
+ )
+ .file("src/main.rs", r#"fn main() { println!("main"); }"#)
+ .build();
+
+ p.cargo("build --workspace").run();
+ p.process(&p.bin("foo")).with_stdout("main\n").run();
+ p.process(&p.bin("bar")).with_stdout("main\n").run();
+}
+
+#[cargo_test]
+fn run_proper_binary_main_rs_as_foo() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file(
+ "src/foo.rs",
+ r#" fn main() { panic!("This should never be run."); }"#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("run --bin foo").run();
+}
+
+#[cargo_test]
+fn rustc_wrapper() {
+ let p = project().file("src/lib.rs", "").build();
+ let wrapper = tools::echo_wrapper();
+ let running = format!(
+ "[RUNNING] `{} rustc --crate-name foo [..]",
+ wrapper.display()
+ );
+ p.cargo("build -v")
+ .env("RUSTC_WRAPPER", &wrapper)
+ .with_stderr_contains(&running)
+ .run();
+ p.build_dir().rm_rf();
+ p.cargo("build -v")
+ .env("RUSTC_WORKSPACE_WRAPPER", &wrapper)
+ .with_stderr_contains(&running)
+ .run();
+}
+
+#[cargo_test]
+fn rustc_wrapper_relative() {
+ Package::new("bar", "1.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ let wrapper = tools::echo_wrapper();
+ let exe_name = wrapper.file_name().unwrap().to_str().unwrap();
+ let relative_path = format!("./{}", exe_name);
+ fs::hard_link(&wrapper, p.root().join(exe_name)).unwrap();
+ let running = format!("[RUNNING] `[ROOT]/foo/./{} rustc[..]", exe_name);
+ p.cargo("build -v")
+ .env("RUSTC_WRAPPER", &relative_path)
+ .with_stderr_contains(&running)
+ .run();
+ p.build_dir().rm_rf();
+ p.cargo("build -v")
+ .env("RUSTC_WORKSPACE_WRAPPER", &relative_path)
+ .with_stderr_contains(&running)
+ .run();
+ p.build_dir().rm_rf();
+ p.change_file(
+ ".cargo/config.toml",
+ &format!(
+ r#"
+ build.rustc-wrapper = "./{}"
+ "#,
+ exe_name
+ ),
+ );
+ p.cargo("build -v").with_stderr_contains(&running).run();
+}
+
+#[cargo_test]
+fn rustc_wrapper_from_path() {
+ let p = project().file("src/lib.rs", "").build();
+ p.cargo("build -v")
+ .env("RUSTC_WRAPPER", "wannabe_sccache")
+ .with_status(101)
+ .with_stderr_contains("[..]`wannabe_sccache rustc [..]")
+ .run();
+ p.build_dir().rm_rf();
+ p.cargo("build -v")
+ .env("RUSTC_WORKSPACE_WRAPPER", "wannabe_sccache")
+ .with_status(101)
+ .with_stderr_contains("[..]`wannabe_sccache rustc [..]")
+ .run();
+}
+
+#[cargo_test]
+fn cdylib_not_lifted() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.1.0"
+
+ [lib]
+ crate-type = ["cdylib"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+
+ let files = if cfg!(windows) {
+ if cfg!(target_env = "msvc") {
+ vec!["foo.dll.lib", "foo.dll.exp", "foo.dll"]
+ } else {
+ vec!["libfoo.dll.a", "foo.dll"]
+ }
+ } else if cfg!(target_os = "macos") {
+ vec!["libfoo.dylib"]
+ } else {
+ vec!["libfoo.so"]
+ };
+
+ for file in files {
+ println!("checking: {}", file);
+ assert!(p.root().join("target/debug/deps").join(&file).is_file());
+ }
+}
+
+#[cargo_test]
+fn cdylib_final_outputs() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo-bar"
+ authors = []
+ version = "0.1.0"
+
+ [lib]
+ crate-type = ["cdylib"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+
+ let files = if cfg!(windows) {
+ if cfg!(target_env = "msvc") {
+ vec!["foo_bar.dll.lib", "foo_bar.dll"]
+ } else {
+ vec!["foo_bar.dll", "libfoo_bar.dll.a"]
+ }
+ } else if cfg!(target_os = "macos") {
+ vec!["libfoo_bar.dylib"]
+ } else {
+ vec!["libfoo_bar.so"]
+ };
+
+ for file in files {
+ println!("checking: {}", file);
+ assert!(p.root().join("target/debug").join(&file).is_file());
+ }
+}
+
+#[cargo_test]
+// NOTE: Windows MSVC and wasm32-unknown-emscripten do not use metadata. Skip them.
+// See <https://github.com/rust-lang/cargo/issues/9325#issuecomment-1030662699>
+#[cfg(not(all(target_os = "windows", target_env = "msvc")))]
+fn no_dep_info_collision_when_cdylib_and_bin_coexist() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "1.0.0"
+
+ [lib]
+ crate-type = ["cdylib"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build -v")
+ .with_stderr_unordered(
+ "\
+[COMPILING] foo v1.0.0 ([CWD])
+[RUNNING] `rustc [..] --crate-type bin [..] -C metadata=[..]`
+[RUNNING] `rustc [..] --crate-type cdylib [..] -C metadata=[..]`
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ let deps_dir = p.target_debug_dir().join("deps");
+ assert!(deps_dir.join("foo.d").exists());
+ let dep_info_count = deps_dir
+ .read_dir()
+ .unwrap()
+ .filter(|e| {
+ let filename = e.as_ref().unwrap().file_name();
+ let filename = filename.to_str().unwrap();
+ filename.starts_with("foo") && filename.ends_with(".d")
+ })
+ .count();
+ // cdylib -> foo.d
+ // bin -> foo-<meta>.d
+ assert_eq!(dep_info_count, 2);
+}
+
+#[cargo_test]
+fn deterministic_cfg_flags() {
+ // This bug is non-deterministic.
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ build = "build.rs"
+
+ [features]
+ default = ["f_a", "f_b", "f_c", "f_d"]
+ f_a = []
+ f_b = []
+ f_c = []
+ f_d = []
+ "#,
+ )
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rustc-cfg=cfg_a");
+ println!("cargo:rustc-cfg=cfg_b");
+ println!("cargo:rustc-cfg=cfg_c");
+ println!("cargo:rustc-cfg=cfg_d");
+ println!("cargo:rustc-cfg=cfg_e");
+ }
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.1.0 [..]
+[RUNNING] [..]
+[RUNNING] [..]
+[RUNNING] `rustc --crate-name foo [..] \
+--cfg[..]default[..]--cfg[..]f_a[..]--cfg[..]f_b[..]\
+--cfg[..]f_c[..]--cfg[..]f_d[..] \
+--cfg cfg_a --cfg cfg_b --cfg cfg_c --cfg cfg_d --cfg cfg_e`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn explicit_bins_without_paths() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [[bin]]
+ name = "foo"
+
+ [[bin]]
+ name = "bar"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("src/main.rs", "fn main() {}")
+ .file("src/bin/bar.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build").run();
+}
+
+#[cargo_test]
+fn no_bin_in_src_with_lib() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/lib.rs", "")
+ .file("src/foo.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ can't find `foo` bin at `src/bin/foo.rs` or `src/bin/foo/main.rs`. [..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn inferred_bins() {
+ let p = project()
+ .file("src/main.rs", "fn main() {}")
+ .file("src/bin/bar.rs", "fn main() {}")
+ .file("src/bin/baz/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build").run();
+ assert!(p.bin("foo").is_file());
+ assert!(p.bin("bar").is_file());
+ assert!(p.bin("baz").is_file());
+}
+
+#[cargo_test]
+fn inferred_bins_duplicate_name() {
+ // this should fail, because we have two binaries with the same name
+ let p = project()
+ .file("src/main.rs", "fn main() {}")
+ .file("src/bin/bar.rs", "fn main() {}")
+ .file("src/bin/bar/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build").with_status(101).with_stderr_contains(
+ "[..]found duplicate binary name bar, but all binary targets must have a unique name[..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn inferred_bin_path() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [[bin]]
+ name = "bar"
+ # Note, no `path` key!
+ "#,
+ )
+ .file("src/bin/bar/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build").run();
+ assert!(p.bin("bar").is_file());
+}
+
+#[cargo_test]
+fn inferred_examples() {
+ let p = project()
+ .file("src/lib.rs", "fn main() {}")
+ .file("examples/bar.rs", "fn main() {}")
+ .file("examples/baz/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build --examples").run();
+ assert!(p.bin("examples/bar").is_file());
+ assert!(p.bin("examples/baz").is_file());
+}
+
+#[cargo_test]
+fn inferred_tests() {
+ let p = project()
+ .file("src/lib.rs", "fn main() {}")
+ .file("tests/bar.rs", "fn main() {}")
+ .file("tests/baz/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("test --test=bar --test=baz").run();
+}
+
+#[cargo_test]
+fn inferred_benchmarks() {
+ let p = project()
+ .file("src/lib.rs", "fn main() {}")
+ .file("benches/bar.rs", "fn main() {}")
+ .file("benches/baz/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("bench --bench=bar --bench=baz").run();
+}
+
+#[cargo_test]
+fn no_infer_dirs() {
+ let p = project()
+ .file("src/lib.rs", "fn main() {}")
+ .file("examples/dir.rs/dummy", "")
+ .file("benches/dir.rs/dummy", "")
+ .file("tests/dir.rs/dummy", "")
+ .build();
+
+ p.cargo("build --examples --benches --tests").run(); // should not fail with "is a directory"
+}
+
+#[cargo_test]
+fn target_edition() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [lib]
+ edition = "2018"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build -v")
+ .with_stderr_contains(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[RUNNING] `rustc [..]--edition=2018 [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn target_edition_override() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ edition = "2018"
+
+ [lib]
+ edition = "2015"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "
+ pub fn async() {}
+ pub fn try() {}
+ pub fn await() {}
+ ",
+ )
+ .build();
+
+ p.cargo("build -v").run();
+}
+
+#[cargo_test]
+fn same_metadata_different_directory() {
+ // A top-level crate built in two different workspaces should have the
+ // same metadata hash.
+ let p = project()
+ .at("foo1")
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .build();
+ let output = t!(String::from_utf8(
+ t!(p.cargo("build -v").exec_with_output()).stderr,
+ ));
+ let metadata = output
+ .split_whitespace()
+ .find(|arg| arg.starts_with("metadata="))
+ .unwrap();
+
+ let p = project()
+ .at("foo2")
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .build();
+
+ p.cargo("build -v")
+ .with_stderr_contains(format!("[..]{}[..]", metadata))
+ .run();
+}
+
+#[cargo_test]
+fn building_a_dependent_crate_without_bin_should_fail() {
+ Package::new("testless", "0.1.0")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "testless"
+ version = "0.1.0"
+
+ [[bin]]
+ name = "a_bin"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ testless = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr_contains(
+ "[..]can't find `a_bin` bin at `src/bin/a_bin.rs` or `src/bin/a_bin/main.rs`[..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+#[cfg(any(target_os = "macos", target_os = "ios"))]
+fn uplift_dsym_of_bin_on_mac() {
+ let p = project()
+ .file("src/main.rs", "fn main() { panic!(); }")
+ .file("src/bin/b.rs", "fn main() { panic!(); }")
+ .file("examples/c.rs", "fn main() { panic!(); }")
+ .file("tests/d.rs", "fn main() { panic!(); }")
+ .build();
+
+ p.cargo("build --bins --examples --tests")
+ .enable_mac_dsym()
+ .run();
+ assert!(p.target_debug_dir().join("foo.dSYM").is_dir());
+ assert!(p.target_debug_dir().join("b.dSYM").is_dir());
+ assert!(p.target_debug_dir().join("b.dSYM").is_symlink());
+ assert!(p.target_debug_dir().join("examples/c.dSYM").is_dir());
+ assert!(!p.target_debug_dir().join("c.dSYM").exists());
+ assert!(!p.target_debug_dir().join("d.dSYM").exists());
+}
+
+#[cargo_test]
+#[cfg(any(target_os = "macos", target_os = "ios"))]
+fn uplift_dsym_of_bin_on_mac_when_broken_link_exists() {
+ let p = project()
+ .file("src/main.rs", "fn main() { panic!(); }")
+ .build();
+ let dsym = p.target_debug_dir().join("foo.dSYM");
+
+ p.cargo("build").enable_mac_dsym().run();
+ assert!(dsym.is_dir());
+
+ // Simulate the situation where the underlying dSYM bundle goes missing
+ // but the uplifted symlink to it remains. This would previously cause
+ // builds to permanently fail until the bad symlink was manually removed.
+ dsym.rm_rf();
+ p.symlink(
+ p.target_debug_dir()
+ .join("deps")
+ .join("foo-baaaaaadbaaaaaad.dSYM"),
+ &dsym,
+ );
+ assert!(dsym.is_symlink());
+ assert!(!dsym.exists());
+
+ p.cargo("build").enable_mac_dsym().run();
+ assert!(dsym.is_dir());
+}
+
+#[cargo_test]
+#[cfg(all(target_os = "windows", target_env = "msvc"))]
+fn uplift_pdb_of_bin_on_windows() {
+ let p = project()
+ .file("src/main.rs", "fn main() { panic!(); }")
+ .file("src/bin/b.rs", "fn main() { panic!(); }")
+ .file("src/bin/foo-bar.rs", "fn main() { panic!(); }")
+ .file("examples/c.rs", "fn main() { panic!(); }")
+ .file("tests/d.rs", "fn main() { panic!(); }")
+ .build();
+
+ p.cargo("build --bins --examples --tests").run();
+ assert!(p.target_debug_dir().join("foo.pdb").is_file());
+ assert!(p.target_debug_dir().join("b.pdb").is_file());
+ assert!(p.target_debug_dir().join("examples/c.pdb").exists());
+ assert!(p.target_debug_dir().join("foo-bar.exe").is_file());
+ assert!(p.target_debug_dir().join("foo_bar.pdb").is_file());
+ assert!(!p.target_debug_dir().join("c.pdb").exists());
+ assert!(!p.target_debug_dir().join("d.pdb").exists());
+}
+
+#[cargo_test]
+#[cfg(target_os = "linux")]
+fn uplift_dwp_of_bin_on_linux() {
+ let p = project()
+ .file("src/main.rs", "fn main() { panic!(); }")
+ .file("src/bin/b.rs", "fn main() { panic!(); }")
+ .file("src/bin/foo-bar.rs", "fn main() { panic!(); }")
+ .file("examples/c.rs", "fn main() { panic!(); }")
+ .file("tests/d.rs", "fn main() { panic!(); }")
+ .build();
+
+ p.cargo("build --bins --examples --tests")
+ .enable_split_debuginfo_packed()
+ .run();
+ assert!(p.target_debug_dir().join("foo.dwp").is_file());
+ assert!(p.target_debug_dir().join("b.dwp").is_file());
+ assert!(p.target_debug_dir().join("examples/c.dwp").exists());
+ assert!(p.target_debug_dir().join("foo-bar").is_file());
+ assert!(p.target_debug_dir().join("foo-bar.dwp").is_file());
+ assert!(!p.target_debug_dir().join("c.dwp").exists());
+ assert!(!p.target_debug_dir().join("d.dwp").exists());
+}
+
+// Ensure that `cargo build` chooses the correct profile for building
+// targets based on filters (assuming `--profile` is not specified).
+#[cargo_test]
+fn build_filter_infer_profile() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file("src/main.rs", "fn main() {}")
+ .file("tests/t1.rs", "")
+ .file("benches/b1.rs", "")
+ .file("examples/ex1.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build -v")
+ .with_stderr_contains(
+ "[RUNNING] `rustc --crate-name foo src/lib.rs [..]--crate-type lib \
+ --emit=[..]link[..]",
+ )
+ .with_stderr_contains(
+ "[RUNNING] `rustc --crate-name foo src/main.rs [..]--crate-type bin \
+ --emit=[..]link[..]",
+ )
+ .run();
+
+ p.root().join("target").rm_rf();
+ p.cargo("build -v --test=t1")
+ .with_stderr_contains(
+ "[RUNNING] `rustc --crate-name foo src/lib.rs [..]--crate-type lib \
+ --emit=[..]link[..]-C debuginfo=2 [..]",
+ )
+ .with_stderr_contains(
+ "[RUNNING] `rustc --crate-name t1 tests/t1.rs [..]--emit=[..]link[..]\
+ -C debuginfo=2 [..]",
+ )
+ .with_stderr_contains(
+ "[RUNNING] `rustc --crate-name foo src/main.rs [..]--crate-type bin \
+ --emit=[..]link[..]-C debuginfo=2 [..]",
+ )
+ .run();
+
+ p.root().join("target").rm_rf();
+ // Bench uses test profile without `--release`.
+ p.cargo("build -v --bench=b1")
+ .with_stderr_contains(
+ "[RUNNING] `rustc --crate-name foo src/lib.rs [..]--crate-type lib \
+ --emit=[..]link[..]-C debuginfo=2 [..]",
+ )
+ .with_stderr_contains(
+ "[RUNNING] `rustc --crate-name b1 benches/b1.rs [..]--emit=[..]link[..]\
+ -C debuginfo=2 [..]",
+ )
+ .with_stderr_does_not_contain("opt-level")
+ .with_stderr_contains(
+ "[RUNNING] `rustc --crate-name foo src/main.rs [..]--crate-type bin \
+ --emit=[..]link[..]-C debuginfo=2 [..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn targets_selected_default() {
+ let p = project().file("src/main.rs", "fn main() {}").build();
+ p.cargo("build -v")
+ // Binaries.
+ .with_stderr_contains(
+ "[RUNNING] `rustc --crate-name foo src/main.rs [..]--crate-type bin \
+ --emit=[..]link[..]",
+ )
+ // Benchmarks.
+ .with_stderr_does_not_contain(
+ "[RUNNING] `rustc --crate-name foo src/main.rs [..]--emit=[..]link \
+ -C opt-level=3 --test [..]",
+ )
+ // Unit tests.
+ .with_stderr_does_not_contain(
+ "[RUNNING] `rustc --crate-name foo src/main.rs [..]--emit=[..]link[..]\
+ -C debuginfo=2 --test [..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn targets_selected_all() {
+ let p = project().file("src/main.rs", "fn main() {}").build();
+ p.cargo("build -v --all-targets")
+ // Binaries.
+ .with_stderr_contains(
+ "[RUNNING] `rustc --crate-name foo src/main.rs [..]--crate-type bin \
+ --emit=[..]link[..]",
+ )
+ // Unit tests.
+ .with_stderr_contains(
+ "[RUNNING] `rustc --crate-name foo src/main.rs [..]--emit=[..]link[..]\
+ -C debuginfo=2 --test [..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn all_targets_no_lib() {
+ let p = project().file("src/main.rs", "fn main() {}").build();
+ p.cargo("build -v --all-targets")
+ // Binaries.
+ .with_stderr_contains(
+ "[RUNNING] `rustc --crate-name foo src/main.rs [..]--crate-type bin \
+ --emit=[..]link[..]",
+ )
+ // Unit tests.
+ .with_stderr_contains(
+ "[RUNNING] `rustc --crate-name foo src/main.rs [..]--emit=[..]link[..]\
+ -C debuginfo=2 --test [..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn no_linkable_target() {
+ // Issue 3169: this is currently not an error as per discussion in PR #4797.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ [dependencies]
+ the_lib = { path = "the_lib" }
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "the_lib/Cargo.toml",
+ r#"
+ [package]
+ name = "the_lib"
+ version = "0.1.0"
+ [lib]
+ name = "the_lib"
+ crate-type = ["staticlib"]
+ "#,
+ )
+ .file("the_lib/src/lib.rs", "pub fn foo() {}")
+ .build();
+ p.cargo("build")
+ .with_stderr_contains(
+ "[WARNING] The package `the_lib` provides no linkable [..] \
+ while compiling `foo`. [..] in `the_lib`'s Cargo.toml. [..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn avoid_dev_deps() {
+ Package::new("foo", "1.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+
+ [dev-dependencies]
+ baz = "1.0.0"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[ERROR] no matching package named `baz` found
+location searched: registry `crates-io`
+required by package `bar v0.1.0 ([..]/foo)`
+",
+ )
+ .run();
+ p.cargo("build -Zavoid-dev-deps")
+ .masquerade_as_nightly_cargo(&["avoid-dev-deps"])
+ .run();
+}
+
+#[cargo_test]
+fn default_cargo_config_jobs() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ r#"
+ [build]
+ jobs = 1
+ "#,
+ )
+ .build();
+ p.cargo("build -v").run();
+}
+
+#[cargo_test]
+fn good_cargo_config_jobs() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ r#"
+ [build]
+ jobs = 4
+ "#,
+ )
+ .build();
+ p.cargo("build -v").run();
+}
+
+#[cargo_test]
+fn good_jobs() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .build();
+
+ p.cargo("build --jobs 1").run();
+
+ p.cargo("build --jobs -1").run();
+}
+
+#[cargo_test]
+fn invalid_cargo_config_jobs() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ r#"
+ [build]
+ jobs = 0
+ "#,
+ )
+ .build();
+ p.cargo("build -v")
+ .with_status(101)
+ .with_stderr_contains("error: jobs may not be 0")
+ .run();
+}
+
+#[cargo_test]
+fn invalid_jobs() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .build();
+
+ p.cargo("build --jobs 0")
+ .with_status(101)
+ .with_stderr_contains("error: jobs may not be 0")
+ .run();
+
+ p.cargo("build --jobs over9000")
+ .with_status(1)
+ .with_stderr("error: Invalid value: could not parse `over9000` as a number")
+ .run();
+}
+
+#[cargo_test]
+fn target_filters_workspace() {
+ let ws = project()
+ .at("ws")
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["a", "b"]
+ "#,
+ )
+ .file("a/Cargo.toml", &basic_lib_manifest("a"))
+ .file("a/src/lib.rs", "")
+ .file("a/examples/ex1.rs", "fn main() {}")
+ .file("b/Cargo.toml", &basic_bin_manifest("b"))
+ .file("b/src/lib.rs", "")
+ .file("b/src/main.rs", "fn main() {}")
+ .build();
+
+ ws.cargo("build -v --example ex")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] no example target named `ex`
+
+<tab>Did you mean `ex1`?",
+ )
+ .run();
+
+ ws.cargo("build -v --example 'ex??'")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] no example target matches pattern `ex??`
+
+<tab>Did you mean `ex1`?",
+ )
+ .run();
+
+ ws.cargo("build -v --lib")
+ .with_stderr_contains("[RUNNING] `rustc [..]a/src/lib.rs[..]")
+ .with_stderr_contains("[RUNNING] `rustc [..]b/src/lib.rs[..]")
+ .run();
+
+ ws.cargo("build -v --example ex1")
+ .with_stderr_contains("[RUNNING] `rustc [..]a/examples/ex1.rs[..]")
+ .run();
+}
+
+#[cargo_test]
+fn target_filters_workspace_not_found() {
+ let ws = project()
+ .at("ws")
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["a", "b"]
+ "#,
+ )
+ .file("a/Cargo.toml", &basic_bin_manifest("a"))
+ .file("a/src/main.rs", "fn main() {}")
+ .file("b/Cargo.toml", &basic_bin_manifest("b"))
+ .file("b/src/main.rs", "fn main() {}")
+ .build();
+
+ ws.cargo("build -v --lib")
+ .with_status(101)
+ .with_stderr("[ERROR] no library targets found in packages: a, b")
+ .run();
+}
+
+#[cfg(unix)]
+#[cargo_test]
+fn signal_display() {
+ // Cause the compiler to crash with a signal.
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ [dependencies]
+ pm = { path = "pm" }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ #[macro_use]
+ extern crate pm;
+
+ #[derive(Foo)]
+ pub struct S;
+ "#,
+ )
+ .file(
+ "pm/Cargo.toml",
+ r#"
+ [package]
+ name = "pm"
+ version = "0.1.0"
+ [lib]
+ proc-macro = true
+ "#,
+ )
+ .file(
+ "pm/src/lib.rs",
+ r#"
+ extern crate proc_macro;
+ use proc_macro::TokenStream;
+
+ #[proc_macro_derive(Foo)]
+ pub fn derive(_input: TokenStream) -> TokenStream {
+ std::process::abort()
+ }
+ "#,
+ )
+ .build();
+
+ foo.cargo("build")
+ .with_stderr(
+ "\
+[COMPILING] pm [..]
+[COMPILING] foo [..]
+[ERROR] could not compile `foo` [..]
+
+Caused by:
+ process didn't exit successfully: `rustc [..]` (signal: 6, SIGABRT: process abort signal)
+",
+ )
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn tricky_pipelining() {
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ [dependencies]
+ bar = { path = "bar" }
+ "#,
+ )
+ .file("src/lib.rs", "extern crate bar;")
+ .file("bar/Cargo.toml", &basic_lib_manifest("bar"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ foo.cargo("build -p bar").run();
+ foo.cargo("build -p foo").run();
+}
+
+#[cargo_test]
+fn pipelining_works() {
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ [dependencies]
+ bar = { path = "bar" }
+ "#,
+ )
+ .file("src/lib.rs", "extern crate bar;")
+ .file("bar/Cargo.toml", &basic_lib_manifest("bar"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ foo.cargo("build")
+ .with_stdout("")
+ .with_stderr(
+ "\
+[COMPILING] [..]
+[COMPILING] [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn pipelining_big_graph() {
+ // Create a crate graph of the form {a,b}{0..29}, where {a,b}(n) depend on {a,b}(n+1)
+ // Then have `foo`, a binary crate, depend on the whole thing.
+ let mut project = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ [dependencies]
+ a1 = { path = "a1" }
+ b1 = { path = "b1" }
+ "#,
+ )
+ .file("src/main.rs", "fn main(){}");
+
+ for n in 0..30 {
+ for x in &["a", "b"] {
+ project = project
+ .file(
+ &format!("{x}{n}/Cargo.toml", x = x, n = n),
+ &format!(
+ r#"
+ [package]
+ name = "{x}{n}"
+ version = "0.1.0"
+ [dependencies]
+ a{np1} = {{ path = "../a{np1}" }}
+ b{np1} = {{ path = "../b{np1}" }}
+ "#,
+ x = x,
+ n = n,
+ np1 = n + 1
+ ),
+ )
+ .file(&format!("{x}{n}/src/lib.rs", x = x, n = n), "");
+ }
+ }
+
+ let foo = project
+ .file("a30/Cargo.toml", &basic_lib_manifest("a30"))
+ .file(
+ "a30/src/lib.rs",
+ r#"compile_error!("don't actually build me");"#,
+ )
+ .file("b30/Cargo.toml", &basic_lib_manifest("b30"))
+ .file("b30/src/lib.rs", "")
+ .build();
+ foo.cargo("build -p foo")
+ .with_status(101)
+ .with_stderr_contains("[ERROR] could not compile `a30`[..]")
+ .run();
+}
+
+#[cargo_test]
+fn forward_rustc_output() {
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = '2018'
+ [dependencies]
+ bar = { path = "bar" }
+ "#,
+ )
+ .file("src/lib.rs", "bar::foo!();")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ [lib]
+ proc-macro = true
+ "#,
+ )
+ .file(
+ "bar/src/lib.rs",
+ r#"
+ extern crate proc_macro;
+ use proc_macro::*;
+
+ #[proc_macro]
+ pub fn foo(input: TokenStream) -> TokenStream {
+ println!("a");
+ println!("b");
+ println!("{{}}");
+ eprintln!("c");
+ eprintln!("d");
+ eprintln!("{{a"); // "malformed json"
+ input
+ }
+ "#,
+ )
+ .build();
+
+ foo.cargo("build")
+ .with_stdout("a\nb\n{}")
+ .with_stderr(
+ "\
+[COMPILING] [..]
+[COMPILING] [..]
+c
+d
+{a
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_lib_only() {
+ let p = project()
+ .file("src/main.rs", "fn main() {}")
+ .file("src/lib.rs", r#" "#)
+ .build();
+
+ p.cargo("build --lib -v")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name foo src/lib.rs [..]--crate-type lib \
+ --emit=[..]link[..]-C debuginfo=2 \
+ -C metadata=[..] \
+ --out-dir [..] \
+ -L dependency=[CWD]/target/debug/deps`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_with_no_lib() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build --lib")
+ .with_status(101)
+ .with_stderr("[ERROR] no library targets found in package `foo`")
+ .run();
+}
+
+#[cargo_test]
+fn build_with_relative_cargo_home_path() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.0.1"
+ authors = ["wycats@example.com"]
+
+ [dependencies]
+
+ "test-dependency" = { path = "src/test_dependency" }
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("src/test_dependency/src/lib.rs", r#" "#)
+ .file(
+ "src/test_dependency/Cargo.toml",
+ &basic_manifest("test-dependency", "0.0.1"),
+ )
+ .build();
+
+ p.cargo("build").env("CARGO_HOME", "./cargo_home/").run();
+}
+
+#[cargo_test]
+fn user_specific_cfgs_are_filtered_out() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/main.rs", r#"fn main() {}"#)
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ assert!(std::env::var_os("CARGO_CFG_PROC_MACRO").is_none());
+ assert!(std::env::var_os("CARGO_CFG_DEBUG_ASSERTIONS").is_none());
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("rustc -- --cfg debug_assertions --cfg proc_macro")
+ .run();
+ p.process(&p.bin("foo")).run();
+}
+
+#[cargo_test]
+fn close_output() {
+ // What happens when stdout or stderr is closed during a build.
+
+ // Server to know when rustc has spawned.
+ let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
+ let addr = listener.local_addr().unwrap();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [lib]
+ proc-macro = true
+
+ [[bin]]
+ name = "foobar"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ &r#"
+ use proc_macro::TokenStream;
+ use std::io::Read;
+
+ #[proc_macro]
+ pub fn repro(_input: TokenStream) -> TokenStream {
+ println!("hello stdout!");
+ eprintln!("hello stderr!");
+ // Tell the test we have started.
+ let mut socket = std::net::TcpStream::connect("__ADDR__").unwrap();
+ // Wait for the test to tell us to start printing.
+ let mut buf = [0];
+ drop(socket.read_exact(&mut buf));
+ let use_stderr = std::env::var("__CARGO_REPRO_STDERR").is_ok();
+ // Emit at least 1MB of data.
+ // Linux pipes can buffer up to 64KB.
+ // This test seems to be sensitive to having other threads
+ // calling fork. My hypothesis is that the stdout/stderr
+ // file descriptors are duplicated into the child process,
+ // and during the short window between fork and exec, the
+ // file descriptor is kept alive long enough for the
+ // build to finish. It's a half-baked theory, but this
+ // seems to prevent the spurious errors in CI.
+ // An alternative solution is to run this test in
+ // a single-threaded environment.
+ for i in 0..100000 {
+ if use_stderr {
+ eprintln!("0123456789{}", i);
+ } else {
+ println!("0123456789{}", i);
+ }
+ }
+ TokenStream::new()
+ }
+ "#
+ .replace("__ADDR__", &addr.to_string()),
+ )
+ .file(
+ "src/bin/foobar.rs",
+ r#"
+ foo::repro!();
+
+ fn main() {}
+ "#,
+ )
+ .build();
+
+ // The `stderr` flag here indicates if this should forcefully close stderr or stdout.
+ let spawn = |stderr: bool| {
+ let mut cmd = p.cargo("build").build_command();
+ cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
+ if stderr {
+ cmd.env("__CARGO_REPRO_STDERR", "1");
+ }
+ let mut child = cmd.spawn().unwrap();
+ // Wait for proc macro to start.
+ let pm_conn = listener.accept().unwrap().0;
+ // Close stderr or stdout.
+ if stderr {
+ drop(child.stderr.take());
+ } else {
+ drop(child.stdout.take());
+ }
+ // Tell the proc-macro to continue;
+ drop(pm_conn);
+ // Read the output from the other channel.
+ let out: &mut dyn Read = if stderr {
+ child.stdout.as_mut().unwrap()
+ } else {
+ child.stderr.as_mut().unwrap()
+ };
+ let mut result = String::new();
+ out.read_to_string(&mut result).unwrap();
+ let status = child.wait().unwrap();
+ assert!(!status.success());
+ result
+ };
+
+ let stderr = spawn(false);
+ compare::match_unordered(
+ "\
+[COMPILING] foo [..]
+hello stderr!
+[ERROR] [..]
+[WARNING] build failed, waiting for other jobs to finish...
+",
+ &stderr,
+ None,
+ )
+ .unwrap();
+
+ // Try again with stderr.
+ p.build_dir().rm_rf();
+ let stdout = spawn(true);
+ assert_eq!(stdout, "hello stdout!\n");
+}
+
+#[cargo_test]
+fn close_output_during_drain() {
+ // Test to close the output during the build phase (drain_the_queue).
+ // There was a bug where it would hang.
+
+ // Server to know when rustc has spawned.
+ let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
+ let addr = listener.local_addr().unwrap();
+
+ // Create a wrapper so the test can know when compiling has started.
+ let rustc_wrapper = {
+ let p = project()
+ .at("compiler")
+ .file("Cargo.toml", &basic_manifest("compiler", "1.0.0"))
+ .file(
+ "src/main.rs",
+ &r#"
+ use std::process::Command;
+ use std::env;
+ use std::io::Read;
+
+ fn main() {
+ // Only wait on the first dependency.
+ if matches!(env::var("CARGO_PKG_NAME").as_deref(), Ok("dep")) {
+ let mut socket = std::net::TcpStream::connect("__ADDR__").unwrap();
+ // Wait for the test to tell us to start printing.
+ let mut buf = [0];
+ drop(socket.read_exact(&mut buf));
+ }
+ let mut cmd = Command::new("rustc");
+ for arg in env::args_os().skip(1) {
+ cmd.arg(arg);
+ }
+ std::process::exit(cmd.status().unwrap().code().unwrap());
+ }
+ "#
+ .replace("__ADDR__", &addr.to_string()),
+ )
+ .build();
+ p.cargo("build").run();
+ p.bin("compiler")
+ };
+
+ Package::new("dep", "1.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ dep = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ // Spawn cargo, wait for the first rustc to start, and then close stderr.
+ let mut cmd = process(&cargo_exe())
+ .arg("check")
+ .cwd(p.root())
+ .env("RUSTC", rustc_wrapper)
+ .build_command();
+ cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
+ let mut child = cmd.spawn().expect("cargo should spawn");
+ // Wait for the rustc wrapper to start.
+ let rustc_conn = listener.accept().unwrap().0;
+ // Close stderr to force an error.
+ drop(child.stderr.take());
+ // Tell the wrapper to continue.
+ drop(rustc_conn);
+ match child.wait() {
+ Ok(status) => assert!(!status.success()),
+ Err(e) => panic!("child wait failed: {}", e),
+ }
+}
+
+use cargo_test_support::registry::Dependency;
+
+#[cargo_test]
+fn reduced_reproduction_8249() {
+ // https://github.com/rust-lang/cargo/issues/8249
+ Package::new("a-src", "0.1.0").links("a").publish();
+ Package::new("a-src", "0.2.0").links("a").publish();
+
+ Package::new("b", "0.1.0")
+ .add_dep(Dependency::new("a-src", "0.1").optional(true))
+ .publish();
+ Package::new("b", "0.2.0")
+ .add_dep(Dependency::new("a-src", "0.2").optional(true))
+ .publish();
+
+ Package::new("c", "1.0.0")
+ .add_dep(&Dependency::new("b", "0.1.0"))
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ b = { version = "*", features = ["a-src"] }
+ a-src = "*"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("generate-lockfile").run();
+ cargo_util::paths::append(&p.root().join("Cargo.toml"), b"c = \"*\"").unwrap();
+ p.cargo("check").run();
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn target_directory_backup_exclusion() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .build();
+
+ // Newly created target/ should have CACHEDIR.TAG inside...
+ p.cargo("build").run();
+ let cachedir_tag = p.build_dir().join("CACHEDIR.TAG");
+ assert!(cachedir_tag.is_file());
+ assert!(fs::read_to_string(&cachedir_tag)
+ .unwrap()
+ .starts_with("Signature: 8a477f597d28d172789f06886806bc55"));
+ // ...but if target/ already exists CACHEDIR.TAG should not be created in it.
+ fs::remove_file(&cachedir_tag).unwrap();
+ p.cargo("build").run();
+ assert!(!&cachedir_tag.is_file());
+}
+
+#[cargo_test]
+fn simple_terminal_width() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn foo() {
+ let _: () = 42;
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build -v")
+ .env("__CARGO_TEST_TTY_WIDTH_DO_NOT_USE_THIS", "20")
+ .with_status(101)
+ .with_stderr_contains("[RUNNING] `rustc [..]--diagnostic-width=20[..]")
+ .run();
+
+ p.cargo("doc -v")
+ .env("__CARGO_TEST_TTY_WIDTH_DO_NOT_USE_THIS", "20")
+ .with_stderr_contains("[RUNNING] `rustdoc [..]--diagnostic-width=20[..]")
+ .run();
+}
+
+#[cargo_test]
+fn build_script_o0_default() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file("build.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build -v --release")
+ .with_stderr_does_not_contain("[..]build_script_build[..]opt-level[..]")
+ .run();
+}
+
+#[cargo_test]
+fn build_script_o0_default_even_with_release() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [profile.release]
+ opt-level = 1
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("build.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build -v --release")
+ .with_stderr_does_not_contain("[..]build_script_build[..]opt-level[..]")
+ .run();
+}
+
+#[cargo_test]
+fn primary_package_env_var() {
+ // Test that CARGO_PRIMARY_PACKAGE is enabled only for "foo" and not for any dependency.
+
+ let is_primary_package = r#"
+ pub fn is_primary_package() -> bool {{
+ option_env!("CARGO_PRIMARY_PACKAGE").is_some()
+ }}
+ "#;
+
+ Package::new("qux", "0.1.0")
+ .file("src/lib.rs", is_primary_package)
+ .publish();
+
+ let baz = git::new("baz", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("src/lib.rs", is_primary_package)
+ });
+
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = {{ path = "bar" }}
+ baz = {{ git = '{}' }}
+ qux = "0.1"
+ "#,
+ baz.url()
+ ),
+ )
+ .file(
+ "src/lib.rs",
+ &format!(
+ r#"
+ extern crate bar;
+ extern crate baz;
+ extern crate qux;
+
+ {}
+
+ #[test]
+ fn verify_primary_package() {{
+ assert!(!bar::is_primary_package());
+ assert!(!baz::is_primary_package());
+ assert!(!qux::is_primary_package());
+ assert!(is_primary_package());
+ }}
+ "#,
+ is_primary_package
+ ),
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", is_primary_package)
+ .build();
+
+ foo.cargo("test").run();
+}
+
+#[cargo_test]
+fn renamed_uplifted_artifact_remains_unmodified_after_rebuild() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.0"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build").run();
+
+ let bin = p.bin("foo");
+ let renamed_bin = p.bin("foo-renamed");
+
+ fs::rename(&bin, &renamed_bin).unwrap();
+
+ p.change_file("src/main.rs", "fn main() { eprintln!(\"hello, world\"); }");
+ p.cargo("build").run();
+
+ let not_the_same = !same_file::is_same_file(bin, renamed_bin).unwrap();
+ assert!(not_the_same, "renamed uplifted artifact must be unmodified");
+}
diff --git a/src/tools/cargo/tests/testsuite/build_plan.rs b/src/tools/cargo/tests/testsuite/build_plan.rs
new file mode 100644
index 000000000..647bc1234
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/build_plan.rs
@@ -0,0 +1,222 @@
+//! Tests for --build-plan feature.
+
+use cargo_test_support::registry::Package;
+use cargo_test_support::{basic_bin_manifest, basic_manifest, main_file, project};
+
+#[cargo_test]
+fn cargo_build_plan_simple() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .build();
+
+ p.cargo("build --build-plan -Zunstable-options")
+ .masquerade_as_nightly_cargo(&["build-plan"])
+ .with_json(
+ r#"
+ {
+ "inputs": [
+ "[..]/foo/Cargo.toml"
+ ],
+ "invocations": [
+ {
+ "args": "{...}",
+ "cwd": "[..]/cit/[..]/foo",
+ "deps": [],
+ "env": "{...}",
+ "kind": null,
+ "links": "{...}",
+ "outputs": "{...}",
+ "package_name": "foo",
+ "package_version": "0.5.0",
+ "program": "rustc",
+ "target_kind": ["bin"],
+ "compile_mode": "build"
+ }
+ ]
+ }
+ "#,
+ )
+ .run();
+ assert!(!p.bin("foo").is_file());
+}
+
+#[cargo_test]
+fn cargo_build_plan_single_dep() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.5.0"
+
+ [dependencies]
+ bar = { path = "bar" }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ extern crate bar;
+ pub fn foo() { bar::bar(); }
+
+ #[test]
+ fn test() { foo(); }
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .build();
+ p.cargo("build --build-plan -Zunstable-options")
+ .masquerade_as_nightly_cargo(&["build-plan"])
+ .with_json(
+ r#"
+ {
+ "inputs": [
+ "[..]/foo/Cargo.toml",
+ "[..]/foo/bar/Cargo.toml"
+ ],
+ "invocations": [
+ {
+ "args": "{...}",
+ "cwd": "[..]/cit/[..]/foo",
+ "deps": [],
+ "env": "{...}",
+ "kind": null,
+ "links": "{...}",
+ "outputs": [
+ "[..]/foo/target/debug/deps/libbar-[..].rlib",
+ "[..]/foo/target/debug/deps/libbar-[..].rmeta"
+ ],
+ "package_name": "bar",
+ "package_version": "0.0.1",
+ "program": "rustc",
+ "target_kind": ["lib"],
+ "compile_mode": "build"
+ },
+ {
+ "args": "{...}",
+ "cwd": "[..]/cit/[..]/foo",
+ "deps": [0],
+ "env": "{...}",
+ "kind": null,
+ "links": "{...}",
+ "outputs": [
+ "[..]/foo/target/debug/deps/libfoo-[..].rlib",
+ "[..]/foo/target/debug/deps/libfoo-[..].rmeta"
+ ],
+ "package_name": "foo",
+ "package_version": "0.5.0",
+ "program": "rustc",
+ "target_kind": ["lib"],
+ "compile_mode": "build"
+ }
+ ]
+ }
+ "#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_build_plan_build_script() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+ build = "build.rs"
+ "#,
+ )
+ .file("src/main.rs", r#"fn main() {}"#)
+ .file("build.rs", r#"fn main() {}"#)
+ .build();
+
+ p.cargo("build --build-plan -Zunstable-options")
+ .masquerade_as_nightly_cargo(&["build-plan"])
+ .with_json(
+ r#"
+ {
+ "inputs": [
+ "[..]/foo/Cargo.toml"
+ ],
+ "invocations": [
+ {
+ "args": "{...}",
+ "cwd": "[..]/cit/[..]/foo",
+ "deps": [],
+ "env": "{...}",
+ "kind": null,
+ "links": "{...}",
+ "outputs": "{...}",
+ "package_name": "foo",
+ "package_version": "0.5.0",
+ "program": "rustc",
+ "target_kind": ["custom-build"],
+ "compile_mode": "build"
+ },
+ {
+ "args": "{...}",
+ "cwd": "[..]/cit/[..]/foo",
+ "deps": [0],
+ "env": "{...}",
+ "kind": null,
+ "links": "{...}",
+ "outputs": [],
+ "package_name": "foo",
+ "package_version": "0.5.0",
+ "program": "[..]/build-script-build",
+ "target_kind": ["custom-build"],
+ "compile_mode": "run-custom-build"
+ },
+ {
+ "args": "{...}",
+ "cwd": "[..]/cit/[..]/foo",
+ "deps": [1],
+ "env": "{...}",
+ "kind": null,
+ "links": "{...}",
+ "outputs": "{...}",
+ "package_name": "foo",
+ "package_version": "0.5.0",
+ "program": "rustc",
+ "target_kind": ["bin"],
+ "compile_mode": "build"
+ }
+ ]
+ }
+ "#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_plan_with_dev_dep() {
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+
+ [dev-dependencies]
+ bar = "*"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build --build-plan -Zunstable-options")
+ .masquerade_as_nightly_cargo(&["build-plan"])
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/build_script.rs b/src/tools/cargo/tests/testsuite/build_script.rs
new file mode 100644
index 000000000..80a24960e
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/build_script.rs
@@ -0,0 +1,5168 @@
+//! Tests for build.rs scripts.
+
+use cargo_test_support::compare::assert_match_exact;
+use cargo_test_support::install::cargo_home;
+use cargo_test_support::paths::CargoPathExt;
+use cargo_test_support::registry::Package;
+use cargo_test_support::tools;
+use cargo_test_support::{
+ basic_manifest, cargo_exe, cross_compile, is_coarse_mtime, project, project_in,
+};
+use cargo_test_support::{rustc_host, sleep_ms, slow_cpu_multiplier, symlink_supported};
+use cargo_util::paths::{self, remove_dir_all};
+use std::env;
+use std::fs;
+use std::io;
+use std::thread;
+
+#[cargo_test]
+fn custom_build_script_failed() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+ build = "build.rs"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("build.rs", "fn main() { std::process::exit(101); }")
+ .build();
+ p.cargo("build -v")
+ .with_status(101)
+ .with_stderr(
+ "\
+[COMPILING] foo v0.5.0 ([CWD])
+[RUNNING] `rustc --crate-name build_script_build build.rs [..]--crate-type bin [..]`
+[RUNNING] `[..]/build-script-build`
+[ERROR] failed to run custom build command for `foo v0.5.0 ([CWD])`
+
+Caused by:
+ process didn't exit successfully: `[..]/build-script-build` (exit [..]: 101)",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn custom_build_script_failed_backtraces_message() {
+ // In this situation (no dependency sharing), debuginfo is turned off in
+ // `dev.build-override`. However, if an error occurs running e.g. a build
+ // script, and backtraces are opted into: a message explaining how to
+ // improve backtraces is also displayed.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+ build = "build.rs"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("build.rs", "fn main() { std::process::exit(101); }")
+ .build();
+ p.cargo("build -v")
+ .env("RUST_BACKTRACE", "1")
+ .with_status(101)
+ .with_stderr(
+ "\
+[COMPILING] foo v0.5.0 ([CWD])
+[RUNNING] `rustc --crate-name build_script_build build.rs [..]--crate-type bin [..]`
+[RUNNING] `[..]/build-script-build`
+[ERROR] failed to run custom build command for `foo v0.5.0 ([CWD])`
+note: To improve backtraces for build dependencies, set the \
+CARGO_PROFILE_DEV_BUILD_OVERRIDE_DEBUG=true environment variable [..]
+
+Caused by:
+ process didn't exit successfully: `[..]/build-script-build` (exit [..]: 101)",
+ )
+ .run();
+
+ p.cargo("check -v")
+ .env("RUST_BACKTRACE", "1")
+ .with_status(101)
+ .with_stderr(
+ "\
+[COMPILING] foo v0.5.0 ([CWD])
+[RUNNING] `[..]/build-script-build`
+[ERROR] failed to run custom build command for `foo v0.5.0 ([CWD])`
+note: To improve backtraces for build dependencies, set the \
+CARGO_PROFILE_DEV_BUILD_OVERRIDE_DEBUG=true environment variable [..]
+
+Caused by:
+ process didn't exit successfully: `[..]/build-script-build` (exit [..]: 101)",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn custom_build_script_failed_backtraces_message_with_debuginfo() {
+ // This is the same test as `custom_build_script_failed_backtraces_message` above, this time
+ // ensuring that the message dedicated to improving backtraces by requesting debuginfo is not
+ // shown when debuginfo is already turned on.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+ build = "build.rs"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("build.rs", "fn main() { std::process::exit(101); }")
+ .build();
+ p.cargo("build -v")
+ .env("RUST_BACKTRACE", "1")
+ .env("CARGO_PROFILE_DEV_BUILD_OVERRIDE_DEBUG", "true")
+ .with_status(101)
+ .with_stderr(
+ "\
+[COMPILING] foo v0.5.0 ([CWD])
+[RUNNING] `rustc --crate-name build_script_build build.rs [..]--crate-type bin [..]`
+[RUNNING] `[..]/build-script-build`
+[ERROR] failed to run custom build command for `foo v0.5.0 ([CWD])`
+
+Caused by:
+ process didn't exit successfully: `[..]/build-script-build` (exit [..]: 101)",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn custom_build_env_vars() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [features]
+ bar_feat = ["bar/foo"]
+
+ [dependencies.bar]
+ path = "bar"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+
+ name = "bar"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+ build = "build.rs"
+
+ [features]
+ foo = []
+ "#,
+ )
+ .file("bar/src/lib.rs", "pub fn hello() {}");
+
+ let cargo = cargo_exe().canonicalize().unwrap();
+ let cargo = cargo.to_str().unwrap();
+ let rustc = paths::resolve_executable("rustc".as_ref())
+ .unwrap()
+ .canonicalize()
+ .unwrap();
+ let rustc = rustc.to_str().unwrap();
+ let file_content = format!(
+ r##"
+ use std::env;
+ use std::path::Path;
+
+ fn main() {{
+ let _target = env::var("TARGET").unwrap();
+ let _ncpus = env::var("NUM_JOBS").unwrap();
+ let _dir = env::var("CARGO_MANIFEST_DIR").unwrap();
+
+ let opt = env::var("OPT_LEVEL").unwrap();
+ assert_eq!(opt, "0");
+
+ let opt = env::var("PROFILE").unwrap();
+ assert_eq!(opt, "debug");
+
+ let debug = env::var("DEBUG").unwrap();
+ assert_eq!(debug, "true");
+
+ let out = env::var("OUT_DIR").unwrap();
+ assert!(out.starts_with(r"{0}"));
+ assert!(Path::new(&out).is_dir());
+
+ let _host = env::var("HOST").unwrap();
+
+ let _feat = env::var("CARGO_FEATURE_FOO").unwrap();
+
+ let cargo = env::var("CARGO").unwrap();
+ if env::var_os("CHECK_CARGO_IS_RUSTC").is_some() {{
+ assert_eq!(cargo, r#"{rustc}"#);
+ }} else {{
+ assert_eq!(cargo, r#"{cargo}"#);
+ }}
+
+ let rustc = env::var("RUSTC").unwrap();
+ assert_eq!(rustc, "rustc");
+
+ let rustdoc = env::var("RUSTDOC").unwrap();
+ assert_eq!(rustdoc, "rustdoc");
+
+ assert!(env::var("RUSTC_WRAPPER").is_err());
+ assert!(env::var("RUSTC_WORKSPACE_WRAPPER").is_err());
+
+ assert!(env::var("RUSTC_LINKER").is_err());
+
+ assert!(env::var("RUSTFLAGS").is_err());
+ let rustflags = env::var("CARGO_ENCODED_RUSTFLAGS").unwrap();
+ assert_eq!(rustflags, "");
+ }}
+ "##,
+ p.root()
+ .join("target")
+ .join("debug")
+ .join("build")
+ .display(),
+ );
+
+ let p = p.file("bar/build.rs", &file_content).build();
+
+ p.cargo("build --features bar_feat").run();
+ p.cargo("build --features bar_feat")
+ // we use rustc since $CARGO is only used if it points to a path that exists
+ .env("CHECK_CARGO_IS_RUSTC", "1")
+ .env(cargo::CARGO_ENV, rustc)
+ .run();
+}
+
+#[cargo_test]
+fn custom_build_env_var_rustflags() {
+ let rustflags = "--cfg=special";
+ let rustflags_alt = "--cfg=notspecial";
+ let p = project()
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [build]
+ rustflags = ["{}"]
+ "#,
+ rustflags
+ ),
+ )
+ .file(
+ "build.rs",
+ &format!(
+ r#"
+ use std::env;
+
+ fn main() {{
+ // Static assertion that exactly one of the cfg paths is always taken.
+ assert!(env::var("RUSTFLAGS").is_err());
+ let x;
+ #[cfg(special)]
+ {{ assert_eq!(env::var("CARGO_ENCODED_RUSTFLAGS").unwrap(), "{}"); x = String::new(); }}
+ #[cfg(notspecial)]
+ {{ assert_eq!(env::var("CARGO_ENCODED_RUSTFLAGS").unwrap(), "{}"); x = String::new(); }}
+ let _ = x;
+ }}
+ "#,
+ rustflags, rustflags_alt,
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check").run();
+
+ // RUSTFLAGS overrides build.rustflags, so --cfg=special shouldn't be passed
+ p.cargo("check").env("RUSTFLAGS", rustflags_alt).run();
+}
+
+#[cargo_test]
+fn custom_build_env_var_encoded_rustflags() {
+ // NOTE: We use "-Clink-arg=-B nope" here rather than, say, "-A missing_docs", since for the
+ // latter it won't matter if the whitespace accidentally gets split, as rustc will do the right
+ // thing either way.
+ let p = project()
+ .file(
+ ".cargo/config",
+ r#"
+ [build]
+ rustflags = ["-Clink-arg=-B nope", "--cfg=foo"]
+ "#,
+ )
+ .file(
+ "build.rs",
+ r#"
+ use std::env;
+
+ fn main() {{
+ assert_eq!(env::var("CARGO_ENCODED_RUSTFLAGS").unwrap(), "-Clink-arg=-B nope\x1f--cfg=foo");
+ }}
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn custom_build_env_var_rustc_wrapper() {
+ let wrapper = tools::echo_wrapper();
+ let p = project()
+ .file(
+ "build.rs",
+ r#"
+ use std::env;
+
+ fn main() {{
+ assert_eq!(
+ env::var("RUSTC_WRAPPER").unwrap(),
+ env::var("CARGO_RUSTC_WRAPPER_CHECK").unwrap()
+ );
+ }}
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .env("CARGO_BUILD_RUSTC_WRAPPER", &wrapper)
+ .env("CARGO_RUSTC_WRAPPER_CHECK", &wrapper)
+ .run();
+}
+
+#[cargo_test]
+fn custom_build_env_var_rustc_workspace_wrapper() {
+ let wrapper = tools::echo_wrapper();
+
+ // Workspace wrapper should be set for any crate we're operating directly on.
+ let p = project()
+ .file(
+ "build.rs",
+ r#"
+ use std::env;
+
+ fn main() {{
+ assert_eq!(
+ env::var("RUSTC_WORKSPACE_WRAPPER").unwrap(),
+ env::var("CARGO_RUSTC_WORKSPACE_WRAPPER_CHECK").unwrap()
+ );
+ }}
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .env("CARGO_BUILD_RUSTC_WORKSPACE_WRAPPER", &wrapper)
+ .env("CARGO_RUSTC_WORKSPACE_WRAPPER_CHECK", &wrapper)
+ .run();
+
+ // But should not be set for a crate from the registry, as then it's not in a workspace.
+ Package::new("bar", "0.1.0")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ links = "a"
+ "#,
+ )
+ .file(
+ "build.rs",
+ r#"
+ use std::env;
+
+ fn main() {{
+ assert!(env::var("RUSTC_WORKSPACE_WRAPPER").is_err());
+ }}
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "0.1"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .env("CARGO_BUILD_RUSTC_WORKSPACE_WRAPPER", &wrapper)
+ .run();
+}
+
+#[cargo_test]
+fn custom_build_env_var_rustc_linker() {
+ if cross_compile::disabled() {
+ return;
+ }
+ let target = cross_compile::alternate();
+ let p = project()
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [target.{}]
+ linker = "/path/to/linker"
+ "#,
+ target
+ ),
+ )
+ .file(
+ "build.rs",
+ r#"
+ use std::env;
+
+ fn main() {
+ assert!(env::var("RUSTC_LINKER").unwrap().ends_with("/path/to/linker"));
+ }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ // no crate type set => linker never called => build succeeds if and
+ // only if build.rs succeeds, despite linker binary not existing.
+ p.cargo("build --target").arg(&target).run();
+}
+
+#[cargo_test]
+fn custom_build_env_var_rustc_linker_bad_host_target() {
+ let target = rustc_host();
+ let p = project()
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [target.{}]
+ linker = "/path/to/linker"
+ "#,
+ target
+ ),
+ )
+ .file("build.rs", "fn main() {}")
+ .file("src/lib.rs", "")
+ .build();
+
+ // build.rs should fail since host == target when no target is set
+ p.cargo("build --verbose")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name build_script_build build.rs [..]--crate-type bin [..]-C linker=[..]/path/to/linker [..]`
+[ERROR] linker `[..]/path/to/linker` not found
+"
+ )
+ .run();
+}
+
+#[cargo_test]
+fn custom_build_env_var_rustc_linker_host_target() {
+ let target = rustc_host();
+ let p = project()
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ target-applies-to-host = false
+ [target.{}]
+ linker = "/path/to/linker"
+ "#,
+ target
+ ),
+ )
+ .file(
+ "build.rs",
+ r#"
+ use std::env;
+
+ fn main() {
+ assert!(env::var("RUSTC_LINKER").unwrap().ends_with("/path/to/linker"));
+ }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ // no crate type set => linker never called => build succeeds if and
+ // only if build.rs succeeds, despite linker binary not existing.
+ p.cargo("build -Z target-applies-to-host --target")
+ .arg(&target)
+ .masquerade_as_nightly_cargo(&["target-applies-to-host"])
+ .run();
+}
+
+#[cargo_test]
+fn custom_build_env_var_rustc_linker_host_target_env() {
+ let target = rustc_host();
+ let p = project()
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [target.{}]
+ linker = "/path/to/linker"
+ "#,
+ target
+ ),
+ )
+ .file(
+ "build.rs",
+ r#"
+ use std::env;
+
+ fn main() {
+ assert!(env::var("RUSTC_LINKER").unwrap().ends_with("/path/to/linker"));
+ }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ // no crate type set => linker never called => build succeeds if and
+ // only if build.rs succeeds, despite linker binary not existing.
+ p.cargo("build -Z target-applies-to-host --target")
+ .env("CARGO_TARGET_APPLIES_TO_HOST", "false")
+ .arg(&target)
+ .masquerade_as_nightly_cargo(&["target-applies-to-host"])
+ .run();
+}
+
+#[cargo_test]
+fn custom_build_invalid_host_config_feature_flag() {
+ let target = rustc_host();
+ let p = project()
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [target.{}]
+ linker = "/path/to/linker"
+ "#,
+ target
+ ),
+ )
+ .file("build.rs", "fn main() {}")
+ .file("src/lib.rs", "")
+ .build();
+
+ // build.rs should fail due to -Zhost-config being set without -Ztarget-applies-to-host
+ p.cargo("build -Z host-config --target")
+ .arg(&target)
+ .masquerade_as_nightly_cargo(&["host-config"])
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+error: the -Zhost-config flag requires the -Ztarget-applies-to-host flag to be set
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn custom_build_linker_host_target_with_bad_host_config() {
+ let target = rustc_host();
+ let p = project()
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [host]
+ linker = "/path/to/host/linker"
+ [target.{}]
+ linker = "/path/to/target/linker"
+ "#,
+ target
+ ),
+ )
+ .file("build.rs", "fn main() {}")
+ .file("src/lib.rs", "")
+ .build();
+
+ // build.rs should fail due to bad host linker being set
+ p.cargo("build -Z target-applies-to-host -Z host-config --verbose --target")
+ .arg(&target)
+ .masquerade_as_nightly_cargo(&["target-applies-to-host", "host-config"])
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name build_script_build build.rs [..]--crate-type bin [..]-C linker=[..]/path/to/host/linker [..]`
+[ERROR] linker `[..]/path/to/host/linker` not found
+"
+ )
+ .run();
+}
+
+#[cargo_test]
+fn custom_build_linker_bad_host() {
+ let target = rustc_host();
+ let p = project()
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [host]
+ linker = "/path/to/host/linker"
+ [target.{}]
+ linker = "/path/to/target/linker"
+ "#,
+ target
+ ),
+ )
+ .file("build.rs", "fn main() {}")
+ .file("src/lib.rs", "")
+ .build();
+
+ // build.rs should fail due to bad host linker being set
+ p.cargo("build -Z target-applies-to-host -Z host-config --verbose --target")
+ .arg(&target)
+ .masquerade_as_nightly_cargo(&["target-applies-to-host", "host-config"])
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name build_script_build build.rs [..]--crate-type bin [..]-C linker=[..]/path/to/host/linker [..]`
+[ERROR] linker `[..]/path/to/host/linker` not found
+"
+ )
+ .run();
+}
+
+#[cargo_test]
+fn custom_build_linker_bad_host_with_arch() {
+ let target = rustc_host();
+ let p = project()
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [host]
+ linker = "/path/to/host/linker"
+ [host.{}]
+ linker = "/path/to/host/arch/linker"
+ [target.{}]
+ linker = "/path/to/target/linker"
+ "#,
+ target, target
+ ),
+ )
+ .file("build.rs", "fn main() {}")
+ .file("src/lib.rs", "")
+ .build();
+
+ // build.rs should fail due to bad host linker being set
+ p.cargo("build -Z target-applies-to-host -Z host-config --verbose --target")
+ .arg(&target)
+ .masquerade_as_nightly_cargo(&["target-applies-to-host", "host-config"])
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name build_script_build build.rs [..]--crate-type bin [..]-C linker=[..]/path/to/host/arch/linker [..]`
+[ERROR] linker `[..]/path/to/host/arch/linker` not found
+"
+ )
+ .run();
+}
+
+#[cargo_test]
+fn custom_build_env_var_rustc_linker_cross_arch_host() {
+ let target = rustc_host();
+ let cross_target = cross_compile::alternate();
+ let p = project()
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [host.{}]
+ linker = "/path/to/host/arch/linker"
+ [target.{}]
+ linker = "/path/to/target/linker"
+ "#,
+ cross_target, target
+ ),
+ )
+ .file(
+ "build.rs",
+ r#"
+ use std::env;
+
+ fn main() {
+ assert!(env::var("RUSTC_LINKER").unwrap().ends_with("/path/to/target/linker"));
+ }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ // build.rs should be built fine since cross target != host target.
+ // assertion should succeed since it's still passed the target linker
+ p.cargo("build -Z target-applies-to-host -Z host-config --verbose --target")
+ .arg(&target)
+ .masquerade_as_nightly_cargo(&["target-applies-to-host", "host-config"])
+ .run();
+}
+
+#[cargo_test]
+fn custom_build_linker_bad_cross_arch_host() {
+ let target = rustc_host();
+ let cross_target = cross_compile::alternate();
+ let p = project()
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [host]
+ linker = "/path/to/host/linker"
+ [host.{}]
+ linker = "/path/to/host/arch/linker"
+ [target.{}]
+ linker = "/path/to/target/linker"
+ "#,
+ cross_target, target
+ ),
+ )
+ .file("build.rs", "fn main() {}")
+ .file("src/lib.rs", "")
+ .build();
+
+ // build.rs should fail due to bad host linker being set
+ p.cargo("build -Z target-applies-to-host -Z host-config --verbose --target")
+ .arg(&target)
+ .masquerade_as_nightly_cargo(&["target-applies-to-host", "host-config"])
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name build_script_build build.rs [..]--crate-type bin [..]-C linker=[..]/path/to/host/linker [..]`
+[ERROR] linker `[..]/path/to/host/linker` not found
+"
+ )
+ .run();
+}
+
+#[cargo_test]
+fn custom_build_script_wrong_rustc_flags() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+ build = "build.rs"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "build.rs",
+ r#"fn main() { println!("cargo:rustc-flags=-aaa -bbb"); }"#,
+ )
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr_contains(
+ "[ERROR] Only `-l` and `-L` flags are allowed in build script of `foo v0.5.0 ([CWD])`: \
+ `-aaa -bbb`",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn custom_build_script_rustc_flags() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "bar"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.foo]
+ path = "foo"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+ build = "build.rs"
+ "#,
+ )
+ .file("foo/src/lib.rs", "")
+ .file(
+ "foo/build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rustc-flags=-l nonexistinglib -L /dummy/path1 -L /dummy/path2");
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build --verbose")
+ .with_stderr(
+ "\
+[COMPILING] foo [..]
+[RUNNING] `rustc --crate-name build_script_build foo/build.rs [..]
+[RUNNING] `[..]build-script-build`
+[RUNNING] `rustc --crate-name foo foo/src/lib.rs [..]\
+ -L dependency=[CWD]/target/debug/deps \
+ -L /dummy/path1 -L /dummy/path2 -l nonexistinglib`
+[COMPILING] bar [..]
+[RUNNING] `rustc --crate-name bar src/main.rs [..]\
+ -L dependency=[CWD]/target/debug/deps \
+ --extern foo=[..]libfoo-[..] \
+ -L /dummy/path1 -L /dummy/path2`
+[FINISHED] dev [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn custom_build_script_rustc_flags_no_space() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "bar"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.foo]
+ path = "foo"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+ build = "build.rs"
+ "#,
+ )
+ .file("foo/src/lib.rs", "")
+ .file(
+ "foo/build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rustc-flags=-lnonexistinglib -L/dummy/path1 -L/dummy/path2");
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build --verbose")
+ .with_stderr(
+ "\
+[COMPILING] foo [..]
+[RUNNING] `rustc --crate-name build_script_build foo/build.rs [..]
+[RUNNING] `[..]build-script-build`
+[RUNNING] `rustc --crate-name foo foo/src/lib.rs [..]\
+ -L dependency=[CWD]/target/debug/deps \
+ -L /dummy/path1 -L /dummy/path2 -l nonexistinglib`
+[COMPILING] bar [..]
+[RUNNING] `rustc --crate-name bar src/main.rs [..]\
+ -L dependency=[CWD]/target/debug/deps \
+ --extern foo=[..]libfoo-[..] \
+ -L /dummy/path1 -L /dummy/path2`
+[FINISHED] dev [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn links_no_build_cmd() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ links = "a"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]/foo/Cargo.toml`
+
+Caused by:
+ package `foo v0.5.0 ([CWD])` specifies that it links to `a` but does \
+not have a custom build script
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn links_duplicates() {
+ // this tests that the links_duplicates are caught at resolver time
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ links = "a"
+ build = "build.rs"
+
+ [dependencies.a-sys]
+ path = "a-sys"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("build.rs", "")
+ .file(
+ "a-sys/Cargo.toml",
+ r#"
+ [package]
+ name = "a-sys"
+ version = "0.5.0"
+ authors = []
+ links = "a"
+ build = "build.rs"
+ "#,
+ )
+ .file("a-sys/src/lib.rs", "")
+ .file("a-sys/build.rs", "")
+ .build();
+
+ p.cargo("build").with_status(101)
+ .with_stderr("\
+error: failed to select a version for `a-sys`.
+ ... required by package `foo v0.5.0 ([..])`
+versions that meet the requirements `*` are: 0.5.0
+
+the package `a-sys` links to the native library `a`, but it conflicts with a previous package which links to `a` as well:
+package `foo v0.5.0 ([..])`
+Only one package in the dependency graph may specify the same links value. This helps ensure that only one copy of a native library is linked in the final binary. Try to adjust your dependencies so that only one package uses the links ='a-sys' value. For more information, see https://doc.rust-lang.org/cargo/reference/resolver.html#links.
+
+failed to select a version for `a-sys` which could resolve this conflict
+").run();
+}
+
+#[cargo_test]
+fn links_duplicates_old_registry() {
+ // Test old links validator. See `validate_links`.
+ Package::new("bar", "0.1.0")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ links = "a"
+ "#,
+ )
+ .file("build.rs", "fn main() {}")
+ .file("src/lib.rs", "")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ links = "a"
+
+ [dependencies]
+ bar = "0.1"
+ "#,
+ )
+ .file("build.rs", "fn main() {}")
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.1.0 ([..])
+[ERROR] multiple packages link to native library `a`, \
+ but a native library can be linked only once
+
+package `bar v0.1.0`
+ ... which satisfies dependency `bar = \"^0.1\"` (locked to 0.1.0) of package `foo v0.1.0 ([..]foo)`
+links to native library `a`
+
+package `foo v0.1.0 ([..]foo)`
+also links to native library `a`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn links_duplicates_deep_dependency() {
+ // this tests that the links_duplicates are caught at resolver time
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ links = "a"
+ build = "build.rs"
+
+ [dependencies.a]
+ path = "a"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("build.rs", "")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.5.0"
+ authors = []
+ build = "build.rs"
+
+ [dependencies.a-sys]
+ path = "a-sys"
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .file("a/build.rs", "")
+ .file(
+ "a/a-sys/Cargo.toml",
+ r#"
+ [package]
+ name = "a-sys"
+ version = "0.5.0"
+ authors = []
+ links = "a"
+ build = "build.rs"
+ "#,
+ )
+ .file("a/a-sys/src/lib.rs", "")
+ .file("a/a-sys/build.rs", "")
+ .build();
+
+ p.cargo("build").with_status(101)
+ .with_stderr("\
+error: failed to select a version for `a-sys`.
+ ... required by package `a v0.5.0 ([..])`
+ ... which satisfies path dependency `a` of package `foo v0.5.0 ([..])`
+versions that meet the requirements `*` are: 0.5.0
+
+the package `a-sys` links to the native library `a`, but it conflicts with a previous package which links to `a` as well:
+package `foo v0.5.0 ([..])`
+Only one package in the dependency graph may specify the same links value. This helps ensure that only one copy of a native library is linked in the final binary. Try to adjust your dependencies so that only one package uses the links ='a-sys' value. For more information, see https://doc.rust-lang.org/cargo/reference/resolver.html#links.
+
+failed to select a version for `a-sys` which could resolve this conflict
+").run();
+}
+
+#[cargo_test]
+fn overrides_and_links() {
+ let target = rustc_host();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ build = "build.rs"
+
+ [dependencies.a]
+ path = "a"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ use std::env;
+ fn main() {
+ assert_eq!(env::var("DEP_FOO_FOO").ok().expect("FOO missing"),
+ "bar");
+ assert_eq!(env::var("DEP_FOO_BAR").ok().expect("BAR missing"),
+ "baz");
+ }
+ "#,
+ )
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [target.{}.foo]
+ rustc-flags = "-L foo -L bar"
+ foo = "bar"
+ bar = "baz"
+ "#,
+ target
+ ),
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.5.0"
+ authors = []
+ links = "foo"
+ build = "build.rs"
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .file("a/build.rs", "not valid rust code")
+ .build();
+
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[..]
+[..]
+[..]
+[..]
+[..]
+[RUNNING] `rustc --crate-name foo [..] -L foo -L bar`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn unused_overrides() {
+ let target = rustc_host();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("build.rs", "fn main() {}")
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [target.{}.foo]
+ rustc-flags = "-L foo -L bar"
+ foo = "bar"
+ bar = "baz"
+ "#,
+ target
+ ),
+ )
+ .build();
+
+ p.cargo("build -v").run();
+}
+
+#[cargo_test]
+fn links_passes_env_vars() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ build = "build.rs"
+
+ [dependencies.a]
+ path = "a"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ use std::env;
+ fn main() {
+ assert_eq!(env::var("DEP_FOO_FOO").unwrap(), "bar");
+ assert_eq!(env::var("DEP_FOO_BAR").unwrap(), "baz");
+ }
+ "#,
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.5.0"
+ authors = []
+ links = "foo"
+ build = "build.rs"
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .file(
+ "a/build.rs",
+ r#"
+ use std::env;
+ fn main() {
+ let lib = env::var("CARGO_MANIFEST_LINKS").unwrap();
+ assert_eq!(lib, "foo");
+
+ println!("cargo:foo=bar");
+ println!("cargo:bar=baz");
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build -v").run();
+}
+
+#[cargo_test]
+fn only_rerun_build_script() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("build.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build -v").run();
+ p.root().move_into_the_past();
+
+ p.change_file("some-new-file", "");
+ p.root().move_into_the_past();
+
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[DIRTY] foo v0.5.0 ([CWD]): the precalculated components changed
+[COMPILING] foo v0.5.0 ([CWD])
+[RUNNING] `[..]/build-script-build`
+[RUNNING] `rustc --crate-name foo [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rebuild_continues_to_pass_env_vars() {
+ let a = project()
+ .at("a")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.5.0"
+ authors = []
+ links = "foo"
+ build = "build.rs"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ use std::time::Duration;
+ fn main() {
+ println!("cargo:foo=bar");
+ println!("cargo:bar=baz");
+ std::thread::sleep(Duration::from_millis(500));
+ }
+ "#,
+ )
+ .build();
+ a.root().move_into_the_past();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ build = "build.rs"
+
+ [dependencies.a]
+ path = '{}'
+ "#,
+ a.root().display()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ use std::env;
+ fn main() {
+ assert_eq!(env::var("DEP_FOO_FOO").unwrap(), "bar");
+ assert_eq!(env::var("DEP_FOO_BAR").unwrap(), "baz");
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build -v").run();
+ p.root().move_into_the_past();
+
+ p.change_file("some-new-file", "");
+ p.root().move_into_the_past();
+
+ p.cargo("build -v").run();
+}
+
+#[cargo_test]
+fn testing_and_such() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("build.rs", "fn main() {}")
+ .build();
+
+ println!("build");
+ p.cargo("build -v").run();
+ p.root().move_into_the_past();
+
+ p.change_file("src/lib.rs", "");
+ p.root().move_into_the_past();
+
+ println!("test");
+ p.cargo("test -vj1")
+ .with_stderr(
+ "\
+[DIRTY] foo v0.5.0 ([CWD]): the precalculated components changed
+[COMPILING] foo v0.5.0 ([CWD])
+[RUNNING] `[..]/build-script-build`
+[RUNNING] `rustc --crate-name foo [..]`
+[RUNNING] `rustc --crate-name foo [..]`
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `[..]/foo-[..][EXE]`
+[DOCTEST] foo
+[RUNNING] `rustdoc [..]--test [..]`",
+ )
+ .with_stdout_contains_n("running 0 tests", 2)
+ .run();
+
+ println!("doc");
+ p.cargo("doc -v")
+ .with_stderr(
+ "\
+[DOCUMENTING] foo v0.5.0 ([CWD])
+[RUNNING] `rustdoc [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.change_file("src/main.rs", "fn main() {}");
+ println!("run");
+ p.cargo("run")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.5.0 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `target/debug/foo[EXE]`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn propagation_of_l_flags() {
+ let target = rustc_host();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ [dependencies.a]
+ path = "a"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.5.0"
+ authors = []
+ links = "bar"
+ build = "build.rs"
+
+ [dependencies.b]
+ path = "../b"
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .file(
+ "a/build.rs",
+ r#"fn main() { println!("cargo:rustc-flags=-L bar"); }"#,
+ )
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.5.0"
+ authors = []
+ links = "foo"
+ build = "build.rs"
+ "#,
+ )
+ .file("b/src/lib.rs", "")
+ .file("b/build.rs", "bad file")
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [target.{}.foo]
+ rustc-flags = "-L foo"
+ "#,
+ target
+ ),
+ )
+ .build();
+
+ p.cargo("build -v -j1")
+ .with_stderr_contains(
+ "\
+[RUNNING] `rustc --crate-name a [..] -L bar[..]-L foo[..]`
+[COMPILING] foo v0.5.0 ([CWD])
+[RUNNING] `rustc --crate-name foo [..] -L bar -L foo`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn propagation_of_l_flags_new() {
+ let target = rustc_host();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ [dependencies.a]
+ path = "a"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.5.0"
+ authors = []
+ links = "bar"
+ build = "build.rs"
+
+ [dependencies.b]
+ path = "../b"
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .file(
+ "a/build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rustc-link-search=bar");
+ }
+ "#,
+ )
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.5.0"
+ authors = []
+ links = "foo"
+ build = "build.rs"
+ "#,
+ )
+ .file("b/src/lib.rs", "")
+ .file("b/build.rs", "bad file")
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [target.{}.foo]
+ rustc-link-search = ["foo"]
+ "#,
+ target
+ ),
+ )
+ .build();
+
+ p.cargo("build -v -j1")
+ .with_stderr_contains(
+ "\
+[RUNNING] `rustc --crate-name a [..] -L bar[..]-L foo[..]`
+[COMPILING] foo v0.5.0 ([CWD])
+[RUNNING] `rustc --crate-name foo [..] -L bar -L foo`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_deps_simple() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ build = "build.rs"
+ [build-dependencies.a]
+ path = "a"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ "
+ #[allow(unused_extern_crates)]
+ extern crate a;
+ fn main() {}
+ ",
+ )
+ .file("a/Cargo.toml", &basic_manifest("a", "0.5.0"))
+ .file("a/src/lib.rs", "")
+ .build();
+
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[COMPILING] a v0.5.0 ([CWD]/a)
+[RUNNING] `rustc --crate-name a [..]`
+[COMPILING] foo v0.5.0 ([CWD])
+[RUNNING] `rustc [..] build.rs [..] --extern a=[..]`
+[RUNNING] `[..]/foo-[..]/build-script-build`
+[RUNNING] `rustc --crate-name foo [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_deps_not_for_normal() {
+ let target = rustc_host();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ build = "build.rs"
+ [build-dependencies.aaaaa]
+ path = "a"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "#[allow(unused_extern_crates)] extern crate aaaaa;",
+ )
+ .file(
+ "build.rs",
+ "
+ #[allow(unused_extern_crates)]
+ extern crate aaaaa;
+ fn main() {}
+ ",
+ )
+ .file("a/Cargo.toml", &basic_manifest("aaaaa", "0.5.0"))
+ .file("a/src/lib.rs", "")
+ .build();
+
+ p.cargo("build -v --target")
+ .arg(&target)
+ .with_status(101)
+ .with_stderr_contains("[..]can't find crate for `aaaaa`[..]")
+ .with_stderr_contains(
+ "\
+[ERROR] could not compile `foo` (lib) due to previous error
+
+Caused by:
+ process didn't exit successfully: [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_cmd_with_a_build_cmd() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ build = "build.rs"
+
+ [build-dependencies.a]
+ path = "a"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ "
+ #[allow(unused_extern_crates)]
+ extern crate a;
+ fn main() {}
+ ",
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.5.0"
+ authors = []
+ build = "build.rs"
+
+ [build-dependencies.b]
+ path = "../b"
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .file(
+ "a/build.rs",
+ "#[allow(unused_extern_crates)] extern crate b; fn main() {}",
+ )
+ .file("b/Cargo.toml", &basic_manifest("b", "0.5.0"))
+ .file("b/src/lib.rs", "")
+ .build();
+
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[COMPILING] b v0.5.0 ([CWD]/b)
+[RUNNING] `rustc --crate-name b [..]`
+[COMPILING] a v0.5.0 ([CWD]/a)
+[RUNNING] `rustc [..] a/build.rs [..] --extern b=[..]`
+[RUNNING] `[..]/a-[..]/build-script-build`
+[RUNNING] `rustc --crate-name a [..]lib.rs [..]--crate-type lib \
+ --emit=[..]link[..] \
+ -C metadata=[..] \
+ --out-dir [..]target/debug/deps \
+ -L [..]target/debug/deps`
+[COMPILING] foo v0.5.0 ([CWD])
+[RUNNING] `rustc --crate-name build_script_build build.rs [..]--crate-type bin \
+ --emit=[..]link[..]\
+ -C metadata=[..] --out-dir [..] \
+ -L [..]target/debug/deps \
+ --extern a=[..]liba[..].rlib`
+[RUNNING] `[..]/foo-[..]/build-script-build`
+[RUNNING] `rustc --crate-name foo [..]lib.rs [..]--crate-type lib \
+ --emit=[..]link[..]-C debuginfo=2 \
+ -C metadata=[..] \
+ --out-dir [..] \
+ -L [..]target/debug/deps`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn out_dir_is_preserved() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ use std::env;
+ use std::fs::File;
+ use std::path::Path;
+ fn main() {
+ let out = env::var("OUT_DIR").unwrap();
+ File::create(Path::new(&out).join("foo")).unwrap();
+ }
+ "#,
+ )
+ .build();
+
+ // Make the file
+ p.cargo("build -v").run();
+
+ // Change to asserting that it's there
+ p.change_file(
+ "build.rs",
+ r#"
+ use std::env;
+ use std::fs::File;
+ use std::path::Path;
+ fn main() {
+ let out = env::var("OUT_DIR").unwrap();
+ File::open(&Path::new(&out).join("foo")).unwrap();
+ }
+ "#,
+ );
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[DIRTY] foo [..]: the file `build.rs` has changed ([..])
+[COMPILING] foo [..]
+[RUNNING] `rustc --crate-name build_script_build [..]
+[RUNNING] `[..]/build-script-build`
+[RUNNING] `rustc --crate-name foo [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ // Run a fresh build where file should be preserved
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[FRESH] foo [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ // One last time to make sure it's still there.
+ p.change_file("foo", "");
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[DIRTY] foo [..]: the precalculated components changed
+[COMPILING] foo [..]
+[RUNNING] `[..]build-script-build`
+[RUNNING] `rustc --crate-name foo [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn output_separate_lines() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rustc-flags=-L foo");
+ println!("cargo:rustc-flags=-l static=foo");
+ }
+ "#,
+ )
+ .build();
+ p.cargo("build -v")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+[COMPILING] foo v0.5.0 ([CWD])
+[RUNNING] `rustc [..] build.rs [..]`
+[RUNNING] `[..]/foo-[..]/build-script-build`
+[RUNNING] `rustc --crate-name foo [..] -L foo -l static=foo`
+[ERROR] could not find native static library [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn output_separate_lines_new() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rustc-link-search=foo");
+ println!("cargo:rustc-link-lib=static=foo");
+ println!("cargo:rustc-link-lib=bar");
+ println!("cargo:rustc-link-search=bar");
+ }
+ "#,
+ )
+ .build();
+ // The order of the arguments passed to rustc is important.
+ p.cargo("build -v")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+[COMPILING] foo v0.5.0 ([CWD])
+[RUNNING] `rustc [..] build.rs [..]`
+[RUNNING] `[..]/foo-[..]/build-script-build`
+[RUNNING] `rustc --crate-name foo [..] -L foo -L bar -l static=foo -l bar`
+[ERROR] could not find native static library [..]
+",
+ )
+ .run();
+}
+
+#[cfg(not(windows))] // FIXME(#867)
+#[cargo_test]
+fn code_generation() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ include!(concat!(env!("OUT_DIR"), "/hello.rs"));
+
+ fn main() {
+ println!("{}", message());
+ }
+ "#,
+ )
+ .file(
+ "build.rs",
+ r#"
+ use std::env;
+ use std::fs;
+ use std::path::PathBuf;
+
+ fn main() {
+ let dst = PathBuf::from(env::var("OUT_DIR").unwrap());
+ fs::write(dst.join("hello.rs"),
+ "
+ pub fn message() -> &'static str {
+ \"Hello, World!\"
+ }
+ ")
+ .unwrap();
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("run")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.5.0 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `target/debug/foo`",
+ )
+ .with_stdout("Hello, World!")
+ .run();
+
+ p.cargo("test").run();
+}
+
+#[cargo_test]
+fn release_with_build_script() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {}
+ "#,
+ )
+ .build();
+
+ p.cargo("build -v --release").run();
+}
+
+#[cargo_test]
+fn build_script_only() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file("build.rs", r#"fn main() {}"#)
+ .build();
+ p.cargo("build -v")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ no targets specified in the manifest
+ either src/lib.rs, src/main.rs, a [lib] section, or [[bin]] section must be present",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn shared_dep_with_a_build_script() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ build = "build.rs"
+
+ [dependencies.a]
+ path = "a"
+
+ [build-dependencies.b]
+ path = "b"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("build.rs", "fn main() {}")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.5.0"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file("a/build.rs", "fn main() {}")
+ .file("a/src/lib.rs", "")
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies.a]
+ path = "../a"
+ "#,
+ )
+ .file("b/src/lib.rs", "")
+ .build();
+ p.cargo("build -v").run();
+}
+
+#[cargo_test]
+fn transitive_dep_host() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ build = "build.rs"
+
+ [build-dependencies.b]
+ path = "b"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("build.rs", "fn main() {}")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.5.0"
+ authors = []
+ links = "foo"
+ build = "build.rs"
+ "#,
+ )
+ .file("a/build.rs", "fn main() {}")
+ .file("a/src/lib.rs", "")
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.5.0"
+ authors = []
+
+ [lib]
+ name = "b"
+ plugin = true
+
+ [dependencies.a]
+ path = "../a"
+ "#,
+ )
+ .file("b/src/lib.rs", "")
+ .build();
+ p.cargo("build").run();
+}
+
+#[cargo_test]
+fn test_a_lib_with_a_build_command() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ include!(concat!(env!("OUT_DIR"), "/foo.rs"));
+
+ /// ```
+ /// foo::bar();
+ /// ```
+ pub fn bar() {
+ assert_eq!(foo(), 1);
+ }
+ "#,
+ )
+ .file(
+ "build.rs",
+ r#"
+ use std::env;
+ use std::fs;
+ use std::path::PathBuf;
+
+ fn main() {
+ let out = PathBuf::from(env::var("OUT_DIR").unwrap());
+ fs::write(out.join("foo.rs"), "fn foo() -> i32 { 1 }").unwrap();
+ }
+ "#,
+ )
+ .build();
+ p.cargo("test").run();
+}
+
+#[cargo_test]
+fn test_dev_dep_build_script() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+
+ [dev-dependencies.a]
+ path = "a"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.5.0"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file("a/build.rs", "fn main() {}")
+ .file("a/src/lib.rs", "")
+ .build();
+
+ p.cargo("test").run();
+}
+
+#[cargo_test]
+fn build_script_with_dynamic_native_dependency() {
+ let build = project()
+ .at("builder")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "builder"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ name = "builder"
+ crate-type = ["dylib"]
+ "#,
+ )
+ .file("src/lib.rs", "#[no_mangle] pub extern fn foo() {}")
+ .build();
+
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+
+ [build-dependencies.bar]
+ path = "bar"
+ "#,
+ )
+ .file("build.rs", "extern crate bar; fn main() { bar::bar() }")
+ .file("src/lib.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file(
+ "bar/build.rs",
+ r#"
+ use std::env;
+ use std::fs;
+ use std::path::PathBuf;
+
+ fn main() {
+ let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
+ let root = PathBuf::from(env::var("BUILDER_ROOT").unwrap());
+ let file = format!("{}builder{}",
+ env::consts::DLL_PREFIX,
+ env::consts::DLL_SUFFIX);
+ let src = root.join(&file);
+ let dst = out_dir.join(&file);
+ fs::copy(src, dst).unwrap();
+ if cfg!(target_env = "msvc") {
+ fs::copy(root.join("builder.dll.lib"),
+ out_dir.join("builder.dll.lib")).unwrap();
+ }
+ println!("cargo:rustc-link-search=native={}", out_dir.display());
+ }
+ "#,
+ )
+ .file(
+ "bar/src/lib.rs",
+ r#"
+ pub fn bar() {
+ #[cfg_attr(not(target_env = "msvc"), link(name = "builder"))]
+ #[cfg_attr(target_env = "msvc", link(name = "builder.dll"))]
+ extern { fn foo(); }
+ unsafe { foo() }
+ }
+ "#,
+ )
+ .build();
+
+ build
+ .cargo("build -v")
+ .env("CARGO_LOG", "cargo::ops::cargo_rustc")
+ .run();
+
+ let root = build.root().join("target").join("debug");
+ foo.cargo("build -v")
+ .env("BUILDER_ROOT", root)
+ .env("CARGO_LOG", "cargo::ops::cargo_rustc")
+ .run();
+}
+
+#[cargo_test]
+fn profile_and_opt_level_set_correctly() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ use std::env;
+
+ fn main() {
+ assert_eq!(env::var("OPT_LEVEL").unwrap(), "3");
+ assert_eq!(env::var("PROFILE").unwrap(), "release");
+ assert_eq!(env::var("DEBUG").unwrap(), "false");
+ }
+ "#,
+ )
+ .build();
+ p.cargo("bench").run();
+}
+
+#[cargo_test]
+fn profile_debug_0() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [profile.dev]
+ debug = 0
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ use std::env;
+
+ fn main() {
+ assert_eq!(env::var("OPT_LEVEL").unwrap(), "0");
+ assert_eq!(env::var("PROFILE").unwrap(), "debug");
+ assert_eq!(env::var("DEBUG").unwrap(), "false");
+ }
+ "#,
+ )
+ .build();
+ p.cargo("build").run();
+}
+
+#[cargo_test]
+fn build_script_with_lto() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+
+ [profile.dev]
+ lto = true
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("build.rs", "fn main() {}")
+ .build();
+ p.cargo("build").run();
+}
+
+#[cargo_test]
+fn test_duplicate_deps() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ build = "build.rs"
+
+ [dependencies.bar]
+ path = "bar"
+
+ [build-dependencies.bar]
+ path = "bar"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ extern crate bar;
+ fn main() { bar::do_nothing() }
+ "#,
+ )
+ .file(
+ "build.rs",
+ r#"
+ extern crate bar;
+ fn main() { bar::do_nothing() }
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn do_nothing() {}")
+ .build();
+
+ p.cargo("build").run();
+}
+
+#[cargo_test]
+fn cfg_feedback() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file("src/main.rs", "#[cfg(foo)] fn main() {}")
+ .file(
+ "build.rs",
+ r#"fn main() { println!("cargo:rustc-cfg=foo"); }"#,
+ )
+ .build();
+ p.cargo("build -v").run();
+}
+
+#[cargo_test]
+fn cfg_override() {
+ let target = rustc_host();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ links = "a"
+ build = "build.rs"
+ "#,
+ )
+ .file("src/main.rs", "#[cfg(foo)] fn main() {}")
+ .file("build.rs", "")
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [target.{}.a]
+ rustc-cfg = ["foo"]
+ "#,
+ target
+ ),
+ )
+ .build();
+
+ p.cargo("build -v").run();
+}
+
+#[cargo_test]
+fn cfg_test() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file(
+ "build.rs",
+ r#"fn main() { println!("cargo:rustc-cfg=foo"); }"#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ ///
+ /// ```
+ /// extern crate foo;
+ ///
+ /// fn main() {
+ /// foo::foo()
+ /// }
+ /// ```
+ ///
+ #[cfg(foo)]
+ pub fn foo() {}
+
+ #[cfg(foo)]
+ #[test]
+ fn test_foo() {
+ foo()
+ }
+ "#,
+ )
+ .file("tests/test.rs", "#[cfg(foo)] #[test] fn test_bar() {}")
+ .build();
+ p.cargo("test -v")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] [..] build.rs [..]
+[RUNNING] `[..]/build-script-build`
+[RUNNING] [..] --cfg foo[..]
+[RUNNING] [..] --cfg foo[..]
+[RUNNING] [..] --cfg foo[..]
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `[..]/foo-[..][EXE]`
+[RUNNING] `[..]/test-[..][EXE]`
+[DOCTEST] foo
+[RUNNING] [..] --cfg foo[..]",
+ )
+ .with_stdout_contains("test test_foo ... ok")
+ .with_stdout_contains("test test_bar ... ok")
+ .with_stdout_contains_n("test [..] ... ok", 3)
+ .run();
+}
+
+#[cargo_test]
+fn cfg_doc() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+
+ [dependencies.bar]
+ path = "bar"
+ "#,
+ )
+ .file(
+ "build.rs",
+ r#"fn main() { println!("cargo:rustc-cfg=foo"); }"#,
+ )
+ .file("src/lib.rs", "#[cfg(foo)] pub fn foo() {}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file(
+ "bar/build.rs",
+ r#"fn main() { println!("cargo:rustc-cfg=bar"); }"#,
+ )
+ .file("bar/src/lib.rs", "#[cfg(bar)] pub fn bar() {}")
+ .build();
+ p.cargo("doc").run();
+ assert!(p.root().join("target/doc").is_dir());
+ assert!(p.root().join("target/doc/foo/fn.foo.html").is_file());
+ assert!(p.root().join("target/doc/bar/fn.bar.html").is_file());
+}
+
+#[cargo_test]
+fn cfg_override_test() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+ links = "a"
+ "#,
+ )
+ .file("build.rs", "")
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [target.{}.a]
+ rustc-cfg = ["foo"]
+ "#,
+ rustc_host()
+ ),
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ ///
+ /// ```
+ /// extern crate foo;
+ ///
+ /// fn main() {
+ /// foo::foo()
+ /// }
+ /// ```
+ ///
+ #[cfg(foo)]
+ pub fn foo() {}
+
+ #[cfg(foo)]
+ #[test]
+ fn test_foo() {
+ foo()
+ }
+ "#,
+ )
+ .file("tests/test.rs", "#[cfg(foo)] #[test] fn test_bar() {}")
+ .build();
+ p.cargo("test -v")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `[..]`
+[RUNNING] `[..]`
+[RUNNING] `[..]`
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `[..]/foo-[..][EXE]`
+[RUNNING] `[..]/test-[..][EXE]`
+[DOCTEST] foo
+[RUNNING] [..] --cfg foo[..]",
+ )
+ .with_stdout_contains("test test_foo ... ok")
+ .with_stdout_contains("test test_bar ... ok")
+ .with_stdout_contains_n("test [..] ... ok", 3)
+ .run();
+}
+
+#[cargo_test]
+fn cfg_override_doc() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+ links = "a"
+
+ [dependencies.bar]
+ path = "bar"
+ "#,
+ )
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [target.{target}.a]
+ rustc-cfg = ["foo"]
+ [target.{target}.b]
+ rustc-cfg = ["bar"]
+ "#,
+ target = rustc_host()
+ ),
+ )
+ .file("build.rs", "")
+ .file("src/lib.rs", "#[cfg(foo)] pub fn foo() {}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+ links = "b"
+ "#,
+ )
+ .file("bar/build.rs", "")
+ .file("bar/src/lib.rs", "#[cfg(bar)] pub fn bar() {}")
+ .build();
+ p.cargo("doc").run();
+ assert!(p.root().join("target/doc").is_dir());
+ assert!(p.root().join("target/doc/foo/fn.foo.html").is_file());
+ assert!(p.root().join("target/doc/bar/fn.bar.html").is_file());
+}
+
+#[cargo_test]
+fn env_build() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ const FOO: &'static str = env!("FOO");
+ fn main() {
+ println!("{}", FOO);
+ }
+ "#,
+ )
+ .file(
+ "build.rs",
+ r#"fn main() { println!("cargo:rustc-env=FOO=foo"); }"#,
+ )
+ .build();
+ p.cargo("build -v").run();
+ p.cargo("run -v").with_stdout("foo\n").run();
+}
+
+#[cargo_test]
+fn env_test() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file(
+ "build.rs",
+ r#"fn main() { println!("cargo:rustc-env=FOO=foo"); }"#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"pub const FOO: &'static str = env!("FOO"); "#,
+ )
+ .file(
+ "tests/test.rs",
+ r#"
+ extern crate foo;
+
+ #[test]
+ fn test_foo() {
+ assert_eq!("foo", foo::FOO);
+ }
+ "#,
+ )
+ .build();
+ p.cargo("test -v")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] [..] build.rs [..]
+[RUNNING] `[..]/build-script-build`
+[RUNNING] [..] --crate-name foo[..]
+[RUNNING] [..] --crate-name foo[..]
+[RUNNING] [..] --crate-name test[..]
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `[..]/foo-[..][EXE]`
+[RUNNING] `[..]/test-[..][EXE]`
+[DOCTEST] foo
+[RUNNING] [..] --crate-name foo[..]",
+ )
+ .with_stdout_contains_n("running 0 tests", 2)
+ .with_stdout_contains("test test_foo ... ok")
+ .run();
+}
+
+#[cargo_test]
+fn env_doc() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ const FOO: &'static str = env!("FOO");
+ fn main() {}
+ "#,
+ )
+ .file(
+ "build.rs",
+ r#"fn main() { println!("cargo:rustc-env=FOO=foo"); }"#,
+ )
+ .build();
+ p.cargo("doc -v").run();
+}
+
+#[cargo_test]
+fn flags_go_into_tests() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ b = { path = "b" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("tests/foo.rs", "")
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.5.0"
+ authors = []
+ [dependencies]
+ a = { path = "../a" }
+ "#,
+ )
+ .file("b/src/lib.rs", "")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.5.0"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .file(
+ "a/build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rustc-link-search=test");
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("test -v --test=foo")
+ .with_stderr(
+ "\
+[COMPILING] a v0.5.0 ([..]
+[RUNNING] `rustc [..] a/build.rs [..]`
+[RUNNING] `[..]/build-script-build`
+[RUNNING] `rustc [..] a/src/lib.rs [..] -L test[..]`
+[COMPILING] b v0.5.0 ([..]
+[RUNNING] `rustc [..] b/src/lib.rs [..] -L test[..]`
+[COMPILING] foo v0.5.0 ([..]
+[RUNNING] `rustc [..] src/lib.rs [..] -L test[..]`
+[RUNNING] `rustc [..] tests/foo.rs [..] -L test[..]`
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `[..]/foo-[..][EXE]`",
+ )
+ .with_stdout_contains("running 0 tests")
+ .run();
+
+ p.cargo("test -v -pb --lib")
+ .with_stderr(
+ "\
+[FRESH] a v0.5.0 ([..]
+[COMPILING] b v0.5.0 ([..]
+[RUNNING] `rustc [..] b/src/lib.rs [..] -L test[..]`
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `[..]/b-[..][EXE]`",
+ )
+ .with_stdout_contains("running 0 tests")
+ .run();
+}
+
+#[cargo_test]
+fn diamond_passes_args_only_once() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ a = { path = "a" }
+ b = { path = "b" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("tests/foo.rs", "")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.5.0"
+ authors = []
+ [dependencies]
+ b = { path = "../b" }
+ c = { path = "../c" }
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.5.0"
+ authors = []
+ [dependencies]
+ c = { path = "../c" }
+ "#,
+ )
+ .file("b/src/lib.rs", "")
+ .file(
+ "c/Cargo.toml",
+ r#"
+ [package]
+ name = "c"
+ version = "0.5.0"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file(
+ "c/build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rustc-link-search=native=test");
+ }
+ "#,
+ )
+ .file("c/src/lib.rs", "")
+ .build();
+
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[COMPILING] c v0.5.0 ([..]
+[RUNNING] `rustc [..]`
+[RUNNING] `[..]`
+[RUNNING] `rustc [..]`
+[COMPILING] b v0.5.0 ([..]
+[RUNNING] `rustc [..]`
+[COMPILING] a v0.5.0 ([..]
+[RUNNING] `rustc [..]`
+[COMPILING] foo v0.5.0 ([..]
+[RUNNING] `[..]rmeta -L native=test`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn adding_an_override_invalidates() {
+ let target = rustc_host();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ links = "foo"
+ build = "build.rs"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(".cargo/config", "")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rustc-link-search=native=foo");
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.5.0 ([..]
+[RUNNING] `rustc [..]`
+[RUNNING] `[..]`
+[RUNNING] `rustc [..] -L native=foo`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.change_file(
+ ".cargo/config",
+ &format!(
+ "
+ [target.{}.foo]
+ rustc-link-search = [\"native=bar\"]
+ ",
+ target
+ ),
+ );
+
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.5.0 ([..]
+[RUNNING] `rustc [..] -L native=bar`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn changing_an_override_invalidates() {
+ let target = rustc_host();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ links = "foo"
+ build = "build.rs"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ &format!(
+ "
+ [target.{}.foo]
+ rustc-link-search = [\"native=foo\"]
+ ",
+ target
+ ),
+ )
+ .file("build.rs", "")
+ .build();
+
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.5.0 ([..]
+[RUNNING] `rustc [..] -L native=foo`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.change_file(
+ ".cargo/config",
+ &format!(
+ "
+ [target.{}.foo]
+ rustc-link-search = [\"native=bar\"]
+ ",
+ target
+ ),
+ );
+
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[DIRTY] foo v0.5.0 ([..]): the precalculated components changed
+[COMPILING] foo v0.5.0 ([..]
+[RUNNING] `rustc [..] -L native=bar`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn fresh_builds_possible_with_link_libs() {
+ // The bug is non-deterministic. Sometimes you can get a fresh build
+ let target = rustc_host();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ links = "nativefoo"
+ build = "build.rs"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ &format!(
+ "
+ [target.{}.nativefoo]
+ rustc-link-lib = [\"a\"]
+ rustc-link-search = [\"./b\"]
+ rustc-flags = \"-l z -L ./\"
+ ",
+ target
+ ),
+ )
+ .file("build.rs", "")
+ .build();
+
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.5.0 ([..]
+[RUNNING] `rustc [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[FRESH] foo v0.5.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn fresh_builds_possible_with_multiple_metadata_overrides() {
+ // The bug is non-deterministic. Sometimes you can get a fresh build
+ let target = rustc_host();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ links = "foo"
+ build = "build.rs"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ &format!(
+ "
+ [target.{}.foo]
+ a = \"\"
+ b = \"\"
+ c = \"\"
+ d = \"\"
+ e = \"\"
+ ",
+ target
+ ),
+ )
+ .file("build.rs", "")
+ .build();
+
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.5.0 ([..]
+[RUNNING] `rustc [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("build -v")
+ .env("CARGO_LOG", "cargo::ops::cargo_rustc::fingerprint=info")
+ .with_stderr(
+ "\
+[FRESH] foo v0.5.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn generate_good_d_files() {
+ // this is here to stop regression on an issue where build.rs rerun-if-changed paths aren't
+ // made absolute properly, which in turn interacts poorly with the dep-info-basedir setting,
+ // and the dep-info files have other-crate-relative paths spat out in them
+ let p = project()
+ .file(
+ "awoo/Cargo.toml",
+ r#"
+ [package]
+ name = "awoo"
+ version = "0.5.0"
+ build = "build.rs"
+ "#,
+ )
+ .file("awoo/src/lib.rs", "")
+ .file(
+ "awoo/build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rerun-if-changed=build.rs");
+ println!("cargo:rerun-if-changed=barkbarkbark");
+ }
+ "#,
+ )
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "meow"
+ version = "0.5.0"
+ [dependencies]
+ awoo = { path = "awoo" }
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build -v").run();
+
+ let dot_d_path = p.bin("meow").with_extension("d");
+ println!("*meow at* {:?}", dot_d_path);
+ let dot_d = fs::read_to_string(&dot_d_path).unwrap();
+
+ println!("*.d file content*: {}", &dot_d);
+
+ assert_match_exact(
+ "[..]/target/debug/meow[EXE]: [..]/awoo/barkbarkbark [..]/awoo/build.rs[..]",
+ &dot_d,
+ );
+
+ // paths relative to dependency roots should not be allowed
+ assert!(!dot_d
+ .split_whitespace()
+ .any(|v| v == "barkbarkbark" || v == "build.rs"));
+
+ p.change_file(
+ ".cargo/config.toml",
+ r#"
+ [build]
+ dep-info-basedir="."
+ "#,
+ );
+ p.cargo("build -v").run();
+
+ let dot_d = fs::read_to_string(&dot_d_path).unwrap();
+
+ println!("*.d file content with dep-info-basedir*: {}", &dot_d);
+
+ assert_match_exact(
+ "target/debug/meow[EXE]: awoo/barkbarkbark awoo/build.rs[..]",
+ &dot_d,
+ );
+
+ // paths relative to dependency roots should not be allowed
+ assert!(!dot_d
+ .split_whitespace()
+ .any(|v| v == "barkbarkbark" || v == "build.rs"));
+}
+
+#[cargo_test]
+fn generate_good_d_files_for_external_tools() {
+ // This tests having a relative paths going out of the
+ // project root in config's dep-info-basedir
+ let p = project_in("rust_things")
+ .file(
+ "awoo/Cargo.toml",
+ r#"
+ [package]
+ name = "awoo"
+ version = "0.5.0"
+ build = "build.rs"
+ "#,
+ )
+ .file("awoo/src/lib.rs", "")
+ .file(
+ "awoo/build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rerun-if-changed=build.rs");
+ println!("cargo:rerun-if-changed=barkbarkbark");
+ }
+ "#,
+ )
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "meow"
+ version = "0.5.0"
+ [dependencies]
+ awoo = { path = "awoo" }
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ ".cargo/config.toml",
+ r#"
+ [build]
+ dep-info-basedir="../.."
+ "#,
+ )
+ .build();
+
+ p.cargo("build -v").run();
+
+ let dot_d_path = p.bin("meow").with_extension("d");
+ let dot_d = fs::read_to_string(&dot_d_path).unwrap();
+
+ println!("*.d file content with dep-info-basedir*: {}", &dot_d);
+
+ assert_match_exact(
+ concat!(
+ "rust_things/foo/target/debug/meow[EXE]:",
+ " rust_things/foo/awoo/barkbarkbark",
+ " rust_things/foo/awoo/build.rs",
+ " rust_things/foo/awoo/src/lib.rs",
+ " rust_things/foo/src/main.rs",
+ ),
+ &dot_d,
+ );
+}
+
+#[cargo_test]
+fn rebuild_only_on_explicit_paths() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rerun-if-changed=foo");
+ println!("cargo:rerun-if-changed=bar");
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build -v").run();
+
+ // files don't exist, so should always rerun if they don't exist
+ println!("run without");
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[DIRTY] foo v0.5.0 ([..]): the file `foo` is missing
+[COMPILING] foo v0.5.0 ([..])
+[RUNNING] `[..]/build-script-build`
+[RUNNING] `rustc [..] src/lib.rs [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ sleep_ms(1000);
+ p.change_file("foo", "");
+ p.change_file("bar", "");
+ sleep_ms(1000); // make sure the to-be-created outfile has a timestamp distinct from the infiles
+
+ // now the exist, so run once, catch the mtime, then shouldn't run again
+ println!("run with");
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[DIRTY] foo v0.5.0 ([..]): the file `foo` has changed ([..])
+[COMPILING] foo v0.5.0 ([..])
+[RUNNING] `[..]/build-script-build`
+[RUNNING] `rustc [..] src/lib.rs [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ println!("run with2");
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[FRESH] foo v0.5.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ sleep_ms(1000);
+
+ // random other files do not affect freshness
+ println!("run baz");
+ p.change_file("baz", "");
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[FRESH] foo v0.5.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ // but changing dependent files does
+ println!("run foo change");
+ p.change_file("foo", "");
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[DIRTY] foo v0.5.0 ([..]): the file `foo` has changed ([..])
+[COMPILING] foo v0.5.0 ([..])
+[RUNNING] `[..]/build-script-build`
+[RUNNING] `rustc [..] src/lib.rs [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ // .. as does deleting a file
+ println!("run bar delete");
+ fs::remove_file(p.root().join("bar")).unwrap();
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[DIRTY] foo v0.5.0 ([..]): the file `bar` is missing
+[COMPILING] foo v0.5.0 ([..])
+[RUNNING] `[..]/build-script-build`
+[RUNNING] `rustc [..] src/lib.rs [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn doctest_receives_build_link_args() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ [dependencies.a]
+ path = "a"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.5.0"
+ authors = []
+ links = "bar"
+ build = "build.rs"
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .file(
+ "a/build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rustc-link-search=native=bar");
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("test -v")
+ .with_stderr_contains(
+ "[RUNNING] `rustdoc [..]--crate-name foo --test [..]-L native=bar[..]`",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn please_respect_the_dag() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ build = "build.rs"
+
+ [dependencies]
+ a = { path = 'a' }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rustc-link-search=native=foo");
+ }
+ "#,
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.5.0"
+ authors = []
+ links = "bar"
+ build = "build.rs"
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .file(
+ "a/build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rustc-link-search=native=bar");
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build -v")
+ .with_stderr_contains("[RUNNING] `rustc [..] -L native=foo -L native=bar[..]`")
+ .run();
+}
+
+#[cargo_test]
+fn non_utf8_output() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file(
+ "build.rs",
+ r#"
+ use std::io::prelude::*;
+
+ fn main() {
+ let mut out = std::io::stdout();
+ // print something that's not utf8
+ out.write_all(b"\xff\xff\n").unwrap();
+
+ // now print some cargo metadata that's utf8
+ println!("cargo:rustc-cfg=foo");
+
+ // now print more non-utf8
+ out.write_all(b"\xff\xff\n").unwrap();
+ }
+ "#,
+ )
+ .file("src/main.rs", "#[cfg(foo)] fn main() {}")
+ .build();
+
+ p.cargo("build -v").run();
+}
+
+#[cargo_test]
+fn custom_target_dir() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ a = { path = "a" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ r#"
+ [build]
+ target-dir = 'test'
+ "#,
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.5.0"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file("a/build.rs", "fn main() {}")
+ .file("a/src/lib.rs", "")
+ .build();
+
+ p.cargo("build -v").run();
+}
+
+#[cargo_test]
+fn panic_abort_with_build_scripts() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+
+ [profile.release]
+ panic = 'abort'
+
+ [dependencies]
+ a = { path = "a" }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "#[allow(unused_extern_crates)] extern crate a;",
+ )
+ .file("build.rs", "fn main() {}")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.5.0"
+ authors = []
+ build = "build.rs"
+
+ [build-dependencies]
+ b = { path = "../b" }
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .file(
+ "a/build.rs",
+ "#[allow(unused_extern_crates)] extern crate b; fn main() {}",
+ )
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.5.0"
+ authors = []
+ "#,
+ )
+ .file("b/src/lib.rs", "")
+ .build();
+
+ p.cargo("build -v --release").run();
+
+ p.root().join("target").rm_rf();
+
+ p.cargo("test --release -v")
+ .with_stderr_does_not_contain("[..]panic=abort[..]")
+ .run();
+}
+
+#[cargo_test]
+fn warnings_emitted() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:warning=foo");
+ println!("cargo:warning=bar");
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.5.0 ([..])
+[RUNNING] `rustc [..]`
+[RUNNING] `[..]`
+warning: foo
+warning: bar
+[RUNNING] `rustc [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn warnings_emitted_when_build_script_panics() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:warning=foo");
+ println!("cargo:warning=bar");
+ panic!();
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stdout("")
+ .with_stderr_contains("warning: foo\nwarning: bar")
+ .run();
+}
+
+#[cargo_test]
+fn warnings_hidden_for_upstream() {
+ Package::new("bar", "0.1.0")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:warning=foo");
+ println!("cargo:warning=bar");
+ }
+ "#,
+ )
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ bar = "*"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.1.0 ([..])
+[COMPILING] bar v0.1.0
+[RUNNING] `rustc [..]`
+[RUNNING] `[..]`
+[RUNNING] `rustc [..]`
+[COMPILING] foo v0.5.0 ([..])
+[RUNNING] `rustc [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn warnings_printed_on_vv() {
+ Package::new("bar", "0.1.0")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:warning=foo");
+ println!("cargo:warning=bar");
+ }
+ "#,
+ )
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ bar = "*"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build -vv")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.1.0 ([..])
+[COMPILING] bar v0.1.0
+[RUNNING] `[..] rustc [..]`
+[RUNNING] `[..]`
+warning: foo
+warning: bar
+[RUNNING] `[..] rustc [..]`
+[COMPILING] foo v0.5.0 ([..])
+[RUNNING] `[..] rustc [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn output_shows_on_vv() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ use std::io::prelude::*;
+
+ fn main() {
+ std::io::stderr().write_all(b"stderr\n").unwrap();
+ std::io::stdout().write_all(b"stdout\n").unwrap();
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build -vv")
+ .with_stdout("[foo 0.5.0] stdout")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.5.0 ([..])
+[RUNNING] `[..] rustc [..]`
+[RUNNING] `[..]`
+[foo 0.5.0] stderr
+[RUNNING] `[..] rustc [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn links_with_dots() {
+ let target = rustc_host();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ build = "build.rs"
+ links = "a.b"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rustc-link-search=bar")
+ }
+ "#,
+ )
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [target.{}.'a.b']
+ rustc-link-search = ["foo"]
+ "#,
+ target
+ ),
+ )
+ .build();
+
+ p.cargo("build -v")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name foo [..] [..] -L foo[..]`")
+ .run();
+}
+
+#[cargo_test]
+fn rustc_and_rustdoc_set_correctly() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ use std::env;
+
+ fn main() {
+ assert_eq!(env::var("RUSTC").unwrap(), "rustc");
+ assert_eq!(env::var("RUSTDOC").unwrap(), "rustdoc");
+ }
+ "#,
+ )
+ .build();
+ p.cargo("bench").run();
+}
+
+#[cargo_test]
+fn cfg_env_vars_available() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ use std::env;
+
+ fn main() {
+ let fam = env::var("CARGO_CFG_TARGET_FAMILY").unwrap();
+ if cfg!(unix) {
+ assert_eq!(fam, "unix");
+ } else {
+ assert_eq!(fam, "windows");
+ }
+ }
+ "#,
+ )
+ .build();
+ p.cargo("bench").run();
+}
+
+#[cargo_test]
+fn switch_features_rerun() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+
+ [features]
+ foo = []
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ println!(include_str!(concat!(env!("OUT_DIR"), "/output")));
+ }
+ "#,
+ )
+ .file(
+ "build.rs",
+ r#"
+ use std::env;
+ use std::fs;
+ use std::path::Path;
+
+ fn main() {
+ let out_dir = env::var_os("OUT_DIR").unwrap();
+ let output = Path::new(&out_dir).join("output");
+
+ if env::var_os("CARGO_FEATURE_FOO").is_some() {
+ fs::write(output, "foo").unwrap();
+ } else {
+ fs::write(output, "bar").unwrap();
+ }
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build -v --features=foo").run();
+ p.rename_run("foo", "with_foo").with_stdout("foo\n").run();
+ p.cargo("build -v").run();
+ p.rename_run("foo", "without_foo")
+ .with_stdout("bar\n")
+ .run();
+ p.cargo("build -v --features=foo").run();
+ p.rename_run("foo", "with_foo2").with_stdout("foo\n").run();
+}
+
+#[cargo_test]
+fn assume_build_script_when_build_rs_present() {
+ let p = project()
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ if ! cfg!(foo) {
+ panic!("the build script was not run");
+ }
+ }
+ "#,
+ )
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rustc-cfg=foo");
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("run -v").run();
+}
+
+#[cargo_test]
+fn if_build_set_to_false_dont_treat_build_rs_as_build_script() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ build = false
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ if cfg!(foo) {
+ panic!("the build script was run");
+ }
+ }
+ "#,
+ )
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rustc-cfg=foo");
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("run -v").run();
+}
+
+#[cargo_test]
+fn deterministic_rustc_dependency_flags() {
+ // This bug is non-deterministic hence the large number of dependencies
+ // in the hopes it will have a much higher chance of triggering it.
+
+ Package::new("dep1", "0.1.0")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "dep1"
+ version = "0.1.0"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rustc-flags=-L native=test1");
+ }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .publish();
+ Package::new("dep2", "0.1.0")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "dep2"
+ version = "0.1.0"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rustc-flags=-L native=test2");
+ }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .publish();
+ Package::new("dep3", "0.1.0")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "dep3"
+ version = "0.1.0"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rustc-flags=-L native=test3");
+ }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .publish();
+ Package::new("dep4", "0.1.0")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "dep4"
+ version = "0.1.0"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rustc-flags=-L native=test4");
+ }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ dep1 = "*"
+ dep2 = "*"
+ dep3 = "*"
+ dep4 = "*"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build -v")
+ .with_stderr_contains(
+ "\
+[RUNNING] `rustc --crate-name foo [..] -L native=test1 -L native=test2 \
+-L native=test3 -L native=test4`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn links_duplicates_with_cycle() {
+ // this tests that the links_duplicates are caught at resolver time
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ links = "a"
+ build = "build.rs"
+
+ [dependencies.a]
+ path = "a"
+
+ [dev-dependencies]
+ b = { path = "b" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("build.rs", "")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.5.0"
+ authors = []
+ links = "a"
+ build = "build.rs"
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .file("a/build.rs", "")
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ foo = { path = ".." }
+ "#,
+ )
+ .file("b/src/lib.rs", "")
+ .build();
+
+ p.cargo("build").with_status(101)
+ .with_stderr("\
+error: failed to select a version for `a`.
+ ... required by package `foo v0.5.0 ([..])`
+versions that meet the requirements `*` are: 0.5.0
+
+the package `a` links to the native library `a`, but it conflicts with a previous package which links to `a` as well:
+package `foo v0.5.0 ([..])`
+Only one package in the dependency graph may specify the same links value. This helps ensure that only one copy of a native library is linked in the final binary. Try to adjust your dependencies so that only one package uses the links ='a' value. For more information, see https://doc.rust-lang.org/cargo/reference/resolver.html#links.
+
+failed to select a version for `a` which could resolve this conflict
+").run();
+}
+
+#[cargo_test]
+fn rename_with_link_search_path() {
+ _rename_with_link_search_path(false);
+}
+
+#[cargo_test]
+#[cfg_attr(
+ target_os = "macos",
+ ignore = "don't have a cdylib cross target on macos"
+)]
+fn rename_with_link_search_path_cross() {
+ if cross_compile::disabled() {
+ return;
+ }
+
+ _rename_with_link_search_path(true);
+}
+
+fn _rename_with_link_search_path(cross: bool) {
+ let target_arg = if cross {
+ format!(" --target={}", cross_compile::alternate())
+ } else {
+ "".to_string()
+ };
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+
+ [lib]
+ crate-type = ["cdylib"]
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "#[no_mangle] pub extern fn cargo_test_foo() {}",
+ );
+ let p = p.build();
+
+ p.cargo(&format!("build{}", target_arg)).run();
+
+ let p2 = project()
+ .at("bar")
+ .file("Cargo.toml", &basic_manifest("bar", "0.5.0"))
+ .file(
+ "build.rs",
+ r#"
+ use std::env;
+ use std::fs;
+ use std::path::PathBuf;
+
+ fn main() {
+ // Move the `libfoo.so` from the root of our project into the
+ // build directory. This way Cargo should automatically manage
+ // `LD_LIBRARY_PATH` and such.
+ let root = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
+ let file = format!("{}foo{}", env::consts::DLL_PREFIX, env::consts::DLL_SUFFIX);
+ let src = root.join(&file);
+
+ let dst_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
+ let dst = dst_dir.join(&file);
+
+ fs::copy(&src, &dst).unwrap();
+ // handle windows, like below
+ drop(fs::copy(root.join("foo.dll.lib"), dst_dir.join("foo.dll.lib")));
+
+ println!("cargo:rerun-if-changed=build.rs");
+ if cfg!(target_env = "msvc") {
+ println!("cargo:rustc-link-lib=foo.dll");
+ } else {
+ println!("cargo:rustc-link-lib=foo");
+ }
+ println!("cargo:rustc-link-search=all={}",
+ dst.parent().unwrap().display());
+ }
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ extern {
+ #[link_name = "cargo_test_foo"]
+ fn foo();
+ }
+
+ fn main() {
+ unsafe { foo(); }
+ }
+ "#,
+ );
+ let p2 = p2.build();
+
+ // Move the output `libfoo.so` into the directory of `p2`, and then delete
+ // the `p` project. On macOS, the `libfoo.dylib` artifact references the
+ // original path in `p` so we want to make sure that it can't find it (hence
+ // the deletion).
+ let root = if cross {
+ p.root()
+ .join("target")
+ .join(cross_compile::alternate())
+ .join("debug")
+ .join("deps")
+ } else {
+ p.root().join("target").join("debug").join("deps")
+ };
+ let file = format!("{}foo{}", env::consts::DLL_PREFIX, env::consts::DLL_SUFFIX);
+ let src = root.join(&file);
+
+ let dst = p2.root().join(&file);
+
+ fs::copy(&src, &dst).unwrap();
+ // copy the import library for windows, if it exists
+ drop(fs::copy(
+ &root.join("foo.dll.lib"),
+ p2.root().join("foo.dll.lib"),
+ ));
+ remove_dir_all(p.root()).unwrap();
+
+ // Everything should work the first time
+ p2.cargo(&format!("run{}", target_arg)).run();
+
+ // Now rename the root directory and rerun `cargo run`. Not only should we
+ // not build anything but we also shouldn't crash.
+ let mut new = p2.root();
+ new.pop();
+ new.push("bar2");
+
+ // For whatever reason on Windows right after we execute a binary it's very
+ // unlikely that we're able to successfully delete or rename that binary.
+ // It's not really clear why this is the case or if it's a bug in Cargo
+ // holding a handle open too long. In an effort to reduce the flakiness of
+ // this test though we throw this in a loop
+ //
+ // For some more information see #5481 and rust-lang/rust#48775
+ let mut i = 0;
+ loop {
+ let error = match fs::rename(p2.root(), &new) {
+ Ok(()) => break,
+ Err(e) => e,
+ };
+ i += 1;
+ if !cfg!(windows) || error.kind() != io::ErrorKind::PermissionDenied || i > 10 {
+ panic!("failed to rename: {}", error);
+ }
+ println!("assuming {} is spurious, waiting to try again", error);
+ thread::sleep(slow_cpu_multiplier(100));
+ }
+
+ p2.cargo(&format!("run{}", target_arg))
+ .cwd(&new)
+ .with_stderr(
+ "\
+[FINISHED] [..]
+[RUNNING] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn optional_build_script_dep() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ bar = { path = "bar", optional = true }
+
+ [build-dependencies]
+ bar = { path = "bar", optional = true }
+ "#,
+ )
+ .file(
+ "build.rs",
+ r#"
+ #[cfg(feature = "bar")]
+ extern crate bar;
+
+ fn main() {
+ #[cfg(feature = "bar")] {
+ println!("cargo:rustc-env=FOO={}", bar::bar());
+ return
+ }
+ println!("cargo:rustc-env=FOO=0");
+ }
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ #[cfg(feature = "bar")]
+ extern crate bar;
+
+ fn main() {
+ println!("{}", env!("FOO"));
+ }
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.5.0"))
+ .file("bar/src/lib.rs", "pub fn bar() -> u32 { 1 }");
+ let p = p.build();
+
+ p.cargo("run").with_stdout("0\n").run();
+ p.cargo("run --features bar").with_stdout("1\n").run();
+}
+
+#[cargo_test]
+fn optional_build_dep_and_required_normal_dep() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ bar = { path = "./bar", optional = true }
+
+ [build-dependencies]
+ bar = { path = "./bar" }
+ "#,
+ )
+ .file("build.rs", "extern crate bar; fn main() { bar::bar(); }")
+ .file(
+ "src/main.rs",
+ r#"
+ #[cfg(feature = "bar")]
+ extern crate bar;
+
+ fn main() {
+ #[cfg(feature = "bar")] {
+ println!("{}", bar::bar());
+ }
+ #[cfg(not(feature = "bar"))] {
+ println!("0");
+ }
+ }
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.5.0"))
+ .file("bar/src/lib.rs", "pub fn bar() -> u32 { 1 }");
+ let p = p.build();
+
+ p.cargo("run")
+ .with_stdout("0")
+ .with_stderr(
+ "\
+[COMPILING] bar v0.5.0 ([..])
+[COMPILING] foo v0.1.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `[..]foo[EXE]`",
+ )
+ .run();
+
+ p.cargo("run --all-features")
+ .with_stdout("1")
+ .with_stderr(
+ "\
+[COMPILING] bar v0.5.0 ([..])
+[COMPILING] foo v0.1.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `[..]foo[EXE]`",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn using_rerun_if_changed_does_not_rebuild() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ "#,
+ )
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rerun-if-changed=build.rs");
+ }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+ p.cargo("build").with_stderr("[FINISHED] [..]").run();
+}
+
+#[cargo_test]
+fn links_interrupted_can_restart() {
+ // Test for a `links` dependent build script getting canceled and then
+ // restarted. Steps:
+ // 1. Build to establish fingerprints.
+ // 2. Change something (an env var in this case) that triggers the
+ // dependent build script to run again. Kill the top-level build script
+ // while it is running (such as hitting Ctrl-C).
+ // 3. Run the build again, it should re-run the build script.
+ let bar = project()
+ .at("bar")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.5.0"
+ authors = []
+ links = "foo"
+ build = "build.rs"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rerun-if-env-changed=SOMEVAR");
+ }
+ "#,
+ )
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ build = "build.rs"
+
+ [dependencies.bar]
+ path = '{}'
+ "#,
+ bar.root().display()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ use std::env;
+ fn main() {
+ println!("cargo:rebuild-if-changed=build.rs");
+ if std::path::Path::new("abort").exists() {
+ panic!("Crash!");
+ }
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build").run();
+ // Simulate the user hitting Ctrl-C during a build.
+ p.change_file("abort", "");
+ // Set SOMEVAR to trigger a rebuild.
+ p.cargo("build")
+ .env("SOMEVAR", "1")
+ .with_stderr_contains("[..]Crash![..]")
+ .with_status(101)
+ .run();
+ fs::remove_file(p.root().join("abort")).unwrap();
+ // Try again without aborting the script.
+ // ***This is currently broken, the script does not re-run.
+ p.cargo("build -v")
+ .env("SOMEVAR", "1")
+ .with_stderr_contains("[RUNNING] [..]/foo-[..]/build-script-build[..]")
+ .run();
+}
+
+#[cargo_test]
+fn dev_dep_with_links() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ links = "x"
+
+ [dev-dependencies]
+ bar = { path = "./bar" }
+ "#,
+ )
+ .file("build.rs", "fn main() {}")
+ .file("src/lib.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+ links = "y"
+
+ [dependencies]
+ foo = { path = ".." }
+ "#,
+ )
+ .file("bar/build.rs", "fn main() {}")
+ .file("bar/src/lib.rs", "")
+ .build();
+ p.cargo("check --tests").run()
+}
+
+#[cargo_test]
+fn rerun_if_directory() {
+ if !symlink_supported() {
+ return;
+ }
+
+ // rerun-if-changed of a directory should rerun if any file in the directory changes.
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rerun-if-changed=somedir");
+ }
+ "#,
+ )
+ .build();
+
+ let dirty = |dirty_line: &str, compile_build_script: bool| {
+ let mut dirty_line = dirty_line.to_string();
+
+ if !dirty_line.is_empty() {
+ dirty_line.push('\n');
+ }
+
+ let compile_build_script_line = if compile_build_script {
+ "[RUNNING] `rustc --crate-name build_script_build [..]\n"
+ } else {
+ ""
+ };
+
+ p.cargo("check -v")
+ .with_stderr(format!(
+ "\
+{dirty_line}\
+[COMPILING] foo [..]
+{compile_build_script_line}\
+[RUNNING] `[..]build-script-build[..]`
+[RUNNING] `rustc --crate-name foo [..]
+[FINISHED] [..]",
+ ))
+ .run();
+ };
+
+ let fresh = || {
+ p.cargo("check").with_stderr("[FINISHED] [..]").run();
+ };
+
+ // Start with a missing directory.
+ dirty("", true);
+ // Because the directory doesn't exist, it will trigger a rebuild every time.
+ // https://github.com/rust-lang/cargo/issues/6003
+ dirty(
+ "[DIRTY] foo v0.1.0 ([..]): the file `somedir` is missing",
+ false,
+ );
+
+ if is_coarse_mtime() {
+ sleep_ms(1000);
+ }
+
+ // Empty directory.
+ fs::create_dir(p.root().join("somedir")).unwrap();
+ dirty(
+ "[DIRTY] foo v0.1.0 ([..]): the file `somedir` has changed ([..])",
+ false,
+ );
+ fresh();
+
+ if is_coarse_mtime() {
+ sleep_ms(1000);
+ }
+
+ // Add a file.
+ p.change_file("somedir/foo", "");
+ p.change_file("somedir/bar", "");
+ dirty(
+ "[DIRTY] foo v0.1.0 ([..]): the file `somedir` has changed ([..])",
+ false,
+ );
+ fresh();
+
+ if is_coarse_mtime() {
+ sleep_ms(1000);
+ }
+
+ // Add a symlink.
+ p.symlink("foo", "somedir/link");
+ dirty(
+ "[DIRTY] foo v0.1.0 ([..]): the file `somedir` has changed ([..])",
+ false,
+ );
+ fresh();
+
+ if is_coarse_mtime() {
+ sleep_ms(1000);
+ }
+
+ // Move the symlink.
+ fs::remove_file(p.root().join("somedir/link")).unwrap();
+ p.symlink("bar", "somedir/link");
+ dirty(
+ "[DIRTY] foo v0.1.0 ([..]): the file `somedir` has changed ([..])",
+ false,
+ );
+ fresh();
+
+ if is_coarse_mtime() {
+ sleep_ms(1000);
+ }
+
+ // Remove a file.
+ fs::remove_file(p.root().join("somedir/foo")).unwrap();
+ dirty(
+ "[DIRTY] foo v0.1.0 ([..]): the file `somedir` has changed ([..])",
+ false,
+ );
+ fresh();
+}
+
+#[cargo_test]
+fn rerun_if_published_directory() {
+ // build script of a dependency contains a `rerun-if-changed` pointing to a directory
+ Package::new("mylib-sys", "1.0.0")
+ .file("mylib/balrog.c", "")
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ // Changing to mylib/balrog.c will not trigger a rebuild
+ println!("cargo:rerun-if-changed=mylib");
+ }
+ "#,
+ )
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies]
+ mylib-sys = "1.0.0"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check").run();
+
+ // Delete regitry src to make directories being recreated with the latest timestamp.
+ cargo_home().join("registry/src").rm_rf();
+
+ p.cargo("check --verbose")
+ .with_stderr(
+ "\
+[FRESH] mylib-sys v1.0.0
+[FRESH] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ // Upgrade of a package should still trigger a rebuild
+ Package::new("mylib-sys", "1.0.1")
+ .file("mylib/balrog.c", "")
+ .file("mylib/balrog.h", "")
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rerun-if-changed=mylib");
+ }
+ "#,
+ )
+ .publish();
+ p.cargo("update").run();
+ p.cargo("fetch").run();
+
+ p.cargo("check -v")
+ .with_stderr(format!(
+ "\
+[COMPILING] mylib-sys [..]
+[RUNNING] `rustc --crate-name build_script_build [..]
+[RUNNING] `[..]build-script-build[..]`
+[RUNNING] `rustc --crate-name mylib_sys [..]
+[CHECKING] foo [..]
+[RUNNING] `rustc --crate-name foo [..]
+[FINISHED] [..]",
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn test_with_dep_metadata() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { path = 'bar' }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ assert_eq!(std::env::var("DEP_BAR_FOO").unwrap(), "bar");
+ }
+ "#,
+ )
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ links = 'bar'
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .file(
+ "bar/build.rs",
+ r#"
+ fn main() {
+ println!("cargo:foo=bar");
+ }
+ "#,
+ )
+ .build();
+ p.cargo("test --lib").run();
+}
+
+#[cargo_test]
+fn duplicate_script_with_extra_env() {
+ // Test where a build script is run twice, that emits different rustc-env
+ // and rustc-cfg values. In this case, one is run for host, the other for
+ // target.
+ if !cross_compile::can_run_on_host() {
+ return;
+ }
+
+ let target = cross_compile::alternate();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["foo", "pm"]
+ "#,
+ )
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ pm = { path = "../pm" }
+ "#,
+ )
+ .file(
+ "foo/src/lib.rs",
+ &r#"
+ //! ```rust
+ //! #[cfg(not(mycfg="{target}"))]
+ //! compile_error!{"expected mycfg set"}
+ //! assert_eq!(env!("CRATE_TARGET"), "{target}");
+ //! assert_eq!(std::env::var("CRATE_TARGET").unwrap(), "{target}");
+ //! ```
+
+ #[test]
+ fn check_target() {
+ #[cfg(not(mycfg="{target}"))]
+ compile_error!{"expected mycfg set"}
+ // Compile-time assertion.
+ assert_eq!(env!("CRATE_TARGET"), "{target}");
+ // Run-time assertion.
+ assert_eq!(std::env::var("CRATE_TARGET").unwrap(), "{target}");
+ }
+ "#
+ .replace("{target}", target),
+ )
+ .file(
+ "foo/build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rustc-env=CRATE_TARGET={}", std::env::var("TARGET").unwrap());
+ println!("cargo:rustc-cfg=mycfg=\"{}\"", std::env::var("TARGET").unwrap());
+ }
+ "#,
+ )
+ .file(
+ "pm/Cargo.toml",
+ r#"
+ [package]
+ name = "pm"
+ version = "0.1.0"
+
+ [lib]
+ proc-macro = true
+ # This is just here to speed things up.
+ doctest = false
+
+ [dev-dependencies]
+ foo = { path = "../foo" }
+ "#,
+ )
+ .file("pm/src/lib.rs", "")
+ .build();
+
+ p.cargo("test --workspace --target")
+ .arg(&target)
+ .with_stdout_contains("test check_target ... ok")
+ .run();
+
+ if cargo_test_support::is_nightly() {
+ p.cargo("test --workspace -Z doctest-xcompile --doc --target")
+ .arg(&target)
+ .masquerade_as_nightly_cargo(&["doctest-xcompile"])
+ .with_stdout_contains("test src/lib.rs - (line 2) ... ok")
+ .run();
+ }
+}
+
+#[cargo_test]
+fn wrong_output() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:example");
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "\
+[COMPILING] foo [..]
+error: invalid output in build script of `foo v0.0.1 ([ROOT]/foo)`: `cargo:example`
+Expected a line with `cargo:key=value` with an `=` character, but none was found.
+See https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script \
+for more information about build script outputs.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn custom_build_closes_stdin() {
+ // Ensure stdin is closed to prevent deadlock.
+ // See https://github.com/rust-lang/cargo/issues/11196
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ build = "build.rs"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "build.rs",
+ r#"fn main() {
+ let mut line = String::new();
+ std::io::stdin().read_line(&mut line).unwrap();
+ }"#,
+ )
+ .build();
+ p.cargo("build").run();
+}
diff --git a/src/tools/cargo/tests/testsuite/build_script_env.rs b/src/tools/cargo/tests/testsuite/build_script_env.rs
new file mode 100644
index 000000000..bc87b7120
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/build_script_env.rs
@@ -0,0 +1,303 @@
+//! Tests for build.rs rerun-if-env-changed and rustc-env
+
+use cargo_test_support::basic_manifest;
+use cargo_test_support::project;
+use cargo_test_support::sleep_ms;
+
+#[cargo_test]
+fn rerun_if_env_changes() {
+ let p = project()
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rerun-if-env-changed=FOO");
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] [..]
+",
+ )
+ .run();
+ p.cargo("check")
+ .env("FOO", "bar")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] [..]
+",
+ )
+ .run();
+ p.cargo("check")
+ .env("FOO", "baz")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] [..]
+",
+ )
+ .run();
+ p.cargo("check")
+ .env("FOO", "baz")
+ .with_stderr("[FINISHED] [..]")
+ .run();
+ p.cargo("check")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rerun_if_env_or_file_changes() {
+ let p = project()
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rerun-if-env-changed=FOO");
+ println!("cargo:rerun-if-changed=foo");
+ }
+ "#,
+ )
+ .file("foo", "")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] [..]
+",
+ )
+ .run();
+ p.cargo("check")
+ .env("FOO", "bar")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] [..]
+",
+ )
+ .run();
+ p.cargo("check")
+ .env("FOO", "bar")
+ .with_stderr("[FINISHED] [..]")
+ .run();
+ sleep_ms(1000);
+ p.change_file("foo", "");
+ p.cargo("check")
+ .env("FOO", "bar")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rustc_bootstrap() {
+ let build_rs = r#"
+ fn main() {
+ println!("cargo:rustc-env=RUSTC_BOOTSTRAP=1");
+ }
+ "#;
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("has-dashes", "0.0.1"))
+ .file("src/lib.rs", "#![feature(rustc_attrs)]")
+ .file("build.rs", build_rs)
+ .build();
+ // RUSTC_BOOTSTRAP unset on stable should error
+ p.cargo("check")
+ .with_stderr_contains("error: Cannot set `RUSTC_BOOTSTRAP=1` [..]")
+ .with_stderr_contains(
+ "help: [..] set the environment variable `RUSTC_BOOTSTRAP=has_dashes` [..]",
+ )
+ .with_status(101)
+ .run();
+ // nightly should warn whether or not RUSTC_BOOTSTRAP is set
+ p.cargo("check")
+ .masquerade_as_nightly_cargo(&["RUSTC_BOOTSTRAP"])
+ // NOTE: uses RUSTC_BOOTSTRAP so it will be propagated to rustc
+ // (this matters when tests are being run with a beta or stable cargo)
+ .env("RUSTC_BOOTSTRAP", "1")
+ .with_stderr_contains("warning: Cannot set `RUSTC_BOOTSTRAP=1` [..]")
+ .run();
+ // RUSTC_BOOTSTRAP set to the name of the library should warn
+ p.cargo("check")
+ .env("RUSTC_BOOTSTRAP", "has_dashes")
+ .with_stderr_contains("warning: Cannot set `RUSTC_BOOTSTRAP=1` [..]")
+ .run();
+ // RUSTC_BOOTSTRAP set to some random value should error
+ p.cargo("check")
+ .env("RUSTC_BOOTSTRAP", "bar")
+ .with_stderr_contains("error: Cannot set `RUSTC_BOOTSTRAP=1` [..]")
+ .with_stderr_contains(
+ "help: [..] set the environment variable `RUSTC_BOOTSTRAP=has_dashes` [..]",
+ )
+ .with_status(101)
+ .run();
+
+ // Tests for binaries instead of libraries
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo", "0.0.1"))
+ .file("src/main.rs", "#![feature(rustc_attrs)] fn main() {}")
+ .file("build.rs", build_rs)
+ .build();
+ // nightly should warn when there's no library whether or not RUSTC_BOOTSTRAP is set
+ p.cargo("check")
+ .masquerade_as_nightly_cargo(&["RUSTC_BOOTSTRAP"])
+ // NOTE: uses RUSTC_BOOTSTRAP so it will be propagated to rustc
+ // (this matters when tests are being run with a beta or stable cargo)
+ .env("RUSTC_BOOTSTRAP", "1")
+ .with_stderr_contains("warning: Cannot set `RUSTC_BOOTSTRAP=1` [..]")
+ .run();
+ // RUSTC_BOOTSTRAP conditionally set when there's no library should error (regardless of the value)
+ p.cargo("check")
+ .env("RUSTC_BOOTSTRAP", "foo")
+ .with_stderr_contains("error: Cannot set `RUSTC_BOOTSTRAP=1` [..]")
+ .with_stderr_contains("help: [..] set the environment variable `RUSTC_BOOTSTRAP=1` [..]")
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+#[cfg(target_arch = "x86_64")]
+fn build_script_sees_cfg_target_feature() {
+ let build_rs = r#"
+ fn main() {
+ let cfg = std::env::var("CARGO_CFG_TARGET_FEATURE").unwrap();
+ eprintln!("CARGO_CFG_TARGET_FEATURE={cfg}");
+ }
+ "#;
+
+ let configs = [
+ r#"
+ [build]
+ rustflags = ["-Ctarget-feature=+sse4.1,+sse4.2"]
+ "#,
+ r#"
+ [target.'cfg(target_arch = "x86_64")']
+ rustflags = ["-Ctarget-feature=+sse4.1,+sse4.2"]
+ "#,
+ ];
+
+ for config in configs {
+ let p = project()
+ .file(".cargo/config.toml", config)
+ .file("src/lib.rs", r#""#)
+ .file("build.rs", build_rs)
+ .build();
+
+ p.cargo("check -vv")
+ .with_stderr_contains("[foo 0.0.1] CARGO_CFG_TARGET_FEATURE=[..]sse4.2[..]")
+ .with_stderr_contains("[..]-Ctarget-feature=[..]+sse4.2[..]")
+ .run();
+ }
+}
+
+/// In this test, the cfg is self-contradictory. There's no *right* answer as to
+/// what the value of `RUSTFLAGS` should be in this case. We chose to give a
+/// warning. However, no matter what we do, it's important that build scripts
+/// and rustc see a consistent picture
+#[cargo_test]
+fn cfg_paradox() {
+ let build_rs = r#"
+ fn main() {
+ let cfg = std::env::var("CARGO_CFG_BERTRAND").is_ok();
+ eprintln!("cfg!(bertrand)={cfg}");
+ }
+ "#;
+
+ let config = r#"
+ [target.'cfg(not(bertrand))']
+ rustflags = ["--cfg=bertrand"]
+ "#;
+
+ let p = project()
+ .file(".cargo/config.toml", config)
+ .file("src/lib.rs", r#""#)
+ .file("build.rs", build_rs)
+ .build();
+
+ p.cargo("check -vv")
+ .with_stderr_contains("[WARNING] non-trivial mutual dependency between target-specific configuration and RUSTFLAGS")
+ .with_stderr_contains("[foo 0.0.1] cfg!(bertrand)=true")
+ .with_stderr_contains("[..]--cfg=bertrand[..]")
+ .run();
+}
+
+/// This test checks how Cargo handles rustc cfgs which are defined both with
+/// and without a value. The expected behavior is that the environment variable
+/// is going to contain all the values.
+///
+/// For example, this configuration:
+/// ```
+/// target_has_atomic
+/// target_has_atomic="16"
+/// target_has_atomic="32"
+/// target_has_atomic="64"
+/// target_has_atomic="8"
+/// target_has_atomic="ptr"
+/// ```
+///
+/// Should result in the following environment variable:
+///
+/// ```
+/// CARGO_CFG_TARGET_HAS_ATOMIC=16,32,64,8,ptr
+/// ```
+///
+/// On the other hand, configuration symbols without any value should result in
+/// an empty string.
+///
+/// For example, this configuration:
+///
+/// ```
+/// target_thread_local
+/// ```
+///
+/// Should result in the following environment variable:
+///
+/// ```
+/// CARGO_CFG_TARGET_THREAD_LOCAL=
+/// ```
+#[cargo_test(nightly, reason = "affected rustc cfg is unstable")]
+#[cfg(target_arch = "x86_64")]
+fn rustc_cfg_with_and_without_value() {
+ let build_rs = r#"
+ fn main() {
+ let cfg = std::env::var("CARGO_CFG_TARGET_HAS_ATOMIC");
+ eprintln!("CARGO_CFG_TARGET_HAS_ATOMIC={cfg:?}");
+ let cfg = std::env::var("CARGO_CFG_WINDOWS");
+ eprintln!("CARGO_CFG_WINDOWS={cfg:?}");
+ let cfg = std::env::var("CARGO_CFG_UNIX");
+ eprintln!("CARGO_CFG_UNIX={cfg:?}");
+ }
+ "#;
+ let p = project()
+ .file("src/lib.rs", r#""#)
+ .file("build.rs", build_rs)
+ .build();
+
+ let mut check = p.cargo("check -vv");
+ #[cfg(target_has_atomic = "64")]
+ check.with_stderr_contains("[foo 0.0.1] CARGO_CFG_TARGET_HAS_ATOMIC=Ok(\"[..]64[..]\")");
+ #[cfg(windows)]
+ check.with_stderr_contains("[foo 0.0.1] CARGO_CFG_WINDOWS=Ok(\"\")");
+ #[cfg(unix)]
+ check.with_stderr_contains("[foo 0.0.1] CARGO_CFG_UNIX=Ok(\"\")");
+ check.run();
+}
diff --git a/src/tools/cargo/tests/testsuite/build_script_extra_link_arg.rs b/src/tools/cargo/tests/testsuite/build_script_extra_link_arg.rs
new file mode 100644
index 000000000..ade262fec
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/build_script_extra_link_arg.rs
@@ -0,0 +1,376 @@
+//! Tests for additional link arguments.
+
+// NOTE: Many of these tests use `without_status()` when passing bogus flags
+// because MSVC link.exe just gives a warning on unknown flags (how helpful!),
+// and other linkers will return an error.
+
+use cargo_test_support::registry::Package;
+use cargo_test_support::{basic_bin_manifest, basic_lib_manifest, basic_manifest, project};
+
+#[cargo_test]
+fn build_script_extra_link_arg_bin() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rustc-link-arg-bins=--this-is-a-bogus-flag");
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build -v")
+ .without_status()
+ .with_stderr_contains(
+ "[RUNNING] `rustc --crate-name foo [..]-C link-arg=--this-is-a-bogus-flag[..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_script_extra_link_arg_bin_single() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "foobar"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [[bin]]
+ name = "foo"
+ [[bin]]
+ name = "bar"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rustc-link-arg-bins=--bogus-flag-all");
+ println!("cargo:rustc-link-arg-bin=foo=--bogus-flag-foo");
+ println!("cargo:rustc-link-arg-bin=bar=--bogus-flag-bar");
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build -v")
+ .without_status()
+ .with_stderr_contains(
+ "[RUNNING] `rustc --crate-name foo [..]-C link-arg=--bogus-flag-all -C link-arg=--bogus-flag-foo[..]",
+ )
+ .with_stderr_contains(
+ "[RUNNING] `rustc --crate-name bar [..]-C link-arg=--bogus-flag-all -C link-arg=--bogus-flag-bar[..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_script_extra_link_arg() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rustc-link-arg=--this-is-a-bogus-flag");
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build -v")
+ .without_status()
+ .with_stderr_contains(
+ "[RUNNING] `rustc --crate-name foo [..]-C link-arg=--this-is-a-bogus-flag[..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn link_arg_missing_target() {
+ // Errors when a given target doesn't exist.
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"fn main() { println!("cargo:rustc-link-arg-cdylib=--bogus"); }"#,
+ )
+ .build();
+
+ // TODO: Uncomment this if cdylib restriction is re-added (see
+ // cdylib_link_arg_transitive below).
+ // p.cargo("check")
+ // .with_status(101)
+ // .with_stderr("\
+ // [COMPILING] foo [..]
+ // error: invalid instruction `cargo:rustc-link-arg-cdylib` from build script of `foo v0.0.1 ([ROOT]/foo)`
+ // The package foo v0.0.1 ([ROOT]/foo) does not have a cdylib target.
+ // ")
+ // .run();
+
+ p.change_file(
+ "build.rs",
+ r#"fn main() { println!("cargo:rustc-link-arg-bins=--bogus"); }"#,
+ );
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr("\
+[COMPILING] foo [..]
+error: invalid instruction `cargo:rustc-link-arg-bins` from build script of `foo v0.0.1 ([ROOT]/foo)`
+The package foo v0.0.1 ([ROOT]/foo) does not have a bin target.
+")
+ .run();
+
+ p.change_file(
+ "build.rs",
+ r#"fn main() { println!("cargo:rustc-link-arg-bin=abc=--bogus"); }"#,
+ );
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[COMPILING] foo [..]
+error: invalid instruction `cargo:rustc-link-arg-bin` from build script of `foo v0.0.1 ([ROOT]/foo)`
+The package foo v0.0.1 ([ROOT]/foo) does not have a bin target with the name `abc`.
+",
+ )
+ .run();
+
+ p.change_file(
+ "build.rs",
+ r#"fn main() { println!("cargo:rustc-link-arg-bin=abc"); }"#,
+ );
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[COMPILING] foo [..]
+error: invalid instruction `cargo:rustc-link-arg-bin=abc` from build script of `foo v0.0.1 ([ROOT]/foo)`
+The instruction should have the form cargo:rustc-link-arg-bin=BIN=ARG
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cdylib_link_arg_transitive() {
+ // There was an unintended regression in 1.50 where rustc-link-arg-cdylib
+ // arguments from dependencies were being applied in the parent package.
+ // Previously it was silently ignored.
+ // See https://github.com/rust-lang/cargo/issues/9562
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [lib]
+ crate-type = ["cdylib"]
+
+ [dependencies]
+ bar = {path="bar"}
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "1.0.0"))
+ .file("bar/src/lib.rs", "")
+ .file(
+ "bar/build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rustc-link-arg-cdylib=--bogus");
+ }
+ "#,
+ )
+ .build();
+ p.cargo("build -v")
+ .without_status()
+ .with_stderr_contains(
+ "\
+[COMPILING] bar v1.0.0 [..]
+[RUNNING] `rustc --crate-name build_script_build bar/build.rs [..]
+[RUNNING] `[..]build-script-build[..]
+warning: cargo:rustc-link-arg-cdylib was specified in the build script of bar v1.0.0 \
+([ROOT]/foo/bar), but that package does not contain a cdylib target
+
+Allowing this was an unintended change in the 1.50 release, and may become an error in \
+the future. For more information, see <https://github.com/rust-lang/cargo/issues/9562>.
+[RUNNING] `rustc --crate-name bar bar/src/lib.rs [..]
+[COMPILING] foo v0.1.0 [..]
+[RUNNING] `rustc --crate-name foo src/lib.rs [..]-C link-arg=--bogus[..]`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn link_arg_transitive_not_allowed() {
+ // Verify that transitive dependencies don't pass link args.
+ //
+ // Note that rustc-link-arg doesn't have any errors or warnings when it is
+ // unused. Perhaps that could be more aggressive, but it is difficult
+ // since it could be used for test binaries.
+ Package::new("bar", "1.0.0")
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rustc-link-arg=--bogus");
+ }
+ "#,
+ )
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [lib]
+ crate-type = ["cdylib"]
+
+ [dependencies]
+ bar = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[DOWNLOADING] [..]
+[DOWNLOADED] [..]
+[COMPILING] bar v1.0.0
+[RUNNING] `rustc --crate-name build_script_build [..]
+[RUNNING] `[..]/build-script-build[..]
+[RUNNING] `rustc --crate-name bar [..]
+[COMPILING] foo v0.1.0 [..]
+[RUNNING] `rustc --crate-name foo src/lib.rs [..]
+[FINISHED] dev [..]
+",
+ )
+ .with_stderr_does_not_contain("--bogus")
+ .run();
+}
+
+#[cargo_test]
+fn link_arg_with_doctest() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ //! ```
+ //! let x = 5;
+ //! assert_eq!(x, 5);
+ //! ```
+ "#,
+ )
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rustc-link-arg=--this-is-a-bogus-flag");
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("test --doc -v")
+ .without_status()
+ .with_stderr_contains(
+ "[RUNNING] `rustdoc [..]--crate-name foo [..]-C link-arg=--this-is-a-bogus-flag[..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_script_extra_link_arg_tests() {
+ let p = project()
+ .file("Cargo.toml", &basic_lib_manifest("foo"))
+ .file("src/lib.rs", "")
+ .file("tests/test_foo.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rustc-link-arg-tests=--this-is-a-bogus-flag");
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("test -v")
+ .without_status()
+ .with_stderr_contains(
+ "[RUNNING] `rustc --crate-name test_foo [..]-C link-arg=--this-is-a-bogus-flag[..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_script_extra_link_arg_benches() {
+ let p = project()
+ .file("Cargo.toml", &basic_lib_manifest("foo"))
+ .file("src/lib.rs", "")
+ .file("benches/bench_foo.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rustc-link-arg-benches=--this-is-a-bogus-flag");
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("bench -v")
+ .without_status()
+ .with_stderr_contains(
+ "[RUNNING] `rustc --crate-name bench_foo [..]-C link-arg=--this-is-a-bogus-flag[..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_script_extra_link_arg_examples() {
+ let p = project()
+ .file("Cargo.toml", &basic_lib_manifest("foo"))
+ .file("src/lib.rs", "")
+ .file("examples/example_foo.rs", "fn main() {}")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rustc-link-arg-examples=--this-is-a-bogus-flag");
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build -v --examples")
+ .without_status()
+ .with_stderr_contains(
+ "[RUNNING] `rustc --crate-name example_foo [..]-C link-arg=--this-is-a-bogus-flag[..]",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/cache_messages.rs b/src/tools/cargo/tests/testsuite/cache_messages.rs
new file mode 100644
index 000000000..b856ed152
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cache_messages.rs
@@ -0,0 +1,488 @@
+//! Tests for caching compiler diagnostics.
+
+use super::messages::raw_rustc_output;
+use cargo_test_support::tools;
+use cargo_test_support::{basic_manifest, is_coarse_mtime, project, registry::Package, sleep_ms};
+
+fn as_str(bytes: &[u8]) -> &str {
+ std::str::from_utf8(bytes).expect("valid utf-8")
+}
+
+#[cargo_test]
+fn simple() {
+ // A simple example that generates two warnings (unused functions).
+ let p = project()
+ .file(
+ "src/lib.rs",
+ "
+ fn a() {}
+ fn b() {}
+ ",
+ )
+ .build();
+
+ // Capture what rustc actually emits. This is done to avoid relying on the
+ // exact message formatting in rustc.
+ let rustc_output = raw_rustc_output(&p, "src/lib.rs", &[]);
+
+ // -q so the output is the same as rustc (no "Compiling" or "Finished").
+ let cargo_output1 = p
+ .cargo("check -q --color=never")
+ .exec_with_output()
+ .expect("cargo to run");
+ assert_eq!(rustc_output, as_str(&cargo_output1.stderr));
+ assert!(cargo_output1.stdout.is_empty());
+ // Check that the cached version is exactly the same.
+ let cargo_output2 = p
+ .cargo("check -q")
+ .exec_with_output()
+ .expect("cargo to run");
+ assert_eq!(rustc_output, as_str(&cargo_output2.stderr));
+ assert!(cargo_output2.stdout.is_empty());
+}
+
+// same as `simple`, except everything is using the short format
+#[cargo_test]
+fn simple_short() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ "
+ fn a() {}
+ fn b() {}
+ ",
+ )
+ .build();
+
+ let rustc_output = raw_rustc_output(&p, "src/lib.rs", &["--error-format=short"]);
+
+ let cargo_output1 = p
+ .cargo("check -q --color=never --message-format=short")
+ .exec_with_output()
+ .expect("cargo to run");
+ assert_eq!(rustc_output, as_str(&cargo_output1.stderr));
+ // assert!(cargo_output1.stdout.is_empty());
+ let cargo_output2 = p
+ .cargo("check -q --message-format=short")
+ .exec_with_output()
+ .expect("cargo to run");
+ println!("{}", String::from_utf8_lossy(&cargo_output2.stdout));
+ assert_eq!(rustc_output, as_str(&cargo_output2.stderr));
+ assert!(cargo_output2.stdout.is_empty());
+}
+
+#[cargo_test]
+fn color() {
+ // Check enabling/disabling color.
+ let p = project().file("src/lib.rs", "fn a() {}").build();
+
+ // Hack for issue in fwdansi 1.1. It is squashing multiple resets
+ // into a single reset.
+ // https://github.com/kennytm/fwdansi/issues/2
+ fn normalize(s: &str) -> String {
+ #[cfg(windows)]
+ return s.replace("\x1b[0m\x1b[0m", "\x1b[0m");
+ #[cfg(not(windows))]
+ return s.to_string();
+ }
+
+ let compare = |a, b| {
+ assert_eq!(normalize(a), normalize(b));
+ };
+
+ // Capture the original color output.
+ let rustc_color = raw_rustc_output(&p, "src/lib.rs", &["--color=always"]);
+ assert!(rustc_color.contains("\x1b["));
+
+ // Capture the original non-color output.
+ let rustc_nocolor = raw_rustc_output(&p, "src/lib.rs", &[]);
+ assert!(!rustc_nocolor.contains("\x1b["));
+
+ // First pass, non-cached, with color, should be the same.
+ let cargo_output1 = p
+ .cargo("check -q --color=always")
+ .exec_with_output()
+ .expect("cargo to run");
+ compare(&rustc_color, as_str(&cargo_output1.stderr));
+
+ // Replay cached, with color.
+ let cargo_output2 = p
+ .cargo("check -q --color=always")
+ .exec_with_output()
+ .expect("cargo to run");
+ compare(&rustc_color, as_str(&cargo_output2.stderr));
+
+ // Replay cached, no color.
+ let cargo_output_nocolor = p
+ .cargo("check -q --color=never")
+ .exec_with_output()
+ .expect("cargo to run");
+ compare(&rustc_nocolor, as_str(&cargo_output_nocolor.stderr));
+}
+
+#[cargo_test]
+fn cached_as_json() {
+ // Check that cached JSON output is the same.
+ let p = project().file("src/lib.rs", "fn a() {}").build();
+
+ // Grab the non-cached output, feature disabled.
+ // NOTE: When stabilizing, this will need to be redone.
+ let cargo_output = p
+ .cargo("check --message-format=json")
+ .exec_with_output()
+ .expect("cargo to run");
+ assert!(cargo_output.status.success());
+ let orig_cargo_out = as_str(&cargo_output.stdout);
+ assert!(orig_cargo_out.contains("compiler-message"));
+ p.cargo("clean").run();
+
+ // Check JSON output, not fresh.
+ let cargo_output1 = p
+ .cargo("check --message-format=json")
+ .exec_with_output()
+ .expect("cargo to run");
+ assert_eq!(as_str(&cargo_output1.stdout), orig_cargo_out);
+
+ // Check JSON output, fresh.
+ let cargo_output2 = p
+ .cargo("check --message-format=json")
+ .exec_with_output()
+ .expect("cargo to run");
+ // The only difference should be this field.
+ let fix_fresh = as_str(&cargo_output2.stdout).replace("\"fresh\":true", "\"fresh\":false");
+ assert_eq!(fix_fresh, orig_cargo_out);
+}
+
+#[cargo_test]
+fn clears_cache_after_fix() {
+ // Make sure the cache is invalidated when there is no output.
+ let p = project().file("src/lib.rs", "fn asdf() {}").build();
+ // Fill the cache.
+ p.cargo("check").with_stderr_contains("[..]asdf[..]").run();
+ let cpath = p
+ .glob("target/debug/.fingerprint/foo-*/output-*")
+ .next()
+ .unwrap()
+ .unwrap();
+ assert!(std::fs::read_to_string(cpath).unwrap().contains("asdf"));
+
+ // Fix it.
+ if is_coarse_mtime() {
+ sleep_ms(1000);
+ }
+ p.change_file("src/lib.rs", "");
+
+ p.cargo("check")
+ .with_stdout("")
+ .with_stderr(
+ "\
+[CHECKING] foo [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+ assert_eq!(
+ p.glob("target/debug/.fingerprint/foo-*/output-*").count(),
+ 0
+ );
+
+ // And again, check the cache is correct.
+ p.cargo("check")
+ .with_stdout("")
+ .with_stderr(
+ "\
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rustdoc() {
+ // Create a warning in rustdoc.
+ let p = project()
+ .file(
+ "src/lib.rs",
+ "
+ #![warn(missing_docs)]
+ pub fn f() {}
+ ",
+ )
+ .build();
+
+ let rustdoc_output = p
+ .cargo("doc -q --color=always")
+ .exec_with_output()
+ .expect("rustdoc to run");
+ assert!(rustdoc_output.status.success());
+ let rustdoc_stderr = as_str(&rustdoc_output.stderr);
+ assert!(rustdoc_stderr.contains("missing"));
+ assert!(rustdoc_stderr.contains("\x1b["));
+ assert_eq!(
+ p.glob("target/debug/.fingerprint/foo-*/output-*").count(),
+ 1
+ );
+
+ // Check the cached output.
+ let rustdoc_output = p
+ .cargo("doc -q --color=always")
+ .exec_with_output()
+ .expect("rustdoc to run");
+ assert_eq!(as_str(&rustdoc_output.stderr), rustdoc_stderr);
+}
+
+#[cargo_test]
+fn fix() {
+ // Make sure `fix` is not broken by caching.
+ let p = project().file("src/lib.rs", "pub fn try() {}").build();
+
+ p.cargo("fix --edition --allow-no-vcs").run();
+
+ assert_eq!(p.read_file("src/lib.rs"), "pub fn r#try() {}");
+}
+
+#[cargo_test]
+fn very_verbose() {
+ // Handle cap-lints in dependencies.
+ Package::new("bar", "1.0.0")
+ .file("src/lib.rs", "fn not_used() {}")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check -vv")
+ .with_stderr_contains("[..]not_used[..]")
+ .run();
+
+ p.cargo("check").with_stderr("[FINISHED] [..]").run();
+
+ p.cargo("check -vv")
+ .with_stderr_contains("[..]not_used[..]")
+ .run();
+}
+
+#[cargo_test]
+fn doesnt_create_extra_files() {
+ // Ensure it doesn't create `output` files when not needed.
+ Package::new("dep", "1.0.0")
+ .file("src/lib.rs", "fn unused() {}")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ dep = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check").run();
+
+ assert_eq!(
+ p.glob("target/debug/.fingerprint/foo-*/output-*").count(),
+ 0
+ );
+ assert_eq!(
+ p.glob("target/debug/.fingerprint/dep-*/output-*").count(),
+ 0
+ );
+ if is_coarse_mtime() {
+ sleep_ms(1000);
+ }
+ p.change_file("src/lib.rs", "fn unused() {}");
+ p.cargo("check").run();
+ assert_eq!(
+ p.glob("target/debug/.fingerprint/foo-*/output-*").count(),
+ 1
+ );
+}
+
+#[cargo_test]
+fn replay_non_json() {
+ // Handles non-json output.
+ let rustc = project()
+ .at("rustc")
+ .file("Cargo.toml", &basic_manifest("rustc_alt", "1.0.0"))
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ eprintln!("line 1");
+ eprintln!("line 2");
+ let r = std::process::Command::new("rustc")
+ .args(std::env::args_os().skip(1))
+ .status();
+ std::process::exit(r.unwrap().code().unwrap_or(2));
+ }
+ "#,
+ )
+ .build();
+ rustc.cargo("build").run();
+ let p = project().file("src/lib.rs", "").build();
+ p.cargo("check")
+ .env("RUSTC", rustc.bin("rustc_alt"))
+ .with_stderr(
+ "\
+[CHECKING] foo [..]
+line 1
+line 2
+[FINISHED] dev [..]
+",
+ )
+ .run();
+
+ p.cargo("check")
+ .env("RUSTC", rustc.bin("rustc_alt"))
+ .with_stderr(
+ "\
+line 1
+line 2
+[FINISHED] dev [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn caching_large_output() {
+ // Handles large number of messages.
+ // This is an arbitrary amount that is greater than the 100 used in
+ // job_queue. This is here to check for deadlocks or any other problems.
+ const COUNT: usize = 250;
+ let rustc = project()
+ .at("rustc")
+ .file("Cargo.toml", &basic_manifest("rustc_alt", "1.0.0"))
+ .file(
+ "src/main.rs",
+ &format!(
+ r#"
+ fn main() {{
+ for i in 0..{} {{
+ eprintln!("{{{{\"message\": \"test message {{}}\", \"level\": \"warning\", \
+ \"spans\": [], \"children\": [], \"rendered\": \"test message {{}}\"}}}}",
+ i, i);
+ }}
+ let r = std::process::Command::new("rustc")
+ .args(std::env::args_os().skip(1))
+ .status();
+ std::process::exit(r.unwrap().code().unwrap_or(2));
+ }}
+ "#,
+ COUNT
+ ),
+ )
+ .build();
+
+ let mut expected = String::new();
+ for i in 0..COUNT {
+ expected.push_str(&format!("test message {}\n", i));
+ }
+
+ rustc.cargo("build").run();
+ let p = project().file("src/lib.rs", "").build();
+ p.cargo("check")
+ .env("RUSTC", rustc.bin("rustc_alt"))
+ .with_stderr(&format!(
+ "\
+[CHECKING] foo [..]
+{}warning: `foo` (lib) generated 250 warnings
+[FINISHED] dev [..]
+",
+ expected
+ ))
+ .run();
+
+ p.cargo("check")
+ .env("RUSTC", rustc.bin("rustc_alt"))
+ .with_stderr(&format!(
+ "\
+{}warning: `foo` (lib) generated 250 warnings
+[FINISHED] dev [..]
+",
+ expected
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn rustc_workspace_wrapper() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ "pub fn f() { assert!(true); }\n\
+ fn unused_func() {}",
+ )
+ .build();
+
+ p.cargo("check -v")
+ .env("RUSTC_WORKSPACE_WRAPPER", tools::echo_wrapper())
+ .with_stderr_contains("WRAPPER CALLED: rustc --crate-name foo src/lib.rs [..]")
+ .run();
+
+ // Check without a wrapper should rebuild
+ p.cargo("check -v")
+ .with_stderr_contains(
+ "\
+[CHECKING] foo [..]
+[RUNNING] `rustc[..]
+[WARNING] [..]unused_func[..]
+",
+ )
+ .with_stdout_does_not_contain("WRAPPER CALLED: rustc --crate-name foo src/lib.rs [..]")
+ .run();
+
+ // Again, reading from the cache.
+ p.cargo("check -v")
+ .env("RUSTC_WORKSPACE_WRAPPER", tools::echo_wrapper())
+ .with_stderr_contains("[FRESH] foo [..]")
+ .with_stdout_does_not_contain("WRAPPER CALLED: rustc --crate-name foo src/lib.rs [..]")
+ .run();
+
+ // And `check` should also be fresh, reading from cache.
+ p.cargo("check -v")
+ .with_stderr_contains("[FRESH] foo [..]")
+ .with_stderr_contains("[WARNING] [..]unused_func[..]")
+ .with_stdout_does_not_contain("WRAPPER CALLED: rustc --crate-name foo src/lib.rs [..]")
+ .run();
+}
+
+#[cargo_test]
+fn wacky_hashless_fingerprint() {
+ // On Windows, executables don't have hashes. This checks for a bad
+ // assumption that caused bad caching.
+ let p = project()
+ .file("src/bin/a.rs", "fn main() { let unused = 1; }")
+ .file("src/bin/b.rs", "fn main() {}")
+ .build();
+ p.cargo("check --bin b")
+ .with_stderr_does_not_contain("[..]unused[..]")
+ .run();
+ p.cargo("check --bin a")
+ .with_stderr_contains("[..]unused[..]")
+ .run();
+ // This should not pick up the cache from `a`.
+ p.cargo("check --bin b")
+ .with_stderr_does_not_contain("[..]unused[..]")
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/add-basic.in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/add-basic.in/Cargo.toml
new file mode 100644
index 000000000..3ecdb6681
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/add-basic.in/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/add-basic.in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/add-basic.in/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/add-basic.in/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/add_basic/in b/src/tools/cargo/tests/testsuite/cargo_add/add_basic/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/add_basic/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/add_basic/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/add_basic/mod.rs
new file mode 100644
index 000000000..33889dffa
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/add_basic/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/add_basic/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/add_basic/out/Cargo.toml
new file mode 100644
index 000000000..5964c87be
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/add_basic/out/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+my-package = "99999.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/add_basic/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/add_basic/stderr.log
new file mode 100644
index 000000000..fd6b711e3
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/add_basic/stderr.log
@@ -0,0 +1,2 @@
+ Updating `dummy-registry` index
+ Adding my-package v99999.0.0 to dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/add_basic/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/add_basic/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/add_basic/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/add_multiple/in b/src/tools/cargo/tests/testsuite/cargo_add/add_multiple/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/add_multiple/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/add_multiple/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/add_multiple/mod.rs
new file mode 100644
index 000000000..a9cc20575
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/add_multiple/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package1 my-package2")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/add_multiple/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/add_multiple/out/Cargo.toml
new file mode 100644
index 000000000..ba8d7eabe
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/add_multiple/out/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+my-package1 = "99999.0.0"
+my-package2 = "99999.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/add_multiple/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/add_multiple/stderr.log
new file mode 100644
index 000000000..d0b4e73c1
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/add_multiple/stderr.log
@@ -0,0 +1,3 @@
+ Updating `dummy-registry` index
+ Adding my-package1 v99999.0.0 to dependencies.
+ Adding my-package2 v99999.0.0 to dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/add_multiple/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/add_multiple/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/add_multiple/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/add_normalized_name_external/in b/src/tools/cargo/tests/testsuite/cargo_add/add_normalized_name_external/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/add_normalized_name_external/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/add_normalized_name_external/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/add_normalized_name_external/mod.rs
new file mode 100644
index 000000000..63605d8dc
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/add_normalized_name_external/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("linked_hash_map Inflector")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/add_normalized_name_external/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/add_normalized_name_external/out/Cargo.toml
new file mode 100644
index 000000000..3d0dec343
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/add_normalized_name_external/out/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+inflector = "0.11.4"
+linked-hash-map = "0.5.4"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/add_normalized_name_external/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/add_normalized_name_external/stderr.log
new file mode 100644
index 000000000..c7d451143
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/add_normalized_name_external/stderr.log
@@ -0,0 +1,18 @@
+ Updating `dummy-registry` index
+warning: translating `linked_hash_map` to `linked-hash-map`
+warning: translating `Inflector` to `inflector`
+ Adding linked-hash-map v0.5.4 to dependencies.
+ Features:
+ - clippy
+ - heapsize
+ - heapsize_impl
+ - nightly
+ - serde
+ - serde_impl
+ - serde_test
+ Adding inflector v0.11.4 to dependencies.
+ Features:
+ + heavyweight
+ + lazy_static
+ + regex
+ - unstable
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/add_normalized_name_external/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/add_normalized_name_external/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/add_normalized_name_external/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/build/in b/src/tools/cargo/tests/testsuite/cargo_add/build/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/build/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/build/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/build/mod.rs
new file mode 100644
index 000000000..130ecfbb0
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/build/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("--build my-build-package1 my-build-package2")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/build/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/build/out/Cargo.toml
new file mode 100644
index 000000000..cceb448ed
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/build/out/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[build-dependencies]
+my-build-package1 = "99999.0.0"
+my-build-package2 = "99999.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/build/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/build/stderr.log
new file mode 100644
index 000000000..b873c5a80
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/build/stderr.log
@@ -0,0 +1,3 @@
+ Updating `dummy-registry` index
+ Adding my-build-package1 v99999.0.0 to build-dependencies.
+ Adding my-build-package2 v99999.0.0 to build-dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/build/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/build/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/build/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/in/Cargo.toml
new file mode 100644
index 000000000..6a6ac823f
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/in/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+exclude = ["dependency"]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+cargo-list-test-fixture-dependency = { version = "0.0.0", path = "dependency", optional = true, default-features = false, features = ["one", "two"], registry = "alternative" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/in/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/in/dependency/Cargo.toml
new file mode 100644
index 000000000..58b909cef
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/in/dependency/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
+
+[features]
+one = []
+two = []
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/in/dependency/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/in/dependency/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/in/dependency/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/in/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/in/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/mod.rs
new file mode 100644
index 000000000..b0bb2e03b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/mod.rs
@@ -0,0 +1,28 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_alt_registry;
+
+#[cargo_test]
+fn case() {
+ init_alt_registry();
+ let project =
+ Project::from_template("tests/testsuite/cargo_add/build_prefer_existing_version/in");
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("cargo-list-test-fixture-dependency --build")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path("tests/testsuite/cargo_add/build_prefer_existing_version/stdout.log")
+ .stderr_matches_path("tests/testsuite/cargo_add/build_prefer_existing_version/stderr.log");
+
+ assert_ui().subset_matches(
+ "tests/testsuite/cargo_add/build_prefer_existing_version/out",
+ &project_root,
+ );
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/out/Cargo.toml
new file mode 100644
index 000000000..123af6d22
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/out/Cargo.toml
@@ -0,0 +1,12 @@
+[workspace]
+exclude = ["dependency"]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+cargo-list-test-fixture-dependency = { version = "0.0.0", path = "dependency", optional = true, default-features = false, features = ["one", "two"], registry = "alternative" }
+
+[build-dependencies]
+cargo-list-test-fixture-dependency = { version = "0.0.0", path = "dependency", registry = "alternative" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/out/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/out/dependency/Cargo.toml
new file mode 100644
index 000000000..58b909cef
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/out/dependency/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
+
+[features]
+one = []
+two = []
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/stderr.log
new file mode 100644
index 000000000..554aa2ef3
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/stderr.log
@@ -0,0 +1,4 @@
+ Adding cargo-list-test-fixture-dependency (local) to build-dependencies.
+ Features:
+ - one
+ - two
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/build_prefer_existing_version/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/change_rename_target/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/change_rename_target/in/Cargo.toml
new file mode 100644
index 000000000..e81a76b4b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/change_rename_target/in/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+some-package = { package = "my-package1", version = "0.1.1", optional = true }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/change_rename_target/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/change_rename_target/in/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/change_rename_target/in/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/change_rename_target/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/change_rename_target/mod.rs
new file mode 100644
index 000000000..94309b3ab
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/change_rename_target/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package2 --rename some-package")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/change_rename_target/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/change_rename_target/out/Cargo.toml
new file mode 100644
index 000000000..70cd31826
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/change_rename_target/out/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+some-package = { package = "my-package2", version = "99999.0.0", optional = true }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/change_rename_target/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/change_rename_target/stderr.log
new file mode 100644
index 000000000..674f62602
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/change_rename_target/stderr.log
@@ -0,0 +1,2 @@
+ Updating `dummy-registry` index
+ Adding my-package2 v99999.0.0 to optional dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/change_rename_target/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/change_rename_target/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/change_rename_target/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/cyclic_features/in b/src/tools/cargo/tests/testsuite/cargo_add/cyclic_features/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/cyclic_features/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/cyclic_features/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/cyclic_features/mod.rs
new file mode 100644
index 000000000..5dffac323
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/cyclic_features/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("test_cyclic_features")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/cyclic_features/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/cyclic_features/out/Cargo.toml
new file mode 100644
index 000000000..27a5c31f8
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/cyclic_features/out/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+test_cyclic_features = "0.1.1"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/cyclic_features/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/cyclic_features/stderr.log
new file mode 100644
index 000000000..2d4a2db4a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/cyclic_features/stderr.log
@@ -0,0 +1,5 @@
+ Updating `dummy-registry` index
+ Adding test_cyclic_features v0.1.1 to dependencies.
+ Features:
+ + feature-one
+ + feature-two
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/cyclic_features/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/cyclic_features/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/cyclic_features/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/default_features/in b/src/tools/cargo/tests/testsuite/cargo_add/default_features/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/default_features/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/default_features/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/default_features/mod.rs
new file mode 100644
index 000000000..88bdd8065
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/default_features/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package1 my-package2@0.4.1 --default-features")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/default_features/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/default_features/out/Cargo.toml
new file mode 100644
index 000000000..c5e017892
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/default_features/out/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+my-package1 = "99999.0.0"
+my-package2 = "0.4.1"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/default_features/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/default_features/stderr.log
new file mode 100644
index 000000000..fb8d4903d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/default_features/stderr.log
@@ -0,0 +1,3 @@
+ Updating `dummy-registry` index
+ Adding my-package1 v99999.0.0 to dependencies.
+ Adding my-package2 v0.4.1 to dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/default_features/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/default_features/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/default_features/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/deprecated_default_features/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/deprecated_default_features/in/Cargo.toml
new file mode 100644
index 000000000..c0fc374ea
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/deprecated_default_features/in/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+my-package = { version = "99999.0.0", default_features = false }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/deprecated_default_features/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/deprecated_default_features/in/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/deprecated_default_features/in/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/deprecated_default_features/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/deprecated_default_features/mod.rs
new file mode 100644
index 000000000..10d4e4e98
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/deprecated_default_features/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package")
+ .current_dir(&cwd)
+ .assert()
+ .failure()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/deprecated_default_features/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/deprecated_default_features/out/Cargo.toml
new file mode 100644
index 000000000..c0fc374ea
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/deprecated_default_features/out/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+my-package = { version = "99999.0.0", default_features = false }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/deprecated_default_features/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/deprecated_default_features/stderr.log
new file mode 100644
index 000000000..46d99d15d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/deprecated_default_features/stderr.log
@@ -0,0 +1 @@
+error: Use of `default_features` in `my-package` is unsupported, please switch to `default-features`
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/deprecated_default_features/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/deprecated_default_features/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/deprecated_default_features/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/deprecated_section/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/deprecated_section/in/Cargo.toml
new file mode 100644
index 000000000..a83d2c621
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/deprecated_section/in/Cargo.toml
@@ -0,0 +1,11 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dev_dependencies]
+my-package = "99999.0.0"
+
+[build_dependencies]
+my-package = "99999.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/deprecated_section/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/deprecated_section/in/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/deprecated_section/in/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/deprecated_section/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/deprecated_section/mod.rs
new file mode 100644
index 000000000..10d4e4e98
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/deprecated_section/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package")
+ .current_dir(&cwd)
+ .assert()
+ .failure()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/deprecated_section/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/deprecated_section/out/Cargo.toml
new file mode 100644
index 000000000..a83d2c621
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/deprecated_section/out/Cargo.toml
@@ -0,0 +1,11 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dev_dependencies]
+my-package = "99999.0.0"
+
+[build_dependencies]
+my-package = "99999.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/deprecated_section/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/deprecated_section/stderr.log
new file mode 100644
index 000000000..b3b9c10f9
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/deprecated_section/stderr.log
@@ -0,0 +1 @@
+error: Deprecated dependency sections are unsupported: dev_dependencies, build_dependencies
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/deprecated_section/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/deprecated_section/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/deprecated_section/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/in/Cargo.toml
new file mode 100644
index 000000000..24c50556b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/in/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+members = ["primary", "dependency"]
+
+[workspace.dependencies]
+foo = { version = "0.0.0", path = "./dependency"} \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/in/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/in/dependency/Cargo.toml
new file mode 100644
index 000000000..2d247d4d2
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/in/dependency/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "foo"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/in/dependency/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/in/dependency/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/in/dependency/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/in/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/in/primary/Cargo.toml
new file mode 100644
index 000000000..b867edbbe
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/in/primary/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "bar"
+version = "0.0.0" \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/in/primary/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/in/primary/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/in/primary/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/mod.rs
new file mode 100644
index 000000000..065fb4f93
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .args(["foo", "-p", "bar"])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/out/Cargo.toml
new file mode 100644
index 000000000..24c50556b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+members = ["primary", "dependency"]
+
+[workspace.dependencies]
+foo = { version = "0.0.0", path = "./dependency"} \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/out/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/out/dependency/Cargo.toml
new file mode 100644
index 000000000..2d247d4d2
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/out/dependency/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "foo"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/out/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/out/primary/Cargo.toml
new file mode 100644
index 000000000..a5740941b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/out/primary/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "bar"
+version = "0.0.0"
+
+[dependencies]
+foo.workspace = true
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/stderr.log
new file mode 100644
index 000000000..d2efcc0c0
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/stderr.log
@@ -0,0 +1 @@
+ Adding foo (workspace) to dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/in/Cargo.toml
new file mode 100644
index 000000000..b1d9b3995
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/in/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+members = ["primary", "dependency"]
+
+[workspace.dependencies]
+foo = { version = "0.0.0", path = "./dependency", features = ["merge"] }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/in/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/in/dependency/Cargo.toml
new file mode 100644
index 000000000..f34d7a685
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/in/dependency/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "foo"
+version = "0.0.0"
+
+[features]
+default-base = []
+default-test-base = []
+default-merge-base = []
+default = ["default-base", "default-test-base", "default-merge-base"]
+test-base = []
+test = ["test-base", "default-test-base"]
+merge-base = []
+merge = ["merge-base", "default-merge-base"]
+unrelated = [] \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/in/dependency/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/in/dependency/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/in/dependency/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/in/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/in/primary/Cargo.toml
new file mode 100644
index 000000000..b867edbbe
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/in/primary/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "bar"
+version = "0.0.0" \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/in/primary/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/in/primary/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/in/primary/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/mod.rs
new file mode 100644
index 000000000..11ab2b1bf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .args(["foo", "-p", "bar", "--features", "test"])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/out/Cargo.toml
new file mode 100644
index 000000000..b1d9b3995
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+members = ["primary", "dependency"]
+
+[workspace.dependencies]
+foo = { version = "0.0.0", path = "./dependency", features = ["merge"] }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/out/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/out/dependency/Cargo.toml
new file mode 100644
index 000000000..f34d7a685
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/out/dependency/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "foo"
+version = "0.0.0"
+
+[features]
+default-base = []
+default-test-base = []
+default-merge-base = []
+default = ["default-base", "default-test-base", "default-merge-base"]
+test-base = []
+test = ["test-base", "default-test-base"]
+merge-base = []
+merge = ["merge-base", "default-merge-base"]
+unrelated = [] \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/out/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/out/primary/Cargo.toml
new file mode 100644
index 000000000..fb4a12619
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/out/primary/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "bar"
+version = "0.0.0"
+
+[dependencies]
+foo = { workspace = true, features = ["test"] }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/stderr.log
new file mode 100644
index 000000000..02dde7a34
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/stderr.log
@@ -0,0 +1,10 @@
+ Adding foo (workspace) to dependencies.
+ Features as of v0.0.0:
+ + default-base
+ + default-merge-base
+ + default-test-base
+ + merge
+ + merge-base
+ + test
+ + test-base
+ - unrelated
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_features/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/in/Cargo.toml
new file mode 100644
index 000000000..24c50556b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/in/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+members = ["primary", "dependency"]
+
+[workspace.dependencies]
+foo = { version = "0.0.0", path = "./dependency"} \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/in/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/in/dependency/Cargo.toml
new file mode 100644
index 000000000..2d247d4d2
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/in/dependency/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "foo"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/in/dependency/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/in/dependency/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/in/dependency/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/in/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/in/primary/Cargo.toml
new file mode 100644
index 000000000..b867edbbe
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/in/primary/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "bar"
+version = "0.0.0" \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/in/primary/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/in/primary/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/in/primary/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/mod.rs
new file mode 100644
index 000000000..7557b520d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .args(["foo", "-p", "bar", "--optional"])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/out/Cargo.toml
new file mode 100644
index 000000000..24c50556b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+members = ["primary", "dependency"]
+
+[workspace.dependencies]
+foo = { version = "0.0.0", path = "./dependency"} \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/out/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/out/dependency/Cargo.toml
new file mode 100644
index 000000000..2d247d4d2
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/out/dependency/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "foo"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/out/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/out/primary/Cargo.toml
new file mode 100644
index 000000000..6dd7fb6d6
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/out/primary/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "bar"
+version = "0.0.0"
+
+[dependencies]
+foo = { workspace = true, optional = true }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/stderr.log
new file mode 100644
index 000000000..da03b11f7
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/stderr.log
@@ -0,0 +1 @@
+ Adding foo (workspace) to optional dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/detect_workspace_inherit_optional/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/dev/in b/src/tools/cargo/tests/testsuite/cargo_add/dev/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/dev/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/dev/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/dev/mod.rs
new file mode 100644
index 000000000..112e92285
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/dev/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("--dev my-dev-package1 my-dev-package2")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/dev/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/dev/out/Cargo.toml
new file mode 100644
index 000000000..28d9e81ce
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/dev/out/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dev-dependencies]
+my-dev-package1 = "99999.0.0"
+my-dev-package2 = "99999.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/dev/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/dev/stderr.log
new file mode 100644
index 000000000..f8e187ce9
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/dev/stderr.log
@@ -0,0 +1,3 @@
+ Updating `dummy-registry` index
+ Adding my-dev-package1 v99999.0.0 to dev-dependencies.
+ Adding my-dev-package2 v99999.0.0 to dev-dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/dev/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/dev/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/dev/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/dev_build_conflict/in b/src/tools/cargo/tests/testsuite/cargo_add/dev_build_conflict/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/dev_build_conflict/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/dev_build_conflict/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/dev_build_conflict/mod.rs
new file mode 100644
index 000000000..3f57c6b76
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/dev_build_conflict/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package --dev --build")
+ .current_dir(cwd)
+ .assert()
+ .code(1)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/dev_build_conflict/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/dev_build_conflict/out/Cargo.toml
new file mode 100644
index 000000000..3ecdb6681
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/dev_build_conflict/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/dev_build_conflict/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/dev_build_conflict/stderr.log
new file mode 100644
index 000000000..69c520912
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/dev_build_conflict/stderr.log
@@ -0,0 +1,7 @@
+error: the argument '--dev' cannot be used with '--build'
+
+Usage: cargo add [OPTIONS] <DEP>[@<VERSION>] ...
+ cargo add [OPTIONS] --path <PATH> ...
+ cargo add [OPTIONS] --git <URL> ...
+
+For more information, try '--help'.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/dev_build_conflict/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/dev_build_conflict/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/dev_build_conflict/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/in/Cargo.toml
new file mode 100644
index 000000000..6a6ac823f
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/in/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+exclude = ["dependency"]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+cargo-list-test-fixture-dependency = { version = "0.0.0", path = "dependency", optional = true, default-features = false, features = ["one", "two"], registry = "alternative" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/in/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/in/dependency/Cargo.toml
new file mode 100644
index 000000000..58b909cef
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/in/dependency/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
+
+[features]
+one = []
+two = []
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/in/dependency/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/in/dependency/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/in/dependency/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/in/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/in/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/mod.rs
new file mode 100644
index 000000000..1785ac820
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_alt_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_alt_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("cargo-list-test-fixture-dependency --dev")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/out/Cargo.toml
new file mode 100644
index 000000000..247f345cf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/out/Cargo.toml
@@ -0,0 +1,12 @@
+[workspace]
+exclude = ["dependency"]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+cargo-list-test-fixture-dependency = { version = "0.0.0", path = "dependency", optional = true, default-features = false, features = ["one", "two"], registry = "alternative" }
+
+[dev-dependencies]
+cargo-list-test-fixture-dependency = { path = "dependency" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/out/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/out/dependency/Cargo.toml
new file mode 100644
index 000000000..58b909cef
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/out/dependency/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
+
+[features]
+one = []
+two = []
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/stderr.log
new file mode 100644
index 000000000..32f9a3e82
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/stderr.log
@@ -0,0 +1,4 @@
+ Adding cargo-list-test-fixture-dependency (local) to dev-dependencies.
+ Features as of v0.0.0:
+ - one
+ - two
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/dev_prefer_existing_version/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/dry_run/in b/src/tools/cargo/tests/testsuite/cargo_add/dry_run/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/dry_run/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/dry_run/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/dry_run/mod.rs
new file mode 100644
index 000000000..209d20873
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/dry_run/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package --dry-run")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/dry_run/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/dry_run/out/Cargo.toml
new file mode 100644
index 000000000..3ecdb6681
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/dry_run/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/dry_run/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/dry_run/stderr.log
new file mode 100644
index 000000000..c80dba942
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/dry_run/stderr.log
@@ -0,0 +1,3 @@
+ Updating `dummy-registry` index
+ Adding my-package v99999.0.0 to dependencies.
+warning: aborting add due to dry run
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/dry_run/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/dry_run/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/dry_run/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features/in b/src/tools/cargo/tests/testsuite/cargo_add/features/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/features/mod.rs
new file mode 100644
index 000000000..5e4115390
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("your-face --features eyes")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/features/out/Cargo.toml
new file mode 100644
index 000000000..11419b203
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features/out/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+your-face = { version = "99999.0.0", features = ["eyes"] }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/features/stderr.log
new file mode 100644
index 000000000..386f3db5a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features/stderr.log
@@ -0,0 +1,7 @@
+ Updating `dummy-registry` index
+ Adding your-face v99999.0.0 to dependencies.
+ Features:
+ + eyes
+ - ears
+ - mouth
+ - nose
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/features/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features_empty/in b/src/tools/cargo/tests/testsuite/cargo_add/features_empty/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features_empty/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features_empty/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/features_empty/mod.rs
new file mode 100644
index 000000000..81dffc1ee
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features_empty/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("your-face --features ''")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features_empty/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/features_empty/out/Cargo.toml
new file mode 100644
index 000000000..79d735a12
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features_empty/out/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+your-face = "99999.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features_empty/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/features_empty/stderr.log
new file mode 100644
index 000000000..796b9601b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features_empty/stderr.log
@@ -0,0 +1,7 @@
+ Updating `dummy-registry` index
+ Adding your-face v99999.0.0 to dependencies.
+ Features:
+ - ears
+ - eyes
+ - mouth
+ - nose
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features_empty/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/features_empty/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features_empty/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features_multiple_occurrences/in b/src/tools/cargo/tests/testsuite/cargo_add/features_multiple_occurrences/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features_multiple_occurrences/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features_multiple_occurrences/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/features_multiple_occurrences/mod.rs
new file mode 100644
index 000000000..db47f860d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features_multiple_occurrences/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("your-face --features eyes --features nose")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features_multiple_occurrences/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/features_multiple_occurrences/out/Cargo.toml
new file mode 100644
index 000000000..0060d24bc
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features_multiple_occurrences/out/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+your-face = { version = "99999.0.0", features = ["eyes", "nose"] }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features_multiple_occurrences/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/features_multiple_occurrences/stderr.log
new file mode 100644
index 000000000..615459052
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features_multiple_occurrences/stderr.log
@@ -0,0 +1,7 @@
+ Updating `dummy-registry` index
+ Adding your-face v99999.0.0 to dependencies.
+ Features:
+ + eyes
+ + nose
+ - ears
+ - mouth
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features_multiple_occurrences/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/features_multiple_occurrences/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features_multiple_occurrences/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features_preserve/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/features_preserve/in/Cargo.toml
new file mode 100644
index 000000000..11419b203
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features_preserve/in/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+your-face = { version = "99999.0.0", features = ["eyes"] }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features_preserve/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/features_preserve/in/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features_preserve/in/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features_preserve/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/features_preserve/mod.rs
new file mode 100644
index 000000000..ed99a3111
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features_preserve/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("your-face")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features_preserve/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/features_preserve/out/Cargo.toml
new file mode 100644
index 000000000..11419b203
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features_preserve/out/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+your-face = { version = "99999.0.0", features = ["eyes"] }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features_preserve/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/features_preserve/stderr.log
new file mode 100644
index 000000000..386f3db5a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features_preserve/stderr.log
@@ -0,0 +1,7 @@
+ Updating `dummy-registry` index
+ Adding your-face v99999.0.0 to dependencies.
+ Features:
+ + eyes
+ - ears
+ - mouth
+ - nose
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features_preserve/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/features_preserve/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features_preserve/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features_spaced_values/in b/src/tools/cargo/tests/testsuite/cargo_add/features_spaced_values/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features_spaced_values/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features_spaced_values/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/features_spaced_values/mod.rs
new file mode 100644
index 000000000..2ef212e59
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features_spaced_values/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("your-face --features eyes,nose")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features_spaced_values/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/features_spaced_values/out/Cargo.toml
new file mode 100644
index 000000000..0060d24bc
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features_spaced_values/out/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+your-face = { version = "99999.0.0", features = ["eyes", "nose"] }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features_spaced_values/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/features_spaced_values/stderr.log
new file mode 100644
index 000000000..615459052
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features_spaced_values/stderr.log
@@ -0,0 +1,7 @@
+ Updating `dummy-registry` index
+ Adding your-face v99999.0.0 to dependencies.
+ Features:
+ + eyes
+ + nose
+ - ears
+ - mouth
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features_spaced_values/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/features_spaced_values/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features_spaced_values/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features_unknown/in b/src/tools/cargo/tests/testsuite/cargo_add/features_unknown/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features_unknown/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features_unknown/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/features_unknown/mod.rs
new file mode 100644
index 000000000..7fd8d9529
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features_unknown/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("your-face --features noze")
+ .current_dir(cwd)
+ .assert()
+ .code(101)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features_unknown/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/features_unknown/out/Cargo.toml
new file mode 100644
index 000000000..3ecdb6681
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features_unknown/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features_unknown/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/features_unknown/stderr.log
new file mode 100644
index 000000000..58afcb66b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features_unknown/stderr.log
@@ -0,0 +1,5 @@
+ Updating `dummy-registry` index
+ Adding your-face v99999.0.0 to dependencies.
+error: unrecognized feature for crate your-face: noze
+disabled features:
+ ears, eyes, mouth, nose
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features_unknown/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/features_unknown/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features_unknown/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features_unknown_no_features/in b/src/tools/cargo/tests/testsuite/cargo_add/features_unknown_no_features/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features_unknown_no_features/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features_unknown_no_features/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/features_unknown_no_features/mod.rs
new file mode 100644
index 000000000..9f59a0353
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features_unknown_no_features/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package --features noze")
+ .current_dir(cwd)
+ .assert()
+ .code(101)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features_unknown_no_features/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/features_unknown_no_features/out/Cargo.toml
new file mode 100644
index 000000000..3ecdb6681
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features_unknown_no_features/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features_unknown_no_features/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/features_unknown_no_features/stderr.log
new file mode 100644
index 000000000..f1d046d53
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features_unknown_no_features/stderr.log
@@ -0,0 +1,4 @@
+ Updating `dummy-registry` index
+ Adding my-package v99999.0.0 to dependencies.
+error: unrecognized feature for crate my-package: noze
+no features available for crate my-package
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/features_unknown_no_features/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/features_unknown_no_features/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/features_unknown_no_features/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git/in b/src/tools/cargo/tests/testsuite/cargo_add/git/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/git/mod.rs
new file mode 100644
index 000000000..bd82b3015
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git/mod.rs
@@ -0,0 +1,34 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+ let git_dep = cargo_test_support::git::new("git-package", |project| {
+ project
+ .file(
+ "Cargo.toml",
+ &cargo_test_support::basic_manifest("git-package", "0.3.0+git-package"),
+ )
+ .file("src/lib.rs", "")
+ });
+ let git_url = git_dep.url().to_string();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .args(["git-package", "--git", &git_url])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/git/out/Cargo.toml
new file mode 100644
index 000000000..7f2d2f188
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git/out/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+git-package = { git = "[ROOTURL]/git-package", version = "0.3.0" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/git/stderr.log
new file mode 100644
index 000000000..839d8bb32
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git/stderr.log
@@ -0,0 +1,3 @@
+ Updating git repository `[ROOTURL]/git-package`
+ Adding git-package (git) to dependencies.
+ Updating git repository `[ROOTURL]/git-package`
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/git/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_branch/in b/src/tools/cargo/tests/testsuite/cargo_add/git_branch/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_branch/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_branch/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/git_branch/mod.rs
new file mode 100644
index 000000000..051564566
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_branch/mod.rs
@@ -0,0 +1,37 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+ let (git_dep, git_repo) = cargo_test_support::git::new_repo("git-package", |project| {
+ project
+ .file(
+ "Cargo.toml",
+ &cargo_test_support::basic_manifest("git-package", "0.3.0+git-package"),
+ )
+ .file("src/lib.rs", "")
+ });
+ let branch = "dev";
+ let find_head = || (git_repo.head().unwrap().peel_to_commit().unwrap());
+ git_repo.branch(branch, &find_head(), false).unwrap();
+ let git_url = git_dep.url().to_string();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .args(["git-package", "--git", &git_url, "--branch", branch])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_branch/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/git_branch/out/Cargo.toml
new file mode 100644
index 000000000..2eb295581
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_branch/out/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+git-package = { git = "[ROOTURL]/git-package", branch = "dev", version = "0.3.0" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_branch/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/git_branch/stderr.log
new file mode 100644
index 000000000..839d8bb32
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_branch/stderr.log
@@ -0,0 +1,3 @@
+ Updating git repository `[ROOTURL]/git-package`
+ Adding git-package (git) to dependencies.
+ Updating git repository `[ROOTURL]/git-package`
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_branch/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/git_branch/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_branch/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_conflicts_namever/in b/src/tools/cargo/tests/testsuite/cargo_add/git_conflicts_namever/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_conflicts_namever/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_conflicts_namever/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/git_conflicts_namever/mod.rs
new file mode 100644
index 000000000..f123298ae
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_conflicts_namever/mod.rs
@@ -0,0 +1,29 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .args([
+ "my-package@0.4.3",
+ "--git",
+ "https://github.com/dcjanus/invalid",
+ ])
+ .current_dir(cwd)
+ .assert()
+ .code(101)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_conflicts_namever/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/git_conflicts_namever/out/Cargo.toml
new file mode 100644
index 000000000..3ecdb6681
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_conflicts_namever/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_conflicts_namever/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/git_conflicts_namever/stderr.log
new file mode 100644
index 000000000..207e0ded3
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_conflicts_namever/stderr.log
@@ -0,0 +1 @@
+error: cannot specify a git URL (`https://github.com/dcjanus/invalid`) with a version (`0.4.3`).
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_conflicts_namever/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/git_conflicts_namever/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_conflicts_namever/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_dev/in b/src/tools/cargo/tests/testsuite/cargo_add/git_dev/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_dev/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_dev/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/git_dev/mod.rs
new file mode 100644
index 000000000..9e14a4007
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_dev/mod.rs
@@ -0,0 +1,34 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+ let git_dep = cargo_test_support::git::new("git-package", |project| {
+ project
+ .file(
+ "Cargo.toml",
+ &cargo_test_support::basic_manifest("git-package", "0.3.0+git-package"),
+ )
+ .file("src/lib.rs", "")
+ });
+ let git_url = git_dep.url().to_string();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .args(["git-package", "--git", &git_url, "--dev"])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_dev/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/git_dev/out/Cargo.toml
new file mode 100644
index 000000000..ceb131757
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_dev/out/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dev-dependencies]
+git-package = { git = "[ROOTURL]/git-package" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_dev/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/git_dev/stderr.log
new file mode 100644
index 000000000..8e53bb4be
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_dev/stderr.log
@@ -0,0 +1,3 @@
+ Updating git repository `[ROOTURL]/git-package`
+ Adding git-package (git) to dev-dependencies.
+ Updating git repository `[ROOTURL]/git-package`
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_dev/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/git_dev/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_dev/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name/in b/src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name/mod.rs
new file mode 100644
index 000000000..52183adf4
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name/mod.rs
@@ -0,0 +1,34 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+ let git_dep = cargo_test_support::git::new("git-package", |project| {
+ project
+ .file(
+ "Cargo.toml",
+ &cargo_test_support::basic_manifest("git-package", "0.3.0+git-package"),
+ )
+ .file("src/lib.rs", "")
+ });
+ let git_url = git_dep.url().to_string();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .args(["--git", &git_url])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name/out/Cargo.toml
new file mode 100644
index 000000000..7f2d2f188
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name/out/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+git-package = { git = "[ROOTURL]/git-package", version = "0.3.0" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name/stderr.log
new file mode 100644
index 000000000..b5e8b1c9b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name/stderr.log
@@ -0,0 +1,4 @@
+ Updating git repository `[ROOTURL]/git-package`
+ Updating git repository `[ROOTURL]/git-package`
+ Adding git-package (git) to dependencies.
+ Updating git repository `[ROOTURL]/git-package`
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name_multiple/in b/src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name_multiple/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name_multiple/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name_multiple/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name_multiple/mod.rs
new file mode 100644
index 000000000..a708a8ae7
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name_multiple/mod.rs
@@ -0,0 +1,74 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+ let git_dep = cargo_test_support::git::new("git-package", |project| {
+ project
+ .file(
+ "p1/Cargo.toml",
+ &cargo_test_support::basic_manifest("my-package1", "0.3.0+my-package1"),
+ )
+ .file("p1/src/lib.rs", "")
+ .file(
+ "p2/Cargo.toml",
+ &cargo_test_support::basic_manifest("my-package2", "0.3.0+my-package2"),
+ )
+ .file("p2/src/lib.rs", "")
+ .file(
+ "p3/Cargo.toml",
+ &cargo_test_support::basic_manifest("my-package3", "0.3.0+my-package2"),
+ )
+ .file("p3/src/lib.rs", "")
+ .file(
+ "p4/Cargo.toml",
+ &cargo_test_support::basic_manifest("my-package4", "0.3.0+my-package2"),
+ )
+ .file("p4/src/lib.rs", "")
+ .file(
+ "p5/Cargo.toml",
+ &cargo_test_support::basic_manifest("my-package5", "0.3.0+my-package2"),
+ )
+ .file("p5/src/lib.rs", "")
+ .file(
+ "p6/Cargo.toml",
+ &cargo_test_support::basic_manifest("my-package6", "0.3.0+my-package2"),
+ )
+ .file("p6/src/lib.rs", "")
+ .file(
+ "p7/Cargo.toml",
+ &cargo_test_support::basic_manifest("my-package7", "0.3.0+my-package2"),
+ )
+ .file("p7/src/lib.rs", "")
+ .file(
+ "p8/Cargo.toml",
+ &cargo_test_support::basic_manifest("my-package8", "0.3.0+my-package2"),
+ )
+ .file("p8/src/lib.rs", "")
+ .file(
+ "p9/Cargo.toml",
+ &cargo_test_support::basic_manifest("my-package9", "0.3.0+my-package2"),
+ )
+ .file("p9/src/lib.rs", "")
+ });
+ let git_url = git_dep.url().to_string();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .args(["--git", &git_url])
+ .current_dir(cwd)
+ .assert()
+ .code(101)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name_multiple/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name_multiple/out/Cargo.toml
new file mode 100644
index 000000000..3ecdb6681
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name_multiple/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name_multiple/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name_multiple/stderr.log
new file mode 100644
index 000000000..2e045db6f
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name_multiple/stderr.log
@@ -0,0 +1,5 @@
+ Updating git repository `[ROOTURL]/git-package`
+error: multiple packages found at `[ROOTURL]/git-package`:
+ my-package1, my-package2, my-package3, my-package4, my-package5, my-package6
+ my-package7, my-package8, my-package9
+To disambiguate, run `cargo add --git [ROOTURL]/git-package <package>`
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name_multiple/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name_multiple/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_inferred_name_multiple/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_multiple_names/in b/src/tools/cargo/tests/testsuite/cargo_add/git_multiple_names/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_multiple_names/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_multiple_names/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/git_multiple_names/mod.rs
new file mode 100644
index 000000000..39eb6e626
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_multiple_names/mod.rs
@@ -0,0 +1,39 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+ let git_dep = cargo_test_support::git::new("git-package", |project| {
+ project
+ .file(
+ "p1/Cargo.toml",
+ &cargo_test_support::basic_manifest("my-package1", "0.3.0+my-package1"),
+ )
+ .file("p1/src/lib.rs", "")
+ .file(
+ "p2/Cargo.toml",
+ &cargo_test_support::basic_manifest("my-package2", "0.3.0+my-package2"),
+ )
+ .file("p2/src/lib.rs", "")
+ });
+ let git_url = git_dep.url().to_string();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .args(["my-package1", "my-package2", "--git", &git_url])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_multiple_names/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/git_multiple_names/out/Cargo.toml
new file mode 100644
index 000000000..ba9d3c5ea
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_multiple_names/out/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+my-package1 = { git = "[ROOTURL]/git-package", version = "0.3.0" }
+my-package2 = { git = "[ROOTURL]/git-package", version = "0.3.0" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_multiple_names/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/git_multiple_names/stderr.log
new file mode 100644
index 000000000..454f0c797
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_multiple_names/stderr.log
@@ -0,0 +1,4 @@
+ Updating git repository `[ROOTURL]/git-package`
+ Adding my-package1 (git) to dependencies.
+ Adding my-package2 (git) to dependencies.
+ Updating git repository `[ROOTURL]/git-package`
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_multiple_names/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/git_multiple_names/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_multiple_names/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_normalized_name/in b/src/tools/cargo/tests/testsuite/cargo_add/git_normalized_name/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_normalized_name/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_normalized_name/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/git_normalized_name/mod.rs
new file mode 100644
index 000000000..03d861856
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_normalized_name/mod.rs
@@ -0,0 +1,34 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+ let git_dep = cargo_test_support::git::new("git-package", |project| {
+ project
+ .file(
+ "Cargo.toml",
+ &cargo_test_support::basic_manifest("git-package", "0.3.0+git-package"),
+ )
+ .file("src/lib.rs", "")
+ });
+ let git_url = git_dep.url().to_string();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .args(["git_package", "--git", &git_url])
+ .current_dir(cwd)
+ .assert()
+ .failure() // Fuzzy searching for paths isn't supported at this time
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_normalized_name/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/git_normalized_name/out/Cargo.toml
new file mode 100644
index 000000000..3ecdb6681
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_normalized_name/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_normalized_name/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/git_normalized_name/stderr.log
new file mode 100644
index 000000000..fedf82861
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_normalized_name/stderr.log
@@ -0,0 +1,2 @@
+ Updating git repository `[ROOTURL]/git-package`
+error: the crate `git_package@[ROOTURL]/git-package` could not be found at `[ROOTURL]/git-package`
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_normalized_name/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/git_normalized_name/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_normalized_name/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_registry/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/git_registry/in/Cargo.toml
new file mode 100644
index 000000000..3ecdb6681
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_registry/in/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_registry/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/git_registry/in/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_registry/in/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_registry/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/git_registry/mod.rs
new file mode 100644
index 000000000..6bf6f8933
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_registry/mod.rs
@@ -0,0 +1,40 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_alt_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_alt_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+ let git_dep = cargo_test_support::git::new("versioned-package", |project| {
+ project
+ .file(
+ "Cargo.toml",
+ &cargo_test_support::basic_manifest("versioned-package", "0.3.0+versioned-package"),
+ )
+ .file("src/lib.rs", "")
+ });
+ let git_url = git_dep.url().to_string();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .args([
+ "versioned-package",
+ "--git",
+ &git_url,
+ "--registry",
+ "alternative",
+ ])
+ .current_dir(cwd)
+ .assert()
+ .failure()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_registry/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/git_registry/out/Cargo.toml
new file mode 100644
index 000000000..3773d1c80
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_registry/out/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+versioned-package = { git = "[ROOTURL]/versioned-package", version = "0.3.0", registry = "alternative" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_registry/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/git_registry/stderr.log
new file mode 100644
index 000000000..c554c7ec0
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_registry/stderr.log
@@ -0,0 +1,6 @@
+ Updating git repository `[ROOTURL]/versioned-package`
+ Adding versioned-package (git) to dependencies.
+error: failed to parse manifest at `[ROOT]/case/Cargo.toml`
+
+Caused by:
+ dependency (versioned-package) specification is ambiguous. Only one of `git` or `registry` is allowed.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_registry/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/git_registry/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_registry/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_rev/in b/src/tools/cargo/tests/testsuite/cargo_add/git_rev/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_rev/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_rev/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/git_rev/mod.rs
new file mode 100644
index 000000000..612607203
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_rev/mod.rs
@@ -0,0 +1,36 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+ let (git_dep, git_repo) = cargo_test_support::git::new_repo("git-package", |project| {
+ project
+ .file(
+ "Cargo.toml",
+ &cargo_test_support::basic_manifest("git-package", "0.3.0+git-package"),
+ )
+ .file("src/lib.rs", "")
+ });
+ let find_head = || (git_repo.head().unwrap().peel_to_commit().unwrap());
+ let head = find_head().id().to_string();
+ let git_url = git_dep.url().to_string();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .args(["git-package", "--git", &git_url, "--rev", &head])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_rev/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/git_rev/out/Cargo.toml
new file mode 100644
index 000000000..efc00a01a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_rev/out/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+git-package = { git = "[ROOTURL]/git-package", rev = "[..]", version = "0.3.0" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_rev/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/git_rev/stderr.log
new file mode 100644
index 000000000..839d8bb32
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_rev/stderr.log
@@ -0,0 +1,3 @@
+ Updating git repository `[ROOTURL]/git-package`
+ Adding git-package (git) to dependencies.
+ Updating git repository `[ROOTURL]/git-package`
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_rev/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/git_rev/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_rev/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_tag/in b/src/tools/cargo/tests/testsuite/cargo_add/git_tag/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_tag/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_tag/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/git_tag/mod.rs
new file mode 100644
index 000000000..b355b1706
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_tag/mod.rs
@@ -0,0 +1,36 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+ let (git_dep, git_repo) = cargo_test_support::git::new_repo("git-package", |project| {
+ project
+ .file(
+ "Cargo.toml",
+ &cargo_test_support::basic_manifest("git-package", "0.3.0+git-package"),
+ )
+ .file("src/lib.rs", "")
+ });
+ let tag = "v1.0.0";
+ cargo_test_support::git::tag(&git_repo, tag);
+ let git_url = git_dep.url().to_string();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .args(["git-package", "--git", &git_url, "--tag", tag])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_tag/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/git_tag/out/Cargo.toml
new file mode 100644
index 000000000..233f26e65
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_tag/out/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+git-package = { git = "[ROOTURL]/git-package", tag = "v1.0.0", version = "0.3.0" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_tag/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/git_tag/stderr.log
new file mode 100644
index 000000000..839d8bb32
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_tag/stderr.log
@@ -0,0 +1,3 @@
+ Updating git repository `[ROOTURL]/git-package`
+ Adding git-package (git) to dependencies.
+ Updating git repository `[ROOTURL]/git-package`
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/git_tag/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/git_tag/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/git_tag/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/infer_prerelease/in b/src/tools/cargo/tests/testsuite/cargo_add/infer_prerelease/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/infer_prerelease/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/infer_prerelease/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/infer_prerelease/mod.rs
new file mode 100644
index 000000000..94533f979
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/infer_prerelease/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("prerelease_only")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/infer_prerelease/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/infer_prerelease/out/Cargo.toml
new file mode 100644
index 000000000..4a86322ad
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/infer_prerelease/out/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+prerelease_only = "0.2.0-alpha.1"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/infer_prerelease/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/infer_prerelease/stderr.log
new file mode 100644
index 000000000..0696d8f7b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/infer_prerelease/stderr.log
@@ -0,0 +1,2 @@
+ Updating `dummy-registry` index
+ Adding prerelease_only v0.2.0-alpha.1 to dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/infer_prerelease/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/infer_prerelease/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/infer_prerelease/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_arg/in b/src/tools/cargo/tests/testsuite/cargo_add/invalid_arg/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_arg/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_arg/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/invalid_arg/mod.rs
new file mode 100644
index 000000000..265a571bc
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_arg/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package --flag")
+ .current_dir(cwd)
+ .assert()
+ .code(1)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_arg/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/invalid_arg/out/Cargo.toml
new file mode 100644
index 000000000..3ecdb6681
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_arg/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_arg/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/invalid_arg/stderr.log
new file mode 100644
index 000000000..96d067ed1
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_arg/stderr.log
@@ -0,0 +1,9 @@
+error: unexpected argument '--flag' found
+
+ tip: a similar argument exists: '--tag'
+
+Usage: cargo add [OPTIONS] <DEP>[@<VERSION>] ...
+ cargo add [OPTIONS] --path <PATH> ...
+ cargo add [OPTIONS] --git <URL> ...
+
+For more information, try '--help'.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_arg/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/invalid_arg/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_arg/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_git_external/in b/src/tools/cargo/tests/testsuite/cargo_add/invalid_git_external/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_git_external/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_git_external/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/invalid_git_external/mod.rs
new file mode 100644
index 000000000..705182f20
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_git_external/mod.rs
@@ -0,0 +1,28 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+ let git_url = url::Url::from_directory_path(cwd.join("does-not-exist"))
+ .unwrap()
+ .to_string();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .args(["fake-git", "--git", &git_url])
+ .current_dir(cwd)
+ .assert()
+ .code(101)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_git_external/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/invalid_git_external/out/Cargo.toml
new file mode 100644
index 000000000..3ecdb6681
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_git_external/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_git_external/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/invalid_git_external/stderr.log
new file mode 100644
index 000000000..18656300b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_git_external/stderr.log
@@ -0,0 +1,12 @@
+ Updating git repository `[ROOTURL]/case/does-not-exist/`
+...
+error: failed to load source for dependency `fake-git`
+
+Caused by:
+ Unable to update [ROOTURL]/case/does-not-exist/
+
+Caused by:
+ failed to clone into: [ROOT]/home/.cargo/git/db/does-not-exist-[..]
+
+Caused by:
+...
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_git_external/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/invalid_git_external/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_git_external/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_git_name/in b/src/tools/cargo/tests/testsuite/cargo_add/invalid_git_name/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_git_name/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_git_name/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/invalid_git_name/mod.rs
new file mode 100644
index 000000000..0aff8c090
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_git_name/mod.rs
@@ -0,0 +1,34 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+ let git_dep = cargo_test_support::git::new("git-package", |project| {
+ project
+ .file(
+ "Cargo.toml",
+ &cargo_test_support::basic_manifest("git-package", "0.3.0+git-package"),
+ )
+ .file("src/lib.rs", "")
+ });
+ let git_url = git_dep.url().to_string();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .args(["not-in-git", "--git", &git_url])
+ .current_dir(cwd)
+ .assert()
+ .code(101)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_git_name/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/invalid_git_name/out/Cargo.toml
new file mode 100644
index 000000000..3ecdb6681
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_git_name/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_git_name/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/invalid_git_name/stderr.log
new file mode 100644
index 000000000..68fc4e49d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_git_name/stderr.log
@@ -0,0 +1,2 @@
+ Updating git repository `[ROOTURL]/git-package`
+error: the crate `not-in-git@[ROOTURL]/git-package` could not be found at `[ROOTURL]/git-package`
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_git_name/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/invalid_git_name/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_git_name/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/in/Cargo.toml
new file mode 100644
index 000000000..afd30d446
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/in/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+members = ["primary", "dependency"]
+
+[workspace.dependencies]
+foo = { version = "0.0.0", path = "./dependency"}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/in/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/in/dependency/Cargo.toml
new file mode 100644
index 000000000..2d247d4d2
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/in/dependency/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "foo"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/in/dependency/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/in/dependency/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/in/dependency/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/in/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/in/primary/Cargo.toml
new file mode 100644
index 000000000..dd275f440
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/in/primary/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "bar"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/in/primary/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/in/primary/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/in/primary/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/mod.rs
new file mode 100644
index 000000000..837293e5f
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/mod.rs
@@ -0,0 +1,23 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .args(["foo", "--default-features", "-p", "bar"])
+ .current_dir(cwd)
+ .assert()
+ .failure()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/out/Cargo.toml
new file mode 100644
index 000000000..afd30d446
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+members = ["primary", "dependency"]
+
+[workspace.dependencies]
+foo = { version = "0.0.0", path = "./dependency"}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/out/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/out/dependency/Cargo.toml
new file mode 100644
index 000000000..2d247d4d2
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/out/dependency/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "foo"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/out/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/out/primary/Cargo.toml
new file mode 100644
index 000000000..dd275f440
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/out/primary/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "bar"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/stderr.log
new file mode 100644
index 000000000..85bd8da0a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/stderr.log
@@ -0,0 +1 @@
+error: cannot override workspace dependency with `--default-features`, either change `workspace.dependencies.foo.default-features` or define the dependency exclusively in the package's manifest
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_inherit_dependency/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/in/Cargo.toml
new file mode 100644
index 000000000..afd30d446
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/in/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+members = ["primary", "dependency"]
+
+[workspace.dependencies]
+foo = { version = "0.0.0", path = "./dependency"}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/in/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/in/dependency/Cargo.toml
new file mode 100644
index 000000000..2d247d4d2
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/in/dependency/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "foo"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/in/dependency/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/in/dependency/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/in/dependency/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/in/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/in/primary/Cargo.toml
new file mode 100644
index 000000000..a5740941b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/in/primary/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "bar"
+version = "0.0.0"
+
+[dependencies]
+foo.workspace = true
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/in/primary/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/in/primary/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/in/primary/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/mod.rs
new file mode 100644
index 000000000..837293e5f
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/mod.rs
@@ -0,0 +1,23 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .args(["foo", "--default-features", "-p", "bar"])
+ .current_dir(cwd)
+ .assert()
+ .failure()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/out/Cargo.toml
new file mode 100644
index 000000000..afd30d446
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+members = ["primary", "dependency"]
+
+[workspace.dependencies]
+foo = { version = "0.0.0", path = "./dependency"}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/out/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/out/dependency/Cargo.toml
new file mode 100644
index 000000000..2d247d4d2
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/out/dependency/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "foo"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/out/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/out/primary/Cargo.toml
new file mode 100644
index 000000000..a5740941b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/out/primary/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "bar"
+version = "0.0.0"
+
+[dependencies]
+foo.workspace = true
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/stderr.log
new file mode 100644
index 000000000..85bd8da0a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/stderr.log
@@ -0,0 +1 @@
+error: cannot override workspace dependency with `--default-features`, either change `workspace.dependencies.foo.default-features` or define the dependency exclusively in the package's manifest
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_overwrite_inherit_dependency/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/in/Cargo.toml
new file mode 100644
index 000000000..12c6ee5fe
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/in/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+members = ["primary", "dependency", "dependency-alt"]
+
+[workspace.dependencies]
+foo = { version = "0.0.0", path = "./dependency"}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/in/dependency-alt/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/in/dependency-alt/Cargo.toml
new file mode 100644
index 000000000..bb6472901
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/in/dependency-alt/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "foo-alt"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/in/dependency-alt/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/in/dependency-alt/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/in/dependency-alt/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/in/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/in/dependency/Cargo.toml
new file mode 100644
index 000000000..2d247d4d2
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/in/dependency/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "foo"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/in/dependency/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/in/dependency/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/in/dependency/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/in/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/in/primary/Cargo.toml
new file mode 100644
index 000000000..dd275f440
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/in/primary/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "bar"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/in/primary/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/in/primary/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/in/primary/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/mod.rs
new file mode 100644
index 000000000..bee132560
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/mod.rs
@@ -0,0 +1,23 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .args(["--rename", "foo", "foo-alt", "-p", "bar"])
+ .current_dir(cwd)
+ .assert()
+ .failure()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/out/Cargo.toml
new file mode 100644
index 000000000..12c6ee5fe
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+members = ["primary", "dependency", "dependency-alt"]
+
+[workspace.dependencies]
+foo = { version = "0.0.0", path = "./dependency"}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/out/dependency-alt/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/out/dependency-alt/Cargo.toml
new file mode 100644
index 000000000..bb6472901
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/out/dependency-alt/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "foo-alt"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/out/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/out/dependency/Cargo.toml
new file mode 100644
index 000000000..2d247d4d2
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/out/dependency/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "foo"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/out/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/out/primary/Cargo.toml
new file mode 100644
index 000000000..dd275f440
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/out/primary/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "bar"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/stderr.log
new file mode 100644
index 000000000..35bcdb694
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/stderr.log
@@ -0,0 +1 @@
+error: cannot override workspace dependency with `--rename`, either change `workspace.dependencies.foo.package` or define the dependency exclusively in the package's manifest
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_key_rename_inherit_dependency/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_manifest/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/invalid_manifest/in/Cargo.toml
new file mode 100644
index 000000000..94ee95994
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_manifest/in/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "manifest-invalid-test-fixture"
+version = "0.1.0"
+
+[invalid-section]
+key = invalid-value
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_manifest/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/invalid_manifest/in/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_manifest/in/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_manifest/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/invalid_manifest/mod.rs
new file mode 100644
index 000000000..e385cfc3f
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_manifest/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package")
+ .current_dir(cwd)
+ .assert()
+ .code(101)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_manifest/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/invalid_manifest/out/Cargo.toml
new file mode 100644
index 000000000..94ee95994
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_manifest/out/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "manifest-invalid-test-fixture"
+version = "0.1.0"
+
+[invalid-section]
+key = invalid-value
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_manifest/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/invalid_manifest/stderr.log
new file mode 100644
index 000000000..3dabde349
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_manifest/stderr.log
@@ -0,0 +1,12 @@
+error: failed to parse manifest at `[ROOT]/case/Cargo.toml`
+
+Caused by:
+ could not parse input as TOML
+
+Caused by:
+ TOML parse error at line 8, column 7
+ |
+ 8 | key = invalid-value
+ | ^
+ invalid string
+ expected `"`, `'`
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_manifest/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/invalid_manifest/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_manifest/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_name_external/in b/src/tools/cargo/tests/testsuite/cargo_add/invalid_name_external/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_name_external/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_name_external/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/invalid_name_external/mod.rs
new file mode 100644
index 000000000..16e041738
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_name_external/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("lets_hope_nobody_ever_publishes_this_crate")
+ .current_dir(cwd)
+ .assert()
+ .code(101)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_name_external/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/invalid_name_external/out/Cargo.toml
new file mode 100644
index 000000000..3ecdb6681
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_name_external/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_name_external/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/invalid_name_external/stderr.log
new file mode 100644
index 000000000..5e574ceda
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_name_external/stderr.log
@@ -0,0 +1,2 @@
+ Updating `dummy-registry` index
+error: the crate `lets_hope_nobody_ever_publishes_this_crate` could not be found in registry index.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_name_external/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/invalid_name_external/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_name_external/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_path/in b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_path/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path/mod.rs
new file mode 100644
index 000000000..0d26b552d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("cargo-list-test-fixture --path ./tests/fixtures/local")
+ .current_dir(cwd)
+ .assert()
+ .code(101)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_path/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path/out/Cargo.toml
new file mode 100644
index 000000000..3ecdb6681
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_path/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path/stderr.log
new file mode 100644
index 000000000..f6c404330
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path/stderr.log
@@ -0,0 +1,10 @@
+error: failed to load source for dependency `cargo-list-test-fixture`
+
+Caused by:
+ Unable to update [ROOT]/case/tests/fixtures/local
+
+Caused by:
+ failed to read `[ROOT]/case/tests/fixtures/local/Cargo.toml`
+
+Caused by:
+ [..]
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_path/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/in/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/in/dependency/Cargo.toml
new file mode 100644
index 000000000..cbe244113
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/in/dependency/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/in/dependency/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/in/dependency/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/in/dependency/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/in/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/in/primary/Cargo.toml
new file mode 100644
index 000000000..3ecdb6681
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/in/primary/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/in/primary/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/in/primary/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/in/primary/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/mod.rs
new file mode 100644
index 000000000..10d841475
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = project_root.join("primary");
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("not-at-path --path ../dependency")
+ .current_dir(&cwd)
+ .assert()
+ .code(101)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/out/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/out/dependency/Cargo.toml
new file mode 100644
index 000000000..cbe244113
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/out/dependency/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/out/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/out/primary/Cargo.toml
new file mode 100644
index 000000000..3ecdb6681
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/out/primary/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/stderr.log
new file mode 100644
index 000000000..b35ea8233
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/stderr.log
@@ -0,0 +1 @@
+error: the crate `not-at-path@[ROOT]/case/dependency` could not be found at `[ROOT]/case/dependency`
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_name/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_self/in b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_self/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_self/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_self/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_self/mod.rs
new file mode 100644
index 000000000..a64190f44
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_self/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("cargo-list-test-fixture --path .")
+ .current_dir(cwd)
+ .assert()
+ .code(101)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_self/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_self/out/Cargo.toml
new file mode 100644
index 000000000..3ecdb6681
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_self/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_self/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_self/stderr.log
new file mode 100644
index 000000000..62a25dbb4
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_self/stderr.log
@@ -0,0 +1,2 @@
+ Adding cargo-list-test-fixture (local) to dependencies.
+error: cannot add `cargo-list-test-fixture` as a dependency to itself
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_self/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_self/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_path_self/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_target_empty/in b/src/tools/cargo/tests/testsuite/cargo_add/invalid_target_empty/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_target_empty/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_target_empty/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/invalid_target_empty/mod.rs
new file mode 100644
index 000000000..da93c4eb8
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_target_empty/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package --target ''")
+ .current_dir(cwd)
+ .assert()
+ .code(1)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_target_empty/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/invalid_target_empty/out/Cargo.toml
new file mode 100644
index 000000000..3ecdb6681
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_target_empty/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_target_empty/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/invalid_target_empty/stderr.log
new file mode 100644
index 000000000..4b1a2c315
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_target_empty/stderr.log
@@ -0,0 +1,3 @@
+error: a value is required for '--target <TARGET>' but none was supplied
+
+For more information, try '--help'.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_target_empty/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/invalid_target_empty/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_target_empty/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_vers/in b/src/tools/cargo/tests/testsuite/cargo_add/invalid_vers/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_vers/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_vers/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/invalid_vers/mod.rs
new file mode 100644
index 000000000..c3b4d1f97
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_vers/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package@invalid-version-string")
+ .current_dir(cwd)
+ .assert()
+ .code(101)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_vers/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/invalid_vers/out/Cargo.toml
new file mode 100644
index 000000000..3ecdb6681
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_vers/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_vers/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/invalid_vers/stderr.log
new file mode 100644
index 000000000..64f908eac
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_vers/stderr.log
@@ -0,0 +1,4 @@
+error: invalid version requirement `invalid-version-string`
+
+Caused by:
+ unexpected character 'i' while parsing major version number
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/invalid_vers/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/invalid_vers/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/invalid_vers/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/list_features/in b/src/tools/cargo/tests/testsuite/cargo_add/list_features/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/list_features/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/list_features/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/list_features/mod.rs
new file mode 100644
index 000000000..e1e1b212f
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/list_features/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .args(["your-face"])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/list_features/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/list_features/out/Cargo.toml
new file mode 100644
index 000000000..79d735a12
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/list_features/out/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+your-face = "99999.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/list_features/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/list_features/stderr.log
new file mode 100644
index 000000000..796b9601b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/list_features/stderr.log
@@ -0,0 +1,7 @@
+ Updating `dummy-registry` index
+ Adding your-face v99999.0.0 to dependencies.
+ Features:
+ - ears
+ - eyes
+ - mouth
+ - nose
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/list_features/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/list_features/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/list_features/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/in/Cargo.toml
new file mode 100644
index 000000000..299859e79
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/in/Cargo.toml
@@ -0,0 +1,2 @@
+[workspace]
+members = ["primary", "dependency", "optional"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/in/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/in/dependency/Cargo.toml
new file mode 100644
index 000000000..18041a53f
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/in/dependency/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "your-face"
+version = "0.1.3"
+
+[dependencies]
+my-package = "0.1.1"
+optional-dependency = { path = "../optional", optional = true }
+
+[features]
+default = ["mouth"]
+nose = []
+mouth = ["nose"]
+eyes = []
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/in/dependency/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/in/dependency/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/in/dependency/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/in/optional/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/in/optional/Cargo.toml
new file mode 100644
index 000000000..cb61a0514
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/in/optional/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "optional-dependency"
+version = "0.1.3"
+
+[dependencies]
+my-package = "0.1.1"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/in/optional/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/in/optional/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/in/optional/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/in/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/in/primary/Cargo.toml
new file mode 100644
index 000000000..5e20016d7
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/in/primary/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/in/primary/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/in/primary/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/in/primary/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/mod.rs
new file mode 100644
index 000000000..22733b883
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = project_root.join("primary");
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("your-face --path ../dependency")
+ .current_dir(&cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/out/Cargo.toml
new file mode 100644
index 000000000..299859e79
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/out/Cargo.toml
@@ -0,0 +1,2 @@
+[workspace]
+members = ["primary", "dependency", "optional"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/out/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/out/dependency/Cargo.toml
new file mode 100644
index 000000000..18041a53f
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/out/dependency/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "your-face"
+version = "0.1.3"
+
+[dependencies]
+my-package = "0.1.1"
+optional-dependency = { path = "../optional", optional = true }
+
+[features]
+default = ["mouth"]
+nose = []
+mouth = ["nose"]
+eyes = []
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/out/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/out/primary/Cargo.toml
new file mode 100644
index 000000000..2461d0932
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/out/primary/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+your-face = { version = "0.1.3", path = "../dependency" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/stderr.log
new file mode 100644
index 000000000..af6747fe8
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/stderr.log
@@ -0,0 +1,7 @@
+ Adding your-face (local) to dependencies.
+ Features:
+ + mouth
+ + nose
+ - eyes
+ - optional-dependency
+ Updating `dummy-registry` index
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/in/Cargo.toml
new file mode 100644
index 000000000..299859e79
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/in/Cargo.toml
@@ -0,0 +1,2 @@
+[workspace]
+members = ["primary", "dependency", "optional"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/in/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/in/dependency/Cargo.toml
new file mode 100644
index 000000000..18041a53f
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/in/dependency/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "your-face"
+version = "0.1.3"
+
+[dependencies]
+my-package = "0.1.1"
+optional-dependency = { path = "../optional", optional = true }
+
+[features]
+default = ["mouth"]
+nose = []
+mouth = ["nose"]
+eyes = []
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/in/dependency/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/in/dependency/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/in/dependency/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/in/optional/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/in/optional/Cargo.toml
new file mode 100644
index 000000000..cb61a0514
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/in/optional/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "optional-dependency"
+version = "0.1.3"
+
+[dependencies]
+my-package = "0.1.1"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/in/optional/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/in/optional/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/in/optional/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/in/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/in/primary/Cargo.toml
new file mode 100644
index 000000000..5e20016d7
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/in/primary/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/in/primary/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/in/primary/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/in/primary/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/mod.rs
new file mode 100644
index 000000000..f520b2aca
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/mod.rs
@@ -0,0 +1,30 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = project_root.join("primary");
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .args([
+ "your-face",
+ "--path",
+ "../dependency",
+ "--no-default-features",
+ ])
+ .current_dir(&cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/out/Cargo.toml
new file mode 100644
index 000000000..299859e79
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/out/Cargo.toml
@@ -0,0 +1,2 @@
+[workspace]
+members = ["primary", "dependency", "optional"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/out/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/out/dependency/Cargo.toml
new file mode 100644
index 000000000..18041a53f
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/out/dependency/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "your-face"
+version = "0.1.3"
+
+[dependencies]
+my-package = "0.1.1"
+optional-dependency = { path = "../optional", optional = true }
+
+[features]
+default = ["mouth"]
+nose = []
+mouth = ["nose"]
+eyes = []
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/out/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/out/primary/Cargo.toml
new file mode 100644
index 000000000..0b0400d51
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/out/primary/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+your-face = { version = "0.1.3", path = "../dependency", default-features = false }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/stderr.log
new file mode 100644
index 000000000..7f47a220e
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/stderr.log
@@ -0,0 +1,7 @@
+ Adding your-face (local) to dependencies.
+ Features:
+ - eyes
+ - mouth
+ - nose
+ - optional-dependency
+ Updating `dummy-registry` index
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/list_features_path_no_default/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/locked_changed/in b/src/tools/cargo/tests/testsuite/cargo_add/locked_changed/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/locked_changed/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/locked_changed/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/locked_changed/mod.rs
new file mode 100644
index 000000000..9e3e57fe5
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/locked_changed/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package --locked")
+ .current_dir(cwd)
+ .assert()
+ .failure()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/locked_changed/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/locked_changed/out/Cargo.toml
new file mode 100644
index 000000000..3ecdb6681
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/locked_changed/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/locked_changed/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/locked_changed/stderr.log
new file mode 100644
index 000000000..8af168373
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/locked_changed/stderr.log
@@ -0,0 +1,3 @@
+ Updating `dummy-registry` index
+ Adding my-package v99999.0.0 to dependencies.
+error: the manifest file [ROOT]/case/Cargo.toml needs to be updated but --locked was passed to prevent this
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/locked_changed/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/locked_changed/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/locked_changed/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/locked_unchanged/in/Cargo.lock b/src/tools/cargo/tests/testsuite/cargo_add/locked_unchanged/in/Cargo.lock
new file mode 100644
index 000000000..011b33592
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/locked_unchanged/in/Cargo.lock
@@ -0,0 +1,16 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+dependencies = [
+ "my-package",
+]
+
+[[package]]
+name = "my-package"
+version = "99999.0.0+my-package"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62c45acf9e11d2f97f5b318143219c0b4102eafef1c22a4b545b47104691d915"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/locked_unchanged/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/locked_unchanged/in/Cargo.toml
new file mode 100644
index 000000000..5964c87be
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/locked_unchanged/in/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+my-package = "99999.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/locked_unchanged/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/locked_unchanged/in/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/locked_unchanged/in/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/locked_unchanged/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/locked_unchanged/mod.rs
new file mode 100644
index 000000000..aba9918f7
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/locked_unchanged/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package --locked")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/locked_unchanged/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/locked_unchanged/out/Cargo.toml
new file mode 100644
index 000000000..5964c87be
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/locked_unchanged/out/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+my-package = "99999.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/locked_unchanged/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/locked_unchanged/stderr.log
new file mode 100644
index 000000000..fd6b711e3
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/locked_unchanged/stderr.log
@@ -0,0 +1,2 @@
+ Updating `dummy-registry` index
+ Adding my-package v99999.0.0 to dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/locked_unchanged/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/locked_unchanged/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/locked_unchanged/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/lockfile_updated/in/Cargo.lock b/src/tools/cargo/tests/testsuite/cargo_add/lockfile_updated/in/Cargo.lock
new file mode 100644
index 000000000..d9bcc988d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/lockfile_updated/in/Cargo.lock
@@ -0,0 +1,17 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+dependencies = [
+ "my-package",
+ "unrelateed-crate",
+]
+
+[[package]]
+name = "unrelateed-crate"
+version = "0.2.0+my-package"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "266de4849a570b5dfda5e8e082a2aff885e9d2d4965dae8f8b6c8535e1ec731f"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/lockfile_updated/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/lockfile_updated/in/Cargo.toml
new file mode 100644
index 000000000..95276d7c5
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/lockfile_updated/in/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+unrelateed-crate = "0.2.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/lockfile_updated/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/lockfile_updated/in/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/lockfile_updated/in/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/lockfile_updated/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/lockfile_updated/mod.rs
new file mode 100644
index 000000000..33889dffa
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/lockfile_updated/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/lockfile_updated/out/Cargo.lock b/src/tools/cargo/tests/testsuite/cargo_add/lockfile_updated/out/Cargo.lock
new file mode 100644
index 000000000..4b5fb465f
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/lockfile_updated/out/Cargo.lock
@@ -0,0 +1,23 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+dependencies = [
+ "my-package",
+ "unrelateed-crate",
+]
+
+[[package]]
+name = "my-package"
+version = "99999.0.0+my-package"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62c45acf9e11d2f97f5b318143219c0b4102eafef1c22a4b545b47104691d915"
+
+[[package]]
+name = "unrelateed-crate"
+version = "0.2.0+my-package"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "266de4849a570b5dfda5e8e082a2aff885e9d2d4965dae8f8b6c8535e1ec731f"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/lockfile_updated/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/lockfile_updated/out/Cargo.toml
new file mode 100644
index 000000000..3176a986a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/lockfile_updated/out/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+my-package = "99999.0.0"
+unrelateed-crate = "0.2.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/lockfile_updated/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/lockfile_updated/stderr.log
new file mode 100644
index 000000000..fd6b711e3
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/lockfile_updated/stderr.log
@@ -0,0 +1,2 @@
+ Updating `dummy-registry` index
+ Adding my-package v99999.0.0 to dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/lockfile_updated/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/lockfile_updated/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/lockfile_updated/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/in/Cargo.toml
new file mode 100644
index 000000000..57e1f3085
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/in/Cargo.toml
@@ -0,0 +1,2 @@
+[workspace]
+members = ["primary", "dependency"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/in/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/in/dependency/Cargo.toml
new file mode 100644
index 000000000..ca4f36d72
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/in/dependency/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/in/dependency/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/in/dependency/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/in/dependency/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/in/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/in/primary/Cargo.toml
new file mode 100644
index 000000000..5e20016d7
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/in/primary/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/in/primary/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/in/primary/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/in/primary/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/mod.rs
new file mode 100644
index 000000000..008c2d33d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/mod.rs
@@ -0,0 +1,31 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .args([
+ "--manifest-path",
+ "Cargo.toml",
+ "--package",
+ "cargo-list-test-fixture",
+ "cargo-list-test-fixture-dependency",
+ ])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/out/Cargo.toml
new file mode 100644
index 000000000..57e1f3085
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/out/Cargo.toml
@@ -0,0 +1,2 @@
+[workspace]
+members = ["primary", "dependency"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/out/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/out/dependency/Cargo.toml
new file mode 100644
index 000000000..ca4f36d72
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/out/dependency/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/out/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/out/primary/Cargo.toml
new file mode 100644
index 000000000..a693df54f
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/out/primary/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+cargo-list-test-fixture-dependency = { version = "0.0.0", path = "../dependency" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/stderr.log
new file mode 100644
index 000000000..8109d3cc5
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/stderr.log
@@ -0,0 +1 @@
+ Adding cargo-list-test-fixture-dependency (local) to dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/manifest_path_package/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/in/Cargo.toml
new file mode 100644
index 000000000..b1d9b3995
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/in/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+members = ["primary", "dependency"]
+
+[workspace.dependencies]
+foo = { version = "0.0.0", path = "./dependency", features = ["merge"] }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/in/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/in/dependency/Cargo.toml
new file mode 100644
index 000000000..f34d7a685
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/in/dependency/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "foo"
+version = "0.0.0"
+
+[features]
+default-base = []
+default-test-base = []
+default-merge-base = []
+default = ["default-base", "default-test-base", "default-merge-base"]
+test-base = []
+test = ["test-base", "default-test-base"]
+merge-base = []
+merge = ["merge-base", "default-merge-base"]
+unrelated = [] \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/in/dependency/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/in/dependency/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/in/dependency/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/in/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/in/primary/Cargo.toml
new file mode 100644
index 000000000..a131c946d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/in/primary/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "bar"
+version = "0.0.0"
+
+[dependencies]
+foo = { workspace = true, features = ["test"] } \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/in/primary/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/in/primary/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/in/primary/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/mod.rs
new file mode 100644
index 000000000..161783282
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/mod.rs
@@ -0,0 +1,23 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .args(["foo", "-p", "bar"])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/out/Cargo.toml
new file mode 100644
index 000000000..b1d9b3995
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+members = ["primary", "dependency"]
+
+[workspace.dependencies]
+foo = { version = "0.0.0", path = "./dependency", features = ["merge"] }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/out/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/out/dependency/Cargo.toml
new file mode 100644
index 000000000..f34d7a685
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/out/dependency/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "foo"
+version = "0.0.0"
+
+[features]
+default-base = []
+default-test-base = []
+default-merge-base = []
+default = ["default-base", "default-test-base", "default-merge-base"]
+test-base = []
+test = ["test-base", "default-test-base"]
+merge-base = []
+merge = ["merge-base", "default-merge-base"]
+unrelated = [] \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/out/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/out/primary/Cargo.toml
new file mode 100644
index 000000000..fb4a12619
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/out/primary/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "bar"
+version = "0.0.0"
+
+[dependencies]
+foo = { workspace = true, features = ["test"] }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/stderr.log
new file mode 100644
index 000000000..02dde7a34
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/stderr.log
@@ -0,0 +1,10 @@
+ Adding foo (workspace) to dependencies.
+ Features as of v0.0.0:
+ + default-base
+ + default-merge-base
+ + default-test-base
+ + merge
+ + merge-base
+ + test
+ + test-base
+ - unrelated
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/merge_activated_features/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/mod.rs
new file mode 100644
index 000000000..ca58474d2
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/mod.rs
@@ -0,0 +1,203 @@
+mod add_basic;
+mod add_multiple;
+mod add_normalized_name_external;
+mod build;
+mod build_prefer_existing_version;
+mod change_rename_target;
+mod cyclic_features;
+mod default_features;
+mod deprecated_default_features;
+mod deprecated_section;
+mod detect_workspace_inherit;
+mod detect_workspace_inherit_features;
+mod detect_workspace_inherit_optional;
+mod dev;
+mod dev_build_conflict;
+mod dev_prefer_existing_version;
+mod dry_run;
+mod features;
+mod features_empty;
+mod features_multiple_occurrences;
+mod features_preserve;
+mod features_spaced_values;
+mod features_unknown;
+mod features_unknown_no_features;
+mod git;
+mod git_branch;
+mod git_conflicts_namever;
+mod git_dev;
+mod git_inferred_name;
+mod git_inferred_name_multiple;
+mod git_multiple_names;
+mod git_normalized_name;
+mod git_registry;
+mod git_rev;
+mod git_tag;
+mod infer_prerelease;
+mod invalid_arg;
+mod invalid_git_external;
+mod invalid_git_name;
+mod invalid_key_inherit_dependency;
+mod invalid_key_overwrite_inherit_dependency;
+mod invalid_key_rename_inherit_dependency;
+mod invalid_manifest;
+mod invalid_name_external;
+mod invalid_path;
+mod invalid_path_name;
+mod invalid_path_self;
+mod invalid_target_empty;
+mod invalid_vers;
+mod list_features;
+mod list_features_path;
+mod list_features_path_no_default;
+mod locked_changed;
+mod locked_unchanged;
+mod lockfile_updated;
+mod manifest_path_package;
+mod merge_activated_features;
+mod multiple_conflicts_with_features;
+mod multiple_conflicts_with_rename;
+mod namever;
+mod no_args;
+mod no_default_features;
+mod no_optional;
+mod offline_empty_cache;
+mod optional;
+mod overwrite_default_features;
+mod overwrite_default_features_with_no_default_features;
+mod overwrite_features;
+mod overwrite_git_with_path;
+mod overwrite_inherit_features_noop;
+mod overwrite_inherit_noop;
+mod overwrite_inherit_optional_noop;
+mod overwrite_inline_features;
+mod overwrite_name_dev_noop;
+mod overwrite_name_noop;
+mod overwrite_no_default_features;
+mod overwrite_no_default_features_with_default_features;
+mod overwrite_no_optional;
+mod overwrite_no_optional_with_optional;
+mod overwrite_optional;
+mod overwrite_optional_with_no_optional;
+mod overwrite_path_noop;
+mod overwrite_path_with_version;
+mod overwrite_preserves_inline_table;
+mod overwrite_rename_with_no_rename;
+mod overwrite_rename_with_rename;
+mod overwrite_rename_with_rename_noop;
+mod overwrite_version_with_git;
+mod overwrite_version_with_path;
+mod overwrite_with_rename;
+mod overwrite_workspace_dep;
+mod overwrite_workspace_dep_features;
+mod path;
+mod path_dev;
+mod path_inferred_name;
+mod path_inferred_name_conflicts_full_feature;
+mod path_normalized_name;
+mod preserve_sorted;
+mod preserve_unsorted;
+mod quiet;
+mod registry;
+mod rename;
+mod require_weak;
+mod sorted_table_with_dotted_item;
+mod target;
+mod target_cfg;
+mod unknown_inherited_feature;
+mod vers;
+mod workspace_name;
+mod workspace_path;
+mod workspace_path_dev;
+
+fn init_registry() {
+ cargo_test_support::registry::init();
+ add_registry_packages(false);
+}
+
+fn init_alt_registry() {
+ cargo_test_support::registry::alt_init();
+ add_registry_packages(true);
+}
+
+fn add_registry_packages(alt: bool) {
+ for name in [
+ "my-package",
+ "my-package1",
+ "my-package2",
+ "my-dev-package1",
+ "my-dev-package2",
+ "my-build-package1",
+ "my-build-package2",
+ "toml",
+ "versioned-package",
+ "cargo-list-test-fixture-dependency",
+ "unrelateed-crate",
+ ] {
+ cargo_test_support::registry::Package::new(name, "0.1.1+my-package")
+ .alternative(alt)
+ .publish();
+ cargo_test_support::registry::Package::new(name, "0.2.0+my-package")
+ .alternative(alt)
+ .publish();
+ cargo_test_support::registry::Package::new(name, "0.2.3+my-package")
+ .alternative(alt)
+ .publish();
+ cargo_test_support::registry::Package::new(name, "0.4.1+my-package")
+ .alternative(alt)
+ .publish();
+ cargo_test_support::registry::Package::new(name, "20.0.0+my-package")
+ .alternative(alt)
+ .publish();
+ cargo_test_support::registry::Package::new(name, "99999.0.0+my-package")
+ .alternative(alt)
+ .publish();
+ cargo_test_support::registry::Package::new(name, "99999.0.0-alpha.1+my-package")
+ .alternative(alt)
+ .publish();
+ }
+
+ cargo_test_support::registry::Package::new("prerelease_only", "0.2.0-alpha.1")
+ .alternative(alt)
+ .publish();
+ cargo_test_support::registry::Package::new("test_breaking", "0.2.0")
+ .alternative(alt)
+ .publish();
+ cargo_test_support::registry::Package::new("test_nonbreaking", "0.1.1")
+ .alternative(alt)
+ .publish();
+ cargo_test_support::registry::Package::new("test_cyclic_features", "0.1.1")
+ .alternative(alt)
+ .feature("default", &["feature-one", "feature-two"])
+ .feature("feature-one", &["feature-two"])
+ .feature("feature-two", &["feature-one"])
+ .publish();
+
+ // Normalization
+ cargo_test_support::registry::Package::new("linked-hash-map", "0.5.4")
+ .alternative(alt)
+ .feature("clippy", &[])
+ .feature("heapsize", &[])
+ .feature("heapsize_impl", &[])
+ .feature("nightly", &[])
+ .feature("serde", &[])
+ .feature("serde_impl", &[])
+ .feature("serde_test", &[])
+ .publish();
+ cargo_test_support::registry::Package::new("inflector", "0.11.4")
+ .alternative(alt)
+ .feature("default", &["heavyweight", "lazy_static", "regex"])
+ .feature("heavyweight", &[])
+ .feature("lazy_static", &[])
+ .feature("regex", &[])
+ .feature("unstable", &[])
+ .publish();
+
+ cargo_test_support::registry::Package::new("your-face", "99999.0.0+my-package")
+ .alternative(alt)
+ .feature("nose", &[])
+ .feature("mouth", &[])
+ .feature("eyes", &[])
+ .feature("ears", &[])
+ .publish();
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_features/in b/src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_features/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_features/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_features/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_features/mod.rs
new file mode 100644
index 000000000..10f824484
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_features/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package1 your-face --features nose")
+ .current_dir(cwd)
+ .assert()
+ .code(101)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_features/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_features/out/Cargo.toml
new file mode 100644
index 000000000..3ecdb6681
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_features/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_features/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_features/stderr.log
new file mode 100644
index 000000000..72fd9fc9d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_features/stderr.log
@@ -0,0 +1 @@
+error: feature `nose` must be qualified by the dependency it's being activated for, like `my-package1/nose`, `your-face/nose`
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_features/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_features/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_features/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_rename/in b/src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_rename/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_rename/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_rename/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_rename/mod.rs
new file mode 100644
index 000000000..293ed3eea
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_rename/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package1 my-package2 --rename renamed")
+ .current_dir(cwd)
+ .assert()
+ .code(101)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_rename/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_rename/out/Cargo.toml
new file mode 100644
index 000000000..3ecdb6681
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_rename/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_rename/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_rename/stderr.log
new file mode 100644
index 000000000..e83250e73
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_rename/stderr.log
@@ -0,0 +1 @@
+error: cannot specify multiple crates with `--rename`
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_rename/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_rename/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/multiple_conflicts_with_rename/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/namever/in b/src/tools/cargo/tests/testsuite/cargo_add/namever/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/namever/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/namever/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/namever/mod.rs
new file mode 100644
index 000000000..90fda1a9f
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/namever/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package1@>=0.1.1 my-package2@0.2.3 my-package")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/namever/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/namever/out/Cargo.toml
new file mode 100644
index 000000000..1704d3435
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/namever/out/Cargo.toml
@@ -0,0 +1,10 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+my-package = "99999.0.0"
+my-package1 = ">=0.1.1"
+my-package2 = "0.2.3"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/namever/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/namever/stderr.log
new file mode 100644
index 000000000..17be8f9d8
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/namever/stderr.log
@@ -0,0 +1,4 @@
+ Updating `dummy-registry` index
+ Adding my-package1 >=0.1.1 to dependencies.
+ Adding my-package2 v0.2.3 to dependencies.
+ Adding my-package v99999.0.0 to dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/namever/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/namever/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/namever/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/no_args/in b/src/tools/cargo/tests/testsuite/cargo_add/no_args/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/no_args/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/no_args/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/no_args/mod.rs
new file mode 100644
index 000000000..7eca17b56
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/no_args/mod.rs
@@ -0,0 +1,24 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .current_dir(cwd)
+ .assert()
+ .code(1)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/no_args/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/no_args/out/Cargo.toml
new file mode 100644
index 000000000..3ecdb6681
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/no_args/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/no_args/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/no_args/stderr.log
new file mode 100644
index 000000000..0274950a5
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/no_args/stderr.log
@@ -0,0 +1,8 @@
+error: the following required arguments were not provided:
+ <DEP_ID|--path <PATH>|--git <URI>>
+
+Usage: cargo add [OPTIONS] <DEP>[@<VERSION>] ...
+ cargo add [OPTIONS] --path <PATH> ...
+ cargo add [OPTIONS] --git <URL> ...
+
+For more information, try '--help'.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/no_args/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/no_args/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/no_args/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/no_default_features/in b/src/tools/cargo/tests/testsuite/cargo_add/no_default_features/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/no_default_features/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/no_default_features/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/no_default_features/mod.rs
new file mode 100644
index 000000000..e72ca3be2
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/no_default_features/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package1 my-package2@0.4.1 --no-default-features")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/no_default_features/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/no_default_features/out/Cargo.toml
new file mode 100644
index 000000000..ddd02b1f0
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/no_default_features/out/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+my-package1 = { version = "99999.0.0", default-features = false }
+my-package2 = { version = "0.4.1", default-features = false }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/no_default_features/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/no_default_features/stderr.log
new file mode 100644
index 000000000..fb8d4903d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/no_default_features/stderr.log
@@ -0,0 +1,3 @@
+ Updating `dummy-registry` index
+ Adding my-package1 v99999.0.0 to dependencies.
+ Adding my-package2 v0.4.1 to dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/no_default_features/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/no_default_features/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/no_default_features/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/no_optional/in b/src/tools/cargo/tests/testsuite/cargo_add/no_optional/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/no_optional/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/no_optional/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/no_optional/mod.rs
new file mode 100644
index 000000000..fdb983b21
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/no_optional/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package1 my-package2@0.4.1 --no-optional")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/no_optional/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/no_optional/out/Cargo.toml
new file mode 100644
index 000000000..c5e017892
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/no_optional/out/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+my-package1 = "99999.0.0"
+my-package2 = "0.4.1"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/no_optional/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/no_optional/stderr.log
new file mode 100644
index 000000000..fb8d4903d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/no_optional/stderr.log
@@ -0,0 +1,3 @@
+ Updating `dummy-registry` index
+ Adding my-package1 v99999.0.0 to dependencies.
+ Adding my-package2 v0.4.1 to dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/no_optional/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/no_optional/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/no_optional/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/offline_empty_cache/in b/src/tools/cargo/tests/testsuite/cargo_add/offline_empty_cache/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/offline_empty_cache/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/offline_empty_cache/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/offline_empty_cache/mod.rs
new file mode 100644
index 000000000..ae7485979
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/offline_empty_cache/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("--offline my-package")
+ .current_dir(cwd)
+ .assert()
+ .code(101)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/offline_empty_cache/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/offline_empty_cache/out/Cargo.toml
new file mode 100644
index 000000000..3ecdb6681
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/offline_empty_cache/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/offline_empty_cache/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/offline_empty_cache/stderr.log
new file mode 100644
index 000000000..e0260b795
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/offline_empty_cache/stderr.log
@@ -0,0 +1 @@
+error: the crate `my-package` could not be found in registry index.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/offline_empty_cache/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/offline_empty_cache/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/offline_empty_cache/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/optional/in b/src/tools/cargo/tests/testsuite/cargo_add/optional/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/optional/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/optional/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/optional/mod.rs
new file mode 100644
index 000000000..94d1cbf34
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/optional/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package1 my-package2@0.4.1 --optional")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/optional/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/optional/out/Cargo.toml
new file mode 100644
index 000000000..eda5445c5
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/optional/out/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+my-package1 = { version = "99999.0.0", optional = true }
+my-package2 = { version = "0.4.1", optional = true }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/optional/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/optional/stderr.log
new file mode 100644
index 000000000..8cf4812cf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/optional/stderr.log
@@ -0,0 +1,3 @@
+ Updating `dummy-registry` index
+ Adding my-package1 v99999.0.0 to optional dependencies.
+ Adding my-package2 v0.4.1 to optional dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/optional/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/optional/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/optional/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features/in/Cargo.toml
new file mode 100644
index 000000000..c5e017892
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features/in/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+my-package1 = "99999.0.0"
+my-package2 = "0.4.1"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features/in/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features/in/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features/mod.rs
new file mode 100644
index 000000000..88bdd8065
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package1 my-package2@0.4.1 --default-features")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features/out/Cargo.toml
new file mode 100644
index 000000000..c5e017892
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features/out/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+my-package1 = "99999.0.0"
+my-package2 = "0.4.1"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features/stderr.log
new file mode 100644
index 000000000..fb8d4903d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features/stderr.log
@@ -0,0 +1,3 @@
+ Updating `dummy-registry` index
+ Adding my-package1 v99999.0.0 to dependencies.
+ Adding my-package2 v0.4.1 to dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features_with_no_default_features/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features_with_no_default_features/in/Cargo.toml
new file mode 100644
index 000000000..73f56a7a3
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features_with_no_default_features/in/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+my-package1 = { version = "99999.0.0", default-features = true }
+my-package2 = { version = "0.4.1", default-features = true }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features_with_no_default_features/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features_with_no_default_features/in/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features_with_no_default_features/in/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features_with_no_default_features/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features_with_no_default_features/mod.rs
new file mode 100644
index 000000000..e72ca3be2
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features_with_no_default_features/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package1 my-package2@0.4.1 --no-default-features")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features_with_no_default_features/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features_with_no_default_features/out/Cargo.toml
new file mode 100644
index 000000000..ddd02b1f0
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features_with_no_default_features/out/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+my-package1 = { version = "99999.0.0", default-features = false }
+my-package2 = { version = "0.4.1", default-features = false }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features_with_no_default_features/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features_with_no_default_features/stderr.log
new file mode 100644
index 000000000..fb8d4903d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features_with_no_default_features/stderr.log
@@ -0,0 +1,3 @@
+ Updating `dummy-registry` index
+ Adding my-package1 v99999.0.0 to dependencies.
+ Adding my-package2 v0.4.1 to dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features_with_no_default_features/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features_with_no_default_features/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_default_features_with_no_default_features/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_features/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_features/in/Cargo.toml
new file mode 100644
index 000000000..11419b203
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_features/in/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+your-face = { version = "99999.0.0", features = ["eyes"] }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_features/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_features/in/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_features/in/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_features/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_features/mod.rs
new file mode 100644
index 000000000..0b2ab18b8
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_features/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("your-face --features nose")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_features/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_features/out/Cargo.toml
new file mode 100644
index 000000000..0060d24bc
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_features/out/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+your-face = { version = "99999.0.0", features = ["eyes", "nose"] }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_features/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_features/stderr.log
new file mode 100644
index 000000000..615459052
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_features/stderr.log
@@ -0,0 +1,7 @@
+ Updating `dummy-registry` index
+ Adding your-face v99999.0.0 to dependencies.
+ Features:
+ + eyes
+ + nose
+ - ears
+ - mouth
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_features/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_features/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_features/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/in/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/in/dependency/Cargo.toml
new file mode 100644
index 000000000..cbe244113
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/in/dependency/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/in/dependency/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/in/dependency/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/in/dependency/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/in/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/in/primary/Cargo.toml
new file mode 100644
index 000000000..6cb4d6a7d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/in/primary/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+cargo-list-test-fixture-dependency = { git = "git://git.git", branch = "main", optional = true }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/in/primary/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/in/primary/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/in/primary/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/mod.rs
new file mode 100644
index 000000000..ab89e3a6d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = project_root.join("primary");
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("cargo-list-test-fixture-dependency --path ../dependency")
+ .current_dir(&cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/out/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/out/dependency/Cargo.toml
new file mode 100644
index 000000000..cbe244113
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/out/dependency/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/out/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/out/primary/Cargo.toml
new file mode 100644
index 000000000..ad1205481
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/out/primary/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+cargo-list-test-fixture-dependency = { optional = true, path = "../dependency", version = "0.0.0" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/stderr.log
new file mode 100644
index 000000000..98abcfc99
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/stderr.log
@@ -0,0 +1 @@
+ Adding cargo-list-test-fixture-dependency (local) to optional dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_git_with_path/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/in/Cargo.toml
new file mode 100644
index 000000000..24c50556b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/in/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+members = ["primary", "dependency"]
+
+[workspace.dependencies]
+foo = { version = "0.0.0", path = "./dependency"} \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/in/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/in/dependency/Cargo.toml
new file mode 100644
index 000000000..bed932047
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/in/dependency/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "foo"
+version = "0.0.0"
+
+[features]
+test = [] \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/in/dependency/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/in/dependency/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/in/dependency/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/in/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/in/primary/Cargo.toml
new file mode 100644
index 000000000..a131c946d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/in/primary/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "bar"
+version = "0.0.0"
+
+[dependencies]
+foo = { workspace = true, features = ["test"] } \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/in/primary/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/in/primary/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/in/primary/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/mod.rs
new file mode 100644
index 000000000..161783282
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/mod.rs
@@ -0,0 +1,23 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .args(["foo", "-p", "bar"])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/out/Cargo.toml
new file mode 100644
index 000000000..24c50556b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+members = ["primary", "dependency"]
+
+[workspace.dependencies]
+foo = { version = "0.0.0", path = "./dependency"} \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/out/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/out/dependency/Cargo.toml
new file mode 100644
index 000000000..bed932047
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/out/dependency/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "foo"
+version = "0.0.0"
+
+[features]
+test = [] \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/out/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/out/primary/Cargo.toml
new file mode 100644
index 000000000..fb4a12619
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/out/primary/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "bar"
+version = "0.0.0"
+
+[dependencies]
+foo = { workspace = true, features = ["test"] }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/stderr.log
new file mode 100644
index 000000000..3c7133bbc
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/stderr.log
@@ -0,0 +1,3 @@
+ Adding foo (workspace) to dependencies.
+ Features as of v0.0.0:
+ + test
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_features_noop/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/in/Cargo.toml
new file mode 100644
index 000000000..24c50556b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/in/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+members = ["primary", "dependency"]
+
+[workspace.dependencies]
+foo = { version = "0.0.0", path = "./dependency"} \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/in/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/in/dependency/Cargo.toml
new file mode 100644
index 000000000..2d247d4d2
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/in/dependency/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "foo"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/in/dependency/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/in/dependency/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/in/dependency/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/in/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/in/primary/Cargo.toml
new file mode 100644
index 000000000..2ac789d55
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/in/primary/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "bar"
+version = "0.0.0"
+
+[dependencies]
+foo.workspace = true \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/in/primary/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/in/primary/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/in/primary/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/mod.rs
new file mode 100644
index 000000000..065fb4f93
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .args(["foo", "-p", "bar"])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/out/Cargo.toml
new file mode 100644
index 000000000..24c50556b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+members = ["primary", "dependency"]
+
+[workspace.dependencies]
+foo = { version = "0.0.0", path = "./dependency"} \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/out/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/out/dependency/Cargo.toml
new file mode 100644
index 000000000..2d247d4d2
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/out/dependency/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "foo"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/out/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/out/primary/Cargo.toml
new file mode 100644
index 000000000..a5740941b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/out/primary/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "bar"
+version = "0.0.0"
+
+[dependencies]
+foo.workspace = true
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/stderr.log
new file mode 100644
index 000000000..d2efcc0c0
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/stderr.log
@@ -0,0 +1 @@
+ Adding foo (workspace) to dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_noop/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/in/Cargo.toml
new file mode 100644
index 000000000..24c50556b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/in/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+members = ["primary", "dependency"]
+
+[workspace.dependencies]
+foo = { version = "0.0.0", path = "./dependency"} \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/in/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/in/dependency/Cargo.toml
new file mode 100644
index 000000000..2d247d4d2
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/in/dependency/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "foo"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/in/dependency/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/in/dependency/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/in/dependency/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/in/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/in/primary/Cargo.toml
new file mode 100644
index 000000000..228aef664
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/in/primary/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "bar"
+version = "0.0.0"
+
+[dependencies]
+foo = { workspace = true, optional = true } \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/in/primary/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/in/primary/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/in/primary/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/mod.rs
new file mode 100644
index 000000000..065fb4f93
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .args(["foo", "-p", "bar"])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/out/Cargo.toml
new file mode 100644
index 000000000..24c50556b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+members = ["primary", "dependency"]
+
+[workspace.dependencies]
+foo = { version = "0.0.0", path = "./dependency"} \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/out/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/out/dependency/Cargo.toml
new file mode 100644
index 000000000..2d247d4d2
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/out/dependency/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "foo"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/out/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/out/primary/Cargo.toml
new file mode 100644
index 000000000..6dd7fb6d6
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/out/primary/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "bar"
+version = "0.0.0"
+
+[dependencies]
+foo = { workspace = true, optional = true }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/stderr.log
new file mode 100644
index 000000000..da03b11f7
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/stderr.log
@@ -0,0 +1 @@
+ Adding foo (workspace) to optional dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inherit_optional_noop/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inline_features/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inline_features/in/Cargo.toml
new file mode 100644
index 000000000..11419b203
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inline_features/in/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+your-face = { version = "99999.0.0", features = ["eyes"] }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inline_features/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inline_features/in/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inline_features/in/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inline_features/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inline_features/mod.rs
new file mode 100644
index 000000000..356b4d788
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inline_features/mod.rs
@@ -0,0 +1,27 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line(
+ "unrelateed-crate your-face --features your-face/nose,your-face/mouth -Fyour-face/ears",
+ )
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inline_features/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inline_features/out/Cargo.toml
new file mode 100644
index 000000000..8e9579dc6
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inline_features/out/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+unrelateed-crate = "99999.0.0"
+your-face = { version = "99999.0.0", features = ["eyes", "nose", "mouth", "ears"] }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inline_features/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inline_features/stderr.log
new file mode 100644
index 000000000..a686cba55
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inline_features/stderr.log
@@ -0,0 +1,8 @@
+ Updating `dummy-registry` index
+ Adding unrelateed-crate v99999.0.0 to dependencies.
+ Adding your-face v99999.0.0 to dependencies.
+ Features:
+ + ears
+ + eyes
+ + mouth
+ + nose
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inline_features/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inline_features/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_inline_features/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/in/Cargo.toml
new file mode 100644
index 000000000..b69b5d38e
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/in/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+exclude = ["dependency"]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dev-dependencies]
+your-face = { version = "0.0.0", path = "dependency", default-features = false, features = ["nose", "mouth"], registry = "alternative" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/in/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/in/dependency/Cargo.toml
new file mode 100644
index 000000000..8243797eb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/in/dependency/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "your-face"
+version = "0.0.0"
+
+[features]
+mouth = []
+nose = []
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/in/dependency/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/in/dependency/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/in/dependency/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/in/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/in/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/mod.rs
new file mode 100644
index 000000000..b418c7809
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_alt_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_alt_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("your-face --dev")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/out/Cargo.toml
new file mode 100644
index 000000000..b69b5d38e
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/out/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+exclude = ["dependency"]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dev-dependencies]
+your-face = { version = "0.0.0", path = "dependency", default-features = false, features = ["nose", "mouth"], registry = "alternative" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/out/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/out/dependency/Cargo.toml
new file mode 100644
index 000000000..8243797eb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/out/dependency/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "your-face"
+version = "0.0.0"
+
+[features]
+mouth = []
+nose = []
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/stderr.log
new file mode 100644
index 000000000..2fe3f6a29
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/stderr.log
@@ -0,0 +1,4 @@
+ Adding your-face (local) to dev-dependencies.
+ Features:
+ + mouth
+ + nose
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_dev_noop/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/in/Cargo.toml
new file mode 100644
index 000000000..bbaf4f552
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/in/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+exclude = ["dependency"]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+your-face = { version = "0.0.0", path = "dependency", optional = true, default-features = false, features = ["nose", "mouth"], registry = "alternative" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/in/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/in/dependency/Cargo.toml
new file mode 100644
index 000000000..8243797eb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/in/dependency/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "your-face"
+version = "0.0.0"
+
+[features]
+mouth = []
+nose = []
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/in/dependency/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/in/dependency/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/in/dependency/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/in/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/in/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/mod.rs
new file mode 100644
index 000000000..193c5880b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_alt_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_alt_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("your-face")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/out/Cargo.toml
new file mode 100644
index 000000000..bbaf4f552
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/out/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+exclude = ["dependency"]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+your-face = { version = "0.0.0", path = "dependency", optional = true, default-features = false, features = ["nose", "mouth"], registry = "alternative" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/out/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/out/dependency/Cargo.toml
new file mode 100644
index 000000000..8243797eb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/out/dependency/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "your-face"
+version = "0.0.0"
+
+[features]
+mouth = []
+nose = []
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/stderr.log
new file mode 100644
index 000000000..2f0b90de0
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/stderr.log
@@ -0,0 +1,4 @@
+ Adding your-face (local) to optional dependencies.
+ Features:
+ + mouth
+ + nose
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_name_noop/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features/in/Cargo.toml
new file mode 100644
index 000000000..c5e017892
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features/in/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+my-package1 = "99999.0.0"
+my-package2 = "0.4.1"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features/in/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features/in/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features/mod.rs
new file mode 100644
index 000000000..e72ca3be2
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package1 my-package2@0.4.1 --no-default-features")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features/out/Cargo.toml
new file mode 100644
index 000000000..ddd02b1f0
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features/out/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+my-package1 = { version = "99999.0.0", default-features = false }
+my-package2 = { version = "0.4.1", default-features = false }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features/stderr.log
new file mode 100644
index 000000000..fb8d4903d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features/stderr.log
@@ -0,0 +1,3 @@
+ Updating `dummy-registry` index
+ Adding my-package1 v99999.0.0 to dependencies.
+ Adding my-package2 v0.4.1 to dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features_with_default_features/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features_with_default_features/in/Cargo.toml
new file mode 100644
index 000000000..ddd02b1f0
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features_with_default_features/in/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+my-package1 = { version = "99999.0.0", default-features = false }
+my-package2 = { version = "0.4.1", default-features = false }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features_with_default_features/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features_with_default_features/in/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features_with_default_features/in/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features_with_default_features/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features_with_default_features/mod.rs
new file mode 100644
index 000000000..88bdd8065
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features_with_default_features/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package1 my-package2@0.4.1 --default-features")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features_with_default_features/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features_with_default_features/out/Cargo.toml
new file mode 100644
index 000000000..b9e8985c6
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features_with_default_features/out/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+my-package1 = { version = "99999.0.0" }
+my-package2 = { version = "0.4.1" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features_with_default_features/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features_with_default_features/stderr.log
new file mode 100644
index 000000000..fb8d4903d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features_with_default_features/stderr.log
@@ -0,0 +1,3 @@
+ Updating `dummy-registry` index
+ Adding my-package1 v99999.0.0 to dependencies.
+ Adding my-package2 v0.4.1 to dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features_with_default_features/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features_with_default_features/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_default_features_with_default_features/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional/in/Cargo.toml
new file mode 100644
index 000000000..c5e017892
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional/in/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+my-package1 = "99999.0.0"
+my-package2 = "0.4.1"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional/in/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional/in/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional/mod.rs
new file mode 100644
index 000000000..fdb983b21
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package1 my-package2@0.4.1 --no-optional")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional/out/Cargo.toml
new file mode 100644
index 000000000..c5e017892
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional/out/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+my-package1 = "99999.0.0"
+my-package2 = "0.4.1"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional/stderr.log
new file mode 100644
index 000000000..fb8d4903d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional/stderr.log
@@ -0,0 +1,3 @@
+ Updating `dummy-registry` index
+ Adding my-package1 v99999.0.0 to dependencies.
+ Adding my-package2 v0.4.1 to dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional_with_optional/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional_with_optional/in/Cargo.toml
new file mode 100644
index 000000000..8cd2616d4
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional_with_optional/in/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+my-package1 = { version = "99999.0.0", optional = false }
+my-package2 = { version = "0.4.1", optional = false }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional_with_optional/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional_with_optional/in/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional_with_optional/in/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional_with_optional/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional_with_optional/mod.rs
new file mode 100644
index 000000000..94d1cbf34
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional_with_optional/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package1 my-package2@0.4.1 --optional")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional_with_optional/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional_with_optional/out/Cargo.toml
new file mode 100644
index 000000000..eda5445c5
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional_with_optional/out/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+my-package1 = { version = "99999.0.0", optional = true }
+my-package2 = { version = "0.4.1", optional = true }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional_with_optional/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional_with_optional/stderr.log
new file mode 100644
index 000000000..8cf4812cf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional_with_optional/stderr.log
@@ -0,0 +1,3 @@
+ Updating `dummy-registry` index
+ Adding my-package1 v99999.0.0 to optional dependencies.
+ Adding my-package2 v0.4.1 to optional dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional_with_optional/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional_with_optional/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_no_optional_with_optional/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional/in/Cargo.toml
new file mode 100644
index 000000000..c5e017892
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional/in/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+my-package1 = "99999.0.0"
+my-package2 = "0.4.1"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional/in/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional/in/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional/mod.rs
new file mode 100644
index 000000000..94d1cbf34
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package1 my-package2@0.4.1 --optional")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional/out/Cargo.toml
new file mode 100644
index 000000000..eda5445c5
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional/out/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+my-package1 = { version = "99999.0.0", optional = true }
+my-package2 = { version = "0.4.1", optional = true }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional/stderr.log
new file mode 100644
index 000000000..8cf4812cf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional/stderr.log
@@ -0,0 +1,3 @@
+ Updating `dummy-registry` index
+ Adding my-package1 v99999.0.0 to optional dependencies.
+ Adding my-package2 v0.4.1 to optional dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional_with_no_optional/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional_with_no_optional/in/Cargo.toml
new file mode 100644
index 000000000..5ef953209
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional_with_no_optional/in/Cargo.toml
@@ -0,0 +1,13 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[features]
+default = ["your-face"]
+other = ["your-face/nose"]
+
+[dependencies]
+your-face = { version = "99999.0.0", optional = true }
+my-package2 = { version = "0.4.1", optional = true }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional_with_no_optional/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional_with_no_optional/in/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional_with_no_optional/in/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional_with_no_optional/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional_with_no_optional/mod.rs
new file mode 100644
index 000000000..c34c293f9
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional_with_no_optional/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("your-face my-package2@0.4.1 --no-optional")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional_with_no_optional/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional_with_no_optional/out/Cargo.toml
new file mode 100644
index 000000000..bf6c52963
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional_with_no_optional/out/Cargo.toml
@@ -0,0 +1,13 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[features]
+default = []
+other = ["your-face/nose"]
+
+[dependencies]
+your-face = { version = "99999.0.0" }
+my-package2 = { version = "0.4.1" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional_with_no_optional/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional_with_no_optional/stderr.log
new file mode 100644
index 000000000..5fe113e86
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional_with_no_optional/stderr.log
@@ -0,0 +1,8 @@
+ Updating `dummy-registry` index
+ Adding your-face v99999.0.0 to dependencies.
+ Features:
+ - ears
+ - eyes
+ - mouth
+ - nose
+ Adding my-package2 v0.4.1 to dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional_with_no_optional/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional_with_no_optional/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_optional_with_no_optional/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/in/Cargo.toml
new file mode 100644
index 000000000..bbaf4f552
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/in/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+exclude = ["dependency"]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+your-face = { version = "0.0.0", path = "dependency", optional = true, default-features = false, features = ["nose", "mouth"], registry = "alternative" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/in/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/in/dependency/Cargo.toml
new file mode 100644
index 000000000..8243797eb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/in/dependency/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "your-face"
+version = "0.0.0"
+
+[features]
+mouth = []
+nose = []
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/in/dependency/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/in/dependency/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/in/dependency/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/in/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/in/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/mod.rs
new file mode 100644
index 000000000..f04405a34
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_alt_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_alt_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("your-face --path ./dependency")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/out/Cargo.toml
new file mode 100644
index 000000000..bbaf4f552
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/out/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+exclude = ["dependency"]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+your-face = { version = "0.0.0", path = "dependency", optional = true, default-features = false, features = ["nose", "mouth"], registry = "alternative" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/out/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/out/dependency/Cargo.toml
new file mode 100644
index 000000000..8243797eb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/out/dependency/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "your-face"
+version = "0.0.0"
+
+[features]
+mouth = []
+nose = []
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/stderr.log
new file mode 100644
index 000000000..2f0b90de0
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/stderr.log
@@ -0,0 +1,4 @@
+ Adding your-face (local) to optional dependencies.
+ Features:
+ + mouth
+ + nose
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_noop/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/in/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/in/dependency/Cargo.toml
new file mode 100644
index 000000000..cbe244113
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/in/dependency/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/in/dependency/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/in/dependency/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/in/dependency/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/in/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/in/primary/Cargo.toml
new file mode 100644
index 000000000..9d20b2240
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/in/primary/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+cargo-list-test-fixture-dependency = { optional = true, path = "../dependency" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/in/primary/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/in/primary/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/in/primary/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/mod.rs
new file mode 100644
index 000000000..32674e23d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = project_root.join("primary");
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("cargo-list-test-fixture-dependency@20.0")
+ .current_dir(&cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/out/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/out/dependency/Cargo.toml
new file mode 100644
index 000000000..cbe244113
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/out/dependency/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/out/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/out/primary/Cargo.toml
new file mode 100644
index 000000000..a20f2095d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/out/primary/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+cargo-list-test-fixture-dependency = { optional = true, version = "20.0" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/stderr.log
new file mode 100644
index 000000000..d0b3a4cf2
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/stderr.log
@@ -0,0 +1,2 @@
+ Updating `dummy-registry` index
+ Adding cargo-list-test-fixture-dependency v20.0 to optional dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_path_with_version/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_preserves_inline_table/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_preserves_inline_table/in/Cargo.toml
new file mode 100644
index 000000000..3dddbbd10
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_preserves_inline_table/in/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+your-face={version="99999.0.0",features=["eyes"]} # Hello world
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_preserves_inline_table/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_preserves_inline_table/in/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_preserves_inline_table/in/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_preserves_inline_table/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_preserves_inline_table/mod.rs
new file mode 100644
index 000000000..0b2ab18b8
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_preserves_inline_table/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("your-face --features nose")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_preserves_inline_table/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_preserves_inline_table/out/Cargo.toml
new file mode 100644
index 000000000..f204a895e
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_preserves_inline_table/out/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+your-face={ version = "99999.0.0", features = ["eyes", "nose"] } # Hello world
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_preserves_inline_table/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_preserves_inline_table/stderr.log
new file mode 100644
index 000000000..615459052
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_preserves_inline_table/stderr.log
@@ -0,0 +1,7 @@
+ Updating `dummy-registry` index
+ Adding your-face v99999.0.0 to dependencies.
+ Features:
+ + eyes
+ + nose
+ - ears
+ - mouth
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_preserves_inline_table/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_preserves_inline_table/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_preserves_inline_table/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_no_rename/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_no_rename/in/Cargo.toml
new file mode 100644
index 000000000..450229245
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_no_rename/in/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+a1 = { package = "versioned-package", version = "0.1.1", optional = true }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_no_rename/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_no_rename/in/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_no_rename/in/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_no_rename/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_no_rename/mod.rs
new file mode 100644
index 000000000..a006c95fd
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_no_rename/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("versioned-package")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_no_rename/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_no_rename/out/Cargo.toml
new file mode 100644
index 000000000..9951492da
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_no_rename/out/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+a1 = { package = "versioned-package", version = "0.1.1", optional = true }
+versioned-package = "99999.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_no_rename/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_no_rename/stderr.log
new file mode 100644
index 000000000..305b89f26
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_no_rename/stderr.log
@@ -0,0 +1,2 @@
+ Updating `dummy-registry` index
+ Adding versioned-package v99999.0.0 to dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_no_rename/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_no_rename/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_no_rename/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename/in/Cargo.toml
new file mode 100644
index 000000000..450229245
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename/in/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+a1 = { package = "versioned-package", version = "0.1.1", optional = true }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename/in/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename/in/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename/mod.rs
new file mode 100644
index 000000000..e14282bc1
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("versioned-package --rename a2")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename/out/Cargo.toml
new file mode 100644
index 000000000..790f6546c
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename/out/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+a1 = { package = "versioned-package", version = "0.1.1", optional = true }
+a2 = { version = "99999.0.0", package = "versioned-package" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename/stderr.log
new file mode 100644
index 000000000..305b89f26
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename/stderr.log
@@ -0,0 +1,2 @@
+ Updating `dummy-registry` index
+ Adding versioned-package v99999.0.0 to dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename_noop/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename_noop/in/Cargo.toml
new file mode 100644
index 000000000..450229245
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename_noop/in/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+a1 = { package = "versioned-package", version = "0.1.1", optional = true }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename_noop/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename_noop/in/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename_noop/in/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename_noop/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename_noop/mod.rs
new file mode 100644
index 000000000..c0ca2e552
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename_noop/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("versioned-package --rename a1")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename_noop/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename_noop/out/Cargo.toml
new file mode 100644
index 000000000..450229245
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename_noop/out/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+a1 = { package = "versioned-package", version = "0.1.1", optional = true }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename_noop/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename_noop/stderr.log
new file mode 100644
index 000000000..d69dc92cd
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename_noop/stderr.log
@@ -0,0 +1,2 @@
+ Updating `dummy-registry` index
+ Adding versioned-package v0.1.1 to optional dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename_noop/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename_noop/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_rename_with_rename_noop/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_git/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_git/in/Cargo.toml
new file mode 100644
index 000000000..fe41f2a90
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_git/in/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+versioned-package = { version = "0.1.1", optional = true }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_git/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_git/in/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_git/in/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_git/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_git/mod.rs
new file mode 100644
index 000000000..ce7a0acb0
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_git/mod.rs
@@ -0,0 +1,34 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+ let git_dep = cargo_test_support::git::new("versioned-package", |project| {
+ project
+ .file(
+ "Cargo.toml",
+ &cargo_test_support::basic_manifest("versioned-package", "0.3.0+versioned-package"),
+ )
+ .file("src/lib.rs", "")
+ });
+ let git_url = git_dep.url().to_string();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .args(["versioned-package", "--git", &git_url])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_git/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_git/out/Cargo.toml
new file mode 100644
index 000000000..260014024
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_git/out/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+versioned-package = { version = "0.3.0", optional = true, git = "[ROOTURL]/versioned-package" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_git/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_git/stderr.log
new file mode 100644
index 000000000..1b77cbe0e
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_git/stderr.log
@@ -0,0 +1,3 @@
+ Updating git repository `[ROOTURL]/versioned-package`
+ Adding versioned-package (git) to optional dependencies.
+ Updating git repository `[ROOTURL]/versioned-package`
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_git/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_git/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_git/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/in/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/in/dependency/Cargo.toml
new file mode 100644
index 000000000..cbe244113
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/in/dependency/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/in/dependency/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/in/dependency/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/in/dependency/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/in/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/in/primary/Cargo.toml
new file mode 100644
index 000000000..063b89192
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/in/primary/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+cargo-list-test-fixture-dependency = { version = "0.1.1", optional = true }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/in/primary/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/in/primary/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/in/primary/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/mod.rs
new file mode 100644
index 000000000..ab89e3a6d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = project_root.join("primary");
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("cargo-list-test-fixture-dependency --path ../dependency")
+ .current_dir(&cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/out/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/out/dependency/Cargo.toml
new file mode 100644
index 000000000..cbe244113
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/out/dependency/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/out/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/out/primary/Cargo.toml
new file mode 100644
index 000000000..07253670a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/out/primary/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+cargo-list-test-fixture-dependency = { version = "0.0.0", optional = true, path = "../dependency" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/stderr.log
new file mode 100644
index 000000000..98abcfc99
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/stderr.log
@@ -0,0 +1 @@
+ Adding cargo-list-test-fixture-dependency (local) to optional dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_version_with_path/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_with_rename/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_with_rename/in/Cargo.toml
new file mode 100644
index 000000000..fe41f2a90
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_with_rename/in/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+versioned-package = { version = "0.1.1", optional = true }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_with_rename/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_with_rename/in/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_with_rename/in/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_with_rename/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_with_rename/mod.rs
new file mode 100644
index 000000000..05cc2d109
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_with_rename/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("versioned-package --rename renamed")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_with_rename/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_with_rename/out/Cargo.toml
new file mode 100644
index 000000000..4b7485181
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_with_rename/out/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+renamed = { version = "99999.0.0", package = "versioned-package" }
+versioned-package = { version = "0.1.1", optional = true }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_with_rename/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_with_rename/stderr.log
new file mode 100644
index 000000000..305b89f26
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_with_rename/stderr.log
@@ -0,0 +1,2 @@
+ Updating `dummy-registry` index
+ Adding versioned-package v99999.0.0 to dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_with_rename/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_with_rename/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_with_rename/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/in/Cargo.toml
new file mode 100644
index 000000000..a80d49949
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/in/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+members = ["primary", "dependency"]
+
+[workspace.dependencies]
+foo = { version = "0.0.0", path = "./dependency" } \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/in/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/in/dependency/Cargo.toml
new file mode 100644
index 000000000..2d247d4d2
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/in/dependency/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "foo"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/in/dependency/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/in/dependency/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/in/dependency/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/in/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/in/primary/Cargo.toml
new file mode 100644
index 000000000..2ac789d55
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/in/primary/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "bar"
+version = "0.0.0"
+
+[dependencies]
+foo.workspace = true \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/in/primary/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/in/primary/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/in/primary/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/mod.rs
new file mode 100644
index 000000000..87ed58f7f
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .args(["foo", "--path", "./dependency", "-p", "bar"])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/out/Cargo.toml
new file mode 100644
index 000000000..a80d49949
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+members = ["primary", "dependency"]
+
+[workspace.dependencies]
+foo = { version = "0.0.0", path = "./dependency" } \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/out/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/out/dependency/Cargo.toml
new file mode 100644
index 000000000..2d247d4d2
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/out/dependency/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "foo"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/out/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/out/primary/Cargo.toml
new file mode 100644
index 000000000..da32f4ead
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/out/primary/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "bar"
+version = "0.0.0"
+
+[dependencies]
+foo = { version = "0.0.0", path = "../dependency" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/stderr.log
new file mode 100644
index 000000000..d1bc50757
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/stderr.log
@@ -0,0 +1 @@
+ Adding foo (local) to dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/in/Cargo.toml
new file mode 100644
index 000000000..a80d49949
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/in/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+members = ["primary", "dependency"]
+
+[workspace.dependencies]
+foo = { version = "0.0.0", path = "./dependency" } \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/in/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/in/dependency/Cargo.toml
new file mode 100644
index 000000000..ef9ec7701
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/in/dependency/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "foo"
+version = "0.0.0"
+
+[features]
+default-base = []
+default-test-base = []
+default-merge-base = []
+default = ["default-base", "default-test-base", "default-merge-base"]
+test-base = []
+test = ["test-base", "default-test-base"]
+merge-base = []
+merge = ["merge-base", "default-merge-base"]
+unrelated = []
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/in/dependency/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/in/dependency/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/in/dependency/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/in/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/in/primary/Cargo.toml
new file mode 100644
index 000000000..a131c946d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/in/primary/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "bar"
+version = "0.0.0"
+
+[dependencies]
+foo = { workspace = true, features = ["test"] } \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/in/primary/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/in/primary/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/in/primary/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/mod.rs
new file mode 100644
index 000000000..87ed58f7f
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .args(["foo", "--path", "./dependency", "-p", "bar"])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/out/Cargo.toml
new file mode 100644
index 000000000..a80d49949
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+members = ["primary", "dependency"]
+
+[workspace.dependencies]
+foo = { version = "0.0.0", path = "./dependency" } \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/out/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/out/dependency/Cargo.toml
new file mode 100644
index 000000000..ef9ec7701
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/out/dependency/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "foo"
+version = "0.0.0"
+
+[features]
+default-base = []
+default-test-base = []
+default-merge-base = []
+default = ["default-base", "default-test-base", "default-merge-base"]
+test-base = []
+test = ["test-base", "default-test-base"]
+merge-base = []
+merge = ["merge-base", "default-merge-base"]
+unrelated = []
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/out/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/out/primary/Cargo.toml
new file mode 100644
index 000000000..6f95ded6f
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/out/primary/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "bar"
+version = "0.0.0"
+
+[dependencies]
+foo = { features = ["test"], path = "../dependency", version = "0.0.0" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/stderr.log
new file mode 100644
index 000000000..18ed7c2d8
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/stderr.log
@@ -0,0 +1,10 @@
+ Adding foo (local) to dependencies.
+ Features:
+ + default-base
+ + default-merge-base
+ + default-test-base
+ + test
+ + test-base
+ - merge
+ - merge-base
+ - unrelated
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/overwrite_workspace_dep_features/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path/in/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/path/in/dependency/Cargo.toml
new file mode 100644
index 000000000..cbe244113
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path/in/dependency/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path/in/dependency/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/path/in/dependency/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path/in/dependency/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path/in/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/path/in/primary/Cargo.toml
new file mode 100644
index 000000000..3ecdb6681
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path/in/primary/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path/in/primary/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/path/in/primary/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path/in/primary/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/path/mod.rs
new file mode 100644
index 000000000..ab89e3a6d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = project_root.join("primary");
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("cargo-list-test-fixture-dependency --path ../dependency")
+ .current_dir(&cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path/out/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/path/out/dependency/Cargo.toml
new file mode 100644
index 000000000..cbe244113
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path/out/dependency/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path/out/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/path/out/primary/Cargo.toml
new file mode 100644
index 000000000..93476d743
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path/out/primary/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+cargo-list-test-fixture-dependency = { version = "0.0.0", path = "../dependency" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/path/stderr.log
new file mode 100644
index 000000000..8109d3cc5
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path/stderr.log
@@ -0,0 +1 @@
+ Adding cargo-list-test-fixture-dependency (local) to dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/path/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_dev/in/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/path_dev/in/dependency/Cargo.toml
new file mode 100644
index 000000000..cbe244113
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_dev/in/dependency/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_dev/in/dependency/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/path_dev/in/dependency/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_dev/in/dependency/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_dev/in/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/path_dev/in/primary/Cargo.toml
new file mode 100644
index 000000000..3ecdb6681
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_dev/in/primary/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_dev/in/primary/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/path_dev/in/primary/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_dev/in/primary/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_dev/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/path_dev/mod.rs
new file mode 100644
index 000000000..4ae04c70a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_dev/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = project_root.join("primary");
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("cargo-list-test-fixture-dependency --path ../dependency --dev")
+ .current_dir(&cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_dev/out/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/path_dev/out/dependency/Cargo.toml
new file mode 100644
index 000000000..cbe244113
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_dev/out/dependency/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_dev/out/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/path_dev/out/primary/Cargo.toml
new file mode 100644
index 000000000..92be59dc6
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_dev/out/primary/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dev-dependencies]
+cargo-list-test-fixture-dependency = { path = "../dependency" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_dev/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/path_dev/stderr.log
new file mode 100644
index 000000000..d8093d6ae
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_dev/stderr.log
@@ -0,0 +1 @@
+ Adding cargo-list-test-fixture-dependency (local) to dev-dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_dev/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/path_dev/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_dev/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/in/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/in/dependency/Cargo.toml
new file mode 100644
index 000000000..cbe244113
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/in/dependency/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/in/dependency/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/in/dependency/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/in/dependency/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/in/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/in/primary/Cargo.toml
new file mode 100644
index 000000000..3ecdb6681
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/in/primary/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/in/primary/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/in/primary/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/in/primary/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/mod.rs
new file mode 100644
index 000000000..ab89e3a6d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = project_root.join("primary");
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("cargo-list-test-fixture-dependency --path ../dependency")
+ .current_dir(&cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/out/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/out/dependency/Cargo.toml
new file mode 100644
index 000000000..cbe244113
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/out/dependency/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/out/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/out/primary/Cargo.toml
new file mode 100644
index 000000000..93476d743
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/out/primary/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+cargo-list-test-fixture-dependency = { version = "0.0.0", path = "../dependency" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/stderr.log
new file mode 100644
index 000000000..8109d3cc5
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/stderr.log
@@ -0,0 +1 @@
+ Adding cargo-list-test-fixture-dependency (local) to dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/in/Cargo.toml
new file mode 100644
index 000000000..299859e79
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/in/Cargo.toml
@@ -0,0 +1,2 @@
+[workspace]
+members = ["primary", "dependency", "optional"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/in/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/in/dependency/Cargo.toml
new file mode 100644
index 000000000..34157f411
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/in/dependency/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "your-face"
+version = "0.1.3"
+
+[dependencies]
+toml_edit = "0.1.5"
+atty = "0.2.13"
+optional-dependency = { path = "../optional", optional = true }
+
+[features]
+default = ["mouth"]
+nose = []
+mouth = ["nose"]
+eyes = []
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/in/dependency/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/in/dependency/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/in/dependency/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/in/optional/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/in/optional/Cargo.toml
new file mode 100644
index 000000000..0216dba89
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/in/optional/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "optional-dep"
+version = "0.1.3"
+
+[dependencies]
+toml_edit = "0.1.5"
+atty = "0.2.13"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/in/optional/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/in/optional/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/in/optional/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/in/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/in/primary/Cargo.toml
new file mode 100644
index 000000000..5e20016d7
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/in/primary/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/in/primary/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/in/primary/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/in/primary/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/mod.rs
new file mode 100644
index 000000000..eadd096aa
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = project_root.join("primary");
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("--path ../dependency --features your-face/nose")
+ .current_dir(&cwd)
+ .assert()
+ .code(101)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/out/Cargo.toml
new file mode 100644
index 000000000..299859e79
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/out/Cargo.toml
@@ -0,0 +1,2 @@
+[workspace]
+members = ["primary", "dependency", "optional"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/out/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/out/dependency/Cargo.toml
new file mode 100644
index 000000000..34157f411
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/out/dependency/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "your-face"
+version = "0.1.3"
+
+[dependencies]
+toml_edit = "0.1.5"
+atty = "0.2.13"
+optional-dependency = { path = "../optional", optional = true }
+
+[features]
+default = ["mouth"]
+nose = []
+mouth = ["nose"]
+eyes = []
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/out/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/out/primary/Cargo.toml
new file mode 100644
index 000000000..5e20016d7
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/out/primary/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/stderr.log
new file mode 100644
index 000000000..791ca6008
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/stderr.log
@@ -0,0 +1 @@
+error: `your-face/nose` is unsupported when inferring the crate name, use `nose`
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_inferred_name_conflicts_full_feature/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/in/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/in/dependency/Cargo.toml
new file mode 100644
index 000000000..cbe244113
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/in/dependency/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/in/dependency/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/in/dependency/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/in/dependency/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/in/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/in/primary/Cargo.toml
new file mode 100644
index 000000000..3ecdb6681
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/in/primary/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/in/primary/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/in/primary/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/in/primary/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/mod.rs
new file mode 100644
index 000000000..754f2783f
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = project_root.join("primary");
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("cargo_list_test_fixture_dependency --path ../dependency")
+ .current_dir(&cwd)
+ .assert()
+ .failure() // Fuzzy searching for paths isn't supported at this time
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/out/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/out/dependency/Cargo.toml
new file mode 100644
index 000000000..cbe244113
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/out/dependency/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/out/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/out/primary/Cargo.toml
new file mode 100644
index 000000000..3ecdb6681
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/out/primary/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/stderr.log
new file mode 100644
index 000000000..59b35e3c4
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/stderr.log
@@ -0,0 +1 @@
+error: the crate `cargo_list_test_fixture_dependency@[ROOT]/case/dependency` could not be found at `[ROOT]/case/dependency`
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/path_normalized_name/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/preserve_sorted/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/preserve_sorted/in/Cargo.toml
new file mode 100644
index 000000000..550e41b07
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/preserve_sorted/in/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+my-package = "0.1.1"
+versioned-package = "0.1.1"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/preserve_sorted/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/preserve_sorted/in/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/preserve_sorted/in/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/preserve_sorted/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/preserve_sorted/mod.rs
new file mode 100644
index 000000000..4dfb06ed1
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/preserve_sorted/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("toml")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/preserve_sorted/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/preserve_sorted/out/Cargo.toml
new file mode 100644
index 000000000..cacd510cc
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/preserve_sorted/out/Cargo.toml
@@ -0,0 +1,10 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+my-package = "0.1.1"
+toml = "99999.0.0"
+versioned-package = "0.1.1"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/preserve_sorted/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/preserve_sorted/stderr.log
new file mode 100644
index 000000000..7c83976f8
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/preserve_sorted/stderr.log
@@ -0,0 +1,2 @@
+ Updating `dummy-registry` index
+ Adding toml v99999.0.0 to dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/preserve_sorted/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/preserve_sorted/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/preserve_sorted/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/preserve_unsorted/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/preserve_unsorted/in/Cargo.toml
new file mode 100644
index 000000000..f803120a3
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/preserve_unsorted/in/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+versioned-package = "0.1.1"
+my-package = "0.1.1"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/preserve_unsorted/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/preserve_unsorted/in/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/preserve_unsorted/in/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/preserve_unsorted/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/preserve_unsorted/mod.rs
new file mode 100644
index 000000000..4dfb06ed1
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/preserve_unsorted/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("toml")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/preserve_unsorted/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/preserve_unsorted/out/Cargo.toml
new file mode 100644
index 000000000..244a06ab9
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/preserve_unsorted/out/Cargo.toml
@@ -0,0 +1,10 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+versioned-package = "0.1.1"
+my-package = "0.1.1"
+toml = "99999.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/preserve_unsorted/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/preserve_unsorted/stderr.log
new file mode 100644
index 000000000..7c83976f8
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/preserve_unsorted/stderr.log
@@ -0,0 +1,2 @@
+ Updating `dummy-registry` index
+ Adding toml v99999.0.0 to dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/preserve_unsorted/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/preserve_unsorted/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/preserve_unsorted/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/quiet/in b/src/tools/cargo/tests/testsuite/cargo_add/quiet/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/quiet/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/quiet/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/quiet/mod.rs
new file mode 100644
index 000000000..357843901
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/quiet/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("--quiet your-face")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/quiet/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/quiet/out/Cargo.toml
new file mode 100644
index 000000000..79d735a12
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/quiet/out/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+your-face = "99999.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/quiet/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/quiet/stderr.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/quiet/stderr.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/quiet/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/quiet/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/quiet/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/registry/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/registry/in/Cargo.toml
new file mode 100644
index 000000000..3ecdb6681
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/registry/in/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/registry/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/registry/in/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/registry/in/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/registry/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/registry/mod.rs
new file mode 100644
index 000000000..d5ba9ef28
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/registry/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_alt_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_alt_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package1 my-package2 --registry alternative")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/registry/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/registry/out/Cargo.toml
new file mode 100644
index 000000000..e856bee5d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/registry/out/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+my-package1 = { version = "99999.0.0", registry = "alternative" }
+my-package2 = { version = "99999.0.0", registry = "alternative" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/registry/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/registry/stderr.log
new file mode 100644
index 000000000..437e780af
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/registry/stderr.log
@@ -0,0 +1,3 @@
+ Updating `alternative` index
+ Adding my-package1 v99999.0.0 to dependencies.
+ Adding my-package2 v99999.0.0 to dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/registry/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/registry/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/registry/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/rename/in b/src/tools/cargo/tests/testsuite/cargo_add/rename/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/rename/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/rename/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/rename/mod.rs
new file mode 100644
index 000000000..3fefcccf3
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/rename/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package --rename renamed")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/rename/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/rename/out/Cargo.toml
new file mode 100644
index 000000000..ebcfbbd99
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/rename/out/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+renamed = { version = "99999.0.0", package = "my-package" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/rename/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/rename/stderr.log
new file mode 100644
index 000000000..fd6b711e3
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/rename/stderr.log
@@ -0,0 +1,2 @@
+ Updating `dummy-registry` index
+ Adding my-package v99999.0.0 to dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/rename/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/rename/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/rename/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/require_weak/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/require_weak/in/Cargo.toml
new file mode 100644
index 000000000..54faf173a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/require_weak/in/Cargo.toml
@@ -0,0 +1,11 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[features]
+eyes = ["your-face?/eyes"]
+
+[dependencies]
+your-face = { version = "99999.0.0", optional = true }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/require_weak/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/require_weak/in/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/require_weak/in/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/require_weak/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/require_weak/mod.rs
new file mode 100644
index 000000000..d99e4482a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/require_weak/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("your-face --no-optional")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/require_weak/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/require_weak/out/Cargo.toml
new file mode 100644
index 000000000..a0e4b9753
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/require_weak/out/Cargo.toml
@@ -0,0 +1,11 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[features]
+eyes = ["your-face/eyes"]
+
+[dependencies]
+your-face = { version = "99999.0.0" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/require_weak/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/require_weak/stderr.log
new file mode 100644
index 000000000..796b9601b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/require_weak/stderr.log
@@ -0,0 +1,7 @@
+ Updating `dummy-registry` index
+ Adding your-face v99999.0.0 to dependencies.
+ Features:
+ - ears
+ - eyes
+ - mouth
+ - nose
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/require_weak/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/require_weak/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/require_weak/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/sorted_table_with_dotted_item/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/sorted_table_with_dotted_item/in/Cargo.toml
new file mode 100644
index 000000000..19aa939d9
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/sorted_table_with_dotted_item/in/Cargo.toml
@@ -0,0 +1,13 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+toml = "0.1.1"
+versioned-package = "0.1.1"
+
+[dependencies.my-build-package1]
+version = "0.1.1"
+
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/sorted_table_with_dotted_item/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/sorted_table_with_dotted_item/in/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/sorted_table_with_dotted_item/in/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/sorted_table_with_dotted_item/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/sorted_table_with_dotted_item/mod.rs
new file mode 100644
index 000000000..55e4c2281
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/sorted_table_with_dotted_item/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("unrelateed-crate")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/sorted_table_with_dotted_item/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/sorted_table_with_dotted_item/out/Cargo.toml
new file mode 100644
index 000000000..008ff4f62
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/sorted_table_with_dotted_item/out/Cargo.toml
@@ -0,0 +1,14 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+toml = "0.1.1"
+unrelateed-crate = "99999.0.0"
+versioned-package = "0.1.1"
+
+[dependencies.my-build-package1]
+version = "0.1.1"
+
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/sorted_table_with_dotted_item/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/sorted_table_with_dotted_item/stderr.log
new file mode 100644
index 000000000..be1db1c4d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/sorted_table_with_dotted_item/stderr.log
@@ -0,0 +1,2 @@
+ Updating `dummy-registry` index
+ Adding unrelateed-crate v99999.0.0 to dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/sorted_table_with_dotted_item/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/sorted_table_with_dotted_item/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/sorted_table_with_dotted_item/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/target/in b/src/tools/cargo/tests/testsuite/cargo_add/target/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/target/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/target/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/target/mod.rs
new file mode 100644
index 000000000..e263bad36
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/target/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package1 my-package2 --target i686-unknown-linux-gnu")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/target/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/target/out/Cargo.toml
new file mode 100644
index 000000000..9c96ede51
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/target/out/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[target.i686-unknown-linux-gnu.dependencies]
+my-package1 = "99999.0.0"
+my-package2 = "99999.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/target/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/target/stderr.log
new file mode 100644
index 000000000..3413bcc1b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/target/stderr.log
@@ -0,0 +1,3 @@
+ Updating `dummy-registry` index
+ Adding my-package1 v99999.0.0 to dependencies for target `i686-unknown-linux-gnu`.
+ Adding my-package2 v99999.0.0 to dependencies for target `i686-unknown-linux-gnu`.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/target/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/target/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/target/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/target_cfg/in b/src/tools/cargo/tests/testsuite/cargo_add/target_cfg/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/target_cfg/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/target_cfg/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/target_cfg/mod.rs
new file mode 100644
index 000000000..43efe8e8d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/target_cfg/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package1 my-package2 --target cfg(unix)")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/target_cfg/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/target_cfg/out/Cargo.toml
new file mode 100644
index 000000000..212ec571b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/target_cfg/out/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[target."cfg(unix)".dependencies]
+my-package1 = "99999.0.0"
+my-package2 = "99999.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/target_cfg/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/target_cfg/stderr.log
new file mode 100644
index 000000000..e405c8dc1
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/target_cfg/stderr.log
@@ -0,0 +1,3 @@
+ Updating `dummy-registry` index
+ Adding my-package1 v99999.0.0 to dependencies for target `cfg(unix)`.
+ Adding my-package2 v99999.0.0 to dependencies for target `cfg(unix)`.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/target_cfg/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/target_cfg/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/target_cfg/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/in/Cargo.toml
new file mode 100644
index 000000000..b2a34c92e
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/in/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+members = ["primary", "dependency"]
+
+[workspace.dependencies]
+foo = { version = "0.0.0", path = "./dependency", features = ["not_recognized"] }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/in/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/in/dependency/Cargo.toml
new file mode 100644
index 000000000..9a7bc7f77
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/in/dependency/Cargo.toml
@@ -0,0 +1,20 @@
+[package]
+name = "foo"
+version = "0.0.0"
+
+[features]
+default-base = []
+default-test-base = []
+default-merge-base = []
+long-feature-name-because-of-formatting-reasons = []
+default = [
+ "default-base",
+ "default-test-base",
+ "default-merge-base",
+ "long-feature-name-because-of-formatting-reasons",
+]
+test-base = []
+test = ["test-base", "default-test-base"]
+merge-base = []
+merge = ["merge-base", "default-merge-base"]
+unrelated = []
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/in/dependency/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/in/dependency/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/in/dependency/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/in/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/in/primary/Cargo.toml
new file mode 100644
index 000000000..fb4a12619
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/in/primary/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "bar"
+version = "0.0.0"
+
+[dependencies]
+foo = { workspace = true, features = ["test"] }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/in/primary/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/in/primary/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/in/primary/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/mod.rs
new file mode 100644
index 000000000..8184dac8f
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/mod.rs
@@ -0,0 +1,23 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .args(["foo", "-p", "bar"])
+ .current_dir(cwd)
+ .assert()
+ .failure()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/out/Cargo.toml
new file mode 100644
index 000000000..b2a34c92e
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+members = ["primary", "dependency"]
+
+[workspace.dependencies]
+foo = { version = "0.0.0", path = "./dependency", features = ["not_recognized"] }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/out/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/out/dependency/Cargo.toml
new file mode 100644
index 000000000..9a7bc7f77
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/out/dependency/Cargo.toml
@@ -0,0 +1,20 @@
+[package]
+name = "foo"
+version = "0.0.0"
+
+[features]
+default-base = []
+default-test-base = []
+default-merge-base = []
+long-feature-name-because-of-formatting-reasons = []
+default = [
+ "default-base",
+ "default-test-base",
+ "default-merge-base",
+ "long-feature-name-because-of-formatting-reasons",
+]
+test-base = []
+test = ["test-base", "default-test-base"]
+merge-base = []
+merge = ["merge-base", "default-merge-base"]
+unrelated = []
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/out/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/out/primary/Cargo.toml
new file mode 100644
index 000000000..fb4a12619
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/out/primary/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "bar"
+version = "0.0.0"
+
+[dependencies]
+foo = { workspace = true, features = ["test"] }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/stderr.log
new file mode 100644
index 000000000..c5aee4dc1
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/stderr.log
@@ -0,0 +1,7 @@
+ Adding foo (workspace) to dependencies.
+error: unrecognized feature for crate foo: not_recognized
+disabled features:
+ merge, merge-base, unrelated
+enabled features:
+ default-base, default-merge-base, default-test-base
+ long-feature-name-because-of-formatting-reasons, test, test-base
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/unknown_inherited_feature/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/vers/in b/src/tools/cargo/tests/testsuite/cargo_add/vers/in
new file mode 120000
index 000000000..6c6a27fcf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/vers/in
@@ -0,0 +1 @@
+../add-basic.in \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/vers/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/vers/mod.rs
new file mode 100644
index 000000000..fb78739e9
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/vers/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("my-package@>=0.1.1")
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/vers/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/vers/out/Cargo.toml
new file mode 100644
index 000000000..c6ca3d67a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/vers/out/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+my-package = ">=0.1.1"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/vers/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/vers/stderr.log
new file mode 100644
index 000000000..7ef92d22e
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/vers/stderr.log
@@ -0,0 +1,2 @@
+ Updating `dummy-registry` index
+ Adding my-package >=0.1.1 to dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/vers/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/vers/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/vers/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/workspace_name/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/workspace_name/in/Cargo.toml
new file mode 100644
index 000000000..57e1f3085
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/workspace_name/in/Cargo.toml
@@ -0,0 +1,2 @@
+[workspace]
+members = ["primary", "dependency"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/workspace_name/in/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/workspace_name/in/dependency/Cargo.toml
new file mode 100644
index 000000000..ca4f36d72
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/workspace_name/in/dependency/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/workspace_name/in/dependency/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/workspace_name/in/dependency/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/workspace_name/in/dependency/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/workspace_name/in/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/workspace_name/in/primary/Cargo.toml
new file mode 100644
index 000000000..5e20016d7
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/workspace_name/in/primary/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/workspace_name/in/primary/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/workspace_name/in/primary/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/workspace_name/in/primary/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/workspace_name/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/workspace_name/mod.rs
new file mode 100644
index 000000000..ccaf850f9
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/workspace_name/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = project_root.join("primary");
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("cargo-list-test-fixture-dependency")
+ .current_dir(&cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/workspace_name/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/workspace_name/out/Cargo.toml
new file mode 100644
index 000000000..57e1f3085
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/workspace_name/out/Cargo.toml
@@ -0,0 +1,2 @@
+[workspace]
+members = ["primary", "dependency"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/workspace_name/out/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/workspace_name/out/dependency/Cargo.toml
new file mode 100644
index 000000000..ca4f36d72
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/workspace_name/out/dependency/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/workspace_name/out/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/workspace_name/out/primary/Cargo.toml
new file mode 100644
index 000000000..a693df54f
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/workspace_name/out/primary/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+cargo-list-test-fixture-dependency = { version = "0.0.0", path = "../dependency" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/workspace_name/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/workspace_name/stderr.log
new file mode 100644
index 000000000..8109d3cc5
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/workspace_name/stderr.log
@@ -0,0 +1 @@
+ Adding cargo-list-test-fixture-dependency (local) to dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/workspace_name/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/workspace_name/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/workspace_name/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/workspace_path/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path/in/Cargo.toml
new file mode 100644
index 000000000..57e1f3085
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path/in/Cargo.toml
@@ -0,0 +1,2 @@
+[workspace]
+members = ["primary", "dependency"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/workspace_path/in/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path/in/dependency/Cargo.toml
new file mode 100644
index 000000000..ca4f36d72
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path/in/dependency/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/workspace_path/in/dependency/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path/in/dependency/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path/in/dependency/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/workspace_path/in/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path/in/primary/Cargo.toml
new file mode 100644
index 000000000..5e20016d7
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path/in/primary/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/workspace_path/in/primary/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path/in/primary/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path/in/primary/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/workspace_path/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path/mod.rs
new file mode 100644
index 000000000..ab89e3a6d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = project_root.join("primary");
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("cargo-list-test-fixture-dependency --path ../dependency")
+ .current_dir(&cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/workspace_path/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path/out/Cargo.toml
new file mode 100644
index 000000000..57e1f3085
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path/out/Cargo.toml
@@ -0,0 +1,2 @@
+[workspace]
+members = ["primary", "dependency"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/workspace_path/out/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path/out/dependency/Cargo.toml
new file mode 100644
index 000000000..ca4f36d72
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path/out/dependency/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/workspace_path/out/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path/out/primary/Cargo.toml
new file mode 100644
index 000000000..a693df54f
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path/out/primary/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dependencies]
+cargo-list-test-fixture-dependency = { version = "0.0.0", path = "../dependency" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/workspace_path/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path/stderr.log
new file mode 100644
index 000000000..8109d3cc5
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path/stderr.log
@@ -0,0 +1 @@
+ Adding cargo-list-test-fixture-dependency (local) to dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/workspace_path/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/in/Cargo.toml
new file mode 100644
index 000000000..57e1f3085
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/in/Cargo.toml
@@ -0,0 +1,2 @@
+[workspace]
+members = ["primary", "dependency"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/in/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/in/dependency/Cargo.toml
new file mode 100644
index 000000000..ca4f36d72
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/in/dependency/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/in/dependency/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/in/dependency/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/in/dependency/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/in/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/in/primary/Cargo.toml
new file mode 100644
index 000000000..5e20016d7
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/in/primary/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/in/primary/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/in/primary/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/in/primary/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/mod.rs
new file mode 100644
index 000000000..4ae04c70a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use crate::cargo_add::init_registry;
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = project_root.join("primary");
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("cargo-list-test-fixture-dependency --path ../dependency --dev")
+ .current_dir(&cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/out/Cargo.toml
new file mode 100644
index 000000000..57e1f3085
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/out/Cargo.toml
@@ -0,0 +1,2 @@
+[workspace]
+members = ["primary", "dependency"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/out/dependency/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/out/dependency/Cargo.toml
new file mode 100644
index 000000000..ca4f36d72
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/out/dependency/Cargo.toml
@@ -0,0 +1,3 @@
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/out/primary/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/out/primary/Cargo.toml
new file mode 100644
index 000000000..8dfa5c218
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/out/primary/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+
+[dev-dependencies]
+cargo-list-test-fixture-dependency = { path = "../dependency" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/stderr.log
new file mode 100644
index 000000000..d8093d6ae
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/stderr.log
@@ -0,0 +1 @@
+ Adding cargo-list-test-fixture-dependency (local) to dev-dependencies.
diff --git a/src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_add/workspace_path_dev/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_alias_config.rs b/src/tools/cargo/tests/testsuite/cargo_alias_config.rs
new file mode 100644
index 000000000..fd4aec917
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_alias_config.rs
@@ -0,0 +1,434 @@
+//! Tests for `[alias]` config command aliases.
+
+use std::env;
+
+use cargo_test_support::tools::echo_subcommand;
+use cargo_test_support::{basic_bin_manifest, project};
+
+#[cargo_test]
+fn alias_incorrect_config_type() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ ".cargo/config",
+ r#"
+ [alias]
+ b-cargo-test = 5
+ "#,
+ )
+ .build();
+
+ p.cargo("b-cargo-test -v")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] invalid configuration for key `alias.b-cargo-test`
+expected a list, but found a integer for [..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn alias_malformed_config_string() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ ".cargo/config",
+ r#"
+ [alias]
+ b-cargo-test = `
+ "#,
+ )
+ .build();
+
+ p.cargo("b-cargo-test -v")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] could not load Cargo configuration
+
+Caused by:
+ could not parse TOML configuration in `[..]/config`
+
+Caused by:
+ [..]
+
+Caused by:
+ TOML parse error at line [..]
+ |
+ 3 | b-cargo-test = `
+ | ^
+ invalid string
+ expected `\"`, `'`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn alias_malformed_config_list() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ ".cargo/config",
+ r#"
+ [alias]
+ b-cargo-test = [1, 2]
+ "#,
+ )
+ .build();
+
+ p.cargo("b-cargo-test -v")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] could not load Cargo configuration
+
+Caused by:
+ failed to load TOML configuration from `[..]/config`
+
+Caused by:
+ [..] `alias`
+
+Caused by:
+ [..] `b-cargo-test`
+
+Caused by:
+ expected string but found integer in list
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn alias_config() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ ".cargo/config",
+ r#"
+ [alias]
+ b-cargo-test = "build"
+ "#,
+ )
+ .build();
+
+ p.cargo("b-cargo-test -v")
+ .with_stderr_contains(
+ "\
+[COMPILING] foo v0.5.0 [..]
+[RUNNING] `rustc --crate-name foo [..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn dependent_alias() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ ".cargo/config",
+ r#"
+ [alias]
+ b-cargo-test = "build"
+ a-cargo-test = ["b-cargo-test", "-v"]
+ "#,
+ )
+ .build();
+
+ p.cargo("a-cargo-test")
+ .with_stderr_contains(
+ "\
+[COMPILING] foo v0.5.0 [..]
+[RUNNING] `rustc --crate-name foo [..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn builtin_alias_shadowing_external_subcommand() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/main.rs", "fn main() {}")
+ .executable("cargo-t", "")
+ .build();
+
+ let mut paths: Vec<_> = env::split_paths(&env::var_os("PATH").unwrap_or_default()).collect();
+ paths.push(p.root());
+ let path = env::join_paths(paths).unwrap();
+
+ p.cargo("t")
+ .env("PATH", &path)
+ .with_stderr(
+ "\
+[COMPILING] foo v0.5.0 [..]
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] unittests src/main.rs [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn alias_shadowing_external_subcommand() {
+ let echo = echo_subcommand();
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ ".cargo/config",
+ r#"
+ [alias]
+ echo = "build"
+ "#,
+ )
+ .build();
+
+ let mut paths: Vec<_> = env::split_paths(&env::var_os("PATH").unwrap_or_default()).collect();
+ paths.push(echo.target_debug_dir());
+ let path = env::join_paths(paths).unwrap();
+
+ p.cargo("echo")
+ .env("PATH", &path)
+ .with_stderr("\
+[WARNING] user-defined alias `echo` is shadowing an external subcommand found at: `[ROOT]/cargo-echo/target/debug/cargo-echo[EXE]`
+This was previously accepted but is being phased out; it will become a hard error in a future release.
+For more information, see issue #10049 <https://github.com/rust-lang/cargo/issues/10049>.
+[COMPILING] foo v0.5.0 [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn default_args_alias() {
+ let echo = echo_subcommand();
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ ".cargo/config",
+ r#"
+ [alias]
+ echo = "echo --flag1 --flag2"
+ test-1 = "echo"
+ build = "build --verbose"
+ "#,
+ )
+ .build();
+
+ let mut paths: Vec<_> = env::split_paths(&env::var_os("PATH").unwrap_or_default()).collect();
+ paths.push(echo.target_debug_dir());
+ let path = env::join_paths(paths).unwrap();
+
+ p.cargo("echo")
+ .env("PATH", &path)
+ .with_status(101)
+ .with_stderr("\
+[WARNING] user-defined alias `echo` is shadowing an external subcommand found at: `[ROOT]/cargo-echo/target/debug/cargo-echo[EXE]`
+This was previously accepted but is being phased out; it will become a hard error in a future release.
+For more information, see issue #10049 <https://github.com/rust-lang/cargo/issues/10049>.
+error: alias echo has unresolvable recursive definition: echo -> echo
+",
+ )
+ .run();
+
+ p.cargo("test-1")
+ .env("PATH", &path)
+ .with_status(101)
+ .with_stderr("\
+[WARNING] user-defined alias `echo` is shadowing an external subcommand found at: `[ROOT]/cargo-echo/target/debug/cargo-echo[EXE]`
+This was previously accepted but is being phased out; it will become a hard error in a future release.
+For more information, see issue #10049 <https://github.com/rust-lang/cargo/issues/10049>.
+error: alias test-1 has unresolvable recursive definition: test-1 -> echo -> echo
+",
+ )
+ .run();
+
+ // Builtins are not expanded by rule
+ p.cargo("build")
+ .with_stderr(
+ "\
+[WARNING] user-defined alias `build` is ignored, because it is shadowed by a built-in command
+[COMPILING] foo v0.5.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn corecursive_alias() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ ".cargo/config",
+ r#"
+ [alias]
+ test-1 = "test-2 --flag1"
+ test-2 = "test-3 --flag2"
+ test-3 = "test-1 --flag3"
+ "#,
+ )
+ .build();
+
+ p.cargo("test-1")
+ .with_status(101)
+ .with_stderr(
+ "error: alias test-1 has unresolvable recursive definition: test-1 -> test-2 -> test-3 -> test-1",
+ )
+ .run();
+
+ p.cargo("test-2")
+ .with_status(101)
+ .with_stderr(
+ "error: alias test-2 has unresolvable recursive definition: test-2 -> test-3 -> test-1 -> test-2",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn alias_list_test() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ ".cargo/config",
+ r#"
+ [alias]
+ b-cargo-test = ["build", "--release"]
+ "#,
+ )
+ .build();
+
+ p.cargo("b-cargo-test -v")
+ .with_stderr_contains("[COMPILING] foo v0.5.0 [..]")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name [..]")
+ .run();
+}
+
+#[cargo_test]
+fn alias_with_flags_config() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ ".cargo/config",
+ r#"
+ [alias]
+ b-cargo-test = "build --release"
+ "#,
+ )
+ .build();
+
+ p.cargo("b-cargo-test -v")
+ .with_stderr_contains("[COMPILING] foo v0.5.0 [..]")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name foo [..]")
+ .run();
+}
+
+#[cargo_test]
+fn alias_cannot_shadow_builtin_command() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ ".cargo/config",
+ r#"
+ [alias]
+ build = "fetch"
+ "#,
+ )
+ .build();
+
+ p.cargo("build")
+ .with_stderr(
+ "\
+[WARNING] user-defined alias `build` is ignored, because it is shadowed by a built-in command
+[COMPILING] foo v0.5.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn alias_override_builtin_alias() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ ".cargo/config",
+ r#"
+ [alias]
+ b = "run"
+ "#,
+ )
+ .build();
+
+ p.cargo("b")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.5.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `target/debug/foo[EXE]`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn builtin_alias_takes_options() {
+ // #6381
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(
+ "examples/ex1.rs",
+ r#"fn main() { println!("{}", std::env::args().skip(1).next().unwrap()) }"#,
+ )
+ .build();
+
+ p.cargo("r --example ex1 -- asdf").with_stdout("asdf").run();
+}
+
+#[cargo_test]
+fn global_options_with_alias() {
+ // Check that global options are passed through.
+ let p = project().file("src/lib.rs", "").build();
+
+ p.cargo("-v c")
+ .with_stderr(
+ "\
+[CHECKING] foo [..]
+[RUNNING] `rustc [..]
+[FINISHED] dev [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn weird_check() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("-- check --invalid_argument -some-other-argument")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] trailing arguments after built-in command `check` are unsupported: `--invalid_argument -some-other-argument`
+
+To pass the arguments to the subcommand, remove `--`
+",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_command.rs b/src/tools/cargo/tests/testsuite/cargo_command.rs
new file mode 100644
index 000000000..62869387f
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_command.rs
@@ -0,0 +1,535 @@
+//! Tests for custom cargo commands and other global command features.
+
+use std::env;
+use std::fs;
+use std::io::Read;
+use std::path::{Path, PathBuf};
+use std::process::Stdio;
+use std::str;
+
+use cargo_test_support::basic_manifest;
+use cargo_test_support::paths::CargoPathExt;
+use cargo_test_support::registry::Package;
+use cargo_test_support::tools::echo_subcommand;
+use cargo_test_support::{
+ basic_bin_manifest, cargo_exe, cargo_process, paths, project, project_in_home,
+};
+use cargo_util::paths::join_paths;
+
+fn path() -> Vec<PathBuf> {
+ env::split_paths(&env::var_os("PATH").unwrap_or_default()).collect()
+}
+
+#[cargo_test]
+fn list_commands_with_descriptions() {
+ let p = project().build();
+ p.cargo("--list")
+ .with_stdout_contains(
+ " build Compile a local package and all of its dependencies",
+ )
+ // Assert that `read-manifest` prints the right one-line description followed by another
+ // command, indented.
+ .with_stdout_contains(
+ " read-manifest Print a JSON representation of a Cargo.toml manifest.",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn list_builtin_aliases_with_descriptions() {
+ let p = project().build();
+ p.cargo("--list")
+ .with_stdout_contains(" b alias: build")
+ .with_stdout_contains(" c alias: check")
+ .with_stdout_contains(" r alias: run")
+ .with_stdout_contains(" t alias: test")
+ .run();
+}
+
+#[cargo_test]
+fn list_custom_aliases_with_descriptions() {
+ let p = project_in_home("proj")
+ .file(
+ &paths::home().join(".cargo").join("config"),
+ r#"
+ [alias]
+ myaliasstr = "foo --bar"
+ myaliasvec = ["foo", "--bar"]
+ "#,
+ )
+ .build();
+
+ p.cargo("--list")
+ .with_stdout_contains(" myaliasstr alias: foo --bar")
+ .with_stdout_contains(" myaliasvec alias: foo --bar")
+ .run();
+}
+
+#[cargo_test]
+fn list_dedupe() {
+ let p = project()
+ .executable(Path::new("path-test-1").join("cargo-dupe"), "")
+ .executable(Path::new("path-test-2").join("cargo-dupe"), "")
+ .build();
+
+ let mut path = path();
+ path.push(p.root().join("path-test-1"));
+ path.push(p.root().join("path-test-2"));
+ let path = env::join_paths(path.iter()).unwrap();
+
+ p.cargo("--list")
+ .env("PATH", &path)
+ .with_stdout_contains_n(" dupe", 1)
+ .run();
+}
+
+#[cargo_test]
+fn list_command_looks_at_path() {
+ let proj = project()
+ .executable(Path::new("path-test").join("cargo-1"), "")
+ .build();
+
+ let mut path = path();
+ path.push(proj.root().join("path-test"));
+ let path = env::join_paths(path.iter()).unwrap();
+ let output = cargo_process("-v --list")
+ .env("PATH", &path)
+ .exec_with_output()
+ .unwrap();
+ let output = str::from_utf8(&output.stdout).unwrap();
+ assert!(
+ output.contains("\n 1 "),
+ "missing 1: {}",
+ output
+ );
+}
+
+#[cfg(windows)]
+#[cargo_test]
+fn list_command_looks_at_path_case_mismatch() {
+ let proj = project()
+ .executable(Path::new("path-test").join("cargo-1"), "")
+ .build();
+
+ let mut path = path();
+ path.push(proj.root().join("path-test"));
+ let path = env::join_paths(path.iter()).unwrap();
+
+ // See issue #11814: Environment variable names are case-insensitive on Windows.
+ // We need to check that having "Path" instead of "PATH" is okay.
+ let output = cargo_process("-v --list")
+ .env("Path", &path)
+ .env_remove("PATH")
+ .exec_with_output()
+ .unwrap();
+ let output = str::from_utf8(&output.stdout).unwrap();
+ assert!(
+ output.contains("\n 1 "),
+ "missing 1: {}",
+ output
+ );
+}
+
+#[cargo_test]
+fn list_command_handles_known_external_commands() {
+ let p = project()
+ .executable(Path::new("path-test").join("cargo-fmt"), "")
+ .build();
+
+ let fmt_desc = " fmt Formats all bin and lib files of the current crate using rustfmt.";
+
+ // Without path - fmt isn't there
+ p.cargo("--list")
+ .env("PATH", "")
+ .with_stdout_does_not_contain(fmt_desc)
+ .run();
+
+ // With path - fmt is there with known description
+ let mut path = path();
+ path.push(p.root().join("path-test"));
+ let path = env::join_paths(path.iter()).unwrap();
+
+ p.cargo("--list")
+ .env("PATH", &path)
+ .with_stdout_contains(fmt_desc)
+ .run();
+}
+
+#[cargo_test]
+fn list_command_resolves_symlinks() {
+ let proj = project()
+ .symlink(cargo_exe(), Path::new("path-test").join("cargo-2"))
+ .build();
+
+ let mut path = path();
+ path.push(proj.root().join("path-test"));
+ let path = env::join_paths(path.iter()).unwrap();
+ let output = cargo_process("-v --list")
+ .env("PATH", &path)
+ .exec_with_output()
+ .unwrap();
+ let output = str::from_utf8(&output.stdout).unwrap();
+ assert!(
+ output.contains("\n 2 "),
+ "missing 2: {}",
+ output
+ );
+}
+
+#[cargo_test]
+fn find_closest_capital_c_to_c() {
+ cargo_process("C")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+error: no such command: `C`
+
+<tab>Did you mean `c`?
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn find_closest_capital_b_to_b() {
+ cargo_process("B")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+error: no such command: `B`
+
+<tab>Did you mean `b`?
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn find_closest_biuld_to_build() {
+ cargo_process("biuld")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+error: no such command: `biuld`
+
+<tab>Did you mean `build`?
+",
+ )
+ .run();
+
+ // But, if we actually have `biuld`, it must work!
+ // https://github.com/rust-lang/cargo/issues/5201
+ Package::new("cargo-biuld", "1.0.0")
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ println!("Similar, but not identical to, build");
+ }
+ "#,
+ )
+ .publish();
+
+ cargo_process("install cargo-biuld").run();
+ cargo_process("biuld")
+ .with_stdout("Similar, but not identical to, build\n")
+ .run();
+ cargo_process("--list")
+ .with_stdout_contains(
+ " build Compile a local package and all of its dependencies\n",
+ )
+ .with_stdout_contains(" biuld\n")
+ .run();
+}
+
+#[cargo_test]
+fn find_closest_alias() {
+ let root = paths::root();
+ let my_home = root.join("my_home");
+ fs::create_dir(&my_home).unwrap();
+ fs::write(
+ &my_home.join("config"),
+ r#"
+ [alias]
+ myalias = "build"
+ "#,
+ )
+ .unwrap();
+
+ cargo_process("myalais")
+ .env("CARGO_HOME", &my_home)
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+error: no such command: `myalais`
+
+<tab>Did you mean `myalias`?
+",
+ )
+ .run();
+
+ // But, if no alias is defined, it must not suggest one!
+ cargo_process("myalais")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+error: no such command: `myalais`
+",
+ )
+ .with_stderr_does_not_contain(
+ "\
+<tab>Did you mean `myalias`?
+",
+ )
+ .run();
+}
+
+// If a subcommand is more than an edit distance of 3 away, we don't make a suggestion.
+#[cargo_test]
+fn find_closest_dont_correct_nonsense() {
+ cargo_process("there-is-no-way-that-there-is-a-command-close-to-this")
+ .cwd(&paths::root())
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] no such command: `there-is-no-way-that-there-is-a-command-close-to-this`
+
+<tab>View all installed commands with `cargo --list`",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn displays_subcommand_on_error() {
+ cargo_process("invalid-command")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] no such command: `invalid-command`
+
+<tab>View all installed commands with `cargo --list`",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn override_cargo_home() {
+ let root = paths::root();
+ let my_home = root.join("my_home");
+ fs::create_dir(&my_home).unwrap();
+ fs::write(
+ &my_home.join("config"),
+ r#"
+ [cargo-new]
+ vcs = "none"
+ "#,
+ )
+ .unwrap();
+
+ cargo_process("new foo").env("CARGO_HOME", &my_home).run();
+
+ assert!(!paths::root().join("foo/.git").is_dir());
+
+ cargo_process("new foo2").run();
+
+ assert!(paths::root().join("foo2/.git").is_dir());
+}
+
+#[cargo_test]
+fn cargo_subcommand_env() {
+ let src = format!(
+ r#"
+ use std::env;
+
+ fn main() {{
+ println!("{{}}", env::var("{}").unwrap());
+ }}
+ "#,
+ cargo::CARGO_ENV
+ );
+
+ let p = project()
+ .at("cargo-envtest")
+ .file("Cargo.toml", &basic_bin_manifest("cargo-envtest"))
+ .file("src/main.rs", &src)
+ .build();
+
+ let target_dir = p.target_debug_dir();
+
+ p.cargo("build").run();
+ assert!(p.bin("cargo-envtest").is_file());
+
+ let cargo = cargo_exe().canonicalize().unwrap();
+ let mut path = path();
+ path.push(target_dir.clone());
+ let path = env::join_paths(path.iter()).unwrap();
+
+ cargo_process("envtest")
+ .env("PATH", &path)
+ .with_stdout(cargo.to_str().unwrap())
+ .run();
+
+ // Check that subcommands inherit an overridden $CARGO
+ let envtest_bin = target_dir
+ .join("cargo-envtest")
+ .with_extension(std::env::consts::EXE_EXTENSION)
+ .canonicalize()
+ .unwrap();
+ let envtest_bin = envtest_bin.to_str().unwrap();
+ cargo_process("envtest")
+ .env("PATH", &path)
+ .env(cargo::CARGO_ENV, &envtest_bin)
+ .with_stdout(envtest_bin)
+ .run();
+}
+
+#[cargo_test]
+fn cargo_cmd_bins_vs_explicit_path() {
+ // Set up `cargo-foo` binary in two places: inside `$HOME/.cargo/bin` and outside of it
+ //
+ // Return paths to both places
+ fn set_up_cargo_foo() -> (PathBuf, PathBuf) {
+ let p = project()
+ .at("cargo-foo")
+ .file("Cargo.toml", &basic_manifest("cargo-foo", "1.0.0"))
+ .file(
+ "src/bin/cargo-foo.rs",
+ r#"fn main() { println!("INSIDE"); }"#,
+ )
+ .file(
+ "src/bin/cargo-foo2.rs",
+ r#"fn main() { println!("OUTSIDE"); }"#,
+ )
+ .build();
+ p.cargo("build").run();
+ let cargo_bin_dir = paths::home().join(".cargo/bin");
+ cargo_bin_dir.mkdir_p();
+ let root_bin_dir = paths::root().join("bin");
+ root_bin_dir.mkdir_p();
+ let exe_name = format!("cargo-foo{}", env::consts::EXE_SUFFIX);
+ fs::rename(p.bin("cargo-foo"), cargo_bin_dir.join(&exe_name)).unwrap();
+ fs::rename(p.bin("cargo-foo2"), root_bin_dir.join(&exe_name)).unwrap();
+
+ (root_bin_dir, cargo_bin_dir)
+ }
+
+ let (outside_dir, inside_dir) = set_up_cargo_foo();
+
+ // If `$CARGO_HOME/bin` is not in a path, prefer it over anything in `$PATH`.
+ //
+ // This is the historical behavior we don't want to break.
+ cargo_process("foo").with_stdout_contains("INSIDE").run();
+
+ // When `$CARGO_HOME/bin` is in the `$PATH`
+ // use only `$PATH` so the user-defined ordering is respected.
+ {
+ cargo_process("foo")
+ .env(
+ "PATH",
+ join_paths(&[&inside_dir, &outside_dir], "PATH").unwrap(),
+ )
+ .with_stdout_contains("INSIDE")
+ .run();
+
+ cargo_process("foo")
+ // Note: trailing slash
+ .env(
+ "PATH",
+ join_paths(&[inside_dir.join(""), outside_dir.join("")], "PATH").unwrap(),
+ )
+ .with_stdout_contains("INSIDE")
+ .run();
+
+ cargo_process("foo")
+ .env(
+ "PATH",
+ join_paths(&[&outside_dir, &inside_dir], "PATH").unwrap(),
+ )
+ .with_stdout_contains("OUTSIDE")
+ .run();
+
+ cargo_process("foo")
+ // Note: trailing slash
+ .env(
+ "PATH",
+ join_paths(&[outside_dir.join(""), inside_dir.join("")], "PATH").unwrap(),
+ )
+ .with_stdout_contains("OUTSIDE")
+ .run();
+ }
+}
+
+#[cargo_test]
+fn cargo_subcommand_args() {
+ let p = echo_subcommand();
+ let cargo_foo_bin = p.bin("cargo-echo");
+ assert!(cargo_foo_bin.is_file());
+
+ let mut path = path();
+ path.push(p.target_debug_dir());
+ let path = env::join_paths(path.iter()).unwrap();
+
+ cargo_process("echo bar -v --help")
+ .env("PATH", &path)
+ .with_stdout("echo bar -v --help")
+ .run();
+}
+
+#[cargo_test]
+fn explain() {
+ cargo_process("--explain E0001")
+ .with_stdout_contains(
+ "This error suggests that the expression arm corresponding to the noted pattern",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn closed_output_ok() {
+ // Checks that closed output doesn't cause an error.
+ let mut p = cargo_process("--list").build_command();
+ p.stdout(Stdio::piped()).stderr(Stdio::piped());
+ let mut child = p.spawn().unwrap();
+ // Close stdout
+ drop(child.stdout.take());
+ // Read stderr
+ let mut s = String::new();
+ child
+ .stderr
+ .as_mut()
+ .unwrap()
+ .read_to_string(&mut s)
+ .unwrap();
+ let status = child.wait().unwrap();
+ assert!(status.success());
+ assert!(s.is_empty(), "{}", s);
+}
+
+#[cargo_test]
+fn subcommand_leading_plus_output_contains() {
+ cargo_process("+nightly")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: no such command: `+nightly`
+
+<tab>Cargo does not handle `+toolchain` directives.
+<tab>Did you mean to invoke `cargo` through `rustup` instead?",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn full_did_you_mean() {
+ cargo_process("bluid")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: no such command: `bluid`
+
+<tab>Did you mean `build`?
+
+<tab>View all installed commands with `cargo --list`",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_config.rs b/src/tools/cargo/tests/testsuite/cargo_config.rs
new file mode 100644
index 000000000..e367f8e06
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_config.rs
@@ -0,0 +1,520 @@
+//! Tests for the `cargo config` command.
+
+use super::config::write_config_at;
+use cargo_test_support::paths;
+use std::fs;
+use std::path::PathBuf;
+
+fn cargo_process(s: &str) -> cargo_test_support::Execs {
+ let mut p = cargo_test_support::cargo_process(s);
+ // Clear out some of the environment added by the default cargo_process so
+ // the tests don't need to deal with it.
+ p.env_remove("CARGO_PROFILE_DEV_SPLIT_DEBUGINFO")
+ .env_remove("CARGO_PROFILE_TEST_SPLIT_DEBUGINFO")
+ .env_remove("CARGO_PROFILE_RELEASE_SPLIT_DEBUGINFO")
+ .env_remove("CARGO_PROFILE_BENCH_SPLIT_DEBUGINFO")
+ .env_remove("CARGO_INCREMENTAL");
+ p
+}
+
+#[cargo_test]
+fn gated() {
+ cargo_process("config get")
+ .masquerade_as_nightly_cargo(&["cargo-config"])
+ .with_status(101)
+ .with_stderr("\
+error: the `cargo config` command is unstable, pass `-Z unstable-options` to enable it
+See https://github.com/rust-lang/cargo/issues/9301 for more information about the `cargo config` command.
+")
+ .run();
+}
+
+fn common_setup() -> PathBuf {
+ write_config_at(
+ paths::home().join(".cargo/config.toml"),
+ "
+ [alias]
+ foo = \"abc --xyz\"
+ [build]
+ jobs = 99
+ rustflags = [\"--flag-global\"]
+ [profile.dev]
+ opt-level = 3
+ [profile.dev.package.foo]
+ opt-level = 1
+ [target.'cfg(target_os = \"linux\")']
+ runner = \"runme\"
+
+ # How unknown keys are handled.
+ [extra-table]
+ somekey = \"somevalue\"
+ ",
+ );
+ let sub_folder = paths::root().join("foo/.cargo");
+ write_config_at(
+ sub_folder.join("config.toml"),
+ "
+ [alias]
+ sub-example = [\"sub\", \"example\"]
+ [build]
+ rustflags = [\"--flag-directory\"]
+ ",
+ );
+ sub_folder
+}
+
+#[cargo_test]
+fn get_toml() {
+ // Notes:
+ // - The "extra-table" is shown without a warning. I'm not sure how that
+ // should be handled, since displaying warnings could cause problems
+ // with ingesting the output.
+ // - Environment variables aren't loaded. :(
+ let sub_folder = common_setup();
+ cargo_process("config get -Zunstable-options")
+ .cwd(&sub_folder.parent().unwrap())
+ .masquerade_as_nightly_cargo(&["cargo-config"])
+ .env("CARGO_ALIAS_BAR", "cat dog")
+ .env("CARGO_BUILD_JOBS", "100")
+ // The weird forward slash in the linux line is due to testsuite normalization.
+ .with_stdout(
+ "\
+alias.foo = \"abc --xyz\"
+alias.sub-example = [\"sub\", \"example\"]
+build.jobs = 99
+build.rustflags = [\"--flag-directory\", \"--flag-global\"]
+extra-table.somekey = \"somevalue\"
+profile.dev.opt-level = 3
+profile.dev.package.foo.opt-level = 1
+target.\"cfg(target_os = \\\"linux\\\")\".runner = \"runme\"
+# The following environment variables may affect the loaded values.
+# CARGO_ALIAS_BAR=[..]cat dog[..]
+# CARGO_BUILD_JOBS=100
+# CARGO_HOME=[ROOT]/home/.cargo
+",
+ )
+ .with_stderr("")
+ .run();
+
+ // Env keys work if they are specific.
+ cargo_process("config get build.jobs -Zunstable-options")
+ .cwd(&sub_folder.parent().unwrap())
+ .masquerade_as_nightly_cargo(&["cargo-config"])
+ .env("CARGO_BUILD_JOBS", "100")
+ .with_stdout("build.jobs = 100")
+ .with_stderr("")
+ .run();
+
+ // Array value.
+ cargo_process("config get build.rustflags -Zunstable-options")
+ .cwd(&sub_folder.parent().unwrap())
+ .masquerade_as_nightly_cargo(&["cargo-config"])
+ .with_stdout("build.rustflags = [\"--flag-directory\", \"--flag-global\"]")
+ .with_stderr("")
+ .run();
+
+ // Sub-table
+ cargo_process("config get profile -Zunstable-options")
+ .cwd(&sub_folder.parent().unwrap())
+ .masquerade_as_nightly_cargo(&["cargo-config"])
+ .with_stdout(
+ "\
+profile.dev.opt-level = 3
+profile.dev.package.foo.opt-level = 1
+",
+ )
+ .with_stderr("")
+ .run();
+
+ // Specific profile entry.
+ cargo_process("config get profile.dev.opt-level -Zunstable-options")
+ .cwd(&sub_folder.parent().unwrap())
+ .masquerade_as_nightly_cargo(&["cargo-config"])
+ .with_stdout("profile.dev.opt-level = 3")
+ .with_stderr("")
+ .run();
+
+ // A key that isn't set.
+ cargo_process("config get build.rustc -Zunstable-options")
+ .cwd(&sub_folder.parent().unwrap())
+ .masquerade_as_nightly_cargo(&["cargo-config"])
+ .with_status(101)
+ .with_stdout("")
+ .with_stderr("error: config value `build.rustc` is not set")
+ .run();
+
+ // A key that is not part of Cargo's config schema.
+ cargo_process("config get not.set -Zunstable-options")
+ .cwd(&sub_folder.parent().unwrap())
+ .masquerade_as_nightly_cargo(&["cargo-config"])
+ .with_status(101)
+ .with_stdout("")
+ .with_stderr("error: config value `not.set` is not set")
+ .run();
+}
+
+#[cargo_test]
+fn get_json() {
+ // Notes:
+ // - This does not show env vars at all. :(
+ let all_json = r#"
+ {
+ "alias": {
+ "foo": "abc --xyz",
+ "sub-example": [
+ "sub",
+ "example"
+ ]
+ },
+ "build": {
+ "jobs": 99,
+ "rustflags": [
+ "--flag-directory",
+ "--flag-global"
+ ]
+ },
+ "extra-table": {
+ "somekey": "somevalue"
+ },
+ "profile": {
+ "dev": {
+ "opt-level": 3,
+ "package": {
+ "foo": {
+ "opt-level": 1
+ }
+ }
+ }
+ },
+ "target": {
+ "cfg(target_os = \"linux\")": {
+ "runner": "runme"
+ }
+ }
+ }
+ "#;
+ let sub_folder = common_setup();
+ cargo_process("config get --format=json -Zunstable-options")
+ .cwd(&sub_folder.parent().unwrap())
+ .masquerade_as_nightly_cargo(&["cargo-config"])
+ .env("CARGO_ALIAS_BAR", "cat dog")
+ .env("CARGO_BUILD_JOBS", "100")
+ .with_json(all_json)
+ .with_stderr(
+ "\
+note: The following environment variables may affect the loaded values.
+CARGO_ALIAS_BAR=[..]cat dog[..]
+CARGO_BUILD_JOBS=100
+CARGO_HOME=[ROOT]/home/.cargo
+",
+ )
+ .run();
+
+ // json-value is the same for the entire root table
+ cargo_process("config get --format=json-value -Zunstable-options")
+ .cwd(&sub_folder.parent().unwrap())
+ .masquerade_as_nightly_cargo(&["cargo-config"])
+ .with_json(all_json)
+ .with_stderr(
+ "\
+note: The following environment variables may affect the loaded values.
+CARGO_HOME=[ROOT]/home/.cargo
+",
+ )
+ .run();
+
+ cargo_process("config get --format=json build.jobs -Zunstable-options")
+ .cwd(&sub_folder.parent().unwrap())
+ .masquerade_as_nightly_cargo(&["cargo-config"])
+ .with_json(
+ r#"
+ {"build": {"jobs": 99}}
+ "#,
+ )
+ .with_stderr("")
+ .run();
+
+ cargo_process("config get --format=json-value build.jobs -Zunstable-options")
+ .cwd(&sub_folder.parent().unwrap())
+ .masquerade_as_nightly_cargo(&["cargo-config"])
+ .with_stdout("99")
+ .with_stderr("")
+ .run();
+}
+
+#[cargo_test]
+fn show_origin_toml() {
+ let sub_folder = common_setup();
+ cargo_process("config get --show-origin -Zunstable-options")
+ .cwd(&sub_folder.parent().unwrap())
+ .masquerade_as_nightly_cargo(&["cargo-config"])
+ .with_stdout(
+ "\
+alias.foo = \"abc --xyz\" # [ROOT]/home/.cargo/config.toml
+alias.sub-example = [
+ \"sub\", # [ROOT]/foo/.cargo/config.toml
+ \"example\", # [ROOT]/foo/.cargo/config.toml
+]
+build.jobs = 99 # [ROOT]/home/.cargo/config.toml
+build.rustflags = [
+ \"--flag-directory\", # [ROOT]/foo/.cargo/config.toml
+ \"--flag-global\", # [ROOT]/home/.cargo/config.toml
+]
+extra-table.somekey = \"somevalue\" # [ROOT]/home/.cargo/config.toml
+profile.dev.opt-level = 3 # [ROOT]/home/.cargo/config.toml
+profile.dev.package.foo.opt-level = 1 # [ROOT]/home/.cargo/config.toml
+target.\"cfg(target_os = \\\"linux\\\")\".runner = \"runme\" # [ROOT]/home/.cargo/config.toml
+# The following environment variables may affect the loaded values.
+# CARGO_HOME=[ROOT]/home/.cargo
+",
+ )
+ .with_stderr("")
+ .run();
+
+ cargo_process("config get --show-origin build.rustflags -Zunstable-options")
+ .cwd(&sub_folder.parent().unwrap())
+ .masquerade_as_nightly_cargo(&["cargo-config"])
+ .env("CARGO_BUILD_RUSTFLAGS", "env1 env2")
+ .with_stdout(
+ "\
+build.rustflags = [
+ \"--flag-directory\", # [ROOT]/foo/.cargo/config.toml
+ \"--flag-global\", # [ROOT]/home/.cargo/config.toml
+ \"env1\", # environment variable `CARGO_BUILD_RUSTFLAGS`
+ \"env2\", # environment variable `CARGO_BUILD_RUSTFLAGS`
+]
+",
+ )
+ .with_stderr("")
+ .run();
+}
+
+#[cargo_test]
+fn show_origin_toml_cli() {
+ let sub_folder = common_setup();
+ cargo_process("config get --show-origin build.jobs -Zunstable-options --config build.jobs=123")
+ .cwd(&sub_folder.parent().unwrap())
+ .masquerade_as_nightly_cargo(&["cargo-config"])
+ .env("CARGO_BUILD_JOBS", "1")
+ .with_stdout("build.jobs = 123 # --config cli option")
+ .with_stderr("")
+ .run();
+
+ cargo_process("config get --show-origin build.rustflags -Zunstable-options --config")
+ .arg("build.rustflags=[\"cli1\",\"cli2\"]")
+ .cwd(&sub_folder.parent().unwrap())
+ .masquerade_as_nightly_cargo(&["cargo-config"])
+ .env("CARGO_BUILD_RUSTFLAGS", "env1 env2")
+ .with_stdout(
+ "\
+build.rustflags = [
+ \"--flag-directory\", # [ROOT]/foo/.cargo/config.toml
+ \"--flag-global\", # [ROOT]/home/.cargo/config.toml
+ \"cli1\", # --config cli option
+ \"cli2\", # --config cli option
+ \"env1\", # environment variable `CARGO_BUILD_RUSTFLAGS`
+ \"env2\", # environment variable `CARGO_BUILD_RUSTFLAGS`
+]
+",
+ )
+ .with_stderr("")
+ .run();
+}
+
+#[cargo_test]
+fn show_origin_json() {
+ let sub_folder = common_setup();
+ cargo_process("config get --show-origin --format=json -Zunstable-options")
+ .cwd(&sub_folder.parent().unwrap())
+ .masquerade_as_nightly_cargo(&["cargo-config"])
+ .with_status(101)
+ .with_stderr("error: the `json` format does not support --show-origin, try the `toml` format instead")
+ .run();
+}
+
+#[cargo_test]
+fn unmerged_toml() {
+ let sub_folder = common_setup();
+ cargo_process("config get --merged=no -Zunstable-options")
+ .cwd(&sub_folder.parent().unwrap())
+ .masquerade_as_nightly_cargo(&["cargo-config"])
+ .env("CARGO_ALIAS_BAR", "cat dog")
+ .env("CARGO_BUILD_JOBS", "100")
+ .with_stdout(
+ "\
+# Environment variables
+# CARGO=[..]
+# CARGO_ALIAS_BAR=[..]cat dog[..]
+# CARGO_BUILD_JOBS=100
+# CARGO_HOME=[ROOT]/home/.cargo
+
+# [ROOT]/foo/.cargo/config.toml
+alias.sub-example = [\"sub\", \"example\"]
+build.rustflags = [\"--flag-directory\"]
+
+# [ROOT]/home/.cargo/config.toml
+alias.foo = \"abc --xyz\"
+build.jobs = 99
+build.rustflags = [\"--flag-global\"]
+extra-table.somekey = \"somevalue\"
+profile.dev.opt-level = 3
+profile.dev.package.foo.opt-level = 1
+target.\"cfg(target_os = \\\"linux\\\")\".runner = \"runme\"
+
+",
+ )
+ .with_stderr("")
+ .run();
+
+ cargo_process("config get --merged=no build.rustflags -Zunstable-options")
+ .cwd(&sub_folder.parent().unwrap())
+ .masquerade_as_nightly_cargo(&["cargo-config"])
+ .env("CARGO_BUILD_RUSTFLAGS", "env1 env2")
+ .with_stdout(
+ "\
+# Environment variables
+# CARGO_BUILD_RUSTFLAGS=[..]env1 env2[..]
+
+# [ROOT]/foo/.cargo/config.toml
+build.rustflags = [\"--flag-directory\"]
+
+# [ROOT]/home/.cargo/config.toml
+build.rustflags = [\"--flag-global\"]
+
+",
+ )
+ .with_stderr("")
+ .run();
+
+ cargo_process("config get --merged=no does.not.exist -Zunstable-options")
+ .cwd(&sub_folder.parent().unwrap())
+ .masquerade_as_nightly_cargo(&["cargo-config"])
+ .with_stderr("")
+ .with_stderr("")
+ .run();
+
+ cargo_process("config get --merged=no build.rustflags.extra -Zunstable-options")
+ .cwd(&sub_folder.parent().unwrap())
+ .masquerade_as_nightly_cargo(&["cargo-config"])
+ .with_status(101)
+ .with_stderr(
+ "error: expected table for configuration key `build.rustflags`, \
+ but found array in [ROOT]/foo/.cargo/config.toml",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn unmerged_toml_cli() {
+ let sub_folder = common_setup();
+ cargo_process("config get --merged=no build.rustflags -Zunstable-options --config")
+ .arg("build.rustflags=[\"cli1\",\"cli2\"]")
+ .cwd(&sub_folder.parent().unwrap())
+ .masquerade_as_nightly_cargo(&["cargo-config"])
+ .env("CARGO_BUILD_RUSTFLAGS", "env1 env2")
+ .with_stdout(
+ "\
+# --config cli option
+build.rustflags = [\"cli1\", \"cli2\"]
+
+# Environment variables
+# CARGO_BUILD_RUSTFLAGS=[..]env1 env2[..]
+
+# [ROOT]/foo/.cargo/config.toml
+build.rustflags = [\"--flag-directory\"]
+
+# [ROOT]/home/.cargo/config.toml
+build.rustflags = [\"--flag-global\"]
+
+",
+ )
+ .with_stderr("")
+ .run();
+}
+
+#[cargo_test]
+fn unmerged_json() {
+ let sub_folder = common_setup();
+ cargo_process("config get --merged=no --format=json -Zunstable-options")
+ .cwd(&sub_folder.parent().unwrap())
+ .masquerade_as_nightly_cargo(&["cargo-config"])
+ .with_status(101)
+ .with_stderr(
+ "error: the `json` format does not support --merged=no, try the `toml` format instead",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn includes() {
+ let sub_folder = common_setup();
+ fs::write(
+ sub_folder.join("config.toml"),
+ "
+ include = 'other.toml'
+ [build]
+ rustflags = [\"--flag-directory\"]
+ ",
+ )
+ .unwrap();
+ fs::write(
+ sub_folder.join("other.toml"),
+ "
+ [build]
+ rustflags = [\"--flag-other\"]
+ ",
+ )
+ .unwrap();
+
+ cargo_process("config get build.rustflags -Zunstable-options -Zconfig-include")
+ .cwd(&sub_folder.parent().unwrap())
+ .masquerade_as_nightly_cargo(&["cargo-config", "config-include"])
+ .with_stdout(r#"build.rustflags = ["--flag-other", "--flag-directory", "--flag-global"]"#)
+ .with_stderr("")
+ .run();
+
+ cargo_process("config get build.rustflags --show-origin -Zunstable-options -Zconfig-include")
+ .cwd(&sub_folder.parent().unwrap())
+ .masquerade_as_nightly_cargo(&["cargo-config", "config-include"])
+ .with_stdout(
+ "\
+build.rustflags = [
+ \"--flag-other\", # [ROOT]/foo/.cargo/other.toml
+ \"--flag-directory\", # [ROOT]/foo/.cargo/config.toml
+ \"--flag-global\", # [ROOT]/home/.cargo/config.toml
+]
+",
+ )
+ .with_stderr("")
+ .run();
+
+ cargo_process("config get --merged=no -Zunstable-options -Zconfig-include")
+ .cwd(&sub_folder.parent().unwrap())
+ .masquerade_as_nightly_cargo(&["cargo-config", "config-include"])
+ .with_stdout(
+ "\
+# Environment variables
+# CARGO=[..]
+# CARGO_HOME=[ROOT]/home/.cargo
+
+# [ROOT]/foo/.cargo/other.toml
+build.rustflags = [\"--flag-other\"]
+
+# [ROOT]/foo/.cargo/config.toml
+build.rustflags = [\"--flag-directory\"]
+include = \"other.toml\"
+
+# [ROOT]/home/.cargo/config.toml
+alias.foo = \"abc --xyz\"
+build.jobs = 99
+build.rustflags = [\"--flag-global\"]
+extra-table.somekey = \"somevalue\"
+profile.dev.opt-level = 3
+profile.dev.package.foo.opt-level = 1
+target.\"cfg(target_os = \\\"linux\\\")\".runner = \"runme\"
+
+",
+ )
+ .with_stderr("")
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_env_config.rs b/src/tools/cargo/tests/testsuite/cargo_env_config.rs
new file mode 100644
index 000000000..d80c38d0e
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_env_config.rs
@@ -0,0 +1,181 @@
+//! Tests for `[env]` config.
+
+use cargo_test_support::{basic_bin_manifest, project};
+
+#[cargo_test]
+fn env_basic() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file(
+ "src/main.rs",
+ r#"
+ use std::env;
+ fn main() {
+ println!( "compile-time:{}", env!("ENV_TEST_1233") );
+ println!( "run-time:{}", env::var("ENV_TEST_1233").unwrap());
+ }
+ "#,
+ )
+ .file(
+ ".cargo/config",
+ r#"
+ [env]
+ ENV_TEST_1233 = "Hello"
+ "#,
+ )
+ .build();
+
+ p.cargo("run")
+ .with_stdout_contains("compile-time:Hello")
+ .with_stdout_contains("run-time:Hello")
+ .run();
+}
+
+#[cargo_test]
+fn env_invalid() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ }
+ "#,
+ )
+ .file(
+ ".cargo/config",
+ r#"
+ [env]
+ ENV_TEST_BOOL = false
+ "#,
+ )
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr_contains("[..]could not load config key `env.ENV_TEST_BOOL`")
+ .run();
+}
+
+#[cargo_test]
+fn env_no_cargo_home() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ }
+ "#,
+ )
+ .file(
+ ".cargo/config",
+ r#"
+ [env]
+ CARGO_HOME = "/"
+ "#,
+ )
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr_contains("[..]setting the `CARGO_HOME` environment variable is not supported in the `[env]` configuration table")
+ .run();
+}
+
+#[cargo_test]
+fn env_force() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file(
+ "src/main.rs",
+ r#"
+ use std::env;
+ fn main() {
+ println!( "ENV_TEST_FORCED:{}", env!("ENV_TEST_FORCED") );
+ println!( "ENV_TEST_UNFORCED:{}", env!("ENV_TEST_UNFORCED") );
+ println!( "ENV_TEST_UNFORCED_DEFAULT:{}", env!("ENV_TEST_UNFORCED_DEFAULT") );
+ }
+ "#,
+ )
+ .file(
+ ".cargo/config",
+ r#"
+ [env]
+ ENV_TEST_UNFORCED_DEFAULT = "from-config"
+ ENV_TEST_UNFORCED = { value = "from-config", force = false }
+ ENV_TEST_FORCED = { value = "from-config", force = true }
+ "#,
+ )
+ .build();
+
+ p.cargo("run")
+ .env("ENV_TEST_FORCED", "from-env")
+ .env("ENV_TEST_UNFORCED", "from-env")
+ .env("ENV_TEST_UNFORCED_DEFAULT", "from-env")
+ .with_stdout_contains("ENV_TEST_FORCED:from-config")
+ .with_stdout_contains("ENV_TEST_UNFORCED:from-env")
+ .with_stdout_contains("ENV_TEST_UNFORCED_DEFAULT:from-env")
+ .run();
+}
+
+#[cargo_test]
+fn env_relative() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo2"))
+ .file(
+ "src/main.rs",
+ r#"
+ use std::env;
+ use std::path::Path;
+ fn main() {
+ println!( "ENV_TEST_REGULAR:{}", env!("ENV_TEST_REGULAR") );
+ println!( "ENV_TEST_REGULAR_DEFAULT:{}", env!("ENV_TEST_REGULAR_DEFAULT") );
+ println!( "ENV_TEST_RELATIVE:{}", env!("ENV_TEST_RELATIVE") );
+
+ assert!( Path::new(env!("ENV_TEST_RELATIVE")).is_absolute() );
+ assert!( !Path::new(env!("ENV_TEST_REGULAR")).is_absolute() );
+ assert!( !Path::new(env!("ENV_TEST_REGULAR_DEFAULT")).is_absolute() );
+ }
+ "#,
+ )
+ .file(
+ ".cargo/config",
+ r#"
+ [env]
+ ENV_TEST_REGULAR = { value = "Cargo.toml", relative = false }
+ ENV_TEST_REGULAR_DEFAULT = "Cargo.toml"
+ ENV_TEST_RELATIVE = { value = "Cargo.toml", relative = true }
+ "#,
+ )
+ .build();
+
+ p.cargo("run").run();
+}
+
+#[cargo_test]
+fn env_no_override() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("unchanged"))
+ .file(
+ "src/main.rs",
+ r#"
+ use std::env;
+ fn main() {
+ println!( "CARGO_PKG_NAME:{}", env!("CARGO_PKG_NAME") );
+ }
+ "#,
+ )
+ .file(
+ ".cargo/config",
+ r#"
+ [env]
+ CARGO_PKG_NAME = { value = "from-config", force = true }
+ "#,
+ )
+ .build();
+
+ p.cargo("run")
+ .with_stdout_contains("CARGO_PKG_NAME:unchanged")
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_features.rs b/src/tools/cargo/tests/testsuite/cargo_features.rs
new file mode 100644
index 000000000..6e5531431
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_features.rs
@@ -0,0 +1,714 @@
+//! Tests for `cargo-features` definitions.
+
+use cargo_test_support::registry::Package;
+use cargo_test_support::{project, registry};
+
+#[cargo_test]
+fn feature_required() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+ im-a-teapot = true
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("check")
+ .masquerade_as_nightly_cargo(&["test-dummy-unstable"])
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[..]`
+
+Caused by:
+ the `im-a-teapot` manifest key is unstable and may not work properly in England
+
+Caused by:
+ feature `test-dummy-unstable` is required
+
+ The package requires the Cargo feature called `test-dummy-unstable`, \
+ but that feature is not stabilized in this version of Cargo (1.[..]).
+ Consider adding `cargo-features = [\"test-dummy-unstable\"]` to the top of Cargo.toml \
+ (above the [package] table) to tell Cargo you are opting in to use this unstable feature.
+ See https://doc.rust-lang.org/nightly/cargo/reference/unstable.html for more information \
+ about the status of this feature.
+",
+ )
+ .run();
+
+ // Same, but stable.
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[..]`
+
+Caused by:
+ the `im-a-teapot` manifest key is unstable and may not work properly in England
+
+Caused by:
+ feature `test-dummy-unstable` is required
+
+ The package requires the Cargo feature called `test-dummy-unstable`, \
+ but that feature is not stabilized in this version of Cargo (1.[..]).
+ Consider trying a newer version of Cargo (this may require the nightly release).
+ See https://doc.rust-lang.org/nightly/cargo/reference/unstable.html \
+ for more information about the status of this feature.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn feature_required_dependency() {
+ // The feature has been stabilized by a future version of Cargo, and
+ // someone published something uses it, but this version of Cargo has not
+ // yet stabilized it. Don't suggest editing Cargo.toml, since published
+ // packages shouldn't be edited.
+ Package::new("bar", "1.0.0")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ im-a-teapot = true
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .masquerade_as_nightly_cargo(&["test-dummy-unstable"])
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[DOWNLOADING] [..]
+[DOWNLOADED] bar v1.0.0 [..]
+error: failed to download replaced source registry `crates-io`
+
+Caused by:
+ failed to parse manifest at `[..]/bar-1.0.0/Cargo.toml`
+
+Caused by:
+ the `im-a-teapot` manifest key is unstable and may not work properly in England
+
+Caused by:
+ feature `test-dummy-unstable` is required
+
+ The package requires the Cargo feature called `test-dummy-unstable`, \
+ but that feature is not stabilized in this version of Cargo (1.[..]).
+ Consider trying a more recent nightly release.
+ See https://doc.rust-lang.org/nightly/cargo/reference/unstable.html \
+ for more information about the status of this feature.
+",
+ )
+ .run();
+
+ // Same, but stable.
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to download `bar v1.0.0`
+
+Caused by:
+ unable to get packages from source
+
+Caused by:
+ failed to download replaced source registry `crates-io`
+
+Caused by:
+ failed to parse manifest at `[..]/bar-1.0.0/Cargo.toml`
+
+Caused by:
+ the `im-a-teapot` manifest key is unstable and may not work properly in England
+
+Caused by:
+ feature `test-dummy-unstable` is required
+
+ The package requires the Cargo feature called `test-dummy-unstable`, \
+ but that feature is not stabilized in this version of Cargo (1.[..]).
+ Consider trying a newer version of Cargo (this may require the nightly release).
+ See https://doc.rust-lang.org/nightly/cargo/reference/unstable.html \
+ for more information about the status of this feature.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn unknown_feature() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ cargo-features = ["foo"]
+
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[..]`
+
+Caused by:
+ unknown cargo feature `foo`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn stable_feature_warns() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ cargo-features = ["test-dummy-stable"]
+
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("check")
+ .with_stderr(
+ "\
+warning: the cargo feature `test-dummy-stable` has been stabilized in the 1.0 \
+release and is no longer necessary to be listed in the manifest
+ See https://doc.rust-lang.org/[..]cargo/ for more information about using this feature.
+[CHECKING] a [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test(nightly, reason = "-Zallow-features is unstable")]
+fn allow_features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ cargo-features = ["test-dummy-unstable"]
+
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+ im-a-teapot = true
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("-Zallow-features=test-dummy-unstable check")
+ .masquerade_as_nightly_cargo(&["allow-features", "test-dummy-unstable"])
+ .with_stderr(
+ "\
+[CHECKING] a [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ p.cargo("-Zallow-features=test-dummy-unstable,print-im-a-teapot -Zprint-im-a-teapot check")
+ .masquerade_as_nightly_cargo(&[
+ "allow-features",
+ "test-dummy-unstable",
+ "print-im-a-teapot",
+ ])
+ .with_stdout("im-a-teapot = true")
+ .run();
+
+ p.cargo("-Zallow-features=test-dummy-unstable -Zprint-im-a-teapot check")
+ .masquerade_as_nightly_cargo(&[
+ "allow-features",
+ "test-dummy-unstable",
+ "print-im-a-teapot",
+ ])
+ .with_status(101)
+ .with_stderr(
+ "\
+error: the feature `print-im-a-teapot` is not in the list of allowed features: [test-dummy-unstable]
+",
+ )
+ .run();
+
+ p.cargo("-Zallow-features= check")
+ .masquerade_as_nightly_cargo(&["allow-features", "test-dummy-unstable"])
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[..]`
+
+Caused by:
+ the feature `test-dummy-unstable` is not in the list of allowed features: []
+",
+ )
+ .run();
+}
+
+#[cargo_test(nightly, reason = "-Zallow-features is unstable")]
+fn allow_features_to_rustc() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ #![feature(test_2018_feature)]
+ "#,
+ )
+ .build();
+
+ p.cargo("-Zallow-features= check")
+ .masquerade_as_nightly_cargo(&["allow-features"])
+ .with_status(101)
+ .with_stderr_contains("[..]E0725[..]")
+ .run();
+
+ p.cargo("-Zallow-features=test_2018_feature check")
+ .masquerade_as_nightly_cargo(&["allow-features"])
+ .with_stderr(
+ "\
+[CHECKING] a [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test(nightly, reason = "-Zallow-features is unstable")]
+fn allow_features_in_cfg() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ cargo-features = ["test-dummy-unstable"]
+
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+ im-a-teapot = true
+ "#,
+ )
+ .file(
+ ".cargo/config.toml",
+ r#"
+ [unstable]
+ allow-features = ["test-dummy-unstable", "print-im-a-teapot"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .masquerade_as_nightly_cargo(&[
+ "allow-features",
+ "test-dummy-unstable",
+ "print-im-a-teapot",
+ ])
+ .with_stderr(
+ "\
+[CHECKING] a [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ p.cargo("-Zprint-im-a-teapot check")
+ .masquerade_as_nightly_cargo(&[
+ "allow-features",
+ "test-dummy-unstable",
+ "print-im-a-teapot",
+ ])
+ .with_stdout("im-a-teapot = true")
+ .with_stderr("[FINISHED] [..]")
+ .run();
+
+ p.cargo("-Zunstable-options check")
+ .masquerade_as_nightly_cargo(&["allow-features", "test-dummy-unstable", "print-im-a-teapot"])
+ .with_status(101)
+ .with_stderr(
+ "\
+error: the feature `unstable-options` is not in the list of allowed features: [print-im-a-teapot, test-dummy-unstable]
+",
+ )
+ .run();
+
+ // -Zallow-features overrides .cargo/config
+ p.cargo("-Zallow-features=test-dummy-unstable -Zprint-im-a-teapot check")
+ .masquerade_as_nightly_cargo(&[
+ "allow-features",
+ "test-dummy-unstable",
+ "print-im-a-teapot",
+ ])
+ .with_status(101)
+ .with_stderr(
+ "\
+error: the feature `print-im-a-teapot` is not in the list of allowed features: [test-dummy-unstable]
+",
+ )
+ .run();
+
+ p.cargo("-Zallow-features= check")
+ .masquerade_as_nightly_cargo(&[
+ "allow-features",
+ "test-dummy-unstable",
+ "print-im-a-teapot",
+ ])
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[..]`
+
+Caused by:
+ the feature `test-dummy-unstable` is not in the list of allowed features: []
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn nightly_feature_requires_nightly() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ cargo-features = ["test-dummy-unstable"]
+
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+ im-a-teapot = true
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("check")
+ .masquerade_as_nightly_cargo(&["test-dummy-unstable"])
+ .with_stderr(
+ "\
+[CHECKING] a [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[..]`
+
+Caused by:
+ the cargo feature `test-dummy-unstable` requires a nightly version of Cargo, \
+ but this is the `stable` channel
+ See [..]
+ See https://doc.rust-lang.org/[..]cargo/reference/unstable.html for more \
+ information about using this feature.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn nightly_feature_requires_nightly_in_dep() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ a = { path = "a" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ cargo-features = ["test-dummy-unstable"]
+
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+ im-a-teapot = true
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .build();
+ p.cargo("check")
+ .masquerade_as_nightly_cargo(&["test-dummy-unstable"])
+ .with_stderr(
+ "\
+[CHECKING] a [..]
+[CHECKING] b [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to get `a` as a dependency of package `b v0.0.1 ([..])`
+
+Caused by:
+ failed to load source for dependency `a`
+
+Caused by:
+ Unable to update [..]
+
+Caused by:
+ failed to parse manifest at `[..]`
+
+Caused by:
+ the cargo feature `test-dummy-unstable` requires a nightly version of Cargo, \
+ but this is the `stable` channel
+ See [..]
+ See https://doc.rust-lang.org/[..]cargo/reference/unstable.html for more \
+ information about using this feature.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cant_publish() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ cargo-features = ["test-dummy-unstable"]
+
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+ im-a-teapot = true
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("check")
+ .masquerade_as_nightly_cargo(&["test-dummy-unstable"])
+ .with_stderr(
+ "\
+[CHECKING] a [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[..]`
+
+Caused by:
+ the cargo feature `test-dummy-unstable` requires a nightly version of Cargo, \
+ but this is the `stable` channel
+ See [..]
+ See https://doc.rust-lang.org/[..]cargo/reference/unstable.html for more \
+ information about using this feature.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn z_flags_rejected() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ cargo-features = ["test-dummy-unstable"]
+
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+ im-a-teapot = true
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("check -Zprint-im-a-teapot")
+ .with_status(101)
+ .with_stderr(
+ "error: the `-Z` flag is only accepted on the nightly \
+ channel of Cargo, but this is the `stable` channel\n\
+ See [..]",
+ )
+ .run();
+
+ p.cargo("check -Zarg")
+ .masquerade_as_nightly_cargo(&["test-dummy-unstable"])
+ .with_status(101)
+ .with_stderr("error: unknown `-Z` flag specified: arg")
+ .run();
+
+ p.cargo("check -Zprint-im-a-teapot")
+ .masquerade_as_nightly_cargo(&["test-dummy-unstable"])
+ .with_stdout("im-a-teapot = true\n")
+ .with_stderr(
+ "\
+[CHECKING] a [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn publish_allowed() {
+ let registry = registry::RegistryBuilder::new()
+ .http_api()
+ .http_index()
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ cargo-features = ["test-dummy-unstable"]
+
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("publish")
+ .replace_crates_io(registry.index_url())
+ .masquerade_as_nightly_cargo(&["test-dummy-unstable"])
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[WARNING] [..]
+[..]
+[PACKAGING] a v0.0.1 [..]
+[VERIFYING] a v0.0.1 [..]
+[COMPILING] a v0.0.1 [..]
+[FINISHED] [..]
+[PACKAGED] [..]
+[UPLOADING] a v0.0.1 [..]
+[UPLOADED] a v0.0.1 to registry `crates-io`
+note: Waiting for `a v0.0.1` to be available at registry `crates-io`.
+You may press ctrl-c to skip waiting; the crate should be available shortly.
+[PUBLISHED] a v0.0.1 at registry `crates-io`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn wrong_position() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ cargo-features = ["test-dummy-unstable"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("check")
+ .masquerade_as_nightly_cargo(&["test-dummy-unstable"])
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at [..]
+
+Caused by:
+ cargo-features = [\"test-dummy-unstable\"] was found in the wrong location: it \
+ should be set at the top of Cargo.toml before any tables
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn z_stabilized() {
+ let p = project().file("src/lib.rs", "").build();
+
+ p.cargo("check -Z cache-messages")
+ .masquerade_as_nightly_cargo(&["always_nightly"])
+ .with_stderr(
+ "\
+warning: flag `-Z cache-messages` has been stabilized in the 1.40 release, \
+ and is no longer necessary
+ Message caching is now always enabled.
+
+[CHECKING] foo [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ p.cargo("check -Z offline")
+ .masquerade_as_nightly_cargo(&["always_nightly"])
+ .with_status(101)
+ .with_stderr(
+ "\
+error: flag `-Z offline` has been stabilized in the 1.36 release
+ Offline mode is now available via the --offline CLI option
+
+",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/avoid_empty_tables/in b/src/tools/cargo/tests/testsuite/cargo_remove/avoid_empty_tables/in
new file mode 120000
index 000000000..7fd0ba5eb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/avoid_empty_tables/in
@@ -0,0 +1 @@
+../remove-basic.in/ \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/avoid_empty_tables/mod.rs b/src/tools/cargo/tests/testsuite/cargo_remove/avoid_empty_tables/mod.rs
new file mode 100644
index 000000000..59a2333d6
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/avoid_empty_tables/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::curr_dir;
+use cargo_test_support::CargoCommand;
+use cargo_test_support::Project;
+
+use crate::cargo_remove::init_registry;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("remove")
+ .args(["clippy"])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/avoid_empty_tables/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/avoid_empty_tables/out/Cargo.toml
new file mode 100644
index 000000000..09a9ee86e
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/avoid_empty_tables/out/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "cargo-remove-test-fixture"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[build-dependencies]
+semver = "0.1.0"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/avoid_empty_tables/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/avoid_empty_tables/stderr.log
new file mode 100644
index 000000000..dd71023a8
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/avoid_empty_tables/stderr.log
@@ -0,0 +1,2 @@
+ Removing clippy from dependencies
+ Updating `dummy-registry` index
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/avoid_empty_tables/stdout.log b/src/tools/cargo/tests/testsuite/cargo_remove/avoid_empty_tables/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/avoid_empty_tables/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/build/in b/src/tools/cargo/tests/testsuite/cargo_remove/build/in
new file mode 120000
index 000000000..7fd0ba5eb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/build/in
@@ -0,0 +1 @@
+../remove-basic.in/ \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/build/mod.rs b/src/tools/cargo/tests/testsuite/cargo_remove/build/mod.rs
new file mode 100644
index 000000000..f4c9dcb94
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/build/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::curr_dir;
+use cargo_test_support::CargoCommand;
+use cargo_test_support::Project;
+
+use crate::cargo_remove::init_registry;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("remove")
+ .args(["--build", "semver"])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/build/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/build/out/Cargo.toml
new file mode 100644
index 000000000..babdc0a99
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/build/out/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "cargo-remove-test-fixture"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+clippy = "0.4"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/build/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/build/stderr.log
new file mode 100644
index 000000000..f037ebe28
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/build/stderr.log
@@ -0,0 +1,2 @@
+ Removing semver from build-dependencies
+ Updating `dummy-registry` index
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/build/stdout.log b/src/tools/cargo/tests/testsuite/cargo_remove/build/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/build/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/dev/in b/src/tools/cargo/tests/testsuite/cargo_remove/dev/in
new file mode 120000
index 000000000..7fd0ba5eb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/dev/in
@@ -0,0 +1 @@
+../remove-basic.in/ \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/dev/mod.rs b/src/tools/cargo/tests/testsuite/cargo_remove/dev/mod.rs
new file mode 100644
index 000000000..7d61fa954
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/dev/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::curr_dir;
+use cargo_test_support::CargoCommand;
+use cargo_test_support::Project;
+
+use crate::cargo_remove::init_registry;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("remove")
+ .args(["--dev", "regex"])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/dev/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/dev/out/Cargo.toml
new file mode 100644
index 000000000..40744a566
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/dev/out/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "cargo-remove-test-fixture"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[build-dependencies]
+semver = "0.1.0"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+clippy = "0.4"
+
+[dev-dependencies]
+serde = "1.0.90"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/dev/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/dev/stderr.log
new file mode 100644
index 000000000..c629b26b1
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/dev/stderr.log
@@ -0,0 +1,2 @@
+ Removing regex from dev-dependencies
+ Updating `dummy-registry` index
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/dev/stdout.log b/src/tools/cargo/tests/testsuite/cargo_remove/dev/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/dev/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/dry_run/in b/src/tools/cargo/tests/testsuite/cargo_remove/dry_run/in
new file mode 120000
index 000000000..7fd0ba5eb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/dry_run/in
@@ -0,0 +1 @@
+../remove-basic.in/ \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/dry_run/mod.rs b/src/tools/cargo/tests/testsuite/cargo_remove/dry_run/mod.rs
new file mode 100644
index 000000000..dca189315
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/dry_run/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::curr_dir;
+use cargo_test_support::CargoCommand;
+use cargo_test_support::Project;
+
+use crate::cargo_remove::init_registry;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("remove")
+ .args(["semver", "--dry-run"])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/dry_run/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/dry_run/out/Cargo.toml
new file mode 100644
index 000000000..340f06cda
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/dry_run/out/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "cargo-remove-test-fixture"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[build-dependencies]
+semver = "0.1.0"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+clippy = "0.4"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/dry_run/out/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_remove/dry_run/out/src/lib.rs
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/dry_run/out/src/lib.rs
@@ -0,0 +1 @@
+
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/dry_run/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/dry_run/stderr.log
new file mode 100644
index 000000000..8b118911c
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/dry_run/stderr.log
@@ -0,0 +1,2 @@
+ Removing semver from dependencies
+warning: aborting remove due to dry run
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/dry_run/stdout.log b/src/tools/cargo/tests/testsuite/cargo_remove/dry_run/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/dry_run/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/gc_patch/mod.rs b/src/tools/cargo/tests/testsuite/cargo_remove/gc_patch/mod.rs
new file mode 100644
index 000000000..2c1d592fb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/gc_patch/mod.rs
@@ -0,0 +1,72 @@
+use cargo_test_support::basic_manifest;
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::curr_dir;
+use cargo_test_support::git;
+use cargo_test_support::project;
+use cargo_test_support::CargoCommand;
+
+use crate::cargo_remove::init_registry;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+
+ let git_project1 = git::new("bar1", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("src/lib.rs", "")
+ })
+ .url();
+
+ let git_project2 = git::new("bar2", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("src/lib.rs", "")
+ })
+ .url();
+
+ let in_project = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ "[workspace]\n\
+ members = [ \"my-member\" ]\n\
+ \n\
+ [package]\n\
+ name = \"my-project\"\n\
+ version = \"0.1.0\"\n\
+ \n\
+ [dependencies]\n\
+ bar = {{ git = \"{git_project1}\" }}\n\
+ \n\
+ [patch.\"{git_project1}\"]\n\
+ bar = {{ git = \"{git_project2}\" }}\n\
+ \n\
+ [patch.crates-io]\n\
+ bar = {{ git = \"{git_project2}\" }}\n",
+ ),
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "my-member/Cargo.toml",
+ "[package]\n\
+ name = \"my-member\"\n\
+ version = \"0.1.0\"\n\
+ \n\
+ [dependencies]\n\
+ bar = \"0.1.0\"\n",
+ )
+ .file("my-member/src/lib.rs", "")
+ .build();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("remove")
+ .args(["bar"])
+ .current_dir(&in_project.root())
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &in_project.root());
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/gc_patch/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/gc_patch/out/Cargo.toml
new file mode 100644
index 000000000..2d8c22115
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/gc_patch/out/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+members = [ "my-member" ]
+
+[package]
+name = "my-project"
+version = "0.1.0"
+
+[patch.crates-io]
+bar = { git = "[ROOTURL]/bar2" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/gc_patch/out/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_remove/gc_patch/out/src/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/gc_patch/out/src/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/gc_patch/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/gc_patch/stderr.log
new file mode 100644
index 000000000..1dd2e7757
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/gc_patch/stderr.log
@@ -0,0 +1,3 @@
+ Removing bar from dependencies
+ Updating git repository `[ROOTURL]/bar2`
+ Updating `dummy-registry` index
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/gc_patch/stdout.log b/src/tools/cargo/tests/testsuite/cargo_remove/gc_patch/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/gc_patch/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/gc_profile/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/gc_profile/in/Cargo.toml
new file mode 100644
index 000000000..d781ad5a5
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/gc_profile/in/Cargo.toml
@@ -0,0 +1,36 @@
+[package]
+name = "cargo-remove-test-fixture"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[build-dependencies]
+semver = "0.1.0"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+clippy = "0.4"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+toml = "0.2.3"
+docopt = "0.6"
+
+[features]
+std = ["serde/std", "semver/std"]
+
+[profile.dev.package.docopt]
+opt-level = 3
+
+[profile.dev.package."toml@0.1.0"]
+opt-level = 3
+
+[profile.release.package.toml]
+opt-level = 1
+overflow-checks = false
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/gc_profile/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_remove/gc_profile/in/src/lib.rs
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/gc_profile/in/src/lib.rs
@@ -0,0 +1 @@
+
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/gc_profile/mod.rs b/src/tools/cargo/tests/testsuite/cargo_remove/gc_profile/mod.rs
new file mode 100644
index 000000000..7047c92e2
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/gc_profile/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::curr_dir;
+use cargo_test_support::CargoCommand;
+use cargo_test_support::Project;
+
+use crate::cargo_remove::init_registry;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("remove")
+ .args(["toml"])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/gc_profile/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/gc_profile/out/Cargo.toml
new file mode 100644
index 000000000..21b43fe68
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/gc_profile/out/Cargo.toml
@@ -0,0 +1,32 @@
+[package]
+name = "cargo-remove-test-fixture"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[build-dependencies]
+semver = "0.1.0"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+clippy = "0.4"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+toml = "0.2.3"
+docopt = "0.6"
+
+[features]
+std = ["serde/std", "semver/std"]
+
+[profile.dev.package.docopt]
+opt-level = 3
+
+[profile.release.package.toml]
+opt-level = 1
+overflow-checks = false
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/gc_profile/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/gc_profile/stderr.log
new file mode 100644
index 000000000..0e2e38f26
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/gc_profile/stderr.log
@@ -0,0 +1,2 @@
+ Removing toml from dependencies
+ Updating `dummy-registry` index
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/gc_profile/stdout.log b/src/tools/cargo/tests/testsuite/cargo_remove/gc_profile/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/gc_profile/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/in/Cargo.toml
new file mode 100644
index 000000000..48242c2d3
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/in/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+members = [ "my-package" ]
+
+[replace]
+"toml:0.1.0" = { path = "../toml" }
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/in/my-package/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/in/my-package/Cargo.toml
new file mode 100644
index 000000000..bee343a8b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/in/my-package/Cargo.toml
@@ -0,0 +1,26 @@
+[package]
+name = "my-package"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[build-dependencies]
+semver = "0.1.0"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+clippy = "0.4"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+toml = "0.2.3"
+docopt = "0.6"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/in/my-package/src/main.rs b/src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/in/my-package/src/main.rs
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/in/my-package/src/main.rs
@@ -0,0 +1 @@
+
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/mod.rs b/src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/mod.rs
new file mode 100644
index 000000000..717adef3e
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::curr_dir;
+use cargo_test_support::CargoCommand;
+use cargo_test_support::Project;
+
+use crate::cargo_remove::init_registry;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("remove")
+ .args(["--package", "my-package", "toml"])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/out/Cargo.toml
new file mode 100644
index 000000000..83a6a04d0
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/out/Cargo.toml
@@ -0,0 +1,2 @@
+[workspace]
+members = [ "my-package" ]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/out/my-package/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/out/my-package/Cargo.toml
new file mode 100644
index 000000000..36ddf7a04
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/out/my-package/Cargo.toml
@@ -0,0 +1,25 @@
+[package]
+name = "my-package"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[build-dependencies]
+semver = "0.1.0"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+clippy = "0.4"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+toml = "0.2.3"
+docopt = "0.6"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/out/my-package/src/main.rs b/src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/out/my-package/src/main.rs
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/out/my-package/src/main.rs
@@ -0,0 +1 @@
+
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/stderr.log
new file mode 100644
index 000000000..0e2e38f26
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/stderr.log
@@ -0,0 +1,2 @@
+ Removing toml from dependencies
+ Updating `dummy-registry` index
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/stdout.log b/src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/gc_replace/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_arg/in b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_arg/in
new file mode 120000
index 000000000..7fd0ba5eb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_arg/in
@@ -0,0 +1 @@
+../remove-basic.in/ \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_arg/mod.rs b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_arg/mod.rs
new file mode 100644
index 000000000..eac3c8b46
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_arg/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::curr_dir;
+use cargo_test_support::CargoCommand;
+use cargo_test_support::Project;
+
+use crate::cargo_remove::init_registry;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("remove")
+ .args(["foo", "--flag"])
+ .current_dir(cwd)
+ .assert()
+ .code(1)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_arg/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_arg/out/Cargo.toml
new file mode 100644
index 000000000..340f06cda
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_arg/out/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "cargo-remove-test-fixture"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[build-dependencies]
+semver = "0.1.0"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+clippy = "0.4"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_arg/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_arg/stderr.log
new file mode 100644
index 000000000..ac5f3cfd1
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_arg/stderr.log
@@ -0,0 +1,7 @@
+error: unexpected argument '--flag' found
+
+ tip: to pass '--flag' as a value, use '-- --flag'
+
+Usage: cargo[EXE] remove <DEP_ID>...
+
+For more information, try '--help'.
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_arg/stdout.log b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_arg/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_arg/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_dep/in b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_dep/in
new file mode 120000
index 000000000..7fd0ba5eb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_dep/in
@@ -0,0 +1 @@
+../remove-basic.in/ \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_dep/mod.rs b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_dep/mod.rs
new file mode 100644
index 000000000..c4dbeae91
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_dep/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::curr_dir;
+use cargo_test_support::CargoCommand;
+use cargo_test_support::Project;
+
+use crate::cargo_remove::init_registry;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("remove")
+ .args(["invalid_dependency_name"])
+ .current_dir(cwd)
+ .assert()
+ .code(101)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_dep/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_dep/out/Cargo.toml
new file mode 100644
index 000000000..340f06cda
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_dep/out/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "cargo-remove-test-fixture"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[build-dependencies]
+semver = "0.1.0"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+clippy = "0.4"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_dep/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_dep/stderr.log
new file mode 100644
index 000000000..eea124d65
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_dep/stderr.log
@@ -0,0 +1,2 @@
+ Removing invalid_dependency_name from dependencies
+error: the dependency `invalid_dependency_name` could not be found in `dependencies`.
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_dep/stdout.log b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_dep/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_dep/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/in b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/in
new file mode 120000
index 000000000..e2165e8cb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/in
@@ -0,0 +1 @@
+../remove-package.in/ \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/mod.rs b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/mod.rs
new file mode 100644
index 000000000..bff09882e
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::curr_dir;
+use cargo_test_support::CargoCommand;
+use cargo_test_support::Project;
+
+use crate::cargo_remove::init_registry;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("remove")
+ .args(["docopt", "--package", "dep-c"])
+ .current_dir(cwd)
+ .assert()
+ .code(101)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/out/Cargo.toml
new file mode 100644
index 000000000..733857113
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+members = [
+ "dep-a",
+ "dep-b"
+]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/out/dep-a/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/out/dep-a/Cargo.toml
new file mode 100644
index 000000000..7e87ce314
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/out/dep-a/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "dep-a"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[build-dependencies]
+semver = "0.1.0"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/out/dep-a/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/out/dep-a/src/lib.rs
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/out/dep-a/src/lib.rs
@@ -0,0 +1 @@
+
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/out/dep-b/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/out/dep-b/Cargo.toml
new file mode 100644
index 000000000..37d2d3ddf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/out/dep-b/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "dep-b"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[build-dependencies]
+semver = "0.1.0"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/out/dep-b/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/out/dep-b/src/lib.rs
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/out/dep-b/src/lib.rs
@@ -0,0 +1 @@
+
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/stderr.log
new file mode 100644
index 000000000..683512ca0
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/stderr.log
@@ -0,0 +1 @@
+error: package(s) `dep-c` not found in workspace `[ROOT]/case`
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/stdout.log b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/in b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/in
new file mode 120000
index 000000000..e2165e8cb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/in
@@ -0,0 +1 @@
+../remove-package.in/ \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/mod.rs b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/mod.rs
new file mode 100644
index 000000000..5093d5d2d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::curr_dir;
+use cargo_test_support::CargoCommand;
+use cargo_test_support::Project;
+
+use crate::cargo_remove::init_registry;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("remove")
+ .args(["docopt"])
+ .current_dir(cwd)
+ .assert()
+ .code(101)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/out/Cargo.toml
new file mode 100644
index 000000000..733857113
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+members = [
+ "dep-a",
+ "dep-b"
+]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/out/dep-a/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/out/dep-a/Cargo.toml
new file mode 100644
index 000000000..7e87ce314
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/out/dep-a/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "dep-a"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[build-dependencies]
+semver = "0.1.0"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/out/dep-a/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/out/dep-a/src/lib.rs
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/out/dep-a/src/lib.rs
@@ -0,0 +1 @@
+
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/out/dep-b/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/out/dep-b/Cargo.toml
new file mode 100644
index 000000000..37d2d3ddf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/out/dep-b/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "dep-b"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[build-dependencies]
+semver = "0.1.0"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/out/dep-b/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/out/dep-b/src/lib.rs
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/out/dep-b/src/lib.rs
@@ -0,0 +1 @@
+
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/stderr.log
new file mode 100644
index 000000000..8a03c9e5b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/stderr.log
@@ -0,0 +1,2 @@
+error: `cargo remove` could not determine which package to modify. Use the `--package` option to specify a package.
+available packages: dep-a, dep-b
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/stdout.log b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_package_multiple/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section/in b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section/in
new file mode 120000
index 000000000..7fd0ba5eb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section/in
@@ -0,0 +1 @@
+../remove-basic.in/ \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section/mod.rs b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section/mod.rs
new file mode 100644
index 000000000..80d42be1d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::curr_dir;
+use cargo_test_support::CargoCommand;
+use cargo_test_support::Project;
+
+use crate::cargo_remove::init_registry;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("remove")
+ .args(["--build", "docopt"])
+ .current_dir(cwd)
+ .assert()
+ .code(101)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section/out/Cargo.toml
new file mode 100644
index 000000000..340f06cda
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section/out/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "cargo-remove-test-fixture"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[build-dependencies]
+semver = "0.1.0"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+clippy = "0.4"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section/stderr.log
new file mode 100644
index 000000000..fff5ff00a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section/stderr.log
@@ -0,0 +1,2 @@
+ Removing docopt from build-dependencies
+error: the dependency `docopt` could not be found in `build-dependencies`.
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section/stdout.log b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section_dep/in b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section_dep/in
new file mode 120000
index 000000000..7fd0ba5eb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section_dep/in
@@ -0,0 +1 @@
+../remove-basic.in/ \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section_dep/mod.rs b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section_dep/mod.rs
new file mode 100644
index 000000000..7be8fd628
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section_dep/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::curr_dir;
+use cargo_test_support::CargoCommand;
+use cargo_test_support::Project;
+
+use crate::cargo_remove::init_registry;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("remove")
+ .args(["--dev", "semver", "regex"])
+ .current_dir(cwd)
+ .assert()
+ .code(101)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section_dep/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section_dep/out/Cargo.toml
new file mode 100644
index 000000000..340f06cda
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section_dep/out/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "cargo-remove-test-fixture"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[build-dependencies]
+semver = "0.1.0"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+clippy = "0.4"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section_dep/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section_dep/stderr.log
new file mode 100644
index 000000000..1926f9577
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section_dep/stderr.log
@@ -0,0 +1,2 @@
+ Removing semver from dev-dependencies
+error: the dependency `semver` could not be found in `dev-dependencies`.
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section_dep/stdout.log b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section_dep/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section_dep/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target/in b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target/in
new file mode 120000
index 000000000..d5742d038
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target/in
@@ -0,0 +1 @@
+../remove-target.in/ \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target/mod.rs b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target/mod.rs
new file mode 100644
index 000000000..34deb6cb8
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::curr_dir;
+use cargo_test_support::CargoCommand;
+use cargo_test_support::Project;
+
+use crate::cargo_remove::init_registry;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("remove")
+ .args(["--target", "powerpc-unknown-linux-gnu", "dbus"])
+ .current_dir(cwd)
+ .assert()
+ .code(101)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target/out/Cargo.toml
new file mode 100644
index 000000000..14747c70b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target/out/Cargo.toml
@@ -0,0 +1,33 @@
+[package]
+name = "cargo-remove-target-test-fixture"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[target.x86_64-unknown-freebsd.build-dependencies]
+semver = "0.1.0"
+
+[target.x86_64-unknown-linux-gnu.build-dependencies]
+semver = "0.1.0"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+clippy = "0.4"
+
+[target.x86_64-unknown-linux-gnu.dependencies]
+dbus = "0.6.2"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+
+[target.x86_64-unknown-linux-gnu.dev-dependencies]
+ncurses = "20.0"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target/stderr.log
new file mode 100644
index 000000000..5075b80b7
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target/stderr.log
@@ -0,0 +1,2 @@
+ Removing dbus from dependencies for target `powerpc-unknown-linux-gnu`
+error: the dependency `dbus` could not be found in `target.powerpc-unknown-linux-gnu.dependencies`.
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target/stdout.log b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target_dep/in b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target_dep/in
new file mode 120000
index 000000000..d5742d038
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target_dep/in
@@ -0,0 +1 @@
+../remove-target.in/ \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target_dep/mod.rs b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target_dep/mod.rs
new file mode 100644
index 000000000..e04418fa8
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target_dep/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::curr_dir;
+use cargo_test_support::CargoCommand;
+use cargo_test_support::Project;
+
+use crate::cargo_remove::init_registry;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("remove")
+ .args(["--target", "x86_64-unknown-linux-gnu", "toml"])
+ .current_dir(cwd)
+ .assert()
+ .code(101)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target_dep/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target_dep/out/Cargo.toml
new file mode 100644
index 000000000..14747c70b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target_dep/out/Cargo.toml
@@ -0,0 +1,33 @@
+[package]
+name = "cargo-remove-target-test-fixture"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[target.x86_64-unknown-freebsd.build-dependencies]
+semver = "0.1.0"
+
+[target.x86_64-unknown-linux-gnu.build-dependencies]
+semver = "0.1.0"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+clippy = "0.4"
+
+[target.x86_64-unknown-linux-gnu.dependencies]
+dbus = "0.6.2"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+
+[target.x86_64-unknown-linux-gnu.dev-dependencies]
+ncurses = "20.0"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target_dep/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target_dep/stderr.log
new file mode 100644
index 000000000..54bfe085f
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target_dep/stderr.log
@@ -0,0 +1,2 @@
+ Removing toml from dependencies for target `x86_64-unknown-linux-gnu`
+error: the dependency `toml` could not be found in `target.x86_64-unknown-linux-gnu.dependencies`.
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target_dep/stdout.log b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target_dep/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target_dep/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/mod.rs b/src/tools/cargo/tests/testsuite/cargo_remove/mod.rs
new file mode 100644
index 000000000..fd8b4a233
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/mod.rs
@@ -0,0 +1,88 @@
+mod avoid_empty_tables;
+mod build;
+mod dev;
+mod dry_run;
+mod gc_patch;
+mod gc_profile;
+mod gc_replace;
+mod invalid_arg;
+mod invalid_dep;
+mod invalid_package;
+mod invalid_package_multiple;
+mod invalid_section;
+mod invalid_section_dep;
+mod invalid_target;
+mod invalid_target_dep;
+mod multiple_deps;
+mod multiple_dev;
+mod no_arg;
+mod offline;
+mod optional_dep_feature;
+mod optional_feature;
+mod package;
+mod remove_basic;
+mod target;
+mod target_build;
+mod target_dev;
+mod update_lock_file;
+mod workspace;
+mod workspace_non_virtual;
+mod workspace_preserved;
+
+fn init_registry() {
+ cargo_test_support::registry::init();
+ add_registry_packages(false);
+}
+
+fn add_registry_packages(alt: bool) {
+ for name in [
+ "clippy",
+ "dbus",
+ "docopt",
+ "ncurses",
+ "pad",
+ "regex",
+ "rustc-serialize",
+ "toml",
+ ] {
+ cargo_test_support::registry::Package::new(name, "0.1.1+my-package")
+ .alternative(alt)
+ .publish();
+ cargo_test_support::registry::Package::new(name, "0.2.0+my-package")
+ .alternative(alt)
+ .publish();
+ cargo_test_support::registry::Package::new(name, "0.2.3+my-package")
+ .alternative(alt)
+ .publish();
+ cargo_test_support::registry::Package::new(name, "0.4.1+my-package")
+ .alternative(alt)
+ .publish();
+ cargo_test_support::registry::Package::new(name, "0.6.2+my-package")
+ .alternative(alt)
+ .publish();
+ cargo_test_support::registry::Package::new(name, "0.9.9+my-package")
+ .alternative(alt)
+ .publish();
+ cargo_test_support::registry::Package::new(name, "1.0.90+my-package")
+ .alternative(alt)
+ .publish();
+ cargo_test_support::registry::Package::new(name, "20.0.0+my-package")
+ .alternative(alt)
+ .publish();
+ }
+
+ for name in ["semver", "serde"] {
+ cargo_test_support::registry::Package::new(name, "0.1.1")
+ .alternative(alt)
+ .feature("std", &[])
+ .publish();
+ cargo_test_support::registry::Package::new(name, "0.9.0")
+ .alternative(alt)
+ .feature("std", &[])
+ .publish();
+ cargo_test_support::registry::Package::new(name, "1.0.90")
+ .alternative(alt)
+ .feature("std", &[])
+ .publish();
+ }
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/multiple_deps/in b/src/tools/cargo/tests/testsuite/cargo_remove/multiple_deps/in
new file mode 120000
index 000000000..7fd0ba5eb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/multiple_deps/in
@@ -0,0 +1 @@
+../remove-basic.in/ \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/multiple_deps/mod.rs b/src/tools/cargo/tests/testsuite/cargo_remove/multiple_deps/mod.rs
new file mode 100644
index 000000000..35922b738
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/multiple_deps/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::curr_dir;
+use cargo_test_support::CargoCommand;
+use cargo_test_support::Project;
+
+use crate::cargo_remove::init_registry;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("remove")
+ .args(["docopt", "semver"])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/multiple_deps/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/multiple_deps/out/Cargo.toml
new file mode 100644
index 000000000..53cde0829
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/multiple_deps/out/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "cargo-remove-test-fixture"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[build-dependencies]
+semver = "0.1.0"
+
+[dependencies]
+rustc-serialize = "0.4"
+toml = "0.1"
+clippy = "0.4"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/multiple_deps/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/multiple_deps/stderr.log
new file mode 100644
index 000000000..1eb59aca1
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/multiple_deps/stderr.log
@@ -0,0 +1,3 @@
+ Removing docopt from dependencies
+ Removing semver from dependencies
+ Updating `dummy-registry` index
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/multiple_deps/stdout.log b/src/tools/cargo/tests/testsuite/cargo_remove/multiple_deps/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/multiple_deps/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/multiple_dev/in b/src/tools/cargo/tests/testsuite/cargo_remove/multiple_dev/in
new file mode 120000
index 000000000..7fd0ba5eb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/multiple_dev/in
@@ -0,0 +1 @@
+../remove-basic.in/ \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/multiple_dev/mod.rs b/src/tools/cargo/tests/testsuite/cargo_remove/multiple_dev/mod.rs
new file mode 100644
index 000000000..5eac7e2f8
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/multiple_dev/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::curr_dir;
+use cargo_test_support::CargoCommand;
+use cargo_test_support::Project;
+
+use crate::cargo_remove::init_registry;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("remove")
+ .args(["--dev", "regex", "serde"])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/multiple_dev/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/multiple_dev/out/Cargo.toml
new file mode 100644
index 000000000..d961b2bb1
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/multiple_dev/out/Cargo.toml
@@ -0,0 +1,20 @@
+[package]
+name = "cargo-remove-test-fixture"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[build-dependencies]
+semver = "0.1.0"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+clippy = "0.4"
+
+[features]
+std = ["semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/multiple_dev/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/multiple_dev/stderr.log
new file mode 100644
index 000000000..a3042dcc3
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/multiple_dev/stderr.log
@@ -0,0 +1,3 @@
+ Removing regex from dev-dependencies
+ Removing serde from dev-dependencies
+ Updating `dummy-registry` index
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/multiple_dev/stdout.log b/src/tools/cargo/tests/testsuite/cargo_remove/multiple_dev/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/multiple_dev/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/no_arg/in b/src/tools/cargo/tests/testsuite/cargo_remove/no_arg/in
new file mode 120000
index 000000000..7fd0ba5eb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/no_arg/in
@@ -0,0 +1 @@
+../remove-basic.in/ \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/no_arg/mod.rs b/src/tools/cargo/tests/testsuite/cargo_remove/no_arg/mod.rs
new file mode 100644
index 000000000..d0c66f9b0
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/no_arg/mod.rs
@@ -0,0 +1,24 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::curr_dir;
+use cargo_test_support::CargoCommand;
+use cargo_test_support::Project;
+
+use crate::cargo_remove::init_registry;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("remove")
+ .current_dir(cwd)
+ .assert()
+ .code(1)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/no_arg/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/no_arg/out/Cargo.toml
new file mode 100644
index 000000000..340f06cda
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/no_arg/out/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "cargo-remove-test-fixture"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[build-dependencies]
+semver = "0.1.0"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+clippy = "0.4"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/no_arg/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/no_arg/stderr.log
new file mode 100644
index 000000000..54fa9f424
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/no_arg/stderr.log
@@ -0,0 +1,6 @@
+error: the following required arguments were not provided:
+ <DEP_ID>...
+
+Usage: cargo[EXE] remove <DEP_ID>...
+
+For more information, try '--help'.
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/no_arg/stdout.log b/src/tools/cargo/tests/testsuite/cargo_remove/no_arg/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/no_arg/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/offline/in b/src/tools/cargo/tests/testsuite/cargo_remove/offline/in
new file mode 120000
index 000000000..7fd0ba5eb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/offline/in
@@ -0,0 +1 @@
+../remove-basic.in/ \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/offline/mod.rs b/src/tools/cargo/tests/testsuite/cargo_remove/offline/mod.rs
new file mode 100644
index 000000000..d03463927
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/offline/mod.rs
@@ -0,0 +1,32 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::curr_dir;
+use cargo_test_support::CargoCommand;
+use cargo_test_support::Project;
+
+use crate::cargo_remove::init_registry;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ // run the metadata command to populate the cache
+ snapbox::cmd::Command::cargo_ui()
+ .arg("metadata")
+ .current_dir(cwd)
+ .assert()
+ .success();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("remove")
+ .args(["docopt", "--offline"])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/offline/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/offline/out/Cargo.toml
new file mode 100644
index 000000000..b8628eed1
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/offline/out/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "cargo-remove-test-fixture"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[build-dependencies]
+semver = "0.1.0"
+
+[dependencies]
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+clippy = "0.4"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/offline/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/offline/stderr.log
new file mode 100644
index 000000000..7083976b1
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/offline/stderr.log
@@ -0,0 +1 @@
+ Removing docopt from dependencies
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/offline/stdout.log b/src/tools/cargo/tests/testsuite/cargo_remove/offline/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/offline/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature/in b/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature/in
new file mode 120000
index 000000000..7fd0ba5eb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature/in
@@ -0,0 +1 @@
+../remove-basic.in/ \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature/mod.rs b/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature/mod.rs
new file mode 100644
index 000000000..cae736b34
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::curr_dir;
+use cargo_test_support::CargoCommand;
+use cargo_test_support::Project;
+
+use crate::cargo_remove::init_registry;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("remove")
+ .args(["--dev", "serde"])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature/out/Cargo.toml
new file mode 100644
index 000000000..63112d334
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature/out/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "cargo-remove-test-fixture"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[build-dependencies]
+semver = "0.1.0"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+clippy = "0.4"
+
+[dev-dependencies]
+regex = "0.1.1"
+
+[features]
+std = ["semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature/stderr.log
new file mode 100644
index 000000000..72c9f9217
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature/stderr.log
@@ -0,0 +1,2 @@
+ Removing serde from dev-dependencies
+ Updating `dummy-registry` index
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature/stdout.log b/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/optional_feature/in b/src/tools/cargo/tests/testsuite/cargo_remove/optional_feature/in
new file mode 120000
index 000000000..7fd0ba5eb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/optional_feature/in
@@ -0,0 +1 @@
+../remove-basic.in/ \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/optional_feature/mod.rs b/src/tools/cargo/tests/testsuite/cargo_remove/optional_feature/mod.rs
new file mode 100644
index 000000000..af54226bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/optional_feature/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::curr_dir;
+use cargo_test_support::CargoCommand;
+use cargo_test_support::Project;
+
+use crate::cargo_remove::init_registry;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("remove")
+ .args(["semver"])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/optional_feature/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/optional_feature/out/Cargo.toml
new file mode 100644
index 000000000..9ac0b1b32
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/optional_feature/out/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "cargo-remove-test-fixture"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[build-dependencies]
+semver = "0.1.0"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+toml = "0.1"
+clippy = "0.4"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/optional_feature/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/optional_feature/stderr.log
new file mode 100644
index 000000000..2dc546fa7
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/optional_feature/stderr.log
@@ -0,0 +1,2 @@
+ Removing semver from dependencies
+ Updating `dummy-registry` index
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/optional_feature/stdout.log b/src/tools/cargo/tests/testsuite/cargo_remove/optional_feature/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/optional_feature/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/package/in b/src/tools/cargo/tests/testsuite/cargo_remove/package/in
new file mode 120000
index 000000000..e2165e8cb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/package/in
@@ -0,0 +1 @@
+../remove-package.in/ \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/package/mod.rs b/src/tools/cargo/tests/testsuite/cargo_remove/package/mod.rs
new file mode 100644
index 000000000..2714f3197
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/package/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::curr_dir;
+use cargo_test_support::CargoCommand;
+use cargo_test_support::Project;
+
+use crate::cargo_remove::init_registry;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("remove")
+ .args(["docopt", "--package", "dep-a"])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/package/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/package/out/Cargo.toml
new file mode 100644
index 000000000..733857113
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/package/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+members = [
+ "dep-a",
+ "dep-b"
+]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/package/out/dep-a/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/package/out/dep-a/Cargo.toml
new file mode 100644
index 000000000..5f2bfe6fb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/package/out/dep-a/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "dep-a"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[build-dependencies]
+semver = "0.1.0"
+
+[dependencies]
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/package/out/dep-a/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_remove/package/out/dep-a/src/lib.rs
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/package/out/dep-a/src/lib.rs
@@ -0,0 +1 @@
+
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/package/out/dep-b/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/package/out/dep-b/Cargo.toml
new file mode 100644
index 000000000..37d2d3ddf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/package/out/dep-b/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "dep-b"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[build-dependencies]
+semver = "0.1.0"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/package/out/dep-b/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_remove/package/out/dep-b/src/lib.rs
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/package/out/dep-b/src/lib.rs
@@ -0,0 +1 @@
+
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/package/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/package/stderr.log
new file mode 100644
index 000000000..231026f2b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/package/stderr.log
@@ -0,0 +1,2 @@
+ Removing docopt from dependencies
+ Updating `dummy-registry` index
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/package/stdout.log b/src/tools/cargo/tests/testsuite/cargo_remove/package/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/package/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/remove-basic.in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/remove-basic.in/Cargo.toml
new file mode 100644
index 000000000..340f06cda
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/remove-basic.in/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "cargo-remove-test-fixture"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[build-dependencies]
+semver = "0.1.0"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+clippy = "0.4"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/remove-basic.in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_remove/remove-basic.in/src/lib.rs
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/remove-basic.in/src/lib.rs
@@ -0,0 +1 @@
+
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/remove-package.in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/remove-package.in/Cargo.toml
new file mode 100644
index 000000000..733857113
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/remove-package.in/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+members = [
+ "dep-a",
+ "dep-b"
+]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/remove-package.in/dep-a/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/remove-package.in/dep-a/Cargo.toml
new file mode 100644
index 000000000..7e87ce314
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/remove-package.in/dep-a/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "dep-a"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[build-dependencies]
+semver = "0.1.0"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/remove-package.in/dep-a/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_remove/remove-package.in/dep-a/src/lib.rs
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/remove-package.in/dep-a/src/lib.rs
@@ -0,0 +1 @@
+
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/remove-package.in/dep-b/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/remove-package.in/dep-b/Cargo.toml
new file mode 100644
index 000000000..37d2d3ddf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/remove-package.in/dep-b/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "dep-b"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[build-dependencies]
+semver = "0.1.0"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/remove-package.in/dep-b/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_remove/remove-package.in/dep-b/src/lib.rs
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/remove-package.in/dep-b/src/lib.rs
@@ -0,0 +1 @@
+
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/remove-target.in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/remove-target.in/Cargo.toml
new file mode 100644
index 000000000..14747c70b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/remove-target.in/Cargo.toml
@@ -0,0 +1,33 @@
+[package]
+name = "cargo-remove-target-test-fixture"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[target.x86_64-unknown-freebsd.build-dependencies]
+semver = "0.1.0"
+
+[target.x86_64-unknown-linux-gnu.build-dependencies]
+semver = "0.1.0"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+clippy = "0.4"
+
+[target.x86_64-unknown-linux-gnu.dependencies]
+dbus = "0.6.2"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+
+[target.x86_64-unknown-linux-gnu.dev-dependencies]
+ncurses = "20.0"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/remove_basic/in b/src/tools/cargo/tests/testsuite/cargo_remove/remove_basic/in
new file mode 120000
index 000000000..7fd0ba5eb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/remove_basic/in
@@ -0,0 +1 @@
+../remove-basic.in/ \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/remove_basic/mod.rs b/src/tools/cargo/tests/testsuite/cargo_remove/remove_basic/mod.rs
new file mode 100644
index 000000000..53381e6bc
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/remove_basic/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::curr_dir;
+use cargo_test_support::CargoCommand;
+use cargo_test_support::Project;
+
+use crate::cargo_remove::init_registry;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("remove")
+ .args(["docopt"])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/remove_basic/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/remove_basic/out/Cargo.toml
new file mode 100644
index 000000000..b8628eed1
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/remove_basic/out/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "cargo-remove-test-fixture"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[build-dependencies]
+semver = "0.1.0"
+
+[dependencies]
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+clippy = "0.4"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/remove_basic/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/remove_basic/stderr.log
new file mode 100644
index 000000000..231026f2b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/remove_basic/stderr.log
@@ -0,0 +1,2 @@
+ Removing docopt from dependencies
+ Updating `dummy-registry` index
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/remove_basic/stdout.log b/src/tools/cargo/tests/testsuite/cargo_remove/remove_basic/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/remove_basic/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/target/in b/src/tools/cargo/tests/testsuite/cargo_remove/target/in
new file mode 120000
index 000000000..d5742d038
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/target/in
@@ -0,0 +1 @@
+../remove-target.in/ \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/target/mod.rs b/src/tools/cargo/tests/testsuite/cargo_remove/target/mod.rs
new file mode 100644
index 000000000..1447c753d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/target/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::curr_dir;
+use cargo_test_support::CargoCommand;
+use cargo_test_support::Project;
+
+use crate::cargo_remove::init_registry;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("remove")
+ .args(["--target", "x86_64-unknown-linux-gnu", "dbus"])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/target/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/target/out/Cargo.toml
new file mode 100644
index 000000000..e29fbbd00
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/target/out/Cargo.toml
@@ -0,0 +1,30 @@
+[package]
+name = "cargo-remove-target-test-fixture"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[target.x86_64-unknown-freebsd.build-dependencies]
+semver = "0.1.0"
+
+[target.x86_64-unknown-linux-gnu.build-dependencies]
+semver = "0.1.0"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+clippy = "0.4"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+
+[target.x86_64-unknown-linux-gnu.dev-dependencies]
+ncurses = "20.0"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/target/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/target/stderr.log
new file mode 100644
index 000000000..810abd994
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/target/stderr.log
@@ -0,0 +1,2 @@
+ Removing dbus from dependencies for target `x86_64-unknown-linux-gnu`
+ Updating `dummy-registry` index
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/target/stdout.log b/src/tools/cargo/tests/testsuite/cargo_remove/target/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/target/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/target_build/in b/src/tools/cargo/tests/testsuite/cargo_remove/target_build/in
new file mode 120000
index 000000000..d5742d038
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/target_build/in
@@ -0,0 +1 @@
+../remove-target.in/ \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/target_build/mod.rs b/src/tools/cargo/tests/testsuite/cargo_remove/target_build/mod.rs
new file mode 100644
index 000000000..11afbbf8f
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/target_build/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::curr_dir;
+use cargo_test_support::CargoCommand;
+use cargo_test_support::Project;
+
+use crate::cargo_remove::init_registry;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("remove")
+ .args(["--build", "--target", "x86_64-unknown-linux-gnu", "semver"])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/target_build/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/target_build/out/Cargo.toml
new file mode 100644
index 000000000..7353c7a89
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/target_build/out/Cargo.toml
@@ -0,0 +1,30 @@
+[package]
+name = "cargo-remove-target-test-fixture"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[target.x86_64-unknown-freebsd.build-dependencies]
+semver = "0.1.0"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+clippy = "0.4"
+
+[target.x86_64-unknown-linux-gnu.dependencies]
+dbus = "0.6.2"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+
+[target.x86_64-unknown-linux-gnu.dev-dependencies]
+ncurses = "20.0"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/target_build/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/target_build/stderr.log
new file mode 100644
index 000000000..b06f8f319
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/target_build/stderr.log
@@ -0,0 +1,2 @@
+ Removing semver from build-dependencies for target `x86_64-unknown-linux-gnu`
+ Updating `dummy-registry` index
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/target_build/stdout.log b/src/tools/cargo/tests/testsuite/cargo_remove/target_build/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/target_build/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/target_dev/in b/src/tools/cargo/tests/testsuite/cargo_remove/target_dev/in
new file mode 120000
index 000000000..d5742d038
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/target_dev/in
@@ -0,0 +1 @@
+../remove-target.in/ \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/target_dev/mod.rs b/src/tools/cargo/tests/testsuite/cargo_remove/target_dev/mod.rs
new file mode 100644
index 000000000..d303c2b85
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/target_dev/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::curr_dir;
+use cargo_test_support::CargoCommand;
+use cargo_test_support::Project;
+
+use crate::cargo_remove::init_registry;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("remove")
+ .args(["--dev", "--target", "x86_64-unknown-linux-gnu", "ncurses"])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/target_dev/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/target_dev/out/Cargo.toml
new file mode 100644
index 000000000..a477b3d55
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/target_dev/out/Cargo.toml
@@ -0,0 +1,30 @@
+[package]
+name = "cargo-remove-target-test-fixture"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[target.x86_64-unknown-freebsd.build-dependencies]
+semver = "0.1.0"
+
+[target.x86_64-unknown-linux-gnu.build-dependencies]
+semver = "0.1.0"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+clippy = "0.4"
+
+[target.x86_64-unknown-linux-gnu.dependencies]
+dbus = "0.6.2"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/target_dev/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/target_dev/stderr.log
new file mode 100644
index 000000000..68553a3bd
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/target_dev/stderr.log
@@ -0,0 +1,2 @@
+ Removing ncurses from dev-dependencies for target `x86_64-unknown-linux-gnu`
+ Updating `dummy-registry` index
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/target_dev/stdout.log b/src/tools/cargo/tests/testsuite/cargo_remove/target_dev/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/target_dev/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/in/Cargo.lock b/src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/in/Cargo.lock
new file mode 100644
index 000000000..06c2052d5
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/in/Cargo.lock
@@ -0,0 +1,58 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "cargo-remove-test-fixture"
+version = "0.1.0"
+dependencies = [
+ "clippy",
+ "docopt",
+ "regex",
+ "rustc-serialize",
+ "semver",
+ "serde",
+ "toml",
+]
+
+[[package]]
+name = "clippy"
+version = "0.4.1+my-package"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47ced0eda54e9ddc6063f0e1d0164493cd16c84c6b6a0329a536967c44e205f7"
+
+[[package]]
+name = "docopt"
+version = "0.6.2+my-package"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b600540c4fafb27bf6e6961f0f1e6f547c9d6126ce581ab3a92f878c8e2c9a2c"
+
+[[package]]
+name = "regex"
+version = "0.1.1+my-package"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84949cb53285a6c481d0133065a7b669871acfd9e20f273f4ce1283c309775d5"
+
+[[package]]
+name = "rustc-serialize"
+version = "0.4.1+my-package"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31162e7d23a085553c42dee375787b451a481275473f7779c4a63bcc267a24fd"
+
+[[package]]
+name = "semver"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3031434e07edc922bf1b8262f075fac1522694f17b1ee7ad314c4cabd5d2723f"
+
+[[package]]
+name = "serde"
+version = "1.0.90"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75d9264696ebbf5315a6b068e9910c4df9274365afac2d88abf66525df660218"
+
+[[package]]
+name = "toml"
+version = "0.1.1+my-package"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0f6c7804525ce0a968ef270e55a516cf4bdcf1fea0b09d130e0aa34a66745b3"
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/in/Cargo.toml
new file mode 100644
index 000000000..340f06cda
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/in/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "cargo-remove-test-fixture"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[build-dependencies]
+semver = "0.1.0"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+clippy = "0.4"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/in/src/main.rs b/src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/in/src/main.rs
new file mode 100644
index 000000000..f328e4d9d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/in/src/main.rs
@@ -0,0 +1 @@
+fn main() {}
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/mod.rs b/src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/mod.rs
new file mode 100644
index 000000000..be5bc87f5
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::curr_dir;
+use cargo_test_support::CargoCommand;
+use cargo_test_support::Project;
+
+use crate::cargo_remove::init_registry;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("remove")
+ .args(["rustc-serialize"])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/out/Cargo.lock b/src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/out/Cargo.lock
new file mode 100644
index 000000000..bd8c90f46
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/out/Cargo.lock
@@ -0,0 +1,51 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "cargo-remove-test-fixture"
+version = "0.1.0"
+dependencies = [
+ "clippy",
+ "docopt",
+ "regex",
+ "semver",
+ "serde",
+ "toml",
+]
+
+[[package]]
+name = "clippy"
+version = "0.4.1+my-package"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47ced0eda54e9ddc6063f0e1d0164493cd16c84c6b6a0329a536967c44e205f7"
+
+[[package]]
+name = "docopt"
+version = "0.6.2+my-package"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b600540c4fafb27bf6e6961f0f1e6f547c9d6126ce581ab3a92f878c8e2c9a2c"
+
+[[package]]
+name = "regex"
+version = "0.1.1+my-package"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84949cb53285a6c481d0133065a7b669871acfd9e20f273f4ce1283c309775d5"
+
+[[package]]
+name = "semver"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3031434e07edc922bf1b8262f075fac1522694f17b1ee7ad314c4cabd5d2723f"
+
+[[package]]
+name = "serde"
+version = "1.0.90"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75d9264696ebbf5315a6b068e9910c4df9274365afac2d88abf66525df660218"
+
+[[package]]
+name = "toml"
+version = "0.1.1+my-package"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0f6c7804525ce0a968ef270e55a516cf4bdcf1fea0b09d130e0aa34a66745b3"
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/out/Cargo.toml
new file mode 100644
index 000000000..5e7d7f0a0
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/out/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "cargo-remove-test-fixture"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[build-dependencies]
+semver = "0.1.0"
+
+[dependencies]
+docopt = "0.6"
+semver = "0.1"
+toml = "0.1"
+clippy = "0.4"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/out/src/main.rs b/src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/out/src/main.rs
new file mode 100644
index 000000000..f328e4d9d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/out/src/main.rs
@@ -0,0 +1 @@
+fn main() {}
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/stderr.log
new file mode 100644
index 000000000..164f8f4b9
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/stderr.log
@@ -0,0 +1,2 @@
+ Removing rustc-serialize from dependencies
+ Updating `dummy-registry` index
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/stdout.log b/src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/update_lock_file/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/workspace/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/workspace/in/Cargo.toml
new file mode 100644
index 000000000..fd5e80a8b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/workspace/in/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+members = [ "my-package" ]
+
+[workspace.dependencies]
+semver = "0.1.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/workspace/in/my-package/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/workspace/in/my-package/Cargo.toml
new file mode 100644
index 000000000..6690d593b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/workspace/in/my-package/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "my-package"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[build-dependencies]
+semver = { workspace = true }
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+clippy = "0.4"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/workspace/in/my-package/src/main.rs b/src/tools/cargo/tests/testsuite/cargo_remove/workspace/in/my-package/src/main.rs
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/workspace/in/my-package/src/main.rs
@@ -0,0 +1 @@
+
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/workspace/mod.rs b/src/tools/cargo/tests/testsuite/cargo_remove/workspace/mod.rs
new file mode 100644
index 000000000..225fbec00
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/workspace/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::curr_dir;
+use cargo_test_support::CargoCommand;
+use cargo_test_support::Project;
+
+use crate::cargo_remove::init_registry;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("remove")
+ .args(["--package", "my-package", "--build", "semver"])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/workspace/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/workspace/out/Cargo.toml
new file mode 100644
index 000000000..83a6a04d0
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/workspace/out/Cargo.toml
@@ -0,0 +1,2 @@
+[workspace]
+members = [ "my-package" ]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/workspace/out/my-package/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/workspace/out/my-package/Cargo.toml
new file mode 100644
index 000000000..402780535
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/workspace/out/my-package/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "my-package"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+clippy = "0.4"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/workspace/out/my-package/src/main.rs b/src/tools/cargo/tests/testsuite/cargo_remove/workspace/out/my-package/src/main.rs
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/workspace/out/my-package/src/main.rs
@@ -0,0 +1 @@
+
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/workspace/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/workspace/stderr.log
new file mode 100644
index 000000000..f037ebe28
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/workspace/stderr.log
@@ -0,0 +1,2 @@
+ Removing semver from build-dependencies
+ Updating `dummy-registry` index
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/workspace/stdout.log b/src/tools/cargo/tests/testsuite/cargo_remove/workspace/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/workspace/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/in/Cargo.toml
new file mode 100644
index 000000000..dbac8ab44
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/in/Cargo.toml
@@ -0,0 +1,30 @@
+[workspace]
+members = [ "my-member" ]
+
+[workspace.dependencies]
+semver = "0.1.0"
+
+[package]
+name = "my-package"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[build-dependencies]
+semver = { workspace = true }
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+clippy = "0.4"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/in/my-member/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/in/my-member/Cargo.toml
new file mode 100644
index 000000000..bb78904ff
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/in/my-member/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "my-member"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/in/my-member/src/main.rs b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/in/my-member/src/main.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/in/my-member/src/main.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/mod.rs b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/mod.rs
new file mode 100644
index 000000000..225fbec00
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::curr_dir;
+use cargo_test_support::CargoCommand;
+use cargo_test_support::Project;
+
+use crate::cargo_remove::init_registry;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("remove")
+ .args(["--package", "my-package", "--build", "semver"])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/out/Cargo.toml
new file mode 100644
index 000000000..9a3261484
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/out/Cargo.toml
@@ -0,0 +1,24 @@
+[workspace]
+members = [ "my-member" ]
+
+[package]
+name = "my-package"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+clippy = "0.4"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/out/my-member/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/out/my-member/Cargo.toml
new file mode 100644
index 000000000..bb78904ff
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/out/my-member/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "my-member"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/out/my-member/src/main.rs b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/out/my-member/src/main.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/out/my-member/src/main.rs
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/stderr.log
new file mode 100644
index 000000000..f037ebe28
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/stderr.log
@@ -0,0 +1,2 @@
+ Removing semver from build-dependencies
+ Updating `dummy-registry` index
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/stdout.log b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_non_virtual/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/in/Cargo.toml
new file mode 100644
index 000000000..f1992ac88
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/in/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+members = [ "my-package", "my-other-package" ]
+
+[workspace.dependencies]
+semver = "0.1.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/in/my-other-package/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/in/my-other-package/Cargo.toml
new file mode 100644
index 000000000..d65972868
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/in/my-other-package/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "my-other-package"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+clippy = "0.4"
+
+[dev-dependencies]
+regex = "0.1.1"
+semver = { workspace = true }
+serde = "1.0.90"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/in/my-other-package/src/main.rs b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/in/my-other-package/src/main.rs
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/in/my-other-package/src/main.rs
@@ -0,0 +1 @@
+
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/in/my-package/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/in/my-package/Cargo.toml
new file mode 100644
index 000000000..6690d593b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/in/my-package/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "my-package"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[build-dependencies]
+semver = { workspace = true }
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+clippy = "0.4"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/in/my-package/src/main.rs b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/in/my-package/src/main.rs
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/in/my-package/src/main.rs
@@ -0,0 +1 @@
+
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/mod.rs b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/mod.rs
new file mode 100644
index 000000000..225fbec00
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::curr_dir;
+use cargo_test_support::CargoCommand;
+use cargo_test_support::Project;
+
+use crate::cargo_remove::init_registry;
+
+#[cargo_test]
+fn case() {
+ init_registry();
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("remove")
+ .args(["--package", "my-package", "--build", "semver"])
+ .current_dir(cwd)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/out/Cargo.toml
new file mode 100644
index 000000000..f1992ac88
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/out/Cargo.toml
@@ -0,0 +1,5 @@
+[workspace]
+members = [ "my-package", "my-other-package" ]
+
+[workspace.dependencies]
+semver = "0.1.0"
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/out/my-other-package/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/out/my-other-package/Cargo.toml
new file mode 100644
index 000000000..d65972868
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/out/my-other-package/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "my-other-package"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+clippy = "0.4"
+
+[dev-dependencies]
+regex = "0.1.1"
+semver = { workspace = true }
+serde = "1.0.90"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/out/my-other-package/src/main.rs b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/out/my-other-package/src/main.rs
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/out/my-other-package/src/main.rs
@@ -0,0 +1 @@
+
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/out/my-package/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/out/my-package/Cargo.toml
new file mode 100644
index 000000000..402780535
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/out/my-package/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "my-package"
+version = "0.1.0"
+
+[[bin]]
+name = "main"
+path = "src/main.rs"
+
+[dependencies]
+docopt = "0.6"
+rustc-serialize = "0.4"
+semver = "0.1"
+toml = "0.1"
+clippy = "0.4"
+
+[dev-dependencies]
+regex = "0.1.1"
+serde = "1.0.90"
+
+[features]
+std = ["serde/std", "semver/std"]
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/out/my-package/src/main.rs b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/out/my-package/src/main.rs
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/out/my-package/src/main.rs
@@ -0,0 +1 @@
+
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/stderr.log
new file mode 100644
index 000000000..f037ebe28
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/stderr.log
@@ -0,0 +1,2 @@
+ Removing semver from build-dependencies
+ Updating `dummy-registry` index
diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/stdout.log b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_remove/workspace_preserved/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/cargo_targets.rs b/src/tools/cargo/tests/testsuite/cargo_targets.rs
new file mode 100644
index 000000000..fcf293019
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_targets.rs
@@ -0,0 +1,68 @@
+//! Tests specifically related to target handling (lib, bins, examples, tests, benches).
+
+use cargo_test_support::project;
+
+#[cargo_test]
+fn warn_unmatched_target_filters() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [lib]
+ test = false
+ bench = false
+ "#,
+ )
+ .file("src/lib.rs", r#"fn main() {}"#)
+ .build();
+
+ p.cargo("check --tests --bins --examples --benches")
+ .with_stderr(
+ "\
+[WARNING] Target filters `bins`, `tests`, `examples`, `benches` specified, \
+but no targets matched. This is a no-op
+[FINISHED][..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn reserved_windows_target_name() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [[bin]]
+ name = "con"
+ path = "src/main.rs"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ if cfg!(windows) {
+ p.cargo("check")
+ .with_stderr(
+ "\
+[WARNING] binary target `con` is a reserved Windows filename, \
+this target will not work on Windows platforms
+[CHECKING] foo[..]
+[FINISHED][..]
+",
+ )
+ .run();
+ } else {
+ p.cargo("check")
+ .with_stderr("[CHECKING] foo[..]\n[FINISHED][..]")
+ .run();
+ }
+}
diff --git a/src/tools/cargo/tests/testsuite/cfg.rs b/src/tools/cargo/tests/testsuite/cfg.rs
new file mode 100644
index 000000000..dcce65402
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cfg.rs
@@ -0,0 +1,515 @@
+//! Tests for cfg() expressions.
+
+use cargo_test_support::registry::Package;
+use cargo_test_support::rustc_host;
+use cargo_test_support::{basic_manifest, project};
+
+#[cargo_test]
+fn cfg_easy() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+
+ [target.'cfg(unix)'.dependencies]
+ b = { path = 'b' }
+ [target."cfg(windows)".dependencies]
+ b = { path = 'b' }
+ "#,
+ )
+ .file("src/lib.rs", "extern crate b;")
+ .file("b/Cargo.toml", &basic_manifest("b", "0.0.1"))
+ .file("b/src/lib.rs", "")
+ .build();
+ p.cargo("check -v").run();
+}
+
+#[cargo_test]
+fn dont_include() {
+ let other_family = if cfg!(unix) { "windows" } else { "unix" };
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+
+ [target.'cfg({})'.dependencies]
+ b = {{ path = 'b' }}
+ "#,
+ other_family
+ ),
+ )
+ .file("src/lib.rs", "")
+ .file("b/Cargo.toml", &basic_manifest("b", "0.0.1"))
+ .file("b/src/lib.rs", "")
+ .build();
+ p.cargo("check")
+ .with_stderr(
+ "\
+[CHECKING] a v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn works_through_the_registry() {
+ Package::new("baz", "0.1.0").publish();
+ Package::new("bar", "0.1.0")
+ .target_dep("baz", "0.1.0", "cfg(unix)")
+ .target_dep("baz", "0.1.0", "cfg(windows)")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "#[allow(unused_extern_crates)] extern crate bar;",
+ )
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] [..] index
+[DOWNLOADING] crates ...
+[DOWNLOADED] [..]
+[DOWNLOADED] [..]
+[CHECKING] baz v0.1.0
+[CHECKING] bar v0.1.0
+[CHECKING] foo v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn ignore_version_from_other_platform() {
+ let this_family = if cfg!(unix) { "unix" } else { "windows" };
+ let other_family = if cfg!(unix) { "windows" } else { "unix" };
+ Package::new("bar", "0.1.0").publish();
+ Package::new("bar", "0.2.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [target.'cfg({})'.dependencies]
+ bar = "0.1.0"
+
+ [target.'cfg({})'.dependencies]
+ bar = "0.2.0"
+ "#,
+ this_family, other_family
+ ),
+ )
+ .file(
+ "src/lib.rs",
+ "#[allow(unused_extern_crates)] extern crate bar;",
+ )
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] [..] index
+[DOWNLOADING] crates ...
+[DOWNLOADED] [..]
+[CHECKING] bar v0.1.0
+[CHECKING] foo v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bad_target_spec() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [target.'cfg(4)'.dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ failed to parse `4` as a cfg expression: unexpected character `4` in cfg, [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bad_target_spec2() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [target.'cfg(bar =)'.dependencies]
+ baz = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ failed to parse `bar =` as a cfg expression: expected a string, but cfg expression ended
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn multiple_match_ok() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+
+ [target.'cfg(unix)'.dependencies]
+ b = {{ path = 'b' }}
+ [target.'cfg(target_family = "unix")'.dependencies]
+ b = {{ path = 'b' }}
+ [target."cfg(windows)".dependencies]
+ b = {{ path = 'b' }}
+ [target.'cfg(target_family = "windows")'.dependencies]
+ b = {{ path = 'b' }}
+ [target."cfg(any(windows, unix))".dependencies]
+ b = {{ path = 'b' }}
+
+ [target.{}.dependencies]
+ b = {{ path = 'b' }}
+ "#,
+ rustc_host()
+ ),
+ )
+ .file("src/lib.rs", "extern crate b;")
+ .file("b/Cargo.toml", &basic_manifest("b", "0.0.1"))
+ .file("b/src/lib.rs", "")
+ .build();
+ p.cargo("check -v").run();
+}
+
+#[cargo_test]
+fn any_ok() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+
+ [target."cfg(any(windows, unix))".dependencies]
+ b = { path = 'b' }
+ "#,
+ )
+ .file("src/lib.rs", "extern crate b;")
+ .file("b/Cargo.toml", &basic_manifest("b", "0.0.1"))
+ .file("b/src/lib.rs", "")
+ .build();
+ p.cargo("check -v").run();
+}
+
+// https://github.com/rust-lang/cargo/issues/5313
+#[cargo_test]
+#[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))]
+fn cfg_looks_at_rustflags_for_target() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+
+ [target.'cfg(with_b)'.dependencies]
+ b = { path = 'b' }
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ #[cfg(with_b)]
+ extern crate b;
+
+ fn main() { b::foo(); }
+ "#,
+ )
+ .file("b/Cargo.toml", &basic_manifest("b", "0.0.1"))
+ .file("b/src/lib.rs", "pub fn foo() {}")
+ .build();
+
+ p.cargo("check --target x86_64-unknown-linux-gnu")
+ .env("RUSTFLAGS", "--cfg with_b")
+ .run();
+}
+
+#[cargo_test]
+fn bad_cfg_discovery() {
+ // Check error messages when `rustc -v` and `rustc --print=*` parsing fails.
+ //
+ // This is a `rustc` replacement which behaves differently based on an
+ // environment variable.
+ let p = project()
+ .at("compiler")
+ .file("Cargo.toml", &basic_manifest("compiler", "0.1.0"))
+ .file(
+ "src/main.rs",
+ r#"
+ fn run_rustc() -> String {
+ let mut cmd = std::process::Command::new("rustc");
+ for arg in std::env::args_os().skip(1) {
+ cmd.arg(arg);
+ }
+ String::from_utf8(cmd.output().unwrap().stdout).unwrap()
+ }
+
+ fn main() {
+ let mode = std::env::var("FUNKY_MODE").unwrap();
+ if mode == "bad-version" {
+ println!("foo");
+ return;
+ }
+ if std::env::args_os().any(|a| a == "-vV") {
+ print!("{}", run_rustc());
+ return;
+ }
+ if mode == "no-crate-types" {
+ return;
+ }
+ if mode == "bad-crate-type" {
+ println!("foo");
+ return;
+ }
+ let output = run_rustc();
+ let mut lines = output.lines();
+ let sysroot = loop {
+ let line = lines.next().unwrap();
+ if line.contains("___") {
+ println!("{}", line);
+ } else {
+ break line;
+ }
+ };
+ if mode == "no-sysroot" {
+ return;
+ }
+ println!("{}", sysroot);
+
+ if mode == "no-split-debuginfo" {
+ return;
+ }
+ loop {
+ let line = lines.next().unwrap();
+ if line == "___" {
+ println!("\n{line}");
+ break;
+ } else {
+ // As the number split-debuginfo options varies,
+ // concat them into one line.
+ print!("{line},");
+ }
+ };
+
+ if mode != "bad-cfg" {
+ panic!("unexpected");
+ }
+ println!("123");
+ }
+ "#,
+ )
+ .build();
+ p.cargo("build").run();
+ let funky_rustc = p.bin("compiler");
+
+ let p = project().file("src/lib.rs", "").build();
+
+ p.cargo("check")
+ .env("RUSTC", &funky_rustc)
+ .env("FUNKY_MODE", "bad-version")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] `rustc -vV` didn't have a line for `host:`, got:
+foo
+
+",
+ )
+ .run();
+
+ p.cargo("check")
+ .env("RUSTC", &funky_rustc)
+ .env("FUNKY_MODE", "no-crate-types")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] malformed output when learning about crate-type bin information
+command was: `[..]compiler[..] --crate-name ___ [..]`
+(no output received)
+",
+ )
+ .run();
+
+ p.cargo("check")
+ .env("RUSTC", &funky_rustc)
+ .env("FUNKY_MODE", "no-sysroot")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] output of --print=sysroot missing when learning about target-specific information from rustc
+command was: `[..]compiler[..]--crate-type [..]`
+
+--- stdout
+[..]___[..]
+[..]___[..]
+[..]___[..]
+[..]___[..]
+[..]___[..]
+[..]___[..]
+
+",
+ )
+ .run();
+
+ p.cargo("check")
+ .env("RUSTC", &funky_rustc)
+ .env("FUNKY_MODE", "no-split-debuginfo")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] output of --print=split-debuginfo missing when learning about target-specific information from rustc
+command was: `[..]compiler[..]--crate-type [..]`
+
+--- stdout
+[..]___[..]
+[..]___[..]
+[..]___[..]
+[..]___[..]
+[..]___[..]
+[..]___[..]
+[..]
+
+",
+ )
+ .run();
+
+ p.cargo("check")
+ .env("RUSTC", &funky_rustc)
+ .env("FUNKY_MODE", "bad-cfg")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse the cfg from `rustc --print=cfg`, got:
+[..]___[..]
+[..]___[..]
+[..]___[..]
+[..]___[..]
+[..]___[..]
+[..]___[..]
+[..]
+[..],[..]
+___
+123
+
+
+Caused by:
+ failed to parse `123` as a cfg expression: unexpected character `1` in cfg, \
+ expected parens, a comma, an identifier, or a string
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn exclusive_dep_kinds() {
+ // Checks for a bug where the same package with different cfg expressions
+ // was not being filtered correctly.
+ Package::new("bar", "1.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [target.'cfg(abc)'.dependencies]
+ bar = "1.0"
+
+ [target.'cfg(not(abc))'.build-dependencies]
+ bar = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("build.rs", "extern crate bar; fn main() {}")
+ .build();
+
+ p.cargo("check").run();
+ p.change_file("src/lib.rs", "extern crate bar;");
+ p.cargo("check")
+ .with_status(101)
+ // can't find crate for `bar`
+ .with_stderr_contains("[..]E0463[..]")
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/check.rs b/src/tools/cargo/tests/testsuite/check.rs
new file mode 100644
index 000000000..bbcf750fb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/check.rs
@@ -0,0 +1,1521 @@
+//! Tests for the `cargo check` command.
+
+use std::fmt::{self, Write};
+
+use crate::messages::raw_rustc_output;
+use cargo_test_support::install::exe;
+use cargo_test_support::paths::CargoPathExt;
+use cargo_test_support::registry::Package;
+use cargo_test_support::tools;
+use cargo_test_support::{basic_bin_manifest, basic_manifest, git, project};
+
+#[cargo_test]
+fn check_success() {
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "../bar"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ "extern crate bar; fn main() { ::bar::baz(); }",
+ )
+ .build();
+ let _bar = project()
+ .at("bar")
+ .file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("src/lib.rs", "pub fn baz() {}")
+ .build();
+
+ foo.cargo("check").run();
+}
+
+#[cargo_test]
+fn check_fail() {
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "../bar"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ "extern crate bar; fn main() { ::bar::baz(42); }",
+ )
+ .build();
+ let _bar = project()
+ .at("bar")
+ .file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("src/lib.rs", "pub fn baz() {}")
+ .build();
+
+ foo.cargo("check")
+ .with_status(101)
+ .with_stderr_contains("[..]this function takes 0[..]")
+ .run();
+}
+
+#[cargo_test]
+fn custom_derive() {
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "../bar"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ #[macro_use]
+ extern crate bar;
+
+ trait B {
+ fn b(&self);
+ }
+
+ #[derive(B)]
+ struct A;
+
+ fn main() {
+ let a = A;
+ a.b();
+ }
+ "#,
+ )
+ .build();
+ let _bar = project()
+ .at("bar")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+ [lib]
+ proc-macro = true
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ extern crate proc_macro;
+
+ use proc_macro::TokenStream;
+
+ #[proc_macro_derive(B)]
+ pub fn derive(_input: TokenStream) -> TokenStream {
+ format!("impl B for A {{ fn b(&self) {{}} }}").parse().unwrap()
+ }
+ "#,
+ )
+ .build();
+
+ foo.cargo("check").run();
+}
+
+#[cargo_test]
+fn check_build() {
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "../bar"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ "extern crate bar; fn main() { ::bar::baz(); }",
+ )
+ .build();
+
+ let _bar = project()
+ .at("bar")
+ .file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("src/lib.rs", "pub fn baz() {}")
+ .build();
+
+ foo.cargo("check").run();
+ foo.cargo("build").run();
+}
+
+#[cargo_test]
+fn build_check() {
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "../bar"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ "extern crate bar; fn main() { ::bar::baz(); }",
+ )
+ .build();
+
+ let _bar = project()
+ .at("bar")
+ .file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("src/lib.rs", "pub fn baz() {}")
+ .build();
+
+ foo.cargo("build -v").run();
+ foo.cargo("check -v").run();
+}
+
+// Checks that where a project has both a lib and a bin, the lib is only checked
+// not built.
+#[cargo_test]
+fn issue_3418() {
+ let foo = project()
+ .file("src/lib.rs", "")
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ foo.cargo("check -v")
+ .with_stderr_contains("[..] --emit=[..]metadata [..]")
+ .run();
+}
+
+// Some weirdness that seems to be caused by a crate being built as well as
+// checked, but in this case with a proc macro too.
+#[cargo_test]
+fn issue_3419() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ rustc-serialize = "*"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ extern crate rustc_serialize;
+
+ use rustc_serialize::Decodable;
+
+ pub fn take<T: Decodable>() {}
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ extern crate rustc_serialize;
+
+ extern crate foo;
+
+ #[derive(RustcDecodable)]
+ pub struct Foo;
+
+ fn main() {
+ foo::take::<Foo>();
+ }
+ "#,
+ )
+ .build();
+
+ Package::new("rustc-serialize", "1.0.0")
+ .file(
+ "src/lib.rs",
+ r#"
+ pub trait Decodable: Sized {
+ fn decode<D: Decoder>(d: &mut D) -> Result<Self, D::Error>;
+ }
+ pub trait Decoder {
+ type Error;
+ fn read_struct<T, F>(&mut self, s_name: &str, len: usize, f: F)
+ -> Result<T, Self::Error>
+ where F: FnOnce(&mut Self) -> Result<T, Self::Error>;
+ }
+ "#,
+ )
+ .publish();
+
+ p.cargo("check").run();
+}
+
+// Check on a dylib should have a different metadata hash than build.
+#[cargo_test]
+fn dylib_check_preserves_build_cache() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [lib]
+ crate-type = ["dylib"]
+
+ [dependencies]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build")
+ .with_stderr(
+ "\
+[..]Compiling foo v0.1.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("check").run();
+
+ p.cargo("build")
+ .with_stderr("[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]")
+ .run();
+}
+
+// test `cargo rustc --profile check`
+#[cargo_test]
+fn rustc_check() {
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "../bar"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ "extern crate bar; fn main() { ::bar::baz(); }",
+ )
+ .build();
+ let _bar = project()
+ .at("bar")
+ .file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("src/lib.rs", "pub fn baz() {}")
+ .build();
+
+ foo.cargo("rustc --profile check -- --emit=metadata").run();
+
+ // Verify compatible usage of --profile with --release, issue #7488
+ foo.cargo("rustc --profile check --release -- --emit=metadata")
+ .run();
+ foo.cargo("rustc --profile test --release -- --emit=metadata")
+ .run();
+}
+
+#[cargo_test]
+fn rustc_check_err() {
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "../bar"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ "extern crate bar; fn main() { ::bar::qux(); }",
+ )
+ .build();
+ let _bar = project()
+ .at("bar")
+ .file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("src/lib.rs", "pub fn baz() {}")
+ .build();
+
+ foo.cargo("rustc --profile check -- --emit=metadata")
+ .with_status(101)
+ .with_stderr_contains("[CHECKING] bar [..]")
+ .with_stderr_contains("[CHECKING] foo [..]")
+ .with_stderr_contains("[..]cannot find function `qux` in [..] `bar`")
+ .run();
+}
+
+#[cargo_test]
+fn check_all() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [workspace]
+ [dependencies]
+ b = { path = "b" }
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("examples/a.rs", "fn main() {}")
+ .file("tests/a.rs", "")
+ .file("src/lib.rs", "")
+ .file("b/Cargo.toml", &basic_manifest("b", "0.0.1"))
+ .file("b/src/main.rs", "fn main() {}")
+ .file("b/src/lib.rs", "")
+ .build();
+
+ p.cargo("check --workspace -v")
+ .with_stderr_contains("[..] --crate-name foo src/lib.rs [..]")
+ .with_stderr_contains("[..] --crate-name foo src/main.rs [..]")
+ .with_stderr_contains("[..] --crate-name b b/src/lib.rs [..]")
+ .with_stderr_contains("[..] --crate-name b b/src/main.rs [..]")
+ .run();
+}
+
+#[cargo_test]
+fn check_all_exclude() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar", "baz"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("baz/src/lib.rs", "pub fn baz() { break_the_build(); }")
+ .build();
+
+ p.cargo("check --workspace --exclude baz")
+ .with_stderr_does_not_contain("[CHECKING] baz v0.1.0 [..]")
+ .with_stderr(
+ "\
+[CHECKING] bar v0.1.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn check_all_exclude_glob() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar", "baz"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("baz/src/lib.rs", "pub fn baz() { break_the_build(); }")
+ .build();
+
+ p.cargo("check --workspace --exclude '*z'")
+ .with_stderr_does_not_contain("[CHECKING] baz v0.1.0 [..]")
+ .with_stderr(
+ "\
+[CHECKING] bar v0.1.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn check_virtual_all_implied() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar", "baz"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("baz/src/lib.rs", "pub fn baz() {}")
+ .build();
+
+ p.cargo("check -v")
+ .with_stderr_contains("[..] --crate-name bar bar/src/lib.rs [..]")
+ .with_stderr_contains("[..] --crate-name baz baz/src/lib.rs [..]")
+ .run();
+}
+
+#[cargo_test]
+fn check_virtual_manifest_one_project() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar", "baz"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("baz/src/lib.rs", "pub fn baz() { break_the_build(); }")
+ .build();
+
+ p.cargo("check -p bar")
+ .with_stderr_does_not_contain("[CHECKING] baz v0.1.0 [..]")
+ .with_stderr(
+ "\
+[CHECKING] bar v0.1.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn check_virtual_manifest_glob() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar", "baz"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() { break_the_build(); }")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("baz/src/lib.rs", "pub fn baz() {}")
+ .build();
+
+ p.cargo("check -p '*z'")
+ .with_stderr_does_not_contain("[CHECKING] bar v0.1.0 [..]")
+ .with_stderr(
+ "\
+[CHECKING] baz v0.1.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn exclude_warns_on_non_existing_package() {
+ let p = project().file("src/lib.rs", "").build();
+ p.cargo("check --workspace --exclude bar")
+ .with_stdout("")
+ .with_stderr(
+ "\
+[WARNING] excluded package(s) `bar` not found in workspace `[CWD]`
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn targets_selected_default() {
+ let foo = project()
+ .file("src/main.rs", "fn main() {}")
+ .file("src/lib.rs", "pub fn smth() {}")
+ .file("examples/example1.rs", "fn main() {}")
+ .file("tests/test2.rs", "#[test] fn t() {}")
+ .file("benches/bench3.rs", "")
+ .build();
+
+ foo.cargo("check -v")
+ .with_stderr_contains("[..] --crate-name foo src/lib.rs [..]")
+ .with_stderr_contains("[..] --crate-name foo src/main.rs [..]")
+ .with_stderr_does_not_contain("[..] --crate-name example1 examples/example1.rs [..]")
+ .with_stderr_does_not_contain("[..] --crate-name test2 tests/test2.rs [..]")
+ .with_stderr_does_not_contain("[..] --crate-name bench3 benches/bench3.rs [..]")
+ .run();
+}
+
+#[cargo_test]
+fn targets_selected_all() {
+ let foo = project()
+ .file("src/main.rs", "fn main() {}")
+ .file("src/lib.rs", "pub fn smth() {}")
+ .file("examples/example1.rs", "fn main() {}")
+ .file("tests/test2.rs", "#[test] fn t() {}")
+ .file("benches/bench3.rs", "")
+ .build();
+
+ foo.cargo("check --all-targets -v")
+ .with_stderr_contains("[..] --crate-name foo src/lib.rs [..]")
+ .with_stderr_contains("[..] --crate-name foo src/main.rs [..]")
+ .with_stderr_contains("[..] --crate-name example1 examples/example1.rs [..]")
+ .with_stderr_contains("[..] --crate-name test2 tests/test2.rs [..]")
+ .with_stderr_contains("[..] --crate-name bench3 benches/bench3.rs [..]")
+ .run();
+}
+
+#[cargo_test]
+fn check_unit_test_profile() {
+ let foo = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ #[cfg(test)]
+ mod tests {
+ #[test]
+ fn it_works() {
+ badtext
+ }
+ }
+ "#,
+ )
+ .build();
+
+ foo.cargo("check").run();
+ foo.cargo("check --profile test")
+ .with_status(101)
+ .with_stderr_contains("[..]badtext[..]")
+ .run();
+}
+
+// Verify what is checked with various command-line filters.
+#[cargo_test]
+fn check_filters() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ fn unused_normal_lib() {}
+ #[cfg(test)]
+ mod tests {
+ fn unused_unit_lib() {}
+ }
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {}
+ fn unused_normal_bin() {}
+ #[cfg(test)]
+ mod tests {
+ fn unused_unit_bin() {}
+ }
+ "#,
+ )
+ .file(
+ "tests/t1.rs",
+ r#"
+ fn unused_normal_t1() {}
+ #[cfg(test)]
+ mod tests {
+ fn unused_unit_t1() {}
+ }
+ "#,
+ )
+ .file(
+ "examples/ex1.rs",
+ r#"
+ fn main() {}
+ fn unused_normal_ex1() {}
+ #[cfg(test)]
+ mod tests {
+ fn unused_unit_ex1() {}
+ }
+ "#,
+ )
+ .file(
+ "benches/b1.rs",
+ r#"
+ fn unused_normal_b1() {}
+ #[cfg(test)]
+ mod tests {
+ fn unused_unit_b1() {}
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("check")
+ .with_stderr_contains("[..]unused_normal_lib[..]")
+ .with_stderr_contains("[..]unused_normal_bin[..]")
+ .with_stderr_does_not_contain("[..]unused_normal_t1[..]")
+ .with_stderr_does_not_contain("[..]unused_normal_ex1[..]")
+ .with_stderr_does_not_contain("[..]unused_normal_b1[..]")
+ .with_stderr_does_not_contain("[..]unused_unit_[..]")
+ .run();
+ p.root().join("target").rm_rf();
+ p.cargo("check --tests -v")
+ .with_stderr_contains("[..] --crate-name foo src/lib.rs [..] --test [..]")
+ .with_stderr_contains("[..] --crate-name foo src/lib.rs [..] --crate-type lib [..]")
+ .with_stderr_contains("[..] --crate-name foo src/main.rs [..] --test [..]")
+ .with_stderr_contains("[..]unused_unit_lib[..]")
+ .with_stderr_contains("[..]unused_unit_bin[..]")
+ .with_stderr_contains("[..]unused_normal_lib[..]")
+ .with_stderr_contains("[..]unused_normal_bin[..]")
+ .with_stderr_contains("[..]unused_unit_t1[..]")
+ .with_stderr_does_not_contain("[..]unused_normal_ex1[..]")
+ .with_stderr_does_not_contain("[..]unused_unit_ex1[..]")
+ .with_stderr_does_not_contain("[..]unused_normal_b1[..]")
+ .with_stderr_does_not_contain("[..]unused_unit_b1[..]")
+ .with_stderr_does_not_contain("[..]--crate-type bin[..]")
+ .run();
+ p.root().join("target").rm_rf();
+ p.cargo("check --test t1 -v")
+ .with_stderr_contains("[..]unused_normal_lib[..]")
+ .with_stderr_contains("[..]unused_unit_t1[..]")
+ .with_stderr_does_not_contain("[..]unused_unit_lib[..]")
+ .with_stderr_does_not_contain("[..]unused_normal_bin[..]")
+ .with_stderr_does_not_contain("[..]unused_unit_bin[..]")
+ .with_stderr_does_not_contain("[..]unused_normal_ex1[..]")
+ .with_stderr_does_not_contain("[..]unused_normal_b1[..]")
+ .with_stderr_does_not_contain("[..]unused_unit_ex1[..]")
+ .with_stderr_does_not_contain("[..]unused_unit_b1[..]")
+ .run();
+ p.root().join("target").rm_rf();
+ p.cargo("check --all-targets -v")
+ .with_stderr_contains("[..]unused_normal_lib[..]")
+ .with_stderr_contains("[..]unused_normal_bin[..]")
+ .with_stderr_contains("[..]unused_normal_t1[..]")
+ .with_stderr_contains("[..]unused_normal_ex1[..]")
+ .with_stderr_contains("[..]unused_normal_b1[..]")
+ .with_stderr_contains("[..]unused_unit_b1[..]")
+ .with_stderr_contains("[..]unused_unit_t1[..]")
+ .with_stderr_contains("[..]unused_unit_lib[..]")
+ .with_stderr_contains("[..]unused_unit_bin[..]")
+ .with_stderr_does_not_contain("[..]unused_unit_ex1[..]")
+ .run();
+}
+
+#[cargo_test]
+fn check_artifacts() {
+ // Verify which artifacts are created when running check (#4059).
+ let p = project()
+ .file("src/lib.rs", "")
+ .file("src/main.rs", "fn main() {}")
+ .file("tests/t1.rs", "")
+ .file("examples/ex1.rs", "fn main() {}")
+ .file("benches/b1.rs", "")
+ .build();
+
+ p.cargo("check").run();
+ assert!(!p.root().join("target/debug/libfoo.rmeta").is_file());
+ assert!(!p.root().join("target/debug/libfoo.rlib").is_file());
+ assert!(!p.root().join("target/debug").join(exe("foo")).is_file());
+ assert_eq!(p.glob("target/debug/deps/libfoo-*.rmeta").count(), 2);
+
+ p.root().join("target").rm_rf();
+ p.cargo("check --lib").run();
+ assert!(!p.root().join("target/debug/libfoo.rmeta").is_file());
+ assert!(!p.root().join("target/debug/libfoo.rlib").is_file());
+ assert!(!p.root().join("target/debug").join(exe("foo")).is_file());
+ assert_eq!(p.glob("target/debug/deps/libfoo-*.rmeta").count(), 1);
+
+ p.root().join("target").rm_rf();
+ p.cargo("check --bin foo").run();
+ assert!(!p.root().join("target/debug/libfoo.rmeta").is_file());
+ assert!(!p.root().join("target/debug/libfoo.rlib").is_file());
+ assert!(!p.root().join("target/debug").join(exe("foo")).is_file());
+ assert_eq!(p.glob("target/debug/deps/libfoo-*.rmeta").count(), 2);
+
+ p.root().join("target").rm_rf();
+ p.cargo("check --test t1").run();
+ assert!(!p.root().join("target/debug/libfoo.rmeta").is_file());
+ assert!(!p.root().join("target/debug/libfoo.rlib").is_file());
+ assert!(!p.root().join("target/debug").join(exe("foo")).is_file());
+ assert_eq!(p.glob("target/debug/t1-*").count(), 0);
+ assert_eq!(p.glob("target/debug/deps/libfoo-*.rmeta").count(), 1);
+ assert_eq!(p.glob("target/debug/deps/libt1-*.rmeta").count(), 1);
+
+ p.root().join("target").rm_rf();
+ p.cargo("check --example ex1").run();
+ assert!(!p.root().join("target/debug/libfoo.rmeta").is_file());
+ assert!(!p.root().join("target/debug/libfoo.rlib").is_file());
+ assert!(!p
+ .root()
+ .join("target/debug/examples")
+ .join(exe("ex1"))
+ .is_file());
+ assert_eq!(p.glob("target/debug/deps/libfoo-*.rmeta").count(), 1);
+ assert_eq!(p.glob("target/debug/examples/libex1-*.rmeta").count(), 1);
+
+ p.root().join("target").rm_rf();
+ p.cargo("check --bench b1").run();
+ assert!(!p.root().join("target/debug/libfoo.rmeta").is_file());
+ assert!(!p.root().join("target/debug/libfoo.rlib").is_file());
+ assert!(!p.root().join("target/debug").join(exe("foo")).is_file());
+ assert_eq!(p.glob("target/debug/b1-*").count(), 0);
+ assert_eq!(p.glob("target/debug/deps/libfoo-*.rmeta").count(), 1);
+ assert_eq!(p.glob("target/debug/deps/libb1-*.rmeta").count(), 1);
+}
+
+#[cargo_test]
+fn short_message_format() {
+ let foo = project()
+ .file("src/lib.rs", "fn foo() { let _x: bool = 'a'; }")
+ .build();
+ foo.cargo("check --message-format=short")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+src/lib.rs:1:27: error[E0308]: mismatched types
+error: could not compile `foo` (lib) due to previous error
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn proc_macro() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "demo"
+ version = "0.0.1"
+
+ [lib]
+ proc-macro = true
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ extern crate proc_macro;
+
+ use proc_macro::TokenStream;
+
+ #[proc_macro_derive(Foo)]
+ pub fn demo(_input: TokenStream) -> TokenStream {
+ "".parse().unwrap()
+ }
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ #[macro_use]
+ extern crate demo;
+
+ #[derive(Foo)]
+ struct A;
+
+ fn main() {}
+ "#,
+ )
+ .build();
+ p.cargo("check -v").env("CARGO_LOG", "cargo=trace").run();
+}
+
+#[cargo_test]
+fn check_keep_going() {
+ let foo = project()
+ .file("src/bin/one.rs", "compile_error!(\"ONE\"); fn main() {}")
+ .file("src/bin/two.rs", "compile_error!(\"TWO\"); fn main() {}")
+ .build();
+
+ // Due to -j1, without --keep-going only one of the two bins would be built.
+ foo.cargo("check -j1 --keep-going -Zunstable-options")
+ .masquerade_as_nightly_cargo(&["keep-going"])
+ .with_status(101)
+ .with_stderr_contains("error: ONE")
+ .with_stderr_contains("error: TWO")
+ .run();
+}
+
+#[cargo_test]
+fn does_not_use_empty_rustc_wrapper() {
+ // An empty RUSTC_WRAPPER environment variable won't be used.
+ // The env var will also override the config, essentially unsetting it.
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config.toml",
+ r#"
+ [build]
+ rustc-wrapper = "do-not-execute-me"
+ "#,
+ )
+ .build();
+ p.cargo("check").env("RUSTC_WRAPPER", "").run();
+}
+
+#[cargo_test]
+fn does_not_use_empty_rustc_workspace_wrapper() {
+ let p = project().file("src/lib.rs", "").build();
+ p.cargo("check").env("RUSTC_WORKSPACE_WRAPPER", "").run();
+}
+
+#[cargo_test]
+fn error_from_deep_recursion() -> Result<(), fmt::Error> {
+ let mut big_macro = String::new();
+ writeln!(big_macro, "macro_rules! m {{")?;
+ for i in 0..130 {
+ writeln!(big_macro, "({}) => {{ m!({}); }};", i, i + 1)?;
+ }
+ writeln!(big_macro, "}}")?;
+ writeln!(big_macro, "m!(0);")?;
+
+ let p = project().file("src/lib.rs", &big_macro).build();
+ p.cargo("check --message-format=json")
+ .with_status(101)
+ .with_stdout_contains(
+ "[..]\"message\":\"recursion limit reached while expanding [..]`m[..]`\"[..]",
+ )
+ .run();
+
+ Ok(())
+}
+
+#[cargo_test]
+fn rustc_workspace_wrapper_affects_all_workspace_members() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar", "baz"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("baz/src/lib.rs", "pub fn baz() {}")
+ .build();
+
+ p.cargo("check")
+ .env("RUSTC_WORKSPACE_WRAPPER", tools::echo_wrapper())
+ .with_stderr_contains("WRAPPER CALLED: rustc --crate-name bar [..]")
+ .with_stderr_contains("WRAPPER CALLED: rustc --crate-name baz [..]")
+ .run();
+}
+
+#[cargo_test]
+fn rustc_workspace_wrapper_includes_path_deps() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [workspace]
+ members = ["bar"]
+
+ [dependencies]
+ baz = { path = "baz" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("baz/src/lib.rs", "pub fn baz() {}")
+ .build();
+
+ p.cargo("check --workspace")
+ .env("RUSTC_WORKSPACE_WRAPPER", tools::echo_wrapper())
+ .with_stderr_contains("WRAPPER CALLED: rustc --crate-name foo [..]")
+ .with_stderr_contains("WRAPPER CALLED: rustc --crate-name bar [..]")
+ .with_stderr_contains("WRAPPER CALLED: rustc --crate-name baz [..]")
+ .run();
+}
+
+#[cargo_test]
+fn rustc_workspace_wrapper_respects_primary_units() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar", "baz"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("baz/src/lib.rs", "pub fn baz() {}")
+ .build();
+
+ p.cargo("check -p bar")
+ .env("RUSTC_WORKSPACE_WRAPPER", tools::echo_wrapper())
+ .with_stderr_contains("WRAPPER CALLED: rustc --crate-name bar [..]")
+ .with_stdout_does_not_contain("WRAPPER CALLED: rustc --crate-name baz [..]")
+ .run();
+}
+
+#[cargo_test]
+fn rustc_workspace_wrapper_excludes_published_deps() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [workspace]
+ members = ["bar"]
+
+ [dependencies]
+ baz = "1.0.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ Package::new("baz", "1.0.0").publish();
+
+ p.cargo("check --workspace -v")
+ .env("RUSTC_WORKSPACE_WRAPPER", tools::echo_wrapper())
+ .with_stderr_contains("WRAPPER CALLED: rustc --crate-name foo [..]")
+ .with_stderr_contains("WRAPPER CALLED: rustc --crate-name bar [..]")
+ .with_stderr_contains("[CHECKING] baz [..]")
+ .with_stdout_does_not_contain("WRAPPER CALLED: rustc --crate-name baz [..]")
+ .run();
+}
+
+#[cargo_test]
+fn warn_manifest_package_and_project() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [project]
+ name = "foo"
+ version = "0.0.1"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[WARNING] manifest at `[CWD]` contains both `project` and `package`, this could become a hard error in the future
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn git_manifest_package_and_project() {
+ let p = project();
+ let git_project = git::new("bar", |p| {
+ p.file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+
+ [project]
+ name = "bar"
+ version = "0.0.1"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ });
+
+ let p = p
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies.bar]
+ version = "0.0.1"
+ git = '{}'
+
+ "#,
+ git_project.url()
+ ),
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] git repository `[..]`
+[CHECKING] bar v0.0.1 ([..])
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn warn_manifest_with_project() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [project]
+ name = "foo"
+ version = "0.0.1"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[WARNING] manifest at `[CWD]` contains `[project]` instead of `[package]`, this could become a hard error in the future
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn git_manifest_with_project() {
+ let p = project();
+ let git_project = git::new("bar", |p| {
+ p.file(
+ "Cargo.toml",
+ r#"
+ [project]
+ name = "bar"
+ version = "0.0.1"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ });
+
+ let p = p
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies.bar]
+ version = "0.0.1"
+ git = '{}'
+
+ "#,
+ git_project.url()
+ ),
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] git repository `[..]`
+[CHECKING] bar v0.0.1 ([..])
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn check_fixable_warning() {
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ "#,
+ )
+ .file("src/lib.rs", "use std::io;")
+ .build();
+
+ foo.cargo("check")
+ .with_stderr_contains("[..] (run `cargo fix --lib -p foo` to apply 1 suggestion)")
+ .run();
+}
+
+#[cargo_test]
+fn check_fixable_test_warning() {
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "\
+mod tests {
+ #[test]
+ fn t1() {
+ use std::io;
+ }
+}
+ ",
+ )
+ .build();
+
+ foo.cargo("check --all-targets")
+ .with_stderr_contains("[..] (run `cargo fix --lib -p foo --tests` to apply 1 suggestion)")
+ .run();
+ foo.cargo("fix --lib -p foo --tests --allow-no-vcs").run();
+ assert!(!foo.read_file("src/lib.rs").contains("use std::io;"));
+}
+
+#[cargo_test]
+fn check_fixable_error_no_fix() {
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "use std::io;\n#[derive(Debug(x))]\nstruct Foo;",
+ )
+ .build();
+
+ let rustc_message = raw_rustc_output(&foo, "src/lib.rs", &[]);
+ let expected_output = format!(
+ "\
+[CHECKING] foo v0.0.1 ([..])
+{}\
+[WARNING] `foo` (lib) generated 1 warning
+[ERROR] could not compile `foo` (lib) due to previous error; 1 warning emitted
+",
+ rustc_message
+ );
+ foo.cargo("check")
+ .with_status(101)
+ .with_stderr(expected_output)
+ .run();
+}
+
+#[cargo_test]
+fn check_fixable_warning_workspace() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["foo", "bar"]
+ "#,
+ )
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ "#,
+ )
+ .file("foo/src/lib.rs", "use std::io;")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+
+ [dependencies]
+ foo = { path = "../foo" }
+ "#,
+ )
+ .file("bar/src/lib.rs", "use std::io;")
+ .build();
+
+ p.cargo("check")
+ .with_stderr_contains("[..] (run `cargo fix --lib -p foo` to apply 1 suggestion)")
+ .with_stderr_contains("[..] (run `cargo fix --lib -p bar` to apply 1 suggestion)")
+ .run();
+}
+
+#[cargo_test]
+fn check_fixable_example() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file(
+ "src/main.rs",
+ r#"
+ fn hello() -> &'static str {
+ "hello"
+ }
+
+ pub fn main() {
+ println!("{}", hello())
+ }
+ "#,
+ )
+ .file("examples/ex1.rs", "use std::fmt; fn main() {}")
+ .build();
+ p.cargo("check --all-targets")
+ .with_stderr_contains("[..] (run `cargo fix --example \"ex1\"` to apply 1 suggestion)")
+ .run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn check_fixable_bench() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file(
+ "src/main.rs",
+ r#"
+ #![feature(test)]
+ #[cfg(test)]
+ extern crate test;
+
+ fn hello() -> &'static str {
+ "hello"
+ }
+
+ pub fn main() {
+ println!("{}", hello())
+ }
+
+ #[bench]
+ fn bench_hello(_b: &mut test::Bencher) {
+ use std::io;
+ assert_eq!(hello(), "hello")
+ }
+ "#,
+ )
+ .file(
+ "benches/bench.rs",
+ "
+ #![feature(test)]
+ extern crate test;
+
+ #[bench]
+ fn bench(_b: &mut test::Bencher) { use std::fmt; }
+ ",
+ )
+ .build();
+ p.cargo("check --all-targets")
+ .with_stderr_contains("[..] (run `cargo fix --bench \"bench\"` to apply 1 suggestion)")
+ .run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn check_fixable_mixed() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file(
+ "src/main.rs",
+ r#"
+ #![feature(test)]
+ #[cfg(test)]
+ extern crate test;
+
+ fn hello() -> &'static str {
+ "hello"
+ }
+
+ pub fn main() {
+ println!("{}", hello())
+ }
+
+ #[bench]
+ fn bench_hello(_b: &mut test::Bencher) {
+ use std::io;
+ assert_eq!(hello(), "hello")
+ }
+ #[test]
+ fn t1() {
+ use std::fmt;
+ }
+ "#,
+ )
+ .file("examples/ex1.rs", "use std::fmt; fn main() {}")
+ .file(
+ "benches/bench.rs",
+ "
+ #![feature(test)]
+ extern crate test;
+
+ #[bench]
+ fn bench(_b: &mut test::Bencher) { use std::fmt; }
+ ",
+ )
+ .build();
+ p.cargo("check --all-targets")
+ .with_stderr_contains("[..] (run `cargo fix --bin \"foo\" --tests` to apply 2 suggestions)")
+ .with_stderr_contains("[..] (run `cargo fix --example \"ex1\"` to apply 1 suggestion)")
+ .with_stderr_contains("[..] (run `cargo fix --bench \"bench\"` to apply 1 suggestion)")
+ .run();
+}
+
+#[cargo_test]
+fn check_fixable_warning_for_clippy() {
+ // A wrapper around `rustc` instead of calling `clippy`
+ let clippy_driver = project()
+ .at(cargo_test_support::paths::global_root().join("clippy-driver"))
+ .file("Cargo.toml", &basic_manifest("clippy-driver", "0.0.1"))
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ let mut args = std::env::args_os();
+ let _me = args.next().unwrap();
+ let rustc = args.next().unwrap();
+ let status = std::process::Command::new(rustc).args(args).status().unwrap();
+ std::process::exit(status.code().unwrap_or(1));
+ }
+ "#,
+ )
+ .build();
+ clippy_driver.cargo("build").run();
+
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ "#,
+ )
+ // We don't want to show a warning that is `clippy`
+ // specific since we are using a `rustc` wrapper
+ // inplace of `clippy`
+ .file("src/lib.rs", "use std::io;")
+ .build();
+
+ foo.cargo("check")
+ // We can't use `clippy` so we use a `rustc` workspace wrapper instead
+ .env(
+ "RUSTC_WORKSPACE_WRAPPER",
+ clippy_driver.bin("clippy-driver"),
+ )
+ .with_stderr_contains("[..] (run `cargo clippy --fix --lib -p foo` to apply 1 suggestion)")
+ .run();
+}
+
+#[cargo_test]
+fn check_unused_manifest_keys() {
+ Package::new("dep", "0.1.0").publish();
+ Package::new("foo", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.2.0"
+ authors = []
+
+ [dependencies]
+ dep = { version = "0.1.0", wxz = "wxz" }
+ foo = { version = "0.1.0", abc = "abc" }
+
+ [dev-dependencies]
+ foo = { version = "0.1.0", wxz = "wxz" }
+
+ [build-dependencies]
+ foo = { version = "0.1.0", wxz = "wxz" }
+
+ [target.'cfg(windows)'.dependencies]
+ foo = { version = "0.1.0", wxz = "wxz" }
+
+ [target.x86_64-pc-windows-gnu.dev-dependencies]
+ foo = { version = "0.1.0", wxz = "wxz" }
+
+ [target.bar.build-dependencies]
+ foo = { version = "0.1.0", wxz = "wxz" }
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[WARNING] unused manifest key: dependencies.dep.wxz
+[WARNING] unused manifest key: dependencies.foo.abc
+[WARNING] unused manifest key: dev-dependencies.foo.wxz
+[WARNING] unused manifest key: build-dependencies.foo.wxz
+[WARNING] unused manifest key: target.bar.build-dependencies.foo.wxz
+[WARNING] unused manifest key: target.cfg(windows).dependencies.foo.wxz
+[WARNING] unused manifest key: target.x86_64-pc-windows-gnu.dev-dependencies.foo.wxz
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] foo v0.1.0 ([..])
+[DOWNLOADED] dep v0.1.0 ([..])
+[CHECKING] [..]
+[CHECKING] [..]
+[CHECKING] bar v0.2.0 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/check_cfg.rs b/src/tools/cargo/tests/testsuite/check_cfg.rs
new file mode 100644
index 000000000..c35da637d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/check_cfg.rs
@@ -0,0 +1,588 @@
+//! Tests for -Zcheck-cfg.
+
+use cargo_test_support::{basic_manifest, project};
+
+macro_rules! x {
+ ($tool:tt => $what:tt $(of $who:tt)?) => {{
+ #[cfg(windows)]
+ {
+ concat!("[RUNNING] [..]", $tool, "[..] --check-cfg ",
+ $what, '(', $($who,)* ')', "[..]")
+ }
+ #[cfg(not(windows))]
+ {
+ concat!("[RUNNING] [..]", $tool, "[..] --check-cfg '",
+ $what, '(', $($who,)* ')', "'", "[..]")
+ }
+ }};
+ ($tool:tt => $what:tt of $who:tt with $($values:tt)*) => {{
+ #[cfg(windows)]
+ {
+ concat!("[RUNNING] [..]", $tool, "[..] --check-cfg \"",
+ $what, '(', $who, $(", ", "/\"", $values, "/\"",)* ")", '"', "[..]")
+ }
+ #[cfg(not(windows))]
+ {
+ concat!("[RUNNING] [..]", $tool, "[..] --check-cfg '",
+ $what, '(', $who, $(", ", "\"", $values, "\"",)* ")", "'", "[..]")
+ }
+ }};
+}
+
+#[cargo_test(nightly, reason = "--check-cfg is unstable")]
+fn features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [features]
+ f_a = []
+ f_b = []
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check -v -Zcheck-cfg=features")
+ .masquerade_as_nightly_cargo(&["check-cfg"])
+ .with_stderr_contains(x!("rustc" => "values" of "feature" with "f_a" "f_b"))
+ .run();
+}
+
+#[cargo_test(nightly, reason = "--check-cfg is unstable")]
+fn features_with_deps() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { path = "bar/" }
+
+ [features]
+ f_a = []
+ f_b = []
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "#[allow(dead_code)] fn bar() {}")
+ .build();
+
+ p.cargo("check -v -Zcheck-cfg=features")
+ .masquerade_as_nightly_cargo(&["check-cfg"])
+ .with_stderr_contains(x!("rustc" => "values" of "feature"))
+ .with_stderr_contains(x!("rustc" => "values" of "feature" with "f_a" "f_b"))
+ .run();
+}
+
+#[cargo_test(nightly, reason = "--check-cfg is unstable")]
+fn features_with_opt_deps() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { path = "bar/", optional = true }
+
+ [features]
+ default = ["bar"]
+ f_a = []
+ f_b = []
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "#[allow(dead_code)] fn bar() {}")
+ .build();
+
+ p.cargo("check -v -Zcheck-cfg=features")
+ .masquerade_as_nightly_cargo(&["check-cfg"])
+ .with_stderr_contains(x!("rustc" => "values" of "feature"))
+ .with_stderr_contains(x!("rustc" => "values" of "feature" with "bar" "default" "f_a" "f_b"))
+ .run();
+}
+
+#[cargo_test(nightly, reason = "--check-cfg is unstable")]
+fn features_with_namespaced_features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { path = "bar/", optional = true }
+
+ [features]
+ f_a = ["dep:bar"]
+ f_b = []
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "#[allow(dead_code)] fn bar() {}")
+ .build();
+
+ p.cargo("check -v -Zcheck-cfg=features")
+ .masquerade_as_nightly_cargo(&["check-cfg"])
+ .with_stderr_contains(x!("rustc" => "values" of "feature" with "f_a" "f_b"))
+ .run();
+}
+
+#[cargo_test(nightly, reason = "--check-cfg is unstable")]
+fn well_known_names() {
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check -v -Zcheck-cfg=names")
+ .masquerade_as_nightly_cargo(&["check-cfg"])
+ .with_stderr_contains(x!("rustc" => "names"))
+ .run();
+}
+
+#[cargo_test(nightly, reason = "--check-cfg is unstable")]
+fn well_known_values() {
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check -v -Zcheck-cfg=values")
+ .masquerade_as_nightly_cargo(&["check-cfg"])
+ .with_stderr_contains(x!("rustc" => "values"))
+ .run();
+}
+
+#[cargo_test(nightly, reason = "--check-cfg is unstable")]
+fn cli_all_options() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [features]
+ f_a = []
+ f_b = []
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check -v -Zcheck-cfg=features,names,values")
+ .masquerade_as_nightly_cargo(&["check-cfg"])
+ .with_stderr_contains(x!("rustc" => "names"))
+ .with_stderr_contains(x!("rustc" => "values"))
+ .with_stderr_contains(x!("rustc" => "values" of "feature" with "f_a" "f_b"))
+ .run();
+}
+
+#[cargo_test(nightly, reason = "--check-cfg is unstable")]
+fn features_with_cargo_check() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [features]
+ f_a = []
+ f_b = []
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check -v -Zcheck-cfg=features")
+ .masquerade_as_nightly_cargo(&["check-cfg"])
+ .with_stderr_contains(x!("rustc" => "values" of "feature" with "f_a" "f_b"))
+ .run();
+}
+
+#[cargo_test(nightly, reason = "--check-cfg is unstable")]
+fn well_known_names_with_check() {
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check -v -Zcheck-cfg=names")
+ .masquerade_as_nightly_cargo(&["check-cfg"])
+ .with_stderr_contains(x!("rustc" => "names"))
+ .run();
+}
+
+#[cargo_test(nightly, reason = "--check-cfg is unstable")]
+fn well_known_values_with_check() {
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check -v -Zcheck-cfg=values")
+ .masquerade_as_nightly_cargo(&["check-cfg"])
+ .with_stderr_contains(x!("rustc" => "values"))
+ .run();
+}
+
+#[cargo_test(nightly, reason = "--check-cfg is unstable")]
+fn features_test() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [features]
+ f_a = []
+ f_b = []
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("test -v -Zcheck-cfg=features")
+ .masquerade_as_nightly_cargo(&["check-cfg"])
+ .with_stderr_contains(x!("rustc" => "values" of "feature" with "f_a" "f_b"))
+ .run();
+}
+
+#[cargo_test(nightly, reason = "--check-cfg is unstable")]
+fn features_doctest() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [features]
+ default = ["f_a"]
+ f_a = []
+ f_b = []
+ "#,
+ )
+ .file("src/lib.rs", "#[allow(dead_code)] fn foo() {}")
+ .build();
+
+ p.cargo("test -v --doc -Zcheck-cfg=features")
+ .masquerade_as_nightly_cargo(&["check-cfg"])
+ .with_stderr_contains(x!("rustc" => "values" of "feature" with "default" "f_a" "f_b"))
+ .with_stderr_contains(x!("rustdoc" => "values" of "feature" with "default" "f_a" "f_b"))
+ .run();
+}
+
+#[cargo_test(nightly, reason = "--check-cfg is unstable")]
+fn well_known_names_test() {
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("test -v -Zcheck-cfg=names")
+ .masquerade_as_nightly_cargo(&["check-cfg"])
+ .with_stderr_contains(x!("rustc" => "names"))
+ .run();
+}
+
+#[cargo_test(nightly, reason = "--check-cfg is unstable")]
+fn well_known_values_test() {
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("test -v -Zcheck-cfg=values")
+ .masquerade_as_nightly_cargo(&["check-cfg"])
+ .with_stderr_contains(x!("rustc" => "values"))
+ .run();
+}
+
+#[cargo_test(nightly, reason = "--check-cfg is unstable")]
+fn well_known_names_doctest() {
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("src/lib.rs", "#[allow(dead_code)] fn foo() {}")
+ .build();
+
+ p.cargo("test -v --doc -Zcheck-cfg=names")
+ .masquerade_as_nightly_cargo(&["check-cfg"])
+ .with_stderr_contains(x!("rustc" => "names"))
+ .with_stderr_contains(x!("rustdoc" => "names"))
+ .run();
+}
+
+#[cargo_test(nightly, reason = "--check-cfg is unstable")]
+fn well_known_values_doctest() {
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("src/lib.rs", "#[allow(dead_code)] fn foo() {}")
+ .build();
+
+ p.cargo("test -v --doc -Zcheck-cfg=values")
+ .masquerade_as_nightly_cargo(&["check-cfg"])
+ .with_stderr_contains(x!("rustc" => "values"))
+ .with_stderr_contains(x!("rustdoc" => "values"))
+ .run();
+}
+
+#[cargo_test(nightly, reason = "--check-cfg is unstable")]
+fn features_doc() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [features]
+ default = ["f_a"]
+ f_a = []
+ f_b = []
+ "#,
+ )
+ .file("src/lib.rs", "#[allow(dead_code)] fn foo() {}")
+ .build();
+
+ p.cargo("doc -v -Zcheck-cfg=features")
+ .masquerade_as_nightly_cargo(&["check-cfg"])
+ .with_stderr_contains(x!("rustdoc" => "values" of "feature" with "default" "f_a" "f_b"))
+ .run();
+}
+
+#[cargo_test(nightly, reason = "--check-cfg is unstable")]
+fn build_script_feedback() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "build.rs",
+ r#"fn main() { println!("cargo:rustc-check-cfg=names(foo)"); }"#,
+ )
+ .build();
+
+ p.cargo("check -v -Zcheck-cfg=output")
+ .masquerade_as_nightly_cargo(&["check-cfg"])
+ .with_stderr_contains(x!("rustc" => "names" of "foo"))
+ .run();
+}
+
+#[cargo_test(nightly, reason = "--check-cfg is unstable")]
+fn build_script_doc() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "build.rs",
+ r#"fn main() { println!("cargo:rustc-check-cfg=names(foo)"); }"#,
+ )
+ .build();
+ p.cargo("doc -v -Zcheck-cfg=output")
+ .with_stderr_does_not_contain("rustc [..] --check-cfg [..]")
+ .with_stderr_contains(x!("rustdoc" => "names" of "foo"))
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc [..] build.rs [..]`
+[RUNNING] `[..]/build-script-build`
+[DOCUMENTING] foo [..]
+[RUNNING] `rustdoc [..] src/main.rs [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ )
+ .masquerade_as_nightly_cargo(&["check-cfg"])
+ .run();
+}
+
+#[cargo_test(nightly, reason = "--check-cfg is unstable")]
+fn build_script_override() {
+ let target = cargo_test_support::rustc_host();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ links = "a"
+ build = "build.rs"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("build.rs", "")
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [target.{}.a]
+ rustc-check-cfg = ["names(foo)"]
+ "#,
+ target
+ ),
+ )
+ .build();
+
+ p.cargo("check -v -Zcheck-cfg=output")
+ .with_stderr_contains(x!("rustc" => "names" of "foo"))
+ .masquerade_as_nightly_cargo(&["check-cfg"])
+ .run();
+}
+
+#[cargo_test(nightly, reason = "--check-cfg is unstable")]
+fn build_script_test() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file(
+ "build.rs",
+ r#"fn main() {
+ println!("cargo:rustc-check-cfg=names(foo)");
+ println!("cargo:rustc-cfg=foo");
+ }"#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ ///
+ /// ```
+ /// extern crate foo;
+ ///
+ /// fn main() {
+ /// foo::foo()
+ /// }
+ /// ```
+ ///
+ #[cfg(foo)]
+ pub fn foo() {}
+
+ #[cfg(foo)]
+ #[test]
+ fn test_foo() {
+ foo()
+ }
+ "#,
+ )
+ .file("tests/test.rs", "#[cfg(foo)] #[test] fn test_bar() {}")
+ .build();
+
+ p.cargo("test -v -Zcheck-cfg=output")
+ .with_stderr_contains(x!("rustc" => "names" of "foo"))
+ .with_stderr_contains(x!("rustdoc" => "names" of "foo"))
+ .with_stdout_contains("test test_foo ... ok")
+ .with_stdout_contains("test test_bar ... ok")
+ .with_stdout_contains_n("test [..] ... ok", 3)
+ .masquerade_as_nightly_cargo(&["check-cfg"])
+ .run();
+}
+
+#[cargo_test(nightly, reason = "--check-cfg is unstable")]
+fn config_valid() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [features]
+ f_a = []
+ f_b = []
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ ".cargo/config.toml",
+ r#"
+ [unstable]
+ check-cfg = ["features", "names", "values"]
+ "#,
+ )
+ .build();
+
+ p.cargo("check -v -Zcheck-cfg=features,names,values")
+ .masquerade_as_nightly_cargo(&["check-cfg"])
+ .with_stderr_contains(x!("rustc" => "names"))
+ .with_stderr_contains(x!("rustc" => "values"))
+ .with_stderr_contains(x!("rustc" => "values" of "feature" with "f_a" "f_b"))
+ .run();
+}
+
+#[cargo_test(nightly, reason = "--check-cfg is unstable")]
+fn config_invalid() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ ".cargo/config.toml",
+ r#"
+ [unstable]
+ check-cfg = ["va"]
+ "#,
+ )
+ .build();
+
+ p.cargo("check")
+ .masquerade_as_nightly_cargo(&["check-cfg"])
+ .with_stderr_contains("error: unstable check-cfg only takes `features`, `names`, `values` or `output` as valid inputs")
+ .with_status(101)
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/clean.rs b/src/tools/cargo/tests/testsuite/clean.rs
new file mode 100644
index 000000000..e0885fd26
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/clean.rs
@@ -0,0 +1,675 @@
+//! Tests for the `cargo clean` command.
+
+use cargo_test_support::registry::Package;
+use cargo_test_support::{
+ basic_bin_manifest, basic_manifest, git, main_file, project, project_in, rustc_host,
+};
+use glob::GlobError;
+use std::env;
+use std::path::{Path, PathBuf};
+
+#[cargo_test]
+fn cargo_clean_simple() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .build();
+
+ p.cargo("build").run();
+ assert!(p.build_dir().is_dir());
+
+ p.cargo("clean").run();
+ assert!(!p.build_dir().is_dir());
+}
+
+#[cargo_test]
+fn different_dir() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .file("src/bar/a.rs", "")
+ .build();
+
+ p.cargo("build").run();
+ assert!(p.build_dir().is_dir());
+
+ p.cargo("clean").cwd("src").with_stdout("").run();
+ assert!(!p.build_dir().is_dir());
+}
+
+#[cargo_test]
+fn clean_multiple_packages() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.d1]
+ path = "d1"
+ [dependencies.d2]
+ path = "d2"
+
+ [[bin]]
+ name = "foo"
+ "#,
+ )
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .file("d1/Cargo.toml", &basic_bin_manifest("d1"))
+ .file("d1/src/main.rs", "fn main() { println!(\"d1\"); }")
+ .file("d2/Cargo.toml", &basic_bin_manifest("d2"))
+ .file("d2/src/main.rs", "fn main() { println!(\"d2\"); }")
+ .build();
+
+ p.cargo("build -p d1 -p d2 -p foo").run();
+
+ let d1_path = &p
+ .build_dir()
+ .join("debug")
+ .join(format!("d1{}", env::consts::EXE_SUFFIX));
+ let d2_path = &p
+ .build_dir()
+ .join("debug")
+ .join(format!("d2{}", env::consts::EXE_SUFFIX));
+
+ assert!(p.bin("foo").is_file());
+ assert!(d1_path.is_file());
+ assert!(d2_path.is_file());
+
+ p.cargo("clean -p d1 -p d2")
+ .cwd("src")
+ .with_stdout("")
+ .run();
+ assert!(p.bin("foo").is_file());
+ assert!(!d1_path.is_file());
+ assert!(!d2_path.is_file());
+}
+
+#[cargo_test]
+fn clean_multiple_packages_in_glob_char_path() {
+ let p = project_in("[d1]")
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .build();
+ let foo_path = &p.build_dir().join("debug").join("deps");
+
+ #[cfg(not(target_env = "msvc"))]
+ let file_glob = "foo-*";
+
+ #[cfg(target_env = "msvc")]
+ let file_glob = "foo.pdb";
+
+ // Assert that build artifacts are produced
+ p.cargo("build").run();
+ assert_ne!(get_build_artifacts(foo_path, file_glob).len(), 0);
+
+ // Assert that build artifacts are destroyed
+ p.cargo("clean -p foo").run();
+ assert_eq!(get_build_artifacts(foo_path, file_glob).len(), 0);
+}
+
+fn get_build_artifacts(path: &PathBuf, file_glob: &str) -> Vec<Result<PathBuf, GlobError>> {
+ let pattern = path.to_str().expect("expected utf-8 path");
+ let pattern = glob::Pattern::escape(pattern);
+
+ let path = PathBuf::from(pattern).join(file_glob);
+ let path = path.to_str().expect("expected utf-8 path");
+ glob::glob(path)
+ .expect("expected glob to run")
+ .into_iter()
+ .collect::<Vec<Result<PathBuf, GlobError>>>()
+}
+
+#[cargo_test]
+fn clean_p_only_cleans_specified_package() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = [
+ "foo",
+ "foo_core",
+ "foo-base",
+ ]
+ "#,
+ )
+ .file("foo/Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("foo/src/lib.rs", "//! foo")
+ .file("foo_core/Cargo.toml", &basic_manifest("foo_core", "0.1.0"))
+ .file("foo_core/src/lib.rs", "//! foo_core")
+ .file("foo-base/Cargo.toml", &basic_manifest("foo-base", "0.1.0"))
+ .file("foo-base/src/lib.rs", "//! foo-base")
+ .build();
+
+ let fingerprint_path = &p.build_dir().join("debug").join(".fingerprint");
+
+ p.cargo("build -p foo -p foo_core -p foo-base").run();
+
+ let mut fingerprint_names = get_fingerprints_without_hashes(fingerprint_path);
+
+ // Artifacts present for all after building
+ assert!(fingerprint_names.iter().any(|e| e == "foo"));
+ let num_foo_core_artifacts = fingerprint_names
+ .iter()
+ .filter(|&e| e == "foo_core")
+ .count();
+ assert_ne!(num_foo_core_artifacts, 0);
+ let num_foo_base_artifacts = fingerprint_names
+ .iter()
+ .filter(|&e| e == "foo-base")
+ .count();
+ assert_ne!(num_foo_base_artifacts, 0);
+
+ p.cargo("clean -p foo").run();
+
+ fingerprint_names = get_fingerprints_without_hashes(fingerprint_path);
+
+ // Cleaning `foo` leaves artifacts for the others
+ assert!(!fingerprint_names.iter().any(|e| e == "foo"));
+ assert_eq!(
+ fingerprint_names
+ .iter()
+ .filter(|&e| e == "foo_core")
+ .count(),
+ num_foo_core_artifacts,
+ );
+ assert_eq!(
+ fingerprint_names
+ .iter()
+ .filter(|&e| e == "foo-base")
+ .count(),
+ num_foo_core_artifacts,
+ );
+}
+
+fn get_fingerprints_without_hashes(fingerprint_path: &Path) -> Vec<String> {
+ std::fs::read_dir(fingerprint_path)
+ .expect("Build dir should be readable")
+ .filter_map(|entry| entry.ok())
+ .map(|entry| {
+ let name = entry.file_name();
+ let name = name
+ .into_string()
+ .expect("fingerprint name should be UTF-8");
+ name.rsplit_once('-')
+ .expect("Name should contain at least one hyphen")
+ .0
+ .to_owned()
+ })
+ .collect()
+}
+
+#[cargo_test]
+fn clean_release() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ a = { path = "a" }
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("a/Cargo.toml", &basic_manifest("a", "0.0.1"))
+ .file("a/src/lib.rs", "")
+ .build();
+
+ p.cargo("build --release").run();
+
+ p.cargo("clean -p foo").run();
+ p.cargo("build --release").with_stdout("").run();
+
+ p.cargo("clean -p foo --release").run();
+ p.cargo("build --release")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] release [optimized] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("build").run();
+
+ p.cargo("clean").arg("--release").run();
+ assert!(p.build_dir().is_dir());
+ assert!(p.build_dir().join("debug").is_dir());
+ assert!(!p.build_dir().join("release").is_dir());
+}
+
+#[cargo_test]
+fn clean_doc() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ a = { path = "a" }
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("a/Cargo.toml", &basic_manifest("a", "0.0.1"))
+ .file("a/src/lib.rs", "")
+ .build();
+
+ p.cargo("doc").run();
+
+ let doc_path = &p.build_dir().join("doc");
+
+ assert!(doc_path.is_dir());
+
+ p.cargo("clean --doc").run();
+
+ assert!(!doc_path.is_dir());
+ assert!(p.build_dir().is_dir());
+}
+
+#[cargo_test]
+fn build_script() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "build.rs",
+ r#"
+ use std::path::PathBuf;
+ use std::env;
+
+ fn main() {
+ let out = PathBuf::from(env::var_os("OUT_DIR").unwrap());
+ if env::var("FIRST").is_ok() {
+ std::fs::File::create(out.join("out")).unwrap();
+ } else {
+ assert!(!out.join("out").exists());
+ }
+ }
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .build();
+
+ p.cargo("build").env("FIRST", "1").run();
+ p.cargo("clean -p foo").run();
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[RUNNING] `rustc [..] build.rs [..]`
+[RUNNING] `[..]build-script-build`
+[RUNNING] `rustc [..] src/main.rs [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn clean_git() {
+ let git = git::new("dep", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("dep", "0.5.0"))
+ .file("src/lib.rs", "")
+ });
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ dep = {{ git = '{}' }}
+ "#,
+ git.url()
+ ),
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build").run();
+ p.cargo("clean -p dep").with_stdout("").run();
+ p.cargo("build").run();
+}
+
+#[cargo_test]
+fn registry() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("bar", "0.1.0").publish();
+
+ p.cargo("build").run();
+ p.cargo("clean -p bar").with_stdout("").run();
+ p.cargo("build").run();
+}
+
+#[cargo_test]
+fn clean_verbose() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies]
+ bar = "0.1"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("bar", "0.1.0").publish();
+
+ p.cargo("build").run();
+ let mut expected = String::from(
+ "\
+[REMOVING] [..]target/debug/.fingerprint/bar[..]
+[REMOVING] [..]target/debug/deps/libbar[..].rlib
+[REMOVING] [..]target/debug/deps/bar-[..].d
+[REMOVING] [..]target/debug/deps/libbar[..].rmeta
+",
+ );
+ if cfg!(target_os = "macos") {
+ // Rust 1.69 has changed so that split-debuginfo=unpacked includes unpacked for rlibs.
+ for obj in p.glob("target/debug/deps/bar-*.o") {
+ expected.push_str(&format!("[REMOVING] [..]{}", obj.unwrap().display()));
+ }
+ }
+ p.cargo("clean -p bar --verbose")
+ .with_stderr_unordered(&expected)
+ .run();
+ p.cargo("build").run();
+}
+
+#[cargo_test]
+fn clean_remove_rlib_rmeta() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+ assert!(p.target_debug_dir().join("libfoo.rlib").exists());
+ let rmeta = p.glob("target/debug/deps/*.rmeta").next().unwrap().unwrap();
+ assert!(rmeta.exists());
+ p.cargo("clean -p foo").run();
+ assert!(!p.target_debug_dir().join("libfoo.rlib").exists());
+ assert!(!rmeta.exists());
+}
+
+#[cargo_test]
+fn package_cleans_all_the_things() {
+ // -p cleans everything
+ // Use dashes everywhere to make sure dash/underscore stuff is handled.
+ for crate_type in &["rlib", "dylib", "cdylib", "staticlib", "proc-macro"] {
+ // Try each crate type individually since the behavior changes when
+ // they are combined.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo-bar"
+ version = "0.1.0"
+
+ [lib]
+ crate-type = ["{}"]
+ "#,
+ crate_type
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("build").run();
+ p.cargo("clean -p foo-bar").run();
+ assert_all_clean(&p.build_dir());
+ }
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo-bar"
+ version = "0.1.0"
+ edition = "2018"
+
+ [lib]
+ crate-type = ["rlib", "dylib", "staticlib"]
+
+ [[example]]
+ name = "foo-ex-rlib"
+ crate-type = ["rlib"]
+ test = true
+
+ [[example]]
+ name = "foo-ex-cdylib"
+ crate-type = ["cdylib"]
+ test = true
+
+ [[example]]
+ name = "foo-ex-bin"
+ test = true
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("src/lib/some-main.rs", "fn main() {}")
+ .file("src/bin/other-main.rs", "fn main() {}")
+ .file("examples/foo-ex-rlib.rs", "")
+ .file("examples/foo-ex-cdylib.rs", "")
+ .file("examples/foo-ex-bin.rs", "fn main() {}")
+ .file("tests/foo-test.rs", "")
+ .file("benches/foo-bench.rs", "")
+ .file("build.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build --all-targets")
+ .env("CARGO_INCREMENTAL", "1")
+ .run();
+ p.cargo("test --all-targets")
+ .env("CARGO_INCREMENTAL", "1")
+ .run();
+ p.cargo("check --all-targets")
+ .env("CARGO_INCREMENTAL", "1")
+ .run();
+ p.cargo("clean -p foo-bar").run();
+ assert_all_clean(&p.build_dir());
+
+ // Try some targets.
+ p.cargo("build --all-targets --target")
+ .arg(rustc_host())
+ .run();
+ p.cargo("clean -p foo-bar --target").arg(rustc_host()).run();
+ assert_all_clean(&p.build_dir());
+}
+
+// Ensures that all files for the package have been deleted.
+#[track_caller]
+fn assert_all_clean(build_dir: &Path) {
+ let walker = walkdir::WalkDir::new(build_dir).into_iter();
+ for entry in walker.filter_entry(|e| {
+ let path = e.path();
+ // This is a known limitation, clean can't differentiate between
+ // the different build scripts from different packages.
+ !(path
+ .file_name()
+ .unwrap()
+ .to_str()
+ .unwrap()
+ .starts_with("build_script_build")
+ && path
+ .parent()
+ .unwrap()
+ .file_name()
+ .unwrap()
+ .to_str()
+ .unwrap()
+ == "incremental")
+ }) {
+ let entry = entry.unwrap();
+ let path = entry.path();
+ if let ".rustc_info.json" | ".cargo-lock" | "CACHEDIR.TAG" =
+ path.file_name().unwrap().to_str().unwrap()
+ {
+ continue;
+ }
+ if path.is_symlink() || path.is_file() {
+ panic!("{:?} was not cleaned", path);
+ }
+ }
+}
+
+#[cargo_test]
+fn clean_spec_multiple() {
+ // clean -p foo where foo matches multiple versions
+ Package::new("bar", "1.0.0").publish();
+ Package::new("bar", "2.0.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar1 = {version="1.0", package="bar"}
+ bar2 = {version="2.0", package="bar"}
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+
+ // Check suggestion for bad pkgid.
+ p.cargo("clean -p baz")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: package ID specification `baz` did not match any packages
+
+<tab>Did you mean `bar`?
+",
+ )
+ .run();
+
+ p.cargo("clean -p bar:1.0.0")
+ .with_stderr(
+ "warning: version qualifier in `-p bar:1.0.0` is ignored, \
+ cleaning all versions of `bar` found",
+ )
+ .run();
+ let mut walker = walkdir::WalkDir::new(p.build_dir())
+ .into_iter()
+ .filter_map(|e| e.ok())
+ .filter(|e| {
+ let n = e.file_name().to_str().unwrap();
+ n.starts_with("bar") || n.starts_with("libbar")
+ });
+ if let Some(e) = walker.next() {
+ panic!("{:?} was not cleaned", e.path());
+ }
+}
+
+#[cargo_test]
+fn clean_spec_reserved() {
+ // Clean when a target (like a test) has a reserved name. In this case,
+ // make sure `clean -p` doesn't delete the reserved directory `build` when
+ // there is a test named `build`.
+ Package::new("bar", "1.0.0")
+ .file("src/lib.rs", "")
+ .file("build.rs", "fn main() {}")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("tests/build.rs", "")
+ .build();
+
+ p.cargo("build --all-targets").run();
+ assert!(p.target_debug_dir().join("build").is_dir());
+ let build_test = p.glob("target/debug/deps/build-*").next().unwrap().unwrap();
+ assert!(build_test.exists());
+ // Tests are never "uplifted".
+ assert!(p.glob("target/debug/build-*").next().is_none());
+
+ p.cargo("clean -p foo").run();
+ // Should not delete this.
+ assert!(p.target_debug_dir().join("build").is_dir());
+
+ // This should not rebuild bar.
+ p.cargo("build -v --all-targets")
+ .with_stderr(
+ "\
+[FRESH] bar v1.0.0
+[COMPILING] foo v0.1.0 [..]
+[RUNNING] `rustc [..]
+[RUNNING] `rustc [..]
+[RUNNING] `rustc [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/collisions.rs b/src/tools/cargo/tests/testsuite/collisions.rs
new file mode 100644
index 000000000..77e05dd9c
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/collisions.rs
@@ -0,0 +1,550 @@
+//! Tests for when multiple artifacts have the same output filename.
+//! See https://github.com/rust-lang/cargo/issues/6313 for more details.
+//! Ideally these should never happen, but I don't think we'll ever be able to
+//! prevent all collisions.
+
+use cargo_test_support::registry::Package;
+use cargo_test_support::{basic_manifest, cross_compile, project};
+use std::env;
+
+#[cargo_test]
+fn collision_dylib() {
+ // Path dependencies don't include metadata hash in filename for dylibs.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["a", "b"]
+ "#,
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "1.0.0"
+
+ [lib]
+ crate-type = ["dylib"]
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "1.0.0"
+
+ [lib]
+ crate-type = ["dylib"]
+ name = "a"
+ "#,
+ )
+ .file("b/src/lib.rs", "")
+ .build();
+
+ // `j=1` is required because on Windows you'll get an error due to
+ // two processes writing to the file at the same time.
+ p.cargo("build -j=1")
+ .with_stderr_contains(&format!("\
+[WARNING] output filename collision.
+The lib target `a` in package `b v1.0.0 ([..]/foo/b)` has the same output filename as the lib target `a` in package `a v1.0.0 ([..]/foo/a)`.
+Colliding filename is: [..]/foo/target/debug/deps/{}a{}
+The targets should have unique names.
+Consider changing their names to be unique or compiling them separately.
+This may become a hard error in the future; see <https://github.com/rust-lang/cargo/issues/6313>.
+", env::consts::DLL_PREFIX, env::consts::DLL_SUFFIX))
+ .run();
+}
+
+#[cargo_test]
+fn collision_example() {
+ // Examples in a workspace can easily collide.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["a", "b"]
+ "#,
+ )
+ .file("a/Cargo.toml", &basic_manifest("a", "1.0.0"))
+ .file("a/examples/ex1.rs", "fn main() {}")
+ .file("b/Cargo.toml", &basic_manifest("b", "1.0.0"))
+ .file("b/examples/ex1.rs", "fn main() {}")
+ .build();
+
+ // `j=1` is required because on Windows you'll get an error due to
+ // two processes writing to the file at the same time.
+ p.cargo("build --examples -j=1")
+ .with_stderr_contains("\
+[WARNING] output filename collision.
+The example target `ex1` in package `b v1.0.0 ([..]/foo/b)` has the same output filename as the example target `ex1` in package `a v1.0.0 ([..]/foo/a)`.
+Colliding filename is: [..]/foo/target/debug/examples/ex1[EXE]
+The targets should have unique names.
+Consider changing their names to be unique or compiling them separately.
+This may become a hard error in the future; see <https://github.com/rust-lang/cargo/issues/6313>.
+")
+ .run();
+}
+
+#[cargo_test]
+// See https://github.com/rust-lang/cargo/issues/7493
+#[cfg_attr(
+ any(target_env = "msvc", target_vendor = "apple"),
+ ignore = "--out-dir and examples are currently broken on MSVC and apple"
+)]
+fn collision_export() {
+ // `--out-dir` combines some things which can cause conflicts.
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo", "1.0.0"))
+ .file("examples/foo.rs", "fn main() {}")
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ // -j1 to avoid issues with two processes writing to the same file at the
+ // same time.
+ p.cargo("build -j1 --out-dir=out -Z unstable-options --bins --examples")
+ .masquerade_as_nightly_cargo(&["out-dir"])
+ .with_stderr_contains("\
+[WARNING] `--out-dir` filename collision.
+The example target `foo` in package `foo v1.0.0 ([..]/foo)` has the same output filename as the bin target `foo` in package `foo v1.0.0 ([..]/foo)`.
+Colliding filename is: [..]/foo/out/foo[EXE]
+The exported filenames should be unique.
+Consider changing their names to be unique or compiling them separately.
+This may become a hard error in the future; see <https://github.com/rust-lang/cargo/issues/6313>.
+")
+ .run();
+}
+
+#[cargo_test]
+fn collision_doc() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ foo2 = { path = "foo2" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "foo2/Cargo.toml",
+ r#"
+ [package]
+ name = "foo2"
+ version = "0.1.0"
+
+ [lib]
+ name = "foo"
+ "#,
+ )
+ .file("foo2/src/lib.rs", "")
+ .build();
+
+ p.cargo("doc -j=1")
+ .with_stderr_contains(
+ "\
+[WARNING] output filename collision.
+The lib target `foo` in package `foo2 v0.1.0 ([..]/foo/foo2)` has the same output \
+filename as the lib target `foo` in package `foo v0.1.0 ([..]/foo)`.
+Colliding filename is: [..]/foo/target/doc/foo/index.html
+The targets should have unique names.
+This is a known bug where multiple crates with the same name use
+the same path; see <https://github.com/rust-lang/cargo/issues/6313>.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn collision_doc_multiple_versions() {
+ // Multiple versions of the same package.
+ Package::new("old-dep", "1.0.0").publish();
+ Package::new("bar", "1.0.0").dep("old-dep", "1.0").publish();
+ // Note that this removes "old-dep". Just checking what happens when there
+ // are orphans.
+ Package::new("bar", "2.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "1.0"
+ bar2 = { package="bar", version="2.0" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ // Should only document bar 2.0, should not document old-dep.
+ p.cargo("doc")
+ .with_stderr_unordered(
+ "\
+[UPDATING] [..]
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v2.0.0 [..]
+[DOWNLOADED] bar v1.0.0 [..]
+[DOWNLOADED] old-dep v1.0.0 [..]
+[CHECKING] old-dep v1.0.0
+[CHECKING] bar v2.0.0
+[CHECKING] bar v1.0.0
+[DOCUMENTING] bar v2.0.0
+[FINISHED] [..]
+[DOCUMENTING] foo v0.1.0 [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn collision_doc_host_target_feature_split() {
+ // Same dependency built twice due to different features.
+ //
+ // foo v0.1.0
+ // ├── common v1.0.0
+ // │ └── common-dep v1.0.0
+ // └── pm v0.1.0 (proc-macro)
+ // └── common v1.0.0
+ // └── common-dep v1.0.0
+ // [build-dependencies]
+ // └── common-dep v1.0.0
+ //
+ // Here `common` and `common-dep` are built twice. `common-dep` has
+ // different features for host versus target.
+ Package::new("common-dep", "1.0.0")
+ .feature("bdep-feat", &[])
+ .file(
+ "src/lib.rs",
+ r#"
+ /// Some doc
+ pub fn f() {}
+
+ /// Another doc
+ #[cfg(feature = "bdep-feat")]
+ pub fn bdep_func() {}
+ "#,
+ )
+ .publish();
+ Package::new("common", "1.0.0")
+ .dep("common-dep", "1.0")
+ .file(
+ "src/lib.rs",
+ r#"
+ /// Some doc
+ pub fn f() {}
+ "#,
+ )
+ .publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ resolver = "2"
+
+ [dependencies]
+ pm = { path = "pm" }
+ common = "1.0"
+
+ [build-dependencies]
+ common-dep = { version = "1.0", features = ["bdep-feat"] }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ /// Some doc
+ pub fn f() {}
+ "#,
+ )
+ .file("build.rs", "fn main() {}")
+ .file(
+ "pm/Cargo.toml",
+ r#"
+ [package]
+ name = "pm"
+ version = "0.1.0"
+ edition = "2018"
+
+ [lib]
+ proc-macro = true
+
+ [dependencies]
+ common = "1.0"
+ "#,
+ )
+ .file(
+ "pm/src/lib.rs",
+ r#"
+ use proc_macro::TokenStream;
+
+ /// Some doc
+ #[proc_macro]
+ pub fn pm(_input: TokenStream) -> TokenStream {
+ "".parse().unwrap()
+ }
+ "#,
+ )
+ .build();
+
+ // No warnings, no duplicates, common and common-dep only documented once.
+ p.cargo("doc")
+ // Cannot check full output due to https://github.com/rust-lang/cargo/issues/9076
+ .with_stderr_does_not_contain("[WARNING][..]")
+ .run();
+
+ assert!(p.build_dir().join("doc/common_dep/fn.f.html").exists());
+ assert!(!p
+ .build_dir()
+ .join("doc/common_dep/fn.bdep_func.html")
+ .exists());
+ assert!(p.build_dir().join("doc/common/fn.f.html").exists());
+ assert!(p.build_dir().join("doc/pm/macro.pm.html").exists());
+ assert!(p.build_dir().join("doc/foo/fn.f.html").exists());
+}
+
+#[cargo_test]
+fn collision_doc_profile_split() {
+ // Same dependency built twice due to different profile settings.
+ Package::new("common", "1.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ pm = { path = "pm" }
+ common = "1.0"
+
+ [profile.dev]
+ opt-level = 2
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "pm/Cargo.toml",
+ r#"
+ [package]
+ name = "pm"
+ version = "0.1.0"
+
+ [dependencies]
+ common = "1.0"
+
+ [lib]
+ proc-macro = true
+ "#,
+ )
+ .file("pm/src/lib.rs", "")
+ .build();
+
+ // Just to verify that common is normally built twice.
+ // This is unordered because in rare cases `pm` may start
+ // building in-between the two `common`.
+ p.cargo("build -v")
+ .with_stderr_unordered(
+ "\
+[UPDATING] [..]
+[DOWNLOADING] crates ...
+[DOWNLOADED] common v1.0.0 [..]
+[COMPILING] common v1.0.0
+[RUNNING] `rustc --crate-name common [..]
+[RUNNING] `rustc --crate-name common [..]
+[COMPILING] pm v0.1.0 [..]
+[RUNNING] `rustc --crate-name pm [..]
+[COMPILING] foo v0.1.0 [..]
+[RUNNING] `rustc --crate-name foo [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ // Should only document common once, no warnings.
+ p.cargo("doc")
+ .with_stderr_unordered(
+ "\
+[CHECKING] common v1.0.0
+[DOCUMENTING] common v1.0.0
+[DOCUMENTING] pm v0.1.0 [..]
+[DOCUMENTING] foo v0.1.0 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn collision_doc_sources() {
+ // Different sources with the same package.
+ Package::new("bar", "1.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "1.0"
+ bar2 = { path = "bar", package = "bar" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "1.0.0"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("doc -j=1")
+ .with_stderr_unordered(
+ "\
+[UPDATING] [..]
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v1.0.0 [..]
+[WARNING] output filename collision.
+The lib target `bar` in package `bar v1.0.0` has the same output filename as \
+the lib target `bar` in package `bar v1.0.0 ([..]/foo/bar)`.
+Colliding filename is: [..]/foo/target/doc/bar/index.html
+The targets should have unique names.
+This is a known bug where multiple crates with the same name use
+the same path; see <https://github.com/rust-lang/cargo/issues/6313>.
+[CHECKING] bar v1.0.0 [..]
+[DOCUMENTING] bar v1.0.0 [..]
+[DOCUMENTING] bar v1.0.0
+[CHECKING] bar v1.0.0
+[DOCUMENTING] foo v0.1.0 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn collision_doc_target() {
+ // collision in doc with --target, doesn't fail due to orphans
+ if cross_compile::disabled() {
+ return;
+ }
+
+ Package::new("orphaned", "1.0.0").publish();
+ Package::new("bar", "1.0.0")
+ .dep("orphaned", "1.0")
+ .publish();
+ Package::new("bar", "2.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar2 = { version = "2.0", package="bar" }
+ bar = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("doc --target")
+ .arg(cross_compile::alternate())
+ .with_stderr_unordered(
+ "\
+[UPDATING] [..]
+[DOWNLOADING] crates ...
+[DOWNLOADED] orphaned v1.0.0 [..]
+[DOWNLOADED] bar v2.0.0 [..]
+[DOWNLOADED] bar v1.0.0 [..]
+[CHECKING] orphaned v1.0.0
+[DOCUMENTING] bar v2.0.0
+[CHECKING] bar v2.0.0
+[CHECKING] bar v1.0.0
+[DOCUMENTING] foo v0.1.0 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn collision_with_root() {
+ // Check for a doc collision between a root package and a dependency.
+ // In this case, `foo-macro` comes from both the workspace and crates.io.
+ // This checks that the duplicate correction code doesn't choke on this
+ // by removing the root unit.
+ Package::new("foo-macro", "1.0.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["abc", "foo-macro"]
+ "#,
+ )
+ .file(
+ "abc/Cargo.toml",
+ r#"
+ [package]
+ name = "abc"
+ version = "1.0.0"
+
+ [dependencies]
+ foo-macro = "1.0"
+ "#,
+ )
+ .file("abc/src/lib.rs", "")
+ .file(
+ "foo-macro/Cargo.toml",
+ r#"
+ [package]
+ name = "foo-macro"
+ version = "1.0.0"
+
+ [lib]
+ proc-macro = true
+
+ [dependencies]
+ abc = {path="../abc"}
+ "#,
+ )
+ .file("foo-macro/src/lib.rs", "")
+ .build();
+
+ p.cargo("doc -j=1")
+ .with_stderr_unordered("\
+[UPDATING] [..]
+[DOWNLOADING] crates ...
+[DOWNLOADED] foo-macro v1.0.0 [..]
+warning: output filename collision.
+The lib target `foo-macro` in package `foo-macro v1.0.0` has the same output filename as the lib target `foo-macro` in package `foo-macro v1.0.0 [..]`.
+Colliding filename is: [CWD]/target/doc/foo_macro/index.html
+The targets should have unique names.
+This is a known bug where multiple crates with the same name use
+the same path; see <https://github.com/rust-lang/cargo/issues/6313>.
+[CHECKING] foo-macro v1.0.0
+[DOCUMENTING] foo-macro v1.0.0
+[CHECKING] abc v1.0.0 [..]
+[DOCUMENTING] foo-macro v1.0.0 [..]
+[DOCUMENTING] abc v1.0.0 [..]
+[FINISHED] [..]
+")
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/concurrent.rs b/src/tools/cargo/tests/testsuite/concurrent.rs
new file mode 100644
index 000000000..fe4ecfc42
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/concurrent.rs
@@ -0,0 +1,507 @@
+//! Tests for running multiple `cargo` processes at the same time.
+
+use std::fs;
+use std::net::TcpListener;
+use std::process::Stdio;
+use std::sync::mpsc::channel;
+use std::thread;
+use std::{env, str};
+
+use cargo_test_support::cargo_process;
+use cargo_test_support::git;
+use cargo_test_support::install::{assert_has_installed_exe, cargo_home};
+use cargo_test_support::registry::Package;
+use cargo_test_support::{basic_manifest, execs, project, slow_cpu_multiplier};
+
+fn pkg(name: &str, vers: &str) {
+ Package::new(name, vers)
+ .file("src/main.rs", "fn main() {{}}")
+ .publish();
+}
+
+#[cargo_test]
+fn multiple_installs() {
+ let p = project()
+ .no_manifest()
+ .file("a/Cargo.toml", &basic_manifest("foo", "0.0.0"))
+ .file("a/src/main.rs", "fn main() {}")
+ .file("b/Cargo.toml", &basic_manifest("bar", "0.0.0"))
+ .file("b/src/main.rs", "fn main() {}");
+ let p = p.build();
+
+ let mut a = p.cargo("install").cwd("a").build_command();
+ let mut b = p.cargo("install").cwd("b").build_command();
+
+ a.stdout(Stdio::piped()).stderr(Stdio::piped());
+ b.stdout(Stdio::piped()).stderr(Stdio::piped());
+
+ let a = a.spawn().unwrap();
+ let b = b.spawn().unwrap();
+ let a = thread::spawn(move || a.wait_with_output().unwrap());
+ let b = b.wait_with_output().unwrap();
+ let a = a.join().unwrap();
+
+ execs().run_output(&a);
+ execs().run_output(&b);
+
+ assert_has_installed_exe(cargo_home(), "foo");
+ assert_has_installed_exe(cargo_home(), "bar");
+}
+
+#[cargo_test]
+fn concurrent_installs() {
+ const LOCKED_BUILD: &str = "waiting for file lock on build directory";
+
+ pkg("foo", "0.0.1");
+ pkg("bar", "0.0.1");
+
+ let mut a = cargo_process("install foo").build_command();
+ let mut b = cargo_process("install bar").build_command();
+
+ a.stdout(Stdio::piped()).stderr(Stdio::piped());
+ b.stdout(Stdio::piped()).stderr(Stdio::piped());
+
+ let a = a.spawn().unwrap();
+ let b = b.spawn().unwrap();
+ let a = thread::spawn(move || a.wait_with_output().unwrap());
+ let b = b.wait_with_output().unwrap();
+ let a = a.join().unwrap();
+
+ assert!(!str::from_utf8(&a.stderr).unwrap().contains(LOCKED_BUILD));
+ assert!(!str::from_utf8(&b.stderr).unwrap().contains(LOCKED_BUILD));
+
+ execs().run_output(&a);
+ execs().run_output(&b);
+
+ assert_has_installed_exe(cargo_home(), "foo");
+ assert_has_installed_exe(cargo_home(), "bar");
+}
+
+#[cargo_test]
+fn one_install_should_be_bad() {
+ let p = project()
+ .no_manifest()
+ .file("a/Cargo.toml", &basic_manifest("foo", "0.0.0"))
+ .file("a/src/main.rs", "fn main() {}")
+ .file("b/Cargo.toml", &basic_manifest("foo", "0.0.0"))
+ .file("b/src/main.rs", "fn main() {}");
+ let p = p.build();
+
+ let mut a = p.cargo("install").cwd("a").build_command();
+ let mut b = p.cargo("install").cwd("b").build_command();
+
+ a.stdout(Stdio::piped()).stderr(Stdio::piped());
+ b.stdout(Stdio::piped()).stderr(Stdio::piped());
+
+ let a = a.spawn().unwrap();
+ let b = b.spawn().unwrap();
+ let a = thread::spawn(move || a.wait_with_output().unwrap());
+ let b = b.wait_with_output().unwrap();
+ let a = a.join().unwrap();
+
+ execs().run_output(&a);
+ execs().run_output(&b);
+
+ assert_has_installed_exe(cargo_home(), "foo");
+}
+
+#[cargo_test]
+fn multiple_registry_fetches() {
+ let mut pkg = Package::new("bar", "1.0.2");
+ for i in 0..10 {
+ let name = format!("foo{}", i);
+ Package::new(&name, "1.0.0").publish();
+ pkg.dep(&name, "*");
+ }
+ pkg.publish();
+
+ let p = project()
+ .no_manifest()
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.0"
+
+ [dependencies]
+ bar = "*"
+ "#,
+ )
+ .file("a/src/main.rs", "fn main() {}")
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ authors = []
+ version = "0.0.0"
+
+ [dependencies]
+ bar = "*"
+ "#,
+ )
+ .file("b/src/main.rs", "fn main() {}");
+ let p = p.build();
+
+ let mut a = p.cargo("build").cwd("a").build_command();
+ let mut b = p.cargo("build").cwd("b").build_command();
+
+ a.stdout(Stdio::piped()).stderr(Stdio::piped());
+ b.stdout(Stdio::piped()).stderr(Stdio::piped());
+
+ let a = a.spawn().unwrap();
+ let b = b.spawn().unwrap();
+ let a = thread::spawn(move || a.wait_with_output().unwrap());
+ let b = b.wait_with_output().unwrap();
+ let a = a.join().unwrap();
+
+ execs().run_output(&a);
+ execs().run_output(&b);
+
+ let suffix = env::consts::EXE_SUFFIX;
+ assert!(p
+ .root()
+ .join("a/target/debug")
+ .join(format!("foo{}", suffix))
+ .is_file());
+ assert!(p
+ .root()
+ .join("b/target/debug")
+ .join(format!("bar{}", suffix))
+ .is_file());
+}
+
+#[cargo_test]
+fn git_same_repo_different_tags() {
+ let a = git::new("dep", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("dep", "0.5.0"))
+ .file("src/lib.rs", "pub fn tag1() {}")
+ });
+
+ let repo = git2::Repository::open(&a.root()).unwrap();
+ git::tag(&repo, "tag1");
+
+ a.change_file("src/lib.rs", "pub fn tag2() {}");
+ git::add(&repo);
+ git::commit(&repo);
+ git::tag(&repo, "tag2");
+
+ let p = project()
+ .no_manifest()
+ .file(
+ "a/Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.0"
+
+ [dependencies]
+ dep = {{ git = '{}', tag = 'tag1' }}
+ "#,
+ a.url()
+ ),
+ )
+ .file(
+ "a/src/main.rs",
+ "extern crate dep; fn main() { dep::tag1(); }",
+ )
+ .file(
+ "b/Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "bar"
+ authors = []
+ version = "0.0.0"
+
+ [dependencies]
+ dep = {{ git = '{}', tag = 'tag2' }}
+ "#,
+ a.url()
+ ),
+ )
+ .file(
+ "b/src/main.rs",
+ "extern crate dep; fn main() { dep::tag2(); }",
+ );
+ let p = p.build();
+
+ let mut a = p.cargo("build -v").cwd("a").build_command();
+ let mut b = p.cargo("build -v").cwd("b").build_command();
+
+ a.stdout(Stdio::piped()).stderr(Stdio::piped());
+ b.stdout(Stdio::piped()).stderr(Stdio::piped());
+
+ let a = a.spawn().unwrap();
+ let b = b.spawn().unwrap();
+ let a = thread::spawn(move || a.wait_with_output().unwrap());
+ let b = b.wait_with_output().unwrap();
+ let a = a.join().unwrap();
+
+ execs().run_output(&a);
+ execs().run_output(&b);
+}
+
+#[cargo_test]
+fn git_same_branch_different_revs() {
+ let a = git::new("dep", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("dep", "0.5.0"))
+ .file("src/lib.rs", "pub fn f1() {}")
+ });
+
+ let p = project()
+ .no_manifest()
+ .file(
+ "a/Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.0"
+
+ [dependencies]
+ dep = {{ git = '{}' }}
+ "#,
+ a.url()
+ ),
+ )
+ .file(
+ "a/src/main.rs",
+ "extern crate dep; fn main() { dep::f1(); }",
+ )
+ .file(
+ "b/Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "bar"
+ authors = []
+ version = "0.0.0"
+
+ [dependencies]
+ dep = {{ git = '{}' }}
+ "#,
+ a.url()
+ ),
+ )
+ .file(
+ "b/src/main.rs",
+ "extern crate dep; fn main() { dep::f2(); }",
+ );
+ let p = p.build();
+
+ // Generate a Cargo.lock pointing at the current rev, then clear out the
+ // target directory
+ p.cargo("build").cwd("a").run();
+ fs::remove_dir_all(p.root().join("a/target")).unwrap();
+
+ // Make a new commit on the master branch
+ let repo = git2::Repository::open(&a.root()).unwrap();
+ a.change_file("src/lib.rs", "pub fn f2() {}");
+ git::add(&repo);
+ git::commit(&repo);
+
+ // Now run both builds in parallel. The build of `b` should pick up the
+ // newest commit while the build of `a` should use the locked old commit.
+ let mut a = p.cargo("build").cwd("a").build_command();
+ let mut b = p.cargo("build").cwd("b").build_command();
+
+ a.stdout(Stdio::piped()).stderr(Stdio::piped());
+ b.stdout(Stdio::piped()).stderr(Stdio::piped());
+
+ let a = a.spawn().unwrap();
+ let b = b.spawn().unwrap();
+ let a = thread::spawn(move || a.wait_with_output().unwrap());
+ let b = b.wait_with_output().unwrap();
+ let a = a.join().unwrap();
+
+ execs().run_output(&a);
+ execs().run_output(&b);
+}
+
+#[cargo_test]
+fn same_project() {
+ let p = project()
+ .file("src/main.rs", "fn main() {}")
+ .file("src/lib.rs", "");
+ let p = p.build();
+
+ let mut a = p.cargo("build").build_command();
+ let mut b = p.cargo("build").build_command();
+
+ a.stdout(Stdio::piped()).stderr(Stdio::piped());
+ b.stdout(Stdio::piped()).stderr(Stdio::piped());
+
+ let a = a.spawn().unwrap();
+ let b = b.spawn().unwrap();
+ let a = thread::spawn(move || a.wait_with_output().unwrap());
+ let b = b.wait_with_output().unwrap();
+ let a = a.join().unwrap();
+
+ execs().run_output(&a);
+ execs().run_output(&b);
+}
+
+// Make sure that if Cargo dies while holding a lock that it's released and the
+// next Cargo to come in will take over cleanly.
+#[cargo_test]
+fn killing_cargo_releases_the_lock() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.0"
+ build = "build.rs"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "build.rs",
+ r#"
+ use std::net::TcpStream;
+
+ fn main() {
+ if std::env::var("A").is_ok() {
+ TcpStream::connect(&std::env::var("ADDR").unwrap()[..])
+ .unwrap();
+ std::thread::sleep(std::time::Duration::new(10, 0));
+ }
+ }
+ "#,
+ );
+ let p = p.build();
+
+ // Our build script will connect to our local TCP socket to inform us that
+ // it's started and that's how we know that `a` will have the lock
+ // when we kill it.
+ let l = TcpListener::bind("127.0.0.1:0").unwrap();
+ let mut a = p.cargo("build").build_command();
+ let mut b = p.cargo("build").build_command();
+ a.stdout(Stdio::piped()).stderr(Stdio::piped());
+ b.stdout(Stdio::piped()).stderr(Stdio::piped());
+ a.env("ADDR", l.local_addr().unwrap().to_string())
+ .env("A", "a");
+ b.env("ADDR", l.local_addr().unwrap().to_string())
+ .env_remove("A");
+
+ // Spawn `a`, wait for it to get to the build script (at which point the
+ // lock is held), then kill it.
+ let mut a = a.spawn().unwrap();
+ l.accept().unwrap();
+ a.kill().unwrap();
+
+ // Spawn `b`, then just finish the output of a/b the same way the above
+ // tests does.
+ let b = b.spawn().unwrap();
+ let a = thread::spawn(move || a.wait_with_output().unwrap());
+ let b = b.wait_with_output().unwrap();
+ let a = a.join().unwrap();
+
+ // We killed `a`, so it shouldn't succeed, but `b` should have succeeded.
+ assert!(!a.status.success());
+ execs().run_output(&b);
+}
+
+#[cargo_test]
+fn debug_release_ok() {
+ let p = project().file("src/main.rs", "fn main() {}");
+ let p = p.build();
+
+ p.cargo("build").run();
+ fs::remove_dir_all(p.root().join("target")).unwrap();
+
+ let mut a = p.cargo("build").build_command();
+ let mut b = p.cargo("build --release").build_command();
+ a.stdout(Stdio::piped()).stderr(Stdio::piped());
+ b.stdout(Stdio::piped()).stderr(Stdio::piped());
+ let a = a.spawn().unwrap();
+ let b = b.spawn().unwrap();
+ let a = thread::spawn(move || a.wait_with_output().unwrap());
+ let b = b.wait_with_output().unwrap();
+ let a = a.join().unwrap();
+
+ execs()
+ .with_stderr_contains(
+ "\
+[COMPILING] foo v0.0.1 [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run_output(&a);
+ execs()
+ .with_stderr_contains(
+ "\
+[COMPILING] foo v0.0.1 [..]
+[FINISHED] release [optimized] target(s) in [..]
+",
+ )
+ .run_output(&b);
+}
+
+#[cargo_test]
+fn no_deadlock_with_git_dependencies() {
+ let dep1 = git::new("dep1", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("dep1", "0.5.0"))
+ .file("src/lib.rs", "")
+ });
+
+ let dep2 = git::new("dep2", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("dep2", "0.5.0"))
+ .file("src/lib.rs", "")
+ });
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.0"
+
+ [dependencies]
+ dep1 = {{ git = '{}' }}
+ dep2 = {{ git = '{}' }}
+ "#,
+ dep1.url(),
+ dep2.url()
+ ),
+ )
+ .file("src/main.rs", "fn main() { }");
+ let p = p.build();
+
+ let n_concurrent_builds = 5;
+
+ let (tx, rx) = channel();
+ for _ in 0..n_concurrent_builds {
+ let cmd = p
+ .cargo("build")
+ .build_command()
+ .stdout(Stdio::piped())
+ .stderr(Stdio::piped())
+ .spawn();
+ let tx = tx.clone();
+ thread::spawn(move || {
+ let result = cmd.unwrap().wait_with_output().unwrap();
+ tx.send(result).unwrap()
+ });
+ }
+
+ for _ in 0..n_concurrent_builds {
+ let result = rx.recv_timeout(slow_cpu_multiplier(30)).expect("Deadlock!");
+ execs().run_output(&result);
+ }
+}
diff --git a/src/tools/cargo/tests/testsuite/config.rs b/src/tools/cargo/tests/testsuite/config.rs
new file mode 100644
index 000000000..92e1f4264
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/config.rs
@@ -0,0 +1,1596 @@
+//! Tests for config settings.
+
+use cargo::core::{PackageIdSpec, Shell};
+use cargo::util::config::{self, Config, Definition, SslVersionConfig, StringList};
+use cargo::util::interning::InternedString;
+use cargo::util::toml::{self as cargo_toml, VecStringOrBool as VSOB};
+use cargo::CargoResult;
+use cargo_test_support::compare;
+use cargo_test_support::{panic_error, paths, project, symlink_supported, t};
+use serde::Deserialize;
+use std::borrow::Borrow;
+use std::collections::{BTreeMap, HashMap};
+use std::fs;
+use std::io;
+use std::os;
+use std::path::{Path, PathBuf};
+
+/// Helper for constructing a `Config` object.
+pub struct ConfigBuilder {
+ env: HashMap<String, String>,
+ unstable: Vec<String>,
+ config_args: Vec<String>,
+ cwd: Option<PathBuf>,
+ enable_nightly_features: bool,
+}
+
+impl ConfigBuilder {
+ pub fn new() -> ConfigBuilder {
+ ConfigBuilder {
+ env: HashMap::new(),
+ unstable: Vec::new(),
+ config_args: Vec::new(),
+ cwd: None,
+ enable_nightly_features: false,
+ }
+ }
+
+ /// Passes a `-Z` flag.
+ pub fn unstable_flag(&mut self, s: impl Into<String>) -> &mut Self {
+ self.unstable.push(s.into());
+ self
+ }
+
+ /// Sets an environment variable.
+ pub fn env(&mut self, key: impl Into<String>, val: impl Into<String>) -> &mut Self {
+ self.env.insert(key.into(), val.into());
+ self
+ }
+
+ /// Unconditionally enable nightly features, even on stable channels.
+ pub fn nightly_features_allowed(&mut self, allowed: bool) -> &mut Self {
+ self.enable_nightly_features = allowed;
+ self
+ }
+
+ /// Passes a `--config` flag.
+ pub fn config_arg(&mut self, arg: impl Into<String>) -> &mut Self {
+ self.config_args.push(arg.into());
+ self
+ }
+
+ /// Sets the current working directory where config files will be loaded.
+ pub fn cwd(&mut self, path: impl AsRef<Path>) -> &mut Self {
+ self.cwd = Some(paths::root().join(path.as_ref()));
+ self
+ }
+
+ /// Creates the `Config`.
+ pub fn build(&self) -> Config {
+ self.build_err().unwrap()
+ }
+
+ /// Creates the `Config`, returning a Result.
+ pub fn build_err(&self) -> CargoResult<Config> {
+ let output = Box::new(fs::File::create(paths::root().join("shell.out")).unwrap());
+ let shell = Shell::from_write(output);
+ let cwd = self.cwd.clone().unwrap_or_else(|| paths::root());
+ let homedir = paths::home();
+ let mut config = Config::new(shell, cwd, homedir);
+ config.nightly_features_allowed = self.enable_nightly_features || !self.unstable.is_empty();
+ config.set_env(self.env.clone());
+ config.set_search_stop_path(paths::root());
+ config.configure(
+ 0,
+ false,
+ None,
+ false,
+ false,
+ false,
+ &None,
+ &self.unstable,
+ &self.config_args,
+ )?;
+ Ok(config)
+ }
+}
+
+fn new_config() -> Config {
+ ConfigBuilder::new().build()
+}
+
+/// Read the output from Config.
+pub fn read_output(config: Config) -> String {
+ drop(config); // Paranoid about flushing the file.
+ let path = paths::root().join("shell.out");
+ fs::read_to_string(path).unwrap()
+}
+
+#[cargo_test]
+fn read_env_vars_for_config() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.0"
+ build = "build.rs"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ use std::env;
+ fn main() {
+ assert_eq!(env::var("NUM_JOBS").unwrap(), "100");
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("check").env("CARGO_BUILD_JOBS", "100").run();
+}
+
+pub fn write_config(config: &str) {
+ write_config_at(paths::root().join(".cargo/config"), config);
+}
+
+pub fn write_config_at(path: impl AsRef<Path>, contents: &str) {
+ let path = paths::root().join(path.as_ref());
+ fs::create_dir_all(path.parent().unwrap()).unwrap();
+ fs::write(path, contents).unwrap();
+}
+
+pub fn write_config_toml(config: &str) {
+ write_config_at(paths::root().join(".cargo/config.toml"), config);
+}
+
+#[cfg(unix)]
+fn symlink_file(target: &Path, link: &Path) -> io::Result<()> {
+ os::unix::fs::symlink(target, link)
+}
+
+#[cfg(windows)]
+fn symlink_file(target: &Path, link: &Path) -> io::Result<()> {
+ os::windows::fs::symlink_file(target, link)
+}
+
+fn symlink_config_to_config_toml() {
+ let toml_path = paths::root().join(".cargo/config.toml");
+ let symlink_path = paths::root().join(".cargo/config");
+ t!(symlink_file(&toml_path, &symlink_path));
+}
+
+#[track_caller]
+pub fn assert_error<E: Borrow<anyhow::Error>>(error: E, msgs: &str) {
+ let causes = error
+ .borrow()
+ .chain()
+ .enumerate()
+ .map(|(i, e)| {
+ if i == 0 {
+ e.to_string()
+ } else {
+ format!("Caused by:\n {}", e)
+ }
+ })
+ .collect::<Vec<_>>()
+ .join("\n\n");
+ assert_match(msgs, &causes);
+}
+
+#[track_caller]
+pub fn assert_match(expected: &str, actual: &str) {
+ if let Err(e) = compare::match_exact(expected, actual, "output", "", None) {
+ panic_error("", e);
+ }
+}
+
+#[cargo_test]
+fn get_config() {
+ write_config(
+ "\
+[S]
+f1 = 123
+",
+ );
+
+ let config = new_config();
+
+ #[derive(Debug, Deserialize, Eq, PartialEq)]
+ struct S {
+ f1: Option<i64>,
+ }
+ let s: S = config.get("S").unwrap();
+ assert_eq!(s, S { f1: Some(123) });
+ let config = ConfigBuilder::new().env("CARGO_S_F1", "456").build();
+ let s: S = config.get("S").unwrap();
+ assert_eq!(s, S { f1: Some(456) });
+}
+
+#[cfg(windows)]
+#[cargo_test]
+fn environment_variable_casing() {
+ // Issue #11814: Environment variable names are case-insensitive on Windows.
+ let config = ConfigBuilder::new()
+ .env("Path", "abc")
+ .env("Two-Words", "abc")
+ .env("two_words", "def")
+ .build();
+
+ let var = config.get_env("PATH").unwrap();
+ assert_eq!(var, String::from("abc"));
+
+ let var = config.get_env("path").unwrap();
+ assert_eq!(var, String::from("abc"));
+
+ let var = config.get_env("TWO-WORDS").unwrap();
+ assert_eq!(var, String::from("abc"));
+
+ // Make sure that we can still distinguish between dashes and underscores
+ // in variable names.
+ let var = config.get_env("Two_Words").unwrap();
+ assert_eq!(var, String::from("def"));
+}
+
+#[cargo_test]
+fn config_works_with_extension() {
+ write_config_toml(
+ "\
+[foo]
+f1 = 1
+",
+ );
+
+ let config = new_config();
+
+ assert_eq!(config.get::<Option<i32>>("foo.f1").unwrap(), Some(1));
+}
+
+#[cargo_test]
+fn config_ambiguous_filename_symlink_doesnt_warn() {
+ // Windows requires special permissions to create symlinks.
+ // If we don't have permission, just skip this test.
+ if !symlink_supported() {
+ return;
+ };
+
+ write_config_toml(
+ "\
+[foo]
+f1 = 1
+",
+ );
+
+ symlink_config_to_config_toml();
+
+ let config = new_config();
+
+ assert_eq!(config.get::<Option<i32>>("foo.f1").unwrap(), Some(1));
+
+ // It should NOT have warned for the symlink.
+ let output = read_output(config);
+ assert_eq!(output, "");
+}
+
+#[cargo_test]
+fn config_ambiguous_filename() {
+ write_config(
+ "\
+[foo]
+f1 = 1
+",
+ );
+
+ write_config_toml(
+ "\
+[foo]
+f1 = 2
+",
+ );
+
+ let config = new_config();
+
+ // It should use the value from the one without the extension for
+ // backwards compatibility.
+ assert_eq!(config.get::<Option<i32>>("foo.f1").unwrap(), Some(1));
+
+ // But it also should have warned.
+ let output = read_output(config);
+ let expected = "\
+warning: Both `[..]/.cargo/config` and `[..]/.cargo/config.toml` exist. Using `[..]/.cargo/config`
+";
+ assert_match(expected, &output);
+}
+
+#[cargo_test]
+fn config_unused_fields() {
+ write_config(
+ "\
+[S]
+unused = 456
+",
+ );
+
+ let config = ConfigBuilder::new()
+ .env("CARGO_S_UNUSED2", "1")
+ .env("CARGO_S2_UNUSED", "2")
+ .build();
+
+ #[derive(Debug, Deserialize, Eq, PartialEq)]
+ struct S {
+ f1: Option<i64>,
+ }
+ // This prints a warning (verified below).
+ let s: S = config.get("S").unwrap();
+ assert_eq!(s, S { f1: None });
+ // This does not print anything, we cannot easily/reliably warn for
+ // environment variables.
+ let s: S = config.get("S2").unwrap();
+ assert_eq!(s, S { f1: None });
+
+ // Verify the warnings.
+ let output = read_output(config);
+ let expected = "\
+warning: unused config key `S.unused` in `[..]/.cargo/config`
+";
+ assert_match(expected, &output);
+}
+
+#[cargo_test]
+fn config_load_toml_profile() {
+ write_config(
+ "\
+[profile.dev]
+opt-level = 's'
+lto = true
+codegen-units=4
+debug = true
+debug-assertions = true
+rpath = true
+panic = 'abort'
+overflow-checks = true
+incremental = true
+
+[profile.dev.build-override]
+opt-level = 1
+
+[profile.dev.package.bar]
+codegen-units = 9
+
+[profile.no-lto]
+inherits = 'dev'
+dir-name = 'without-lto'
+lto = false
+",
+ );
+
+ let config = ConfigBuilder::new()
+ .unstable_flag("advanced-env")
+ .env("CARGO_PROFILE_DEV_CODEGEN_UNITS", "5")
+ .env("CARGO_PROFILE_DEV_BUILD_OVERRIDE_CODEGEN_UNITS", "11")
+ .env("CARGO_PROFILE_DEV_PACKAGE_env_CODEGEN_UNITS", "13")
+ .env("CARGO_PROFILE_DEV_PACKAGE_bar_OPT_LEVEL", "2")
+ .build();
+
+ // TODO: don't use actual `tomlprofile`.
+ let p: cargo_toml::TomlProfile = config.get("profile.dev").unwrap();
+ let mut packages = BTreeMap::new();
+ let key =
+ cargo_toml::ProfilePackageSpec::Spec(::cargo::core::PackageIdSpec::parse("bar").unwrap());
+ let o_profile = cargo_toml::TomlProfile {
+ opt_level: Some(cargo_toml::TomlOptLevel("2".to_string())),
+ codegen_units: Some(9),
+ ..Default::default()
+ };
+ packages.insert(key, o_profile);
+ let key =
+ cargo_toml::ProfilePackageSpec::Spec(::cargo::core::PackageIdSpec::parse("env").unwrap());
+ let o_profile = cargo_toml::TomlProfile {
+ codegen_units: Some(13),
+ ..Default::default()
+ };
+ packages.insert(key, o_profile);
+
+ assert_eq!(
+ p,
+ cargo_toml::TomlProfile {
+ opt_level: Some(cargo_toml::TomlOptLevel("s".to_string())),
+ lto: Some(cargo_toml::StringOrBool::Bool(true)),
+ codegen_units: Some(5),
+ debug: Some(cargo_toml::U32OrBool::Bool(true)),
+ debug_assertions: Some(true),
+ rpath: Some(true),
+ panic: Some("abort".to_string()),
+ overflow_checks: Some(true),
+ incremental: Some(true),
+ package: Some(packages),
+ build_override: Some(Box::new(cargo_toml::TomlProfile {
+ opt_level: Some(cargo_toml::TomlOptLevel("1".to_string())),
+ codegen_units: Some(11),
+ ..Default::default()
+ })),
+ ..Default::default()
+ }
+ );
+
+ let p: cargo_toml::TomlProfile = config.get("profile.no-lto").unwrap();
+ assert_eq!(
+ p,
+ cargo_toml::TomlProfile {
+ lto: Some(cargo_toml::StringOrBool::Bool(false)),
+ dir_name: Some(InternedString::new("without-lto")),
+ inherits: Some(InternedString::new("dev")),
+ ..Default::default()
+ }
+ );
+}
+
+#[cargo_test]
+fn profile_env_var_prefix() {
+ // Check for a bug with collision on DEBUG vs DEBUG_ASSERTIONS.
+ let config = ConfigBuilder::new()
+ .env("CARGO_PROFILE_DEV_DEBUG_ASSERTIONS", "false")
+ .build();
+ let p: cargo_toml::TomlProfile = config.get("profile.dev").unwrap();
+ assert_eq!(p.debug_assertions, Some(false));
+ assert_eq!(p.debug, None);
+
+ let config = ConfigBuilder::new()
+ .env("CARGO_PROFILE_DEV_DEBUG", "1")
+ .build();
+ let p: cargo_toml::TomlProfile = config.get("profile.dev").unwrap();
+ assert_eq!(p.debug_assertions, None);
+ assert_eq!(p.debug, Some(cargo_toml::U32OrBool::U32(1)));
+
+ let config = ConfigBuilder::new()
+ .env("CARGO_PROFILE_DEV_DEBUG_ASSERTIONS", "false")
+ .env("CARGO_PROFILE_DEV_DEBUG", "1")
+ .build();
+ let p: cargo_toml::TomlProfile = config.get("profile.dev").unwrap();
+ assert_eq!(p.debug_assertions, Some(false));
+ assert_eq!(p.debug, Some(cargo_toml::U32OrBool::U32(1)));
+}
+
+#[cargo_test]
+fn config_deserialize_any() {
+ // Some tests to exercise deserialize_any for deserializers that need to
+ // be told the format.
+ write_config(
+ "\
+a = true
+b = ['b']
+c = ['c']
+",
+ );
+
+ // advanced-env
+ let config = ConfigBuilder::new()
+ .unstable_flag("advanced-env")
+ .env("CARGO_ENVB", "false")
+ .env("CARGO_C", "['d']")
+ .env("CARGO_ENVL", "['a', 'b']")
+ .build();
+ assert_eq!(config.get::<VSOB>("a").unwrap(), VSOB::Bool(true));
+ assert_eq!(
+ config.get::<VSOB>("b").unwrap(),
+ VSOB::VecString(vec!["b".to_string()])
+ );
+ assert_eq!(
+ config.get::<VSOB>("c").unwrap(),
+ VSOB::VecString(vec!["c".to_string(), "d".to_string()])
+ );
+ assert_eq!(config.get::<VSOB>("envb").unwrap(), VSOB::Bool(false));
+ assert_eq!(
+ config.get::<VSOB>("envl").unwrap(),
+ VSOB::VecString(vec!["a".to_string(), "b".to_string()])
+ );
+
+ // Demonstrate where merging logic isn't very smart. This could be improved.
+ let config = ConfigBuilder::new().env("CARGO_A", "x y").build();
+ assert_error(
+ config.get::<VSOB>("a").unwrap_err(),
+ "\
+error in environment variable `CARGO_A`: could not load config key `a`
+
+Caused by:
+ invalid type: string \"x y\", expected a boolean or vector of strings",
+ );
+
+ // Normal env.
+ let config = ConfigBuilder::new()
+ .unstable_flag("advanced-env")
+ .env("CARGO_B", "d e")
+ .env("CARGO_C", "f g")
+ .build();
+ assert_eq!(
+ config.get::<VSOB>("b").unwrap(),
+ VSOB::VecString(vec!["b".to_string(), "d".to_string(), "e".to_string()])
+ );
+ assert_eq!(
+ config.get::<VSOB>("c").unwrap(),
+ VSOB::VecString(vec!["c".to_string(), "f".to_string(), "g".to_string()])
+ );
+
+ // config-cli
+ // This test demonstrates that ConfigValue::merge isn't very smart.
+ // It would be nice if it was smarter.
+ let config = ConfigBuilder::new().config_arg("a = ['a']").build_err();
+ assert_error(
+ config.unwrap_err(),
+ "\
+failed to merge --config key `a` into `[..]/.cargo/config`
+
+Caused by:
+ failed to merge config value from `--config cli option` into `[..]/.cargo/config`: \
+expected boolean, but found array",
+ );
+
+ // config-cli and advanced-env
+ let config = ConfigBuilder::new()
+ .unstable_flag("advanced-env")
+ .config_arg("b=['clib']")
+ .config_arg("c=['clic']")
+ .env("CARGO_B", "env1 env2")
+ .env("CARGO_C", "['e1', 'e2']")
+ .build();
+ assert_eq!(
+ config.get::<VSOB>("b").unwrap(),
+ VSOB::VecString(vec![
+ "b".to_string(),
+ "clib".to_string(),
+ "env1".to_string(),
+ "env2".to_string()
+ ])
+ );
+ assert_eq!(
+ config.get::<VSOB>("c").unwrap(),
+ VSOB::VecString(vec![
+ "c".to_string(),
+ "clic".to_string(),
+ "e1".to_string(),
+ "e2".to_string()
+ ])
+ );
+}
+
+#[cargo_test]
+fn config_toml_errors() {
+ write_config(
+ "\
+[profile.dev]
+opt-level = 'foo'
+",
+ );
+
+ let config = new_config();
+
+ assert_error(
+ config
+ .get::<cargo_toml::TomlProfile>("profile.dev")
+ .unwrap_err(),
+ "\
+error in [..]/.cargo/config: could not load config key `profile.dev.opt-level`
+
+Caused by:
+ must be `0`, `1`, `2`, `3`, `s` or `z`, but found the string: \"foo\"",
+ );
+
+ let config = ConfigBuilder::new()
+ .env("CARGO_PROFILE_DEV_OPT_LEVEL", "asdf")
+ .build();
+
+ assert_error(
+ config.get::<cargo_toml::TomlProfile>("profile.dev").unwrap_err(),
+ "\
+error in environment variable `CARGO_PROFILE_DEV_OPT_LEVEL`: could not load config key `profile.dev.opt-level`
+
+Caused by:
+ must be `0`, `1`, `2`, `3`, `s` or `z`, but found the string: \"asdf\"",
+ );
+}
+
+#[cargo_test]
+fn load_nested() {
+ write_config(
+ "\
+[nest.foo]
+f1 = 1
+f2 = 2
+[nest.bar]
+asdf = 3
+",
+ );
+
+ let config = ConfigBuilder::new()
+ .unstable_flag("advanced-env")
+ .env("CARGO_NEST_foo_f2", "3")
+ .env("CARGO_NESTE_foo_f1", "1")
+ .env("CARGO_NESTE_foo_f2", "3")
+ .env("CARGO_NESTE_bar_asdf", "3")
+ .build();
+
+ type Nested = HashMap<String, HashMap<String, u8>>;
+
+ let n: Nested = config.get("nest").unwrap();
+ let mut expected = HashMap::new();
+ let mut foo = HashMap::new();
+ foo.insert("f1".to_string(), 1);
+ foo.insert("f2".to_string(), 3);
+ expected.insert("foo".to_string(), foo);
+ let mut bar = HashMap::new();
+ bar.insert("asdf".to_string(), 3);
+ expected.insert("bar".to_string(), bar);
+ assert_eq!(n, expected);
+
+ let n: Nested = config.get("neste").unwrap();
+ assert_eq!(n, expected);
+}
+
+#[cargo_test]
+fn get_errors() {
+ write_config(
+ "\
+[S]
+f1 = 123
+f2 = 'asdf'
+big = 123456789
+",
+ );
+
+ let config = ConfigBuilder::new()
+ .env("CARGO_E_S", "asdf")
+ .env("CARGO_E_BIG", "123456789")
+ .build();
+ assert_error(
+ config.get::<i64>("foo").unwrap_err(),
+ "missing config key `foo`",
+ );
+ assert_error(
+ config.get::<i64>("foo.bar").unwrap_err(),
+ "missing config key `foo.bar`",
+ );
+ assert_error(
+ config.get::<i64>("S.f2").unwrap_err(),
+ "error in [..]/.cargo/config: `S.f2` expected an integer, but found a string",
+ );
+ assert_error(
+ config.get::<u8>("S.big").unwrap_err(),
+ "\
+error in [..].cargo/config: could not load config key `S.big`
+
+Caused by:
+ invalid value: integer `123456789`, expected u8",
+ );
+
+ // Environment variable type errors.
+ assert_error(
+ config.get::<i64>("e.s").unwrap_err(),
+ "error in environment variable `CARGO_E_S`: invalid digit found in string",
+ );
+ assert_error(
+ config.get::<i8>("e.big").unwrap_err(),
+ "\
+error in environment variable `CARGO_E_BIG`: could not load config key `e.big`
+
+Caused by:
+ invalid value: integer `123456789`, expected i8",
+ );
+
+ #[derive(Debug, Deserialize)]
+ #[allow(dead_code)]
+ struct S {
+ f1: i64,
+ f2: String,
+ f3: i64,
+ big: i64,
+ }
+ assert_error(config.get::<S>("S").unwrap_err(), "missing field `f3`");
+}
+
+#[cargo_test]
+fn config_get_option() {
+ write_config(
+ "\
+[foo]
+f1 = 1
+",
+ );
+
+ let config = ConfigBuilder::new().env("CARGO_BAR_ASDF", "3").build();
+
+ assert_eq!(config.get::<Option<i32>>("a").unwrap(), None);
+ assert_eq!(config.get::<Option<i32>>("a.b").unwrap(), None);
+ assert_eq!(config.get::<Option<i32>>("foo.f1").unwrap(), Some(1));
+ assert_eq!(config.get::<Option<i32>>("bar.asdf").unwrap(), Some(3));
+ assert_eq!(config.get::<Option<i32>>("bar.zzzz").unwrap(), None);
+}
+
+#[cargo_test]
+fn config_bad_toml() {
+ write_config("asdf");
+ let config = new_config();
+ assert_error(
+ config.get::<i32>("foo").unwrap_err(),
+ "\
+could not load Cargo configuration
+
+Caused by:
+ could not parse TOML configuration in `[..]/.cargo/config`
+
+Caused by:
+ could not parse input as TOML
+
+Caused by:
+ TOML parse error at line 1, column 5
+ |
+1 | asdf
+ | ^
+expected `.`, `=`",
+ );
+}
+
+#[cargo_test]
+fn config_get_list() {
+ write_config(
+ "\
+l1 = []
+l2 = ['one', 'two']
+l3 = 123
+l4 = ['one', 'two']
+
+[nested]
+l = ['x']
+
+[nested2]
+l = ['y']
+
+[nested-empty]
+",
+ );
+
+ type L = Vec<String>;
+
+ let config = ConfigBuilder::new()
+ .unstable_flag("advanced-env")
+ .env("CARGO_L4", "['three', 'four']")
+ .env("CARGO_L5", "['a']")
+ .env("CARGO_ENV_EMPTY", "[]")
+ .env("CARGO_ENV_BLANK", "")
+ .env("CARGO_ENV_NUM", "1")
+ .env("CARGO_ENV_NUM_LIST", "[1]")
+ .env("CARGO_ENV_TEXT", "asdf")
+ .env("CARGO_LEPAIR", "['a', 'b']")
+ .env("CARGO_NESTED2_L", "['z']")
+ .env("CARGO_NESTEDE_L", "['env']")
+ .env("CARGO_BAD_ENV", "[zzz]")
+ .build();
+
+ assert_eq!(config.get::<L>("unset").unwrap(), vec![] as Vec<String>);
+ assert_eq!(config.get::<L>("l1").unwrap(), vec![] as Vec<String>);
+ assert_eq!(config.get::<L>("l2").unwrap(), vec!["one", "two"]);
+ assert_error(
+ config.get::<L>("l3").unwrap_err(),
+ "\
+invalid configuration for key `l3`
+expected a list, but found a integer for `l3` in [..]/.cargo/config",
+ );
+ assert_eq!(
+ config.get::<L>("l4").unwrap(),
+ vec!["one", "two", "three", "four"]
+ );
+ assert_eq!(config.get::<L>("l5").unwrap(), vec!["a"]);
+ assert_eq!(config.get::<L>("env-empty").unwrap(), vec![] as Vec<String>);
+ assert_eq!(config.get::<L>("env-blank").unwrap(), vec![] as Vec<String>);
+ assert_eq!(config.get::<L>("env-num").unwrap(), vec!["1".to_string()]);
+ assert_error(
+ config.get::<L>("env-num-list").unwrap_err(),
+ "error in environment variable `CARGO_ENV_NUM_LIST`: \
+ expected string, found integer",
+ );
+ assert_eq!(
+ config.get::<L>("env-text").unwrap(),
+ vec!["asdf".to_string()]
+ );
+ // "invalid number" here isn't the best error, but I think it's just toml.rs.
+ assert_error(
+ config.get::<L>("bad-env").unwrap_err(),
+ "\
+error in environment variable `CARGO_BAD_ENV`: could not parse TOML list: TOML parse error at line 1, column 2
+ |
+1 | [zzz]
+ | ^
+invalid array
+expected `]`
+",
+ );
+
+ // Try some other sequence-like types.
+ assert_eq!(
+ config
+ .get::<(String, String, String, String)>("l4")
+ .unwrap(),
+ (
+ "one".to_string(),
+ "two".to_string(),
+ "three".to_string(),
+ "four".to_string()
+ )
+ );
+ assert_eq!(config.get::<(String,)>("l5").unwrap(), ("a".to_string(),));
+
+ // Tuple struct
+ #[derive(Debug, Deserialize, Eq, PartialEq)]
+ struct TupS(String, String);
+ assert_eq!(
+ config.get::<TupS>("lepair").unwrap(),
+ TupS("a".to_string(), "b".to_string())
+ );
+
+ // Nested with an option.
+ #[derive(Debug, Deserialize, Eq, PartialEq)]
+ struct S {
+ l: Option<Vec<String>>,
+ }
+ assert_eq!(config.get::<S>("nested-empty").unwrap(), S { l: None });
+ assert_eq!(
+ config.get::<S>("nested").unwrap(),
+ S {
+ l: Some(vec!["x".to_string()]),
+ }
+ );
+ assert_eq!(
+ config.get::<S>("nested2").unwrap(),
+ S {
+ l: Some(vec!["y".to_string(), "z".to_string()]),
+ }
+ );
+ assert_eq!(
+ config.get::<S>("nestede").unwrap(),
+ S {
+ l: Some(vec!["env".to_string()]),
+ }
+ );
+}
+
+#[cargo_test]
+fn config_get_other_types() {
+ write_config(
+ "\
+ns = 123
+ns2 = 456
+",
+ );
+
+ let config = ConfigBuilder::new()
+ .env("CARGO_NSE", "987")
+ .env("CARGO_NS2", "654")
+ .build();
+
+ #[derive(Debug, Deserialize, Eq, PartialEq)]
+ #[serde(transparent)]
+ struct NewS(i32);
+ assert_eq!(config.get::<NewS>("ns").unwrap(), NewS(123));
+ assert_eq!(config.get::<NewS>("ns2").unwrap(), NewS(654));
+ assert_eq!(config.get::<NewS>("nse").unwrap(), NewS(987));
+ assert_error(
+ config.get::<NewS>("unset").unwrap_err(),
+ "missing config key `unset`",
+ );
+}
+
+#[cargo_test]
+fn config_relative_path() {
+ write_config(&format!(
+ "\
+p1 = 'foo/bar'
+p2 = '../abc'
+p3 = 'b/c'
+abs = '{}'
+",
+ paths::home().display(),
+ ));
+
+ let config = ConfigBuilder::new()
+ .env("CARGO_EPATH", "a/b")
+ .env("CARGO_P3", "d/e")
+ .build();
+
+ assert_eq!(
+ config
+ .get::<config::ConfigRelativePath>("p1")
+ .unwrap()
+ .resolve_path(&config),
+ paths::root().join("foo/bar")
+ );
+ assert_eq!(
+ config
+ .get::<config::ConfigRelativePath>("p2")
+ .unwrap()
+ .resolve_path(&config),
+ paths::root().join("../abc")
+ );
+ assert_eq!(
+ config
+ .get::<config::ConfigRelativePath>("p3")
+ .unwrap()
+ .resolve_path(&config),
+ paths::root().join("d/e")
+ );
+ assert_eq!(
+ config
+ .get::<config::ConfigRelativePath>("abs")
+ .unwrap()
+ .resolve_path(&config),
+ paths::home()
+ );
+ assert_eq!(
+ config
+ .get::<config::ConfigRelativePath>("epath")
+ .unwrap()
+ .resolve_path(&config),
+ paths::root().join("a/b")
+ );
+}
+
+#[cargo_test]
+fn config_get_integers() {
+ write_config(
+ "\
+npos = 123456789
+nneg = -123456789
+i64max = 9223372036854775807
+",
+ );
+
+ let config = ConfigBuilder::new()
+ .env("CARGO_EPOS", "123456789")
+ .env("CARGO_ENEG", "-1")
+ .env("CARGO_EI64MAX", "9223372036854775807")
+ .build();
+
+ assert_eq!(
+ config.get::<u64>("i64max").unwrap(),
+ 9_223_372_036_854_775_807
+ );
+ assert_eq!(
+ config.get::<i64>("i64max").unwrap(),
+ 9_223_372_036_854_775_807
+ );
+ assert_eq!(
+ config.get::<u64>("ei64max").unwrap(),
+ 9_223_372_036_854_775_807
+ );
+ assert_eq!(
+ config.get::<i64>("ei64max").unwrap(),
+ 9_223_372_036_854_775_807
+ );
+
+ assert_error(
+ config.get::<u32>("nneg").unwrap_err(),
+ "\
+error in [..].cargo/config: could not load config key `nneg`
+
+Caused by:
+ invalid value: integer `-123456789`, expected u32",
+ );
+ assert_error(
+ config.get::<u32>("eneg").unwrap_err(),
+ "\
+error in environment variable `CARGO_ENEG`: could not load config key `eneg`
+
+Caused by:
+ invalid value: integer `-1`, expected u32",
+ );
+ assert_error(
+ config.get::<i8>("npos").unwrap_err(),
+ "\
+error in [..].cargo/config: could not load config key `npos`
+
+Caused by:
+ invalid value: integer `123456789`, expected i8",
+ );
+ assert_error(
+ config.get::<i8>("epos").unwrap_err(),
+ "\
+error in environment variable `CARGO_EPOS`: could not load config key `epos`
+
+Caused by:
+ invalid value: integer `123456789`, expected i8",
+ );
+}
+
+#[cargo_test]
+fn config_get_ssl_version_missing() {
+ write_config(
+ "\
+[http]
+hello = 'world'
+",
+ );
+
+ let config = new_config();
+
+ assert!(config
+ .get::<Option<SslVersionConfig>>("http.ssl-version")
+ .unwrap()
+ .is_none());
+}
+
+#[cargo_test]
+fn config_get_ssl_version_single() {
+ write_config(
+ "\
+[http]
+ssl-version = 'tlsv1.2'
+",
+ );
+
+ let config = new_config();
+
+ let a = config
+ .get::<Option<SslVersionConfig>>("http.ssl-version")
+ .unwrap()
+ .unwrap();
+ match a {
+ SslVersionConfig::Single(v) => assert_eq!(&v, "tlsv1.2"),
+ SslVersionConfig::Range(_) => panic!("Did not expect ssl version min/max."),
+ };
+}
+
+#[cargo_test]
+fn config_get_ssl_version_min_max() {
+ write_config(
+ "\
+[http]
+ssl-version.min = 'tlsv1.2'
+ssl-version.max = 'tlsv1.3'
+",
+ );
+
+ let config = new_config();
+
+ let a = config
+ .get::<Option<SslVersionConfig>>("http.ssl-version")
+ .unwrap()
+ .unwrap();
+ match a {
+ SslVersionConfig::Single(_) => panic!("Did not expect exact ssl version."),
+ SslVersionConfig::Range(range) => {
+ assert_eq!(range.min, Some(String::from("tlsv1.2")));
+ assert_eq!(range.max, Some(String::from("tlsv1.3")));
+ }
+ };
+}
+
+#[cargo_test]
+fn config_get_ssl_version_both_forms_configured() {
+ // this is not allowed
+ write_config(
+ "\
+[http]
+ssl-version = 'tlsv1.1'
+ssl-version.min = 'tlsv1.2'
+ssl-version.max = 'tlsv1.3'
+",
+ );
+
+ let config = new_config();
+
+ assert_error(
+ config
+ .get::<SslVersionConfig>("http.ssl-version")
+ .unwrap_err(),
+ "\
+could not load Cargo configuration
+
+Caused by:
+ could not parse TOML configuration in `[..]/.cargo/config`
+
+Caused by:
+ could not parse input as TOML
+
+Caused by:
+ TOML parse error at line 3, column 1
+ |
+3 | ssl-version.min = 'tlsv1.2'
+ | ^
+dotted key `ssl-version` attempted to extend non-table type (string)
+",
+ );
+}
+
+#[cargo_test]
+/// Assert that unstable options can be configured with the `unstable` table in
+/// cargo config files
+fn unstable_table_notation() {
+ write_config(
+ "\
+[unstable]
+print-im-a-teapot = true
+",
+ );
+ let config = ConfigBuilder::new().nightly_features_allowed(true).build();
+ assert_eq!(config.cli_unstable().print_im_a_teapot, true);
+}
+
+#[cargo_test]
+/// Assert that dotted notation works for configuring unstable options
+fn unstable_dotted_notation() {
+ write_config(
+ "\
+unstable.print-im-a-teapot = true
+",
+ );
+ let config = ConfigBuilder::new().nightly_features_allowed(true).build();
+ assert_eq!(config.cli_unstable().print_im_a_teapot, true);
+}
+
+#[cargo_test]
+/// Assert that Zflags on the CLI take precedence over those from config
+fn unstable_cli_precedence() {
+ write_config(
+ "\
+unstable.print-im-a-teapot = true
+",
+ );
+ let config = ConfigBuilder::new().nightly_features_allowed(true).build();
+ assert_eq!(config.cli_unstable().print_im_a_teapot, true);
+
+ let config = ConfigBuilder::new()
+ .unstable_flag("print-im-a-teapot=no")
+ .build();
+ assert_eq!(config.cli_unstable().print_im_a_teapot, false);
+}
+
+#[cargo_test]
+/// Assert that attempting to set an unstable flag that doesn't exist via config
+/// is ignored on stable
+fn unstable_invalid_flag_ignored_on_stable() {
+ write_config(
+ "\
+unstable.an-invalid-flag = 'yes'
+",
+ );
+ assert!(ConfigBuilder::new().build_err().is_ok());
+}
+
+#[cargo_test]
+/// Assert that unstable options can be configured with the `unstable` table in
+/// cargo config files
+fn unstable_flags_ignored_on_stable() {
+ write_config(
+ "\
+[unstable]
+print-im-a-teapot = true
+",
+ );
+ // Enforce stable channel even when testing on nightly.
+ let config = ConfigBuilder::new().nightly_features_allowed(false).build();
+ assert_eq!(config.cli_unstable().print_im_a_teapot, false);
+}
+
+#[cargo_test]
+fn table_merge_failure() {
+ // Config::merge fails to merge entries in two tables.
+ write_config_at(
+ "foo/.cargo/config",
+ "
+ [table]
+ key = ['foo']
+ ",
+ );
+ write_config_at(
+ ".cargo/config",
+ "
+ [table]
+ key = 'bar'
+ ",
+ );
+
+ #[derive(Debug, Deserialize)]
+ #[allow(dead_code)]
+ struct Table {
+ key: StringList,
+ }
+ let config = ConfigBuilder::new().cwd("foo").build();
+ assert_error(
+ config.get::<Table>("table").unwrap_err(),
+ "\
+could not load Cargo configuration
+
+Caused by:
+ failed to merge configuration at `[..]/.cargo/config`
+
+Caused by:
+ failed to merge key `table` between [..]/foo/.cargo/config and [..]/.cargo/config
+
+Caused by:
+ failed to merge key `key` between [..]/foo/.cargo/config and [..]/.cargo/config
+
+Caused by:
+ failed to merge config value from `[..]/.cargo/config` into `[..]/foo/.cargo/config`: \
+ expected array, but found string",
+ );
+}
+
+#[cargo_test]
+fn non_string_in_array() {
+ // Currently only strings are supported.
+ write_config("foo = [1, 2, 3]");
+ let config = new_config();
+ assert_error(
+ config.get::<Vec<i32>>("foo").unwrap_err(),
+ "\
+could not load Cargo configuration
+
+Caused by:
+ failed to load TOML configuration from `[..]/.cargo/config`
+
+Caused by:
+ failed to parse key `foo`
+
+Caused by:
+ expected string but found integer in list",
+ );
+}
+
+#[cargo_test]
+fn struct_with_opt_inner_struct() {
+ // Struct with a key that is Option of another struct.
+ // Check that can be defined with environment variable.
+ #[derive(Deserialize)]
+ struct Inner {
+ value: Option<i32>,
+ }
+ #[derive(Deserialize)]
+ struct Foo {
+ inner: Option<Inner>,
+ }
+ let config = ConfigBuilder::new()
+ .env("CARGO_FOO_INNER_VALUE", "12")
+ .build();
+ let f: Foo = config.get("foo").unwrap();
+ assert_eq!(f.inner.unwrap().value.unwrap(), 12);
+}
+
+#[cargo_test]
+fn struct_with_default_inner_struct() {
+ // Struct with serde defaults.
+ // Check that can be defined with environment variable.
+ #[derive(Deserialize, Default)]
+ #[serde(default)]
+ struct Inner {
+ value: i32,
+ }
+ #[derive(Deserialize, Default)]
+ #[serde(default)]
+ struct Foo {
+ inner: Inner,
+ }
+ let config = ConfigBuilder::new()
+ .env("CARGO_FOO_INNER_VALUE", "12")
+ .build();
+ let f: Foo = config.get("foo").unwrap();
+ assert_eq!(f.inner.value, 12);
+}
+
+#[cargo_test]
+fn overlapping_env_config() {
+ // Issue where one key is a prefix of another.
+ #[derive(Deserialize)]
+ #[serde(rename_all = "kebab-case")]
+ struct Ambig {
+ debug: Option<u32>,
+ debug_assertions: Option<bool>,
+ }
+ let config = ConfigBuilder::new()
+ .env("CARGO_AMBIG_DEBUG_ASSERTIONS", "true")
+ .build();
+
+ let s: Ambig = config.get("ambig").unwrap();
+ assert_eq!(s.debug_assertions, Some(true));
+ assert_eq!(s.debug, None);
+
+ let config = ConfigBuilder::new().env("CARGO_AMBIG_DEBUG", "0").build();
+ let s: Ambig = config.get("ambig").unwrap();
+ assert_eq!(s.debug_assertions, None);
+ assert_eq!(s.debug, Some(0));
+
+ let config = ConfigBuilder::new()
+ .env("CARGO_AMBIG_DEBUG", "1")
+ .env("CARGO_AMBIG_DEBUG_ASSERTIONS", "true")
+ .build();
+ let s: Ambig = config.get("ambig").unwrap();
+ assert_eq!(s.debug_assertions, Some(true));
+ assert_eq!(s.debug, Some(1));
+}
+
+#[cargo_test]
+fn overlapping_env_with_defaults_errors_out() {
+ // Issue where one key is a prefix of another.
+ // This is a limitation of mapping environment variables on to a hierarchy.
+ // Check that we error out when we hit ambiguity in this way, rather than
+ // the more-surprising defaulting through.
+ // If, in the future, we can handle this more correctly, feel free to delete
+ // this test.
+ #[derive(Deserialize, Default)]
+ #[serde(default, rename_all = "kebab-case")]
+ struct Ambig {
+ debug: u32,
+ debug_assertions: bool,
+ }
+ let config = ConfigBuilder::new()
+ .env("CARGO_AMBIG_DEBUG_ASSERTIONS", "true")
+ .build();
+ let err = config.get::<Ambig>("ambig").err().unwrap();
+ assert!(format!("{}", err).contains("missing config key `ambig.debug`"));
+
+ let config = ConfigBuilder::new().env("CARGO_AMBIG_DEBUG", "5").build();
+ let s: Ambig = config.get("ambig").unwrap();
+ assert_eq!(s.debug_assertions, bool::default());
+ assert_eq!(s.debug, 5);
+
+ let config = ConfigBuilder::new()
+ .env("CARGO_AMBIG_DEBUG", "1")
+ .env("CARGO_AMBIG_DEBUG_ASSERTIONS", "true")
+ .build();
+ let s: Ambig = config.get("ambig").unwrap();
+ assert_eq!(s.debug_assertions, true);
+ assert_eq!(s.debug, 1);
+}
+
+#[cargo_test]
+fn struct_with_overlapping_inner_struct_and_defaults() {
+ // Struct with serde defaults.
+ // Check that can be defined with environment variable.
+ #[derive(Deserialize, Default)]
+ #[serde(default)]
+ struct Inner {
+ value: i32,
+ }
+
+ // Containing struct with a prefix of inner
+ //
+ // This is a limitation of mapping environment variables on to a hierarchy.
+ // Check that we error out when we hit ambiguity in this way, rather than
+ // the more-surprising defaulting through.
+ // If, in the future, we can handle this more correctly, feel free to delete
+ // this case.
+ #[derive(Deserialize, Default)]
+ #[serde(default)]
+ struct PrefixContainer {
+ inn: bool,
+ inner: Inner,
+ }
+ let config = ConfigBuilder::new()
+ .env("CARGO_PREFIXCONTAINER_INNER_VALUE", "12")
+ .build();
+ let err = config
+ .get::<PrefixContainer>("prefixcontainer")
+ .err()
+ .unwrap();
+ assert!(format!("{}", err).contains("missing config key `prefixcontainer.inn`"));
+ let config = ConfigBuilder::new()
+ .env("CARGO_PREFIXCONTAINER_INNER_VALUE", "12")
+ .env("CARGO_PREFIXCONTAINER_INN", "true")
+ .build();
+ let f: PrefixContainer = config.get("prefixcontainer").unwrap();
+ assert_eq!(f.inner.value, 12);
+ assert_eq!(f.inn, true);
+
+ // Containing struct where the inner value's field is a prefix of another
+ //
+ // This is a limitation of mapping environment variables on to a hierarchy.
+ // Check that we error out when we hit ambiguity in this way, rather than
+ // the more-surprising defaulting through.
+ // If, in the future, we can handle this more correctly, feel free to delete
+ // this case.
+ #[derive(Deserialize, Default)]
+ #[serde(default)]
+ struct InversePrefixContainer {
+ inner_field: bool,
+ inner: Inner,
+ }
+ let config = ConfigBuilder::new()
+ .env("CARGO_INVERSEPREFIXCONTAINER_INNER_VALUE", "12")
+ .build();
+ let f: InversePrefixContainer = config.get("inverseprefixcontainer").unwrap();
+ assert_eq!(f.inner_field, bool::default());
+ assert_eq!(f.inner.value, 12);
+}
+
+#[cargo_test]
+fn string_list_tricky_env() {
+ // Make sure StringList handles typed env values.
+ let config = ConfigBuilder::new()
+ .env("CARGO_KEY1", "123")
+ .env("CARGO_KEY2", "true")
+ .env("CARGO_KEY3", "1 2")
+ .build();
+ let x = config.get::<StringList>("key1").unwrap();
+ assert_eq!(x.as_slice(), &["123".to_string()]);
+ let x = config.get::<StringList>("key2").unwrap();
+ assert_eq!(x.as_slice(), &["true".to_string()]);
+ let x = config.get::<StringList>("key3").unwrap();
+ assert_eq!(x.as_slice(), &["1".to_string(), "2".to_string()]);
+}
+
+#[cargo_test]
+fn string_list_wrong_type() {
+ // What happens if StringList is given then wrong type.
+ write_config("some_list = 123");
+ let config = ConfigBuilder::new().build();
+ assert_error(
+ config.get::<StringList>("some_list").unwrap_err(),
+ "\
+invalid configuration for key `some_list`
+expected a string or array of strings, but found a integer for `some_list` in [..]/.cargo/config",
+ );
+
+ write_config("some_list = \"1 2\"");
+ let config = ConfigBuilder::new().build();
+ let x = config.get::<StringList>("some_list").unwrap();
+ assert_eq!(x.as_slice(), &["1".to_string(), "2".to_string()]);
+}
+
+#[cargo_test]
+fn string_list_advanced_env() {
+ // StringList with advanced env.
+ let config = ConfigBuilder::new()
+ .unstable_flag("advanced-env")
+ .env("CARGO_KEY1", "[]")
+ .env("CARGO_KEY2", "['1 2', '3']")
+ .env("CARGO_KEY3", "[123]")
+ .build();
+ let x = config.get::<StringList>("key1").unwrap();
+ assert_eq!(x.as_slice(), &[] as &[String]);
+ let x = config.get::<StringList>("key2").unwrap();
+ assert_eq!(x.as_slice(), &["1 2".to_string(), "3".to_string()]);
+ assert_error(
+ config.get::<StringList>("key3").unwrap_err(),
+ "error in environment variable `CARGO_KEY3`: expected string, found integer",
+ );
+}
+
+#[cargo_test]
+fn parse_strip_with_string() {
+ write_config(
+ "\
+[profile.release]
+strip = 'debuginfo'
+",
+ );
+
+ let config = new_config();
+
+ let p: cargo_toml::TomlProfile = config.get("profile.release").unwrap();
+ let strip = p.strip.unwrap();
+ assert_eq!(
+ strip,
+ cargo_toml::StringOrBool::String("debuginfo".to_string())
+ );
+}
+
+#[cargo_test]
+fn cargo_target_empty_cfg() {
+ write_config(
+ "\
+[build]
+target-dir = ''
+",
+ );
+
+ let config = new_config();
+
+ assert_error(
+ config.target_dir().unwrap_err(),
+ "the target directory is set to an empty string in [..]/.cargo/config",
+ );
+}
+
+#[cargo_test]
+fn cargo_target_empty_env() {
+ let project = project().build();
+
+ project.cargo("check")
+ .env("CARGO_TARGET_DIR", "")
+ .with_stderr("error: the target directory is set to an empty string in the `CARGO_TARGET_DIR` environment variable")
+ .with_status(101)
+ .run()
+}
+
+#[cargo_test]
+fn all_profile_options() {
+ // Check that all profile options can be serialized/deserialized.
+ let base_settings = cargo_toml::TomlProfile {
+ opt_level: Some(cargo_toml::TomlOptLevel("0".to_string())),
+ lto: Some(cargo_toml::StringOrBool::String("thin".to_string())),
+ codegen_backend: Some(InternedString::new("example")),
+ codegen_units: Some(123),
+ debug: Some(cargo_toml::U32OrBool::U32(1)),
+ split_debuginfo: Some("packed".to_string()),
+ debug_assertions: Some(true),
+ rpath: Some(true),
+ panic: Some("abort".to_string()),
+ overflow_checks: Some(true),
+ incremental: Some(true),
+ dir_name: Some(InternedString::new("dir_name")),
+ inherits: Some(InternedString::new("debug")),
+ strip: Some(cargo_toml::StringOrBool::String("symbols".to_string())),
+ package: None,
+ build_override: None,
+ rustflags: None,
+ };
+ let mut overrides = BTreeMap::new();
+ let key = cargo_toml::ProfilePackageSpec::Spec(PackageIdSpec::parse("foo").unwrap());
+ overrides.insert(key, base_settings.clone());
+ let profile = cargo_toml::TomlProfile {
+ build_override: Some(Box::new(base_settings.clone())),
+ package: Some(overrides),
+ ..base_settings
+ };
+ let profile_toml = toml::to_string(&profile).unwrap();
+ let roundtrip: cargo_toml::TomlProfile = toml::from_str(&profile_toml).unwrap();
+ let roundtrip_toml = toml::to_string(&roundtrip).unwrap();
+ compare::assert_match_exact(&profile_toml, &roundtrip_toml);
+}
+
+#[cargo_test]
+fn value_in_array() {
+ // Value<String> in an array should work
+ let root_path = paths::root().join(".cargo/config.toml");
+ write_config_at(
+ &root_path,
+ "\
+[net.ssh]
+known-hosts = [
+ \"example.com ...\",
+ \"example.net ...\",
+]
+",
+ );
+
+ let foo_path = paths::root().join("foo/.cargo/config.toml");
+ write_config_at(
+ &foo_path,
+ "\
+[net.ssh]
+known-hosts = [
+ \"example.org ...\",
+]
+",
+ );
+
+ let config = ConfigBuilder::new()
+ .cwd("foo")
+ // environment variables don't actually work for known-hosts due to
+ // space splitting, but this is included here just to validate that
+ // they work (particularly if other Vec<Value> config vars are added
+ // in the future).
+ .env("CARGO_NET_SSH_KNOWN_HOSTS", "env-example")
+ .build();
+ let net_config = config.net_config().unwrap();
+ let kh = net_config
+ .ssh
+ .as_ref()
+ .unwrap()
+ .known_hosts
+ .as_ref()
+ .unwrap();
+ assert_eq!(kh.len(), 4);
+ assert_eq!(kh[0].val, "example.org ...");
+ assert_eq!(kh[0].definition, Definition::Path(foo_path.clone()));
+ assert_eq!(kh[1].val, "example.com ...");
+ assert_eq!(kh[1].definition, Definition::Path(root_path.clone()));
+ assert_eq!(kh[2].val, "example.net ...");
+ assert_eq!(kh[2].definition, Definition::Path(root_path.clone()));
+ assert_eq!(kh[3].val, "env-example");
+ assert_eq!(
+ kh[3].definition,
+ Definition::Environment("CARGO_NET_SSH_KNOWN_HOSTS".to_string())
+ );
+}
diff --git a/src/tools/cargo/tests/testsuite/config_cli.rs b/src/tools/cargo/tests/testsuite/config_cli.rs
new file mode 100644
index 000000000..1120e279d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/config_cli.rs
@@ -0,0 +1,564 @@
+//! Tests for the --config CLI option.
+
+use super::config::{
+ assert_error, assert_match, read_output, write_config, write_config_at, ConfigBuilder,
+};
+use cargo::util::config::Definition;
+use cargo_test_support::paths;
+use std::{collections::HashMap, fs};
+
+#[cargo_test]
+fn basic() {
+ // Simple example.
+ let config = ConfigBuilder::new().config_arg("foo='bar'").build();
+ assert_eq!(config.get::<String>("foo").unwrap(), "bar");
+}
+
+#[cargo_test]
+fn cli_priority() {
+ // Command line takes priority over files and env vars.
+ write_config(
+ "
+ demo_list = ['a']
+ [build]
+ jobs = 3
+ rustc = 'file'
+ [term]
+ quiet = false
+ verbose = false
+ ",
+ );
+ let config = ConfigBuilder::new().build();
+ assert_eq!(config.get::<i32>("build.jobs").unwrap(), 3);
+ assert_eq!(config.get::<String>("build.rustc").unwrap(), "file");
+ assert_eq!(config.get::<bool>("term.quiet").unwrap(), false);
+ assert_eq!(config.get::<bool>("term.verbose").unwrap(), false);
+
+ let config = ConfigBuilder::new()
+ .env("CARGO_BUILD_JOBS", "2")
+ .env("CARGO_BUILD_RUSTC", "env")
+ .env("CARGO_TERM_VERBOSE", "false")
+ .config_arg("build.jobs=1")
+ .config_arg("build.rustc='cli'")
+ .config_arg("term.verbose=true")
+ .build();
+ assert_eq!(config.get::<i32>("build.jobs").unwrap(), 1);
+ assert_eq!(config.get::<String>("build.rustc").unwrap(), "cli");
+ assert_eq!(config.get::<bool>("term.verbose").unwrap(), true);
+
+ // Setting both term.verbose and term.quiet is invalid and is tested
+ // in the run test suite.
+ let config = ConfigBuilder::new()
+ .env("CARGO_TERM_QUIET", "false")
+ .config_arg("term.quiet=true")
+ .build();
+ assert_eq!(config.get::<bool>("term.quiet").unwrap(), true);
+}
+
+#[cargo_test]
+fn merge_primitives_for_multiple_cli_occurrences() {
+ let config_path0 = ".cargo/file0.toml";
+ write_config_at(config_path0, "k = 'file0'");
+ let config_path1 = ".cargo/file1.toml";
+ write_config_at(config_path1, "k = 'file1'");
+
+ // k=env0
+ let config = ConfigBuilder::new().env("CARGO_K", "env0").build();
+ assert_eq!(config.get::<String>("k").unwrap(), "env0");
+
+ // k=env0
+ // --config k='cli0'
+ // --config k='cli1'
+ let config = ConfigBuilder::new()
+ .env("CARGO_K", "env0")
+ .config_arg("k='cli0'")
+ .config_arg("k='cli1'")
+ .build();
+ assert_eq!(config.get::<String>("k").unwrap(), "cli1");
+
+ // Env has a lower priority when comparing with file from CLI arg.
+ //
+ // k=env0
+ // --config k='cli0'
+ // --config k='cli1'
+ // --config .cargo/file0.toml
+ let config = ConfigBuilder::new()
+ .env("CARGO_K", "env0")
+ .config_arg("k='cli0'")
+ .config_arg("k='cli1'")
+ .config_arg(config_path0)
+ .build();
+ assert_eq!(config.get::<String>("k").unwrap(), "file0");
+
+ // k=env0
+ // --config k='cli0'
+ // --config k='cli1'
+ // --config .cargo/file0.toml
+ // --config k='cli2'
+ let config = ConfigBuilder::new()
+ .env("CARGO_K", "env0")
+ .config_arg("k='cli0'")
+ .config_arg("k='cli1'")
+ .config_arg(config_path0)
+ .config_arg("k='cli2'")
+ .build();
+ assert_eq!(config.get::<String>("k").unwrap(), "cli2");
+
+ // k=env0
+ // --config k='cli0'
+ // --config k='cli1'
+ // --config .cargo/file0.toml
+ // --config k='cli2'
+ // --config .cargo/file1.toml
+ let config = ConfigBuilder::new()
+ .env("CARGO_K", "env0")
+ .config_arg("k='cli0'")
+ .config_arg("k='cli1'")
+ .config_arg(config_path0)
+ .config_arg("k='cli2'")
+ .config_arg(config_path1)
+ .build();
+ assert_eq!(config.get::<String>("k").unwrap(), "file1");
+}
+
+#[cargo_test]
+fn merges_array() {
+ // Array entries are appended.
+ write_config(
+ "
+ [build]
+ rustflags = ['--file']
+ ",
+ );
+ let config = ConfigBuilder::new()
+ .config_arg("build.rustflags = ['--cli']")
+ .build();
+ assert_eq!(
+ config.get::<Vec<String>>("build.rustflags").unwrap(),
+ ["--file", "--cli"]
+ );
+
+ // With normal env.
+ let config = ConfigBuilder::new()
+ .env("CARGO_BUILD_RUSTFLAGS", "--env1 --env2")
+ .config_arg("build.rustflags = ['--cli']")
+ .build();
+ // The order of cli/env is a little questionable here, but would require
+ // much more complex merging logic.
+ assert_eq!(
+ config.get::<Vec<String>>("build.rustflags").unwrap(),
+ ["--file", "--cli", "--env1", "--env2"]
+ );
+
+ // With advanced-env.
+ let config = ConfigBuilder::new()
+ .unstable_flag("advanced-env")
+ .env("CARGO_BUILD_RUSTFLAGS", "--env")
+ .config_arg("build.rustflags = ['--cli']")
+ .build();
+ assert_eq!(
+ config.get::<Vec<String>>("build.rustflags").unwrap(),
+ ["--file", "--cli", "--env"]
+ );
+
+ // Merges multiple instances.
+ let config = ConfigBuilder::new()
+ .config_arg("build.rustflags=['--one']")
+ .config_arg("build.rustflags=['--two']")
+ .build();
+ assert_eq!(
+ config.get::<Vec<String>>("build.rustflags").unwrap(),
+ ["--file", "--one", "--two"]
+ );
+}
+
+#[cargo_test]
+fn string_list_array() {
+ // Using the StringList type.
+ write_config(
+ "
+ [build]
+ rustflags = ['--file']
+ ",
+ );
+ let config = ConfigBuilder::new()
+ .config_arg("build.rustflags = ['--cli']")
+ .build();
+ assert_eq!(
+ config
+ .get::<cargo::util::config::StringList>("build.rustflags")
+ .unwrap()
+ .as_slice(),
+ ["--file", "--cli"]
+ );
+
+ // With normal env.
+ let config = ConfigBuilder::new()
+ .env("CARGO_BUILD_RUSTFLAGS", "--env1 --env2")
+ .config_arg("build.rustflags = ['--cli']")
+ .build();
+ assert_eq!(
+ config
+ .get::<cargo::util::config::StringList>("build.rustflags")
+ .unwrap()
+ .as_slice(),
+ ["--file", "--cli", "--env1", "--env2"]
+ );
+
+ // With advanced-env.
+ let config = ConfigBuilder::new()
+ .unstable_flag("advanced-env")
+ .env("CARGO_BUILD_RUSTFLAGS", "['--env']")
+ .config_arg("build.rustflags = ['--cli']")
+ .build();
+ assert_eq!(
+ config
+ .get::<cargo::util::config::StringList>("build.rustflags")
+ .unwrap()
+ .as_slice(),
+ ["--file", "--cli", "--env"]
+ );
+}
+
+#[cargo_test]
+fn merges_table() {
+ // Tables are merged.
+ write_config(
+ "
+ [foo]
+ key1 = 1
+ key2 = 2
+ key3 = 3
+ ",
+ );
+ let config = ConfigBuilder::new()
+ .config_arg("foo.key2 = 4")
+ .config_arg("foo.key3 = 5")
+ .config_arg("foo.key4 = 6")
+ .build();
+ assert_eq!(config.get::<i32>("foo.key1").unwrap(), 1);
+ assert_eq!(config.get::<i32>("foo.key2").unwrap(), 4);
+ assert_eq!(config.get::<i32>("foo.key3").unwrap(), 5);
+ assert_eq!(config.get::<i32>("foo.key4").unwrap(), 6);
+
+ // With env.
+ let config = ConfigBuilder::new()
+ .env("CARGO_FOO_KEY3", "7")
+ .env("CARGO_FOO_KEY4", "8")
+ .env("CARGO_FOO_KEY5", "9")
+ .config_arg("foo.key2 = 4")
+ .config_arg("foo.key3 = 5")
+ .config_arg("foo.key4 = 6")
+ .build();
+ assert_eq!(config.get::<i32>("foo.key1").unwrap(), 1);
+ assert_eq!(config.get::<i32>("foo.key2").unwrap(), 4);
+ assert_eq!(config.get::<i32>("foo.key3").unwrap(), 5);
+ assert_eq!(config.get::<i32>("foo.key4").unwrap(), 6);
+ assert_eq!(config.get::<i32>("foo.key5").unwrap(), 9);
+}
+
+#[cargo_test]
+fn merge_array_mixed_def_paths() {
+ // Merging of arrays with different def sites.
+ write_config(
+ "
+ paths = ['file']
+ ",
+ );
+ // Create a directory for CWD to differentiate the paths.
+ let somedir = paths::root().join("somedir");
+ fs::create_dir(&somedir).unwrap();
+ let config = ConfigBuilder::new()
+ .cwd(&somedir)
+ .config_arg("paths=['cli']")
+ // env is currently ignored for get_list()
+ .env("CARGO_PATHS", "env")
+ .build();
+ let paths = config.get_list("paths").unwrap().unwrap();
+ // The definition for the root value is somewhat arbitrary, but currently starts with the file because that is what is loaded first.
+ assert_eq!(paths.definition, Definition::Path(paths::root()));
+ assert_eq!(paths.val.len(), 2);
+ assert_eq!(paths.val[0].0, "file");
+ assert_eq!(paths.val[0].1.root(&config), paths::root());
+ assert_eq!(paths.val[1].0, "cli");
+ assert_eq!(paths.val[1].1.root(&config), somedir);
+}
+
+#[cargo_test]
+fn enforces_format() {
+ // These dotted key expressions should all be fine.
+ let config = ConfigBuilder::new()
+ .config_arg("a=true")
+ .config_arg(" b.a = true ")
+ .config_arg("c.\"b\".'a'=true")
+ .config_arg("d.\"=\".'='=true")
+ .config_arg("e.\"'\".'\"'=true")
+ .build();
+ assert_eq!(config.get::<bool>("a").unwrap(), true);
+ assert_eq!(
+ config.get::<HashMap<String, bool>>("b").unwrap(),
+ HashMap::from([("a".to_string(), true)])
+ );
+ assert_eq!(
+ config
+ .get::<HashMap<String, HashMap<String, bool>>>("c")
+ .unwrap(),
+ HashMap::from([("b".to_string(), HashMap::from([("a".to_string(), true)]))])
+ );
+ assert_eq!(
+ config
+ .get::<HashMap<String, HashMap<String, bool>>>("d")
+ .unwrap(),
+ HashMap::from([("=".to_string(), HashMap::from([("=".to_string(), true)]))])
+ );
+ assert_eq!(
+ config
+ .get::<HashMap<String, HashMap<String, bool>>>("e")
+ .unwrap(),
+ HashMap::from([("'".to_string(), HashMap::from([("\"".to_string(), true)]))])
+ );
+
+ // But anything that's not a dotted key expression should be disallowed.
+ let _ = ConfigBuilder::new()
+ .config_arg("[a] foo=true")
+ .build_err()
+ .unwrap_err();
+ let _ = ConfigBuilder::new()
+ .config_arg("a = true\nb = true")
+ .build_err()
+ .unwrap_err();
+
+ // We also disallow overwriting with tables since it makes merging unclear.
+ let _ = ConfigBuilder::new()
+ .config_arg("a = { first = true, second = false }")
+ .build_err()
+ .unwrap_err();
+ let _ = ConfigBuilder::new()
+ .config_arg("a = { first = true }")
+ .build_err()
+ .unwrap_err();
+}
+
+#[cargo_test]
+fn unused_key() {
+ // Unused key passed on command line.
+ let config = ConfigBuilder::new().config_arg("build.unused = 2").build();
+
+ config.build_config().unwrap();
+ let output = read_output(config);
+ let expected = "\
+warning: unused config key `build.unused` in `--config cli option`
+";
+ assert_match(expected, &output);
+}
+
+#[cargo_test]
+fn rerooted_remains() {
+ // Re-rooting keeps cli args.
+ let somedir = paths::root().join("somedir");
+ fs::create_dir_all(somedir.join(".cargo")).unwrap();
+ fs::write(
+ somedir.join(".cargo").join("config"),
+ "
+ a = 'file1'
+ b = 'file2'
+ ",
+ )
+ .unwrap();
+ let mut config = ConfigBuilder::new()
+ .cwd(&somedir)
+ .config_arg("b='cli1'")
+ .config_arg("c='cli2'")
+ .build();
+ assert_eq!(config.get::<String>("a").unwrap(), "file1");
+ assert_eq!(config.get::<String>("b").unwrap(), "cli1");
+ assert_eq!(config.get::<String>("c").unwrap(), "cli2");
+
+ config.reload_rooted_at(paths::root()).unwrap();
+
+ assert_eq!(config.get::<Option<String>>("a").unwrap(), None);
+ assert_eq!(config.get::<String>("b").unwrap(), "cli1");
+ assert_eq!(config.get::<String>("c").unwrap(), "cli2");
+}
+
+#[cargo_test]
+fn bad_parse() {
+ // Fail to TOML parse.
+ let config = ConfigBuilder::new().config_arg("abc").build_err();
+ assert_error(
+ config.unwrap_err(),
+ "\
+failed to parse value from --config argument `abc` as a dotted key expression
+
+Caused by:
+ TOML parse error at line 1, column 4
+ |
+1 | abc
+ | ^
+expected `.`, `=`
+",
+ );
+
+ let config = ConfigBuilder::new().config_arg("").build_err();
+ assert_error(
+ config.unwrap_err(),
+ "--config argument `` was not a TOML dotted key expression (such as `build.jobs = 2`)",
+ );
+}
+
+#[cargo_test]
+fn too_many_values() {
+ // Currently restricted to only 1 value.
+ let config = ConfigBuilder::new().config_arg("a=1\nb=2").build_err();
+ assert_error(
+ config.unwrap_err(),
+ "\
+--config argument `a=1
+b=2` was not a TOML dotted key expression (such as `build.jobs = 2`)",
+ );
+}
+
+#[cargo_test]
+fn no_disallowed_values() {
+ let config = ConfigBuilder::new()
+ .config_arg("registry.token=\"hello\"")
+ .build_err();
+ assert_error(
+ config.unwrap_err(),
+ "registry.token cannot be set through --config for security reasons",
+ );
+ let config = ConfigBuilder::new()
+ .config_arg("registries.crates-io.token=\"hello\"")
+ .build_err();
+ assert_error(
+ config.unwrap_err(),
+ "registries.crates-io.token cannot be set through --config for security reasons",
+ );
+ let config = ConfigBuilder::new()
+ .config_arg("registry.secret-key=\"hello\"")
+ .build_err();
+ assert_error(
+ config.unwrap_err(),
+ "registry.secret-key cannot be set through --config for security reasons",
+ );
+ let config = ConfigBuilder::new()
+ .config_arg("registries.crates-io.secret-key=\"hello\"")
+ .build_err();
+ assert_error(
+ config.unwrap_err(),
+ "registries.crates-io.secret-key cannot be set through --config for security reasons",
+ );
+}
+
+#[cargo_test]
+fn no_inline_table_value() {
+ // Disallow inline tables
+ let config = ConfigBuilder::new()
+ .config_arg("a.b={c = \"d\"}")
+ .build_err();
+ assert_error(
+ config.unwrap_err(),
+ "--config argument `a.b={c = \"d\"}` sets a value to an inline table, which is not accepted"
+ );
+}
+
+#[cargo_test]
+fn no_array_of_tables_values() {
+ // Disallow array-of-tables when not in dotted form
+ let config = ConfigBuilder::new()
+ .config_arg("[[a.b]]\nc = \"d\"")
+ .build_err();
+ assert_error(
+ config.unwrap_err(),
+ "\
+--config argument `[[a.b]]
+c = \"d\"` was not a TOML dotted key expression (such as `build.jobs = 2`)",
+ );
+}
+
+#[cargo_test]
+fn no_comments() {
+ // Disallow comments in dotted form.
+ let config = ConfigBuilder::new()
+ .config_arg("a.b = \"c\" # exactly")
+ .build_err();
+ assert_error(
+ config.unwrap_err(),
+ "\
+--config argument `a.b = \"c\" # exactly` includes non-whitespace decoration",
+ );
+
+ let config = ConfigBuilder::new()
+ .config_arg("# exactly\na.b = \"c\"")
+ .build_err();
+ assert_error(
+ config.unwrap_err(),
+ "\
+--config argument `# exactly\na.b = \"c\"` includes non-whitespace decoration",
+ );
+}
+
+#[cargo_test]
+fn bad_cv_convert() {
+ // ConfigValue does not support all TOML types.
+ let config = ConfigBuilder::new().config_arg("a=2019-12-01").build_err();
+ assert_error(
+ config.unwrap_err(),
+ "\
+failed to convert --config argument `a=2019-12-01`
+
+Caused by:
+ failed to parse key `a`
+
+Caused by:
+ found TOML configuration value of unknown type `datetime`",
+ );
+}
+
+#[cargo_test]
+fn fail_to_merge_multiple_args() {
+ // Error message when multiple args fail to merge.
+ let config = ConfigBuilder::new()
+ .config_arg("foo='a'")
+ .config_arg("foo=['a']")
+ .build_err();
+ // This is a little repetitive, but hopefully the user can figure it out.
+ assert_error(
+ config.unwrap_err(),
+ "\
+failed to merge --config argument `foo=['a']`
+
+Caused by:
+ failed to merge key `foo` between --config cli option and --config cli option
+
+Caused by:
+ failed to merge config value from `--config cli option` into `--config cli option`: \
+ expected string, but found array",
+ );
+}
+
+#[cargo_test]
+fn cli_path() {
+ // --config path_to_file
+ fs::write(paths::root().join("myconfig.toml"), "key = 123").unwrap();
+ let config = ConfigBuilder::new()
+ .cwd(paths::root())
+ .config_arg("myconfig.toml")
+ .build();
+ assert_eq!(config.get::<u32>("key").unwrap(), 123);
+
+ let config = ConfigBuilder::new().config_arg("missing.toml").build_err();
+ assert_error(
+ config.unwrap_err(),
+ "\
+failed to parse value from --config argument `missing.toml` as a dotted key expression
+
+Caused by:
+ TOML parse error at line 1, column 13
+ |
+1 | missing.toml
+ | ^
+expected `.`, `=`
+",
+ );
+}
diff --git a/src/tools/cargo/tests/testsuite/config_include.rs b/src/tools/cargo/tests/testsuite/config_include.rs
new file mode 100644
index 000000000..ae568065a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/config_include.rs
@@ -0,0 +1,285 @@
+//! Tests for `include` config field.
+
+use super::config::{assert_error, write_config, write_config_at, ConfigBuilder};
+use cargo_test_support::{no_such_file_err_msg, project};
+
+#[cargo_test]
+fn gated() {
+ // Requires -Z flag.
+ write_config("include='other'");
+ write_config_at(
+ ".cargo/other",
+ "
+ othervalue = 1
+ ",
+ );
+ let config = ConfigBuilder::new().build();
+ assert_eq!(config.get::<Option<i32>>("othervalue").unwrap(), None);
+ let config = ConfigBuilder::new().unstable_flag("config-include").build();
+ assert_eq!(config.get::<i32>("othervalue").unwrap(), 1);
+}
+
+#[cargo_test]
+fn simple() {
+ // Simple test.
+ write_config_at(
+ ".cargo/config",
+ "
+ include = 'other'
+ key1 = 1
+ key2 = 2
+ ",
+ );
+ write_config_at(
+ ".cargo/other",
+ "
+ key2 = 3
+ key3 = 4
+ ",
+ );
+ let config = ConfigBuilder::new().unstable_flag("config-include").build();
+ assert_eq!(config.get::<i32>("key1").unwrap(), 1);
+ assert_eq!(config.get::<i32>("key2").unwrap(), 2);
+ assert_eq!(config.get::<i32>("key3").unwrap(), 4);
+}
+
+#[cargo_test]
+fn works_with_cli() {
+ write_config_at(
+ ".cargo/config.toml",
+ "
+ include = 'other.toml'
+ [build]
+ rustflags = ['-W', 'unused']
+ ",
+ );
+ write_config_at(
+ ".cargo/other.toml",
+ "
+ [build]
+ rustflags = ['-W', 'unsafe-code']
+ ",
+ );
+ let p = project().file("src/lib.rs", "").build();
+ p.cargo("check -v")
+ .with_stderr(
+ "\
+[CHECKING] foo v0.0.1 [..]
+[RUNNING] `rustc [..]-W unused`
+[FINISHED] [..]
+",
+ )
+ .run();
+ p.cargo("check -v -Z config-include")
+ .masquerade_as_nightly_cargo(&["config-include"])
+ .with_stderr(
+ "\
+[DIRTY] foo v0.0.1 ([..]): the rustflags changed
+[CHECKING] foo v0.0.1 [..]
+[RUNNING] `rustc [..]-W unsafe-code -W unused`
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn left_to_right() {
+ // How it merges multiple includes.
+ write_config_at(
+ ".cargo/config",
+ "
+ include = ['one', 'two']
+ primary = 1
+ ",
+ );
+ write_config_at(
+ ".cargo/one",
+ "
+ one = 1
+ primary = 2
+ ",
+ );
+ write_config_at(
+ ".cargo/two",
+ "
+ two = 2
+ primary = 3
+ ",
+ );
+ let config = ConfigBuilder::new().unstable_flag("config-include").build();
+ assert_eq!(config.get::<i32>("primary").unwrap(), 1);
+ assert_eq!(config.get::<i32>("one").unwrap(), 1);
+ assert_eq!(config.get::<i32>("two").unwrap(), 2);
+}
+
+#[cargo_test]
+fn missing_file() {
+ // Error when there's a missing file.
+ write_config("include='missing'");
+ let config = ConfigBuilder::new()
+ .unstable_flag("config-include")
+ .build_err();
+ assert_error(
+ config.unwrap_err(),
+ &format!(
+ "\
+could not load Cargo configuration
+
+Caused by:
+ failed to load config include `missing` from `[..]/.cargo/config`
+
+Caused by:
+ failed to read configuration file `[..]/.cargo/missing`
+
+Caused by:
+ {}",
+ no_such_file_err_msg()
+ ),
+ );
+}
+
+#[cargo_test]
+fn cycle() {
+ // Detects a cycle.
+ write_config_at(".cargo/config", "include='one'");
+ write_config_at(".cargo/one", "include='two'");
+ write_config_at(".cargo/two", "include='config'");
+ let config = ConfigBuilder::new()
+ .unstable_flag("config-include")
+ .build_err();
+ assert_error(
+ config.unwrap_err(),
+ "\
+could not load Cargo configuration
+
+Caused by:
+ failed to load config include `one` from `[..]/.cargo/config`
+
+Caused by:
+ failed to load config include `two` from `[..]/.cargo/one`
+
+Caused by:
+ failed to load config include `config` from `[..]/.cargo/two`
+
+Caused by:
+ config `include` cycle detected with path `[..]/.cargo/config`",
+ );
+}
+
+#[cargo_test]
+fn cli_include() {
+ // Using --config with include.
+ // CLI takes priority over files.
+ write_config_at(
+ ".cargo/config",
+ "
+ foo = 1
+ bar = 2
+ ",
+ );
+ write_config_at(".cargo/config-foo", "foo = 2");
+ let config = ConfigBuilder::new()
+ .unstable_flag("config-include")
+ .config_arg("include='.cargo/config-foo'")
+ .build();
+ assert_eq!(config.get::<i32>("foo").unwrap(), 2);
+ assert_eq!(config.get::<i32>("bar").unwrap(), 2);
+}
+
+#[cargo_test]
+fn bad_format() {
+ // Not a valid format.
+ write_config("include = 1");
+ let config = ConfigBuilder::new()
+ .unstable_flag("config-include")
+ .build_err();
+ assert_error(
+ config.unwrap_err(),
+ "\
+could not load Cargo configuration
+
+Caused by:
+ `include` expected a string or list, but found integer in `[..]/.cargo/config`",
+ );
+}
+
+#[cargo_test]
+fn cli_include_failed() {
+ // Error message when CLI include fails to load.
+ let config = ConfigBuilder::new()
+ .unstable_flag("config-include")
+ .config_arg("include='foobar'")
+ .build_err();
+ assert_error(
+ config.unwrap_err(),
+ &format!(
+ "\
+failed to load --config include
+
+Caused by:
+ failed to load config include `foobar` from `--config cli option`
+
+Caused by:
+ failed to read configuration file `[..]/foobar`
+
+Caused by:
+ {}",
+ no_such_file_err_msg()
+ ),
+ );
+}
+
+#[cargo_test]
+fn cli_merge_failed() {
+ // Error message when CLI include merge fails.
+ write_config("foo = ['a']");
+ write_config_at(
+ ".cargo/other",
+ "
+ foo = 'b'
+ ",
+ );
+ let config = ConfigBuilder::new()
+ .unstable_flag("config-include")
+ .config_arg("include='.cargo/other'")
+ .build_err();
+ // Maybe this error message should mention it was from an include file?
+ assert_error(
+ config.unwrap_err(),
+ "\
+failed to merge --config key `foo` into `[..]/.cargo/config`
+
+Caused by:
+ failed to merge config value from `[..]/.cargo/other` into `[..]/.cargo/config`: \
+ expected array, but found string",
+ );
+}
+
+#[cargo_test]
+fn cli_include_take_priority_over_env() {
+ write_config_at(".cargo/include.toml", "k='include'");
+
+ // k=env
+ let config = ConfigBuilder::new().env("CARGO_K", "env").build();
+ assert_eq!(config.get::<String>("k").unwrap(), "env");
+
+ // k=env
+ // --config 'include=".cargo/include.toml"'
+ let config = ConfigBuilder::new()
+ .env("CARGO_K", "env")
+ .unstable_flag("config-include")
+ .config_arg("include='.cargo/include.toml'")
+ .build();
+ assert_eq!(config.get::<String>("k").unwrap(), "include");
+
+ // k=env
+ // --config '.cargo/foo.toml'
+ write_config_at(".cargo/foo.toml", "include='include.toml'");
+ let config = ConfigBuilder::new()
+ .env("CARGO_K", "env")
+ .unstable_flag("config-include")
+ .config_arg(".cargo/foo.toml")
+ .build();
+ assert_eq!(config.get::<String>("k").unwrap(), "include");
+}
diff --git a/src/tools/cargo/tests/testsuite/corrupt_git.rs b/src/tools/cargo/tests/testsuite/corrupt_git.rs
new file mode 100644
index 000000000..2569e460b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/corrupt_git.rs
@@ -0,0 +1,159 @@
+//! Tests for corrupt git repos.
+
+use cargo_test_support::paths;
+use cargo_test_support::{basic_manifest, git, project};
+use cargo_util::paths as cargopaths;
+use std::fs;
+use std::path::{Path, PathBuf};
+
+#[cargo_test]
+fn deleting_database_files() {
+ let project = project();
+ let git_project = git::new("bar", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("bar", "0.5.0"))
+ .file("src/lib.rs", "")
+ });
+
+ let project = project
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ bar = {{ git = '{}' }}
+ "#,
+ git_project.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ project.cargo("check").run();
+
+ let mut files = Vec::new();
+ find_files(&paths::home().join(".cargo/git/db"), &mut files);
+ assert!(!files.is_empty());
+
+ let log = "cargo::sources::git=trace";
+ for file in files {
+ if !file.exists() {
+ continue;
+ }
+ println!("deleting {}", file.display());
+ cargopaths::remove_file(&file).unwrap();
+ project.cargo("check -v").env("CARGO_LOG", log).run();
+
+ if !file.exists() {
+ continue;
+ }
+ println!("truncating {}", file.display());
+ make_writable(&file);
+ fs::OpenOptions::new()
+ .write(true)
+ .open(&file)
+ .unwrap()
+ .set_len(2)
+ .unwrap();
+ project.cargo("check -v").env("CARGO_LOG", log).run();
+ }
+}
+
+#[cargo_test]
+fn deleting_checkout_files() {
+ let project = project();
+ let git_project = git::new("bar", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("bar", "0.5.0"))
+ .file("src/lib.rs", "")
+ });
+
+ let project = project
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ bar = {{ git = '{}' }}
+ "#,
+ git_project.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ project.cargo("check").run();
+
+ let dir = paths::home()
+ .join(".cargo/git/checkouts")
+ // get the first entry in the checkouts dir for the package's location
+ .read_dir()
+ .unwrap()
+ .next()
+ .unwrap()
+ .unwrap()
+ .path()
+ // get the first child of that checkout dir for our checkout
+ .read_dir()
+ .unwrap()
+ .next()
+ .unwrap()
+ .unwrap()
+ .path()
+ // and throw on .git to corrupt things
+ .join(".git");
+ let mut files = Vec::new();
+ find_files(&dir, &mut files);
+ assert!(!files.is_empty());
+
+ let log = "cargo::sources::git=trace";
+ for file in files {
+ if !file.exists() {
+ continue;
+ }
+ println!("deleting {}", file.display());
+ cargopaths::remove_file(&file).unwrap();
+ project.cargo("check -v").env("CARGO_LOG", log).run();
+
+ if !file.exists() {
+ continue;
+ }
+ println!("truncating {}", file.display());
+ make_writable(&file);
+ fs::OpenOptions::new()
+ .write(true)
+ .open(&file)
+ .unwrap()
+ .set_len(2)
+ .unwrap();
+ project.cargo("check -v").env("CARGO_LOG", log).run();
+ }
+}
+
+fn make_writable(path: &Path) {
+ let mut p = path.metadata().unwrap().permissions();
+ p.set_readonly(false);
+ fs::set_permissions(path, p).unwrap();
+}
+
+fn find_files(path: &Path, dst: &mut Vec<PathBuf>) {
+ for e in path.read_dir().unwrap() {
+ let e = e.unwrap();
+ let path = e.path();
+ if e.file_type().unwrap().is_dir() {
+ find_files(&path, dst);
+ } else {
+ dst.push(path);
+ }
+ }
+}
diff --git a/src/tools/cargo/tests/testsuite/credential_process.rs b/src/tools/cargo/tests/testsuite/credential_process.rs
new file mode 100644
index 000000000..8c202c6a3
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/credential_process.rs
@@ -0,0 +1,504 @@
+//! Tests for credential-process.
+
+use cargo_test_support::registry::TestRegistry;
+use cargo_test_support::{basic_manifest, cargo_process, paths, project, registry, Project};
+use std::fs::{self, read_to_string};
+
+fn toml_bin(proj: &Project, name: &str) -> String {
+ proj.bin(name).display().to_string().replace('\\', "\\\\")
+}
+
+#[cargo_test]
+fn gated() {
+ let _alternative = registry::RegistryBuilder::new()
+ .alternative()
+ .no_configure_token()
+ .build();
+
+ let cratesio = registry::RegistryBuilder::new()
+ .no_configure_token()
+ .build();
+
+ let p = project()
+ .file(
+ ".cargo/config",
+ r#"
+ [registry]
+ credential-process = "false"
+ "#,
+ )
+ .file("Cargo.toml", &basic_manifest("foo", "1.0.0"))
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("publish --no-verify")
+ .replace_crates_io(cratesio.index_url())
+ .masquerade_as_nightly_cargo(&["credential-process"])
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[ERROR] no token found, please run `cargo login`
+or use environment variable CARGO_REGISTRY_TOKEN
+",
+ )
+ .run();
+
+ p.change_file(
+ ".cargo/config",
+ r#"
+ [registry.alternative]
+ credential-process = "false"
+ "#,
+ );
+
+ p.cargo("publish --no-verify --registry alternative")
+ .masquerade_as_nightly_cargo(&["credential-process"])
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[ERROR] no token found for `alternative`, please run `cargo login --registry alternative`
+or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn warn_both_token_and_process() {
+ // Specifying both credential-process and a token in config should issue a warning.
+ let _server = registry::RegistryBuilder::new()
+ .http_api()
+ .http_index()
+ .alternative()
+ .no_configure_token()
+ .build();
+ let p = project()
+ .file(
+ ".cargo/config",
+ r#"
+ [registries.alternative]
+ token = "alternative-sekrit"
+ credential-process = "false"
+ "#,
+ )
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ description = "foo"
+ authors = []
+ license = "MIT"
+ homepage = "https://example.com/"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("publish --no-verify --registry alternative -Z credential-process")
+ .masquerade_as_nightly_cargo(&["credential-process"])
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[ERROR] both `token` and `credential-process` were specified in the config for registry `alternative`.
+Only one of these values may be set, remove one or the other to proceed.
+",
+ )
+ .run();
+
+ // Try with global credential-process, and registry-specific `token`.
+ // This should silently use the config token, and not run the "false" exe.
+ p.change_file(
+ ".cargo/config",
+ r#"
+ [registry]
+ credential-process = "false"
+
+ [registries.alternative]
+ token = "alternative-sekrit"
+ "#,
+ );
+ p.cargo("publish --no-verify --registry alternative -Z credential-process")
+ .masquerade_as_nightly_cargo(&["credential-process"])
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[PACKAGING] foo v0.1.0 [..]
+[PACKAGED] [..]
+[UPLOADING] foo v0.1.0 [..]
+[UPLOADED] foo v0.1.0 [..]
+note: Waiting [..]
+You may press ctrl-c [..]
+[PUBLISHED] foo v0.1.0 [..]
+",
+ )
+ .run();
+}
+
+/// Setup for a test that will issue a command that needs to fetch a token.
+///
+/// This does the following:
+///
+/// * Spawn a thread that will act as an API server.
+/// * Create a simple credential-process that will generate a fake token.
+/// * Create a simple `foo` project to run the test against.
+/// * Configure the credential-process config.
+///
+/// Returns the simple `foo` project to test against and the API server handle.
+fn get_token_test() -> (Project, TestRegistry) {
+ // API server that checks that the token is included correctly.
+ let server = registry::RegistryBuilder::new()
+ .no_configure_token()
+ .token(cargo_test_support::registry::Token::Plaintext(
+ "sekrit".to_string(),
+ ))
+ .alternative()
+ .http_api()
+ .build();
+ // The credential process to use.
+ let cred_proj = project()
+ .at("cred_proj")
+ .file("Cargo.toml", &basic_manifest("test-cred", "1.0.0"))
+ .file(
+ "src/main.rs",
+ r#"
+ use std::fs::File;
+ use std::io::Write;
+ fn main() {
+ let mut f = File::options()
+ .write(true)
+ .create(true)
+ .append(true)
+ .open("runs.log")
+ .unwrap();
+ write!(f, "+");
+ println!("sekrit");
+ } "#,
+ )
+ .build();
+ cred_proj.cargo("build").run();
+
+ let p = project()
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [registries.alternative]
+ index = "{}"
+ credential-process = ["{}"]
+ "#,
+ server.index_url(),
+ toml_bin(&cred_proj, "test-cred")
+ ),
+ )
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ description = "foo"
+ authors = []
+ license = "MIT"
+ homepage = "https://example.com/"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ (p, server)
+}
+
+#[cargo_test]
+fn publish() {
+ // Checks that credential-process is used for `cargo publish`.
+ let (p, _t) = get_token_test();
+
+ p.cargo("publish --no-verify --registry alternative -Z credential-process")
+ .masquerade_as_nightly_cargo(&["credential-process"])
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[PACKAGING] foo v0.1.0 [..]
+[PACKAGED] [..]
+[UPLOADING] foo v0.1.0 [..]
+[UPLOADED] foo v0.1.0 [..]
+note: Waiting [..]
+You may press ctrl-c [..]
+[PUBLISHED] foo v0.1.0 [..]
+",
+ )
+ .run();
+
+ let calls = read_to_string(p.root().join("runs.log")).unwrap().len();
+ assert_eq!(calls, 1);
+}
+
+#[cargo_test]
+fn basic_unsupported() {
+ // Non-action commands don't support login/logout.
+ let registry = registry::RegistryBuilder::new()
+ .no_configure_token()
+ .build();
+ cargo_util::paths::append(
+ &paths::home().join(".cargo/config"),
+ br#"
+ [registry]
+ credential-process = "false"
+ "#,
+ )
+ .unwrap();
+
+ cargo_process("login -Z credential-process abcdefg")
+ .replace_crates_io(registry.index_url())
+ .masquerade_as_nightly_cargo(&["credential-process"])
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] crates.io index
+[ERROR] credential process `false` cannot be used to log in, \
+the credential-process configuration value must pass the \
+`{action}` argument in the config to support this command
+",
+ )
+ .run();
+
+ cargo_process("logout -Z credential-process")
+ .replace_crates_io(registry.index_url())
+ .masquerade_as_nightly_cargo(&["credential-process"])
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] credential process `false` cannot be used to log out, \
+the credential-process configuration value must pass the \
+`{action}` argument in the config to support this command
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn login() {
+ let server = registry::RegistryBuilder::new()
+ .no_configure_token()
+ .build();
+ // The credential process to use.
+ let cred_proj = project()
+ .at("cred_proj")
+ .file("Cargo.toml", &basic_manifest("test-cred", "1.0.0"))
+ .file(
+ "src/main.rs",
+ r#"
+ use std::io::Read;
+
+ fn main() {{
+ assert_eq!(std::env::var("CARGO_REGISTRY_NAME_OPT").unwrap(), "crates-io");
+ assert_eq!(std::env::var("CARGO_REGISTRY_INDEX_URL").unwrap(), "https://github.com/rust-lang/crates.io-index");
+ assert_eq!(std::env::args().skip(1).next().unwrap(), "store");
+ let mut buffer = String::new();
+ std::io::stdin().read_to_string(&mut buffer).unwrap();
+ assert_eq!(buffer, "abcdefg\n");
+ std::fs::write("token-store", buffer).unwrap();
+ }}
+ "#,
+ )
+ .build();
+ cred_proj.cargo("build").run();
+
+ cargo_util::paths::append(
+ &paths::home().join(".cargo/config"),
+ format!(
+ r#"
+ [registry]
+ credential-process = ["{}", "{{action}}"]
+ "#,
+ toml_bin(&cred_proj, "test-cred")
+ )
+ .as_bytes(),
+ )
+ .unwrap();
+
+ cargo_process("login -Z credential-process abcdefg")
+ .masquerade_as_nightly_cargo(&["credential-process"])
+ .replace_crates_io(server.index_url())
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[LOGIN] token for `crates.io` saved
+",
+ )
+ .run();
+ assert_eq!(
+ fs::read_to_string(paths::root().join("token-store")).unwrap(),
+ "abcdefg\n"
+ );
+}
+
+#[cargo_test]
+fn logout() {
+ let server = registry::RegistryBuilder::new()
+ .no_configure_token()
+ .build();
+ // The credential process to use.
+ let cred_proj = project()
+ .at("cred_proj")
+ .file("Cargo.toml", &basic_manifest("test-cred", "1.0.0"))
+ .file(
+ "src/main.rs",
+ r#"
+ use std::io::Read;
+
+ fn main() {{
+ assert_eq!(std::env::var("CARGO_REGISTRY_NAME_OPT").unwrap(), "crates-io");
+ assert_eq!(std::env::var("CARGO_REGISTRY_INDEX_URL").unwrap(), "https://github.com/rust-lang/crates.io-index");
+ assert_eq!(std::env::args().skip(1).next().unwrap(), "erase");
+ std::fs::write("token-store", "").unwrap();
+ eprintln!("token for `crates-io` has been erased!")
+ }}
+ "#,
+ )
+ .build();
+ cred_proj.cargo("build").run();
+
+ cargo_util::paths::append(
+ &paths::home().join(".cargo/config"),
+ format!(
+ r#"
+ [registry]
+ credential-process = ["{}", "{{action}}"]
+ "#,
+ toml_bin(&cred_proj, "test-cred")
+ )
+ .as_bytes(),
+ )
+ .unwrap();
+
+ cargo_process("logout -Z credential-process")
+ .masquerade_as_nightly_cargo(&["credential-process"])
+ .replace_crates_io(server.index_url())
+ .with_stderr(
+ "\
+token for `crates-io` has been erased!
+[LOGOUT] token for `crates-io` has been removed from local storage
+[NOTE] This does not revoke the token on the registry server.
+ If you need to revoke the token, visit <https://crates.io/me> \
+ and follow the instructions there.
+",
+ )
+ .run();
+ assert_eq!(
+ fs::read_to_string(paths::root().join("token-store")).unwrap(),
+ ""
+ );
+}
+
+#[cargo_test]
+fn yank() {
+ let (p, _t) = get_token_test();
+
+ p.cargo("yank --version 0.1.0 --registry alternative -Z credential-process")
+ .masquerade_as_nightly_cargo(&["credential-process"])
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[YANK] foo@0.1.0
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn owner() {
+ let (p, _t) = get_token_test();
+
+ p.cargo("owner --add username --registry alternative -Z credential-process")
+ .masquerade_as_nightly_cargo(&["credential-process"])
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[OWNER] completed!
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn libexec_path() {
+ // cargo: prefixed names use the sysroot
+ let server = registry::RegistryBuilder::new()
+ .no_configure_token()
+ .build();
+ cargo_util::paths::append(
+ &paths::home().join(".cargo/config"),
+ br#"
+ [registry]
+ credential-process = "cargo:doesnotexist"
+ "#,
+ )
+ .unwrap();
+
+ cargo_process("login -Z credential-process abcdefg")
+ .masquerade_as_nightly_cargo(&["credential-process"])
+ .replace_crates_io(server.index_url())
+ .with_status(101)
+ .with_stderr(
+ // FIXME: Update "Caused by" error message once rust/pull/87704 is merged.
+ // On Windows, changing to a custom executable resolver has changed the
+ // error messages.
+ &format!("\
+[UPDATING] [..]
+[ERROR] failed to execute `[..]libexec/cargo-credential-doesnotexist[EXE]` to store authentication token for registry `crates-io`
+
+Caused by:
+ [..]
+"),
+ )
+ .run();
+}
+
+#[cargo_test]
+fn invalid_token_output() {
+ // Error when credential process does not output the expected format for a token.
+ let _server = registry::RegistryBuilder::new()
+ .alternative()
+ .no_configure_token()
+ .build();
+ let cred_proj = project()
+ .at("cred_proj")
+ .file("Cargo.toml", &basic_manifest("test-cred", "1.0.0"))
+ .file("src/main.rs", r#"fn main() { print!("a\nb\n"); } "#)
+ .build();
+ cred_proj.cargo("build").run();
+
+ cargo_util::paths::append(
+ &paths::home().join(".cargo/config"),
+ format!(
+ r#"
+ [registry]
+ credential-process = ["{}"]
+ "#,
+ toml_bin(&cred_proj, "test-cred")
+ )
+ .as_bytes(),
+ )
+ .unwrap();
+
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo", "1.0.0"))
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("publish --no-verify --registry alternative -Z credential-process")
+ .masquerade_as_nightly_cargo(&["credential-process"])
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[ERROR] credential process `[..]test-cred[EXE]` returned more than one line of output; expected a single token
+",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/cross_compile.rs b/src/tools/cargo/tests/testsuite/cross_compile.rs
new file mode 100644
index 000000000..cc9644550
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cross_compile.rs
@@ -0,0 +1,1342 @@
+//! Tests for cross compiling with --target.
+//!
+//! See `cargo_test_support::cross_compile` for more detail.
+
+use cargo_test_support::rustc_host;
+use cargo_test_support::{basic_bin_manifest, basic_manifest, cross_compile, project};
+
+#[cargo_test]
+fn simple_cross() {
+ if cross_compile::disabled() {
+ return;
+ }
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file(
+ "build.rs",
+ &format!(
+ r#"
+ fn main() {{
+ assert_eq!(std::env::var("TARGET").unwrap(), "{}");
+ }}
+ "#,
+ cross_compile::alternate()
+ ),
+ )
+ .file(
+ "src/main.rs",
+ &format!(
+ r#"
+ use std::env;
+ fn main() {{
+ assert_eq!(env::consts::ARCH, "{}");
+ }}
+ "#,
+ cross_compile::alternate_arch()
+ ),
+ )
+ .build();
+
+ let target = cross_compile::alternate();
+ p.cargo("build -v --target").arg(&target).run();
+ assert!(p.target_bin(target, "foo").is_file());
+
+ if cross_compile::can_run_on_host() {
+ p.process(&p.target_bin(target, "foo")).run();
+ }
+}
+
+#[cargo_test]
+fn simple_cross_config() {
+ if cross_compile::disabled() {
+ return;
+ }
+
+ let p = project()
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [build]
+ target = "{}"
+ "#,
+ cross_compile::alternate()
+ ),
+ )
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file(
+ "build.rs",
+ &format!(
+ r#"
+ fn main() {{
+ assert_eq!(std::env::var("TARGET").unwrap(), "{}");
+ }}
+ "#,
+ cross_compile::alternate()
+ ),
+ )
+ .file(
+ "src/main.rs",
+ &format!(
+ r#"
+ use std::env;
+ fn main() {{
+ assert_eq!(env::consts::ARCH, "{}");
+ }}
+ "#,
+ cross_compile::alternate_arch()
+ ),
+ )
+ .build();
+
+ let target = cross_compile::alternate();
+ p.cargo("build -v").run();
+ assert!(p.target_bin(target, "foo").is_file());
+
+ if cross_compile::can_run_on_host() {
+ p.process(&p.target_bin(target, "foo")).run();
+ }
+}
+
+#[cargo_test]
+fn simple_deps() {
+ if cross_compile::disabled() {
+ return;
+ }
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "../bar"
+ "#,
+ )
+ .file("src/main.rs", "extern crate bar; fn main() { bar::bar(); }")
+ .build();
+ let _p2 = project()
+ .at("bar")
+ .file("Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ let target = cross_compile::alternate();
+ p.cargo("build --target").arg(&target).run();
+ assert!(p.target_bin(target, "foo").is_file());
+
+ if cross_compile::can_run_on_host() {
+ p.process(&p.target_bin(target, "foo")).run();
+ }
+}
+
+/// Always take care of setting these so that
+/// `cross_compile::alternate()` is the actually-picked target
+fn per_crate_target_test(
+ default_target: Option<&'static str>,
+ forced_target: Option<&'static str>,
+ arg_target: Option<&'static str>,
+) {
+ if cross_compile::disabled() {
+ return;
+ }
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ cargo-features = ["per-package-target"]
+
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ build = "build.rs"
+ {}
+ {}
+ "#,
+ default_target
+ .map(|t| format!(r#"default-target = "{}""#, t))
+ .unwrap_or(String::new()),
+ forced_target
+ .map(|t| format!(r#"forced-target = "{}""#, t))
+ .unwrap_or(String::new()),
+ ),
+ )
+ .file(
+ "build.rs",
+ &format!(
+ r#"
+ fn main() {{
+ assert_eq!(std::env::var("TARGET").unwrap(), "{}");
+ }}
+ "#,
+ cross_compile::alternate()
+ ),
+ )
+ .file(
+ "src/main.rs",
+ &format!(
+ r#"
+ use std::env;
+ fn main() {{
+ assert_eq!(env::consts::ARCH, "{}");
+ }}
+ "#,
+ cross_compile::alternate_arch()
+ ),
+ )
+ .build();
+
+ let mut cmd = p.cargo("build -v");
+ if let Some(t) = arg_target {
+ cmd.arg("--target").arg(&t);
+ }
+ cmd.masquerade_as_nightly_cargo(&["per-package-target"])
+ .run();
+ assert!(p.target_bin(cross_compile::alternate(), "foo").is_file());
+
+ if cross_compile::can_run_on_host() {
+ p.process(&p.target_bin(cross_compile::alternate(), "foo"))
+ .run();
+ }
+}
+
+#[cargo_test]
+fn per_crate_default_target_is_default() {
+ per_crate_target_test(Some(cross_compile::alternate()), None, None);
+}
+
+#[cargo_test]
+fn per_crate_default_target_gets_overridden() {
+ per_crate_target_test(
+ Some(cross_compile::unused()),
+ None,
+ Some(cross_compile::alternate()),
+ );
+}
+
+#[cargo_test]
+fn per_crate_forced_target_is_default() {
+ per_crate_target_test(None, Some(cross_compile::alternate()), None);
+}
+
+#[cargo_test]
+fn per_crate_forced_target_does_not_get_overridden() {
+ per_crate_target_test(
+ None,
+ Some(cross_compile::alternate()),
+ Some(cross_compile::unused()),
+ );
+}
+
+#[cargo_test]
+fn workspace_with_multiple_targets() {
+ if cross_compile::disabled() {
+ return;
+ }
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["native", "cross"]
+ "#,
+ )
+ .file(
+ "native/Cargo.toml",
+ r#"
+ cargo-features = ["per-package-target"]
+
+ [package]
+ name = "native"
+ version = "0.0.0"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file(
+ "native/build.rs",
+ &format!(
+ r#"
+ fn main() {{
+ assert_eq!(std::env::var("TARGET").unwrap(), "{}");
+ }}
+ "#,
+ cross_compile::native()
+ ),
+ )
+ .file(
+ "native/src/main.rs",
+ &format!(
+ r#"
+ use std::env;
+ fn main() {{
+ assert_eq!(env::consts::ARCH, "{}");
+ }}
+ "#,
+ cross_compile::native_arch()
+ ),
+ )
+ .file(
+ "cross/Cargo.toml",
+ &format!(
+ r#"
+ cargo-features = ["per-package-target"]
+
+ [package]
+ name = "cross"
+ version = "0.0.0"
+ authors = []
+ build = "build.rs"
+ default-target = "{}"
+ "#,
+ cross_compile::alternate(),
+ ),
+ )
+ .file(
+ "cross/build.rs",
+ &format!(
+ r#"
+ fn main() {{
+ assert_eq!(std::env::var("TARGET").unwrap(), "{}");
+ }}
+ "#,
+ cross_compile::alternate()
+ ),
+ )
+ .file(
+ "cross/src/main.rs",
+ &format!(
+ r#"
+ use std::env;
+ fn main() {{
+ assert_eq!(env::consts::ARCH, "{}");
+ }}
+ "#,
+ cross_compile::alternate_arch()
+ ),
+ )
+ .build();
+
+ let mut cmd = p.cargo("build -v");
+ cmd.masquerade_as_nightly_cargo(&["per-package-target"])
+ .run();
+
+ assert!(p.bin("native").is_file());
+ assert!(p.target_bin(cross_compile::alternate(), "cross").is_file());
+
+ p.process(&p.bin("native")).run();
+ if cross_compile::can_run_on_host() {
+ p.process(&p.target_bin(cross_compile::alternate(), "cross"))
+ .run();
+ }
+}
+
+#[cargo_test]
+fn linker() {
+ if cross_compile::disabled() {
+ return;
+ }
+
+ let target = cross_compile::alternate();
+ let p = project()
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [target.{}]
+ linker = "my-linker-tool"
+ "#,
+ target
+ ),
+ )
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file(
+ "src/foo.rs",
+ &format!(
+ r#"
+ use std::env;
+ fn main() {{
+ assert_eq!(env::consts::ARCH, "{}");
+ }}
+ "#,
+ cross_compile::alternate_arch()
+ ),
+ )
+ .build();
+
+ p.cargo("build -v --target")
+ .arg(&target)
+ .with_status(101)
+ .with_stderr_contains(&format!(
+ "\
+[COMPILING] foo v0.5.0 ([CWD])
+[RUNNING] `rustc --crate-name foo src/foo.rs [..]--crate-type bin \
+ --emit=[..]link[..]-C debuginfo=2 \
+ -C metadata=[..] \
+ --out-dir [CWD]/target/{target}/debug/deps \
+ --target {target} \
+ -C linker=my-linker-tool \
+ -L dependency=[CWD]/target/{target}/debug/deps \
+ -L dependency=[CWD]/target/debug/deps`
+",
+ target = target,
+ ))
+ .run();
+}
+
+#[cargo_test(nightly, reason = "plugins are unstable")]
+fn plugin_with_extra_dylib_dep() {
+ if cross_compile::disabled() {
+ return;
+ }
+
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "../bar"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ #![feature(plugin)]
+ #![plugin(bar)]
+
+ fn main() {}
+ "#,
+ )
+ .build();
+ let _bar = project()
+ .at("bar")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ name = "bar"
+ plugin = true
+
+ [dependencies.baz]
+ path = "../baz"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ #![feature(rustc_private)]
+
+ extern crate baz;
+ extern crate rustc_driver;
+
+ use rustc_driver::plugin::Registry;
+
+ #[no_mangle]
+ pub fn __rustc_plugin_registrar(reg: &mut Registry) {
+ println!("{}", baz::baz());
+ }
+ "#,
+ )
+ .build();
+ let _baz = project()
+ .at("baz")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "baz"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ name = "baz"
+ crate_type = ["dylib"]
+ "#,
+ )
+ .file("src/lib.rs", "pub fn baz() -> i32 { 1 }")
+ .build();
+
+ let target = cross_compile::alternate();
+ foo.cargo("build --target").arg(&target).run();
+}
+
+#[cargo_test]
+fn cross_tests() {
+ if !cross_compile::can_run_on_host() {
+ return;
+ }
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.0"
+
+ [[bin]]
+ name = "bar"
+ "#,
+ )
+ .file(
+ "src/bin/bar.rs",
+ &format!(
+ r#"
+ #[allow(unused_extern_crates)]
+ extern crate foo;
+ use std::env;
+ fn main() {{
+ assert_eq!(env::consts::ARCH, "{}");
+ }}
+ #[test] fn test() {{ main() }}
+ "#,
+ cross_compile::alternate_arch()
+ ),
+ )
+ .file(
+ "src/lib.rs",
+ &format!(
+ r#"
+ use std::env;
+ pub fn foo() {{ assert_eq!(env::consts::ARCH, "{}"); }}
+ #[test] fn test_foo() {{ foo() }}
+ "#,
+ cross_compile::alternate_arch()
+ ),
+ )
+ .build();
+
+ let target = cross_compile::alternate();
+ p.cargo("test --target")
+ .arg(&target)
+ .with_stderr(&format!(
+ "\
+[COMPILING] foo v0.0.0 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/{triple}/debug/deps/foo-[..][EXE])
+[RUNNING] [..] (target/{triple}/debug/deps/bar-[..][EXE])",
+ triple = target
+ ))
+ .with_stdout_contains("test test_foo ... ok")
+ .with_stdout_contains("test test ... ok")
+ .run();
+}
+
+#[cargo_test]
+fn no_cross_doctests() {
+ if cross_compile::disabled() {
+ return;
+ }
+
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ //! ```
+ //! extern crate foo;
+ //! assert!(true);
+ //! ```
+ "#,
+ )
+ .build();
+
+ let host_output = "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo-[..][EXE])
+[DOCTEST] foo
+";
+
+ println!("a");
+ p.cargo("test").with_stderr(&host_output).run();
+
+ println!("b");
+ let target = rustc_host();
+ p.cargo("test -v --target")
+ .arg(&target)
+ // Unordered since the two `rustc` invocations happen concurrently.
+ .with_stderr_unordered(&format!(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name foo [..]--crate-type lib[..]
+[RUNNING] `rustc --crate-name foo [..]--test[..]
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `[CWD]/target/{target}/debug/deps/foo-[..][EXE]`
+[DOCTEST] foo
+[RUNNING] `rustdoc [..]--target {target}[..]`
+",
+ ))
+ .with_stdout(
+ "
+running 0 tests
+
+test result: ok. 0 passed[..]
+
+
+running 1 test
+test src/lib.rs - (line 2) ... ok
+
+test result: ok. 1 passed[..]
+
+",
+ )
+ .run();
+
+ println!("c");
+ let target = cross_compile::alternate();
+
+ // This will build the library, but does not build or run doc tests.
+ // This should probably be a warning or error.
+ p.cargo("test -v --doc --target")
+ .arg(&target)
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name foo [..]
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[NOTE] skipping doctests for foo v0.0.1 ([ROOT]/foo) (lib), \
+cross-compilation doctests are not yet supported
+See https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#doctest-xcompile \
+for more information.
+",
+ )
+ .run();
+
+ if !cross_compile::can_run_on_host() {
+ return;
+ }
+
+ // This tests the library, but does not run the doc tests.
+ p.cargo("test -v --target")
+ .arg(&target)
+ .with_stderr(&format!(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name foo [..]--test[..]
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `[CWD]/target/{triple}/debug/deps/foo-[..][EXE]`
+[NOTE] skipping doctests for foo v0.0.1 ([ROOT]/foo) (lib), \
+cross-compilation doctests are not yet supported
+See https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#doctest-xcompile \
+for more information.
+",
+ triple = target
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn simple_cargo_run() {
+ if !cross_compile::can_run_on_host() {
+ return;
+ }
+
+ let p = project()
+ .file(
+ "src/main.rs",
+ &format!(
+ r#"
+ use std::env;
+ fn main() {{
+ assert_eq!(env::consts::ARCH, "{}");
+ }}
+ "#,
+ cross_compile::alternate_arch()
+ ),
+ )
+ .build();
+
+ let target = cross_compile::alternate();
+ p.cargo("run --target").arg(&target).run();
+}
+
+#[cargo_test]
+fn cross_with_a_build_script() {
+ if cross_compile::disabled() {
+ return;
+ }
+
+ let target = cross_compile::alternate();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ build = 'build.rs'
+ "#,
+ )
+ .file(
+ "build.rs",
+ &format!(
+ r#"
+ use std::env;
+ use std::path::PathBuf;
+ fn main() {{
+ assert_eq!(env::var("TARGET").unwrap(), "{0}");
+ let mut path = PathBuf::from(env::var_os("OUT_DIR").unwrap());
+ assert_eq!(path.file_name().unwrap().to_str().unwrap(), "out");
+ path.pop();
+ assert!(path.file_name().unwrap().to_str().unwrap()
+ .starts_with("foo-"));
+ path.pop();
+ assert_eq!(path.file_name().unwrap().to_str().unwrap(), "build");
+ path.pop();
+ assert_eq!(path.file_name().unwrap().to_str().unwrap(), "debug");
+ path.pop();
+ assert_eq!(path.file_name().unwrap().to_str().unwrap(), "{0}");
+ path.pop();
+ assert_eq!(path.file_name().unwrap().to_str().unwrap(), "target");
+ }}
+ "#,
+ target
+ ),
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build -v --target")
+ .arg(&target)
+ .with_stderr(&format!(
+ "\
+[COMPILING] foo v0.0.0 ([CWD])
+[RUNNING] `rustc [..] build.rs [..] --out-dir [CWD]/target/debug/build/foo-[..]`
+[RUNNING] `[CWD]/target/debug/build/foo-[..]/build-script-build`
+[RUNNING] `rustc [..] src/main.rs [..] --target {target} [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ target = target,
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn build_script_needed_for_host_and_target() {
+ if cross_compile::disabled() {
+ return;
+ }
+
+ let target = cross_compile::alternate();
+ let host = rustc_host();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ build = 'build.rs'
+
+ [dependencies.d1]
+ path = "d1"
+ [build-dependencies.d2]
+ path = "d2"
+ "#,
+ )
+ .file(
+ "build.rs",
+ r#"
+ #[allow(unused_extern_crates)]
+ extern crate d2;
+ fn main() { d2::d2(); }
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ "
+ #[allow(unused_extern_crates)]
+ extern crate d1;
+ fn main() { d1::d1(); }
+ ",
+ )
+ .file(
+ "d1/Cargo.toml",
+ r#"
+ [package]
+ name = "d1"
+ version = "0.0.0"
+ authors = []
+ build = 'build.rs'
+ "#,
+ )
+ .file("d1/src/lib.rs", "pub fn d1() {}")
+ .file(
+ "d1/build.rs",
+ r#"
+ use std::env;
+ fn main() {
+ let target = env::var("TARGET").unwrap();
+ println!("cargo:rustc-flags=-L /path/to/{}", target);
+ }
+ "#,
+ )
+ .file(
+ "d2/Cargo.toml",
+ r#"
+ [package]
+ name = "d2"
+ version = "0.0.0"
+ authors = []
+
+ [dependencies.d1]
+ path = "../d1"
+ "#,
+ )
+ .file(
+ "d2/src/lib.rs",
+ "
+ #[allow(unused_extern_crates)]
+ extern crate d1;
+ pub fn d2() { d1::d1(); }
+ ",
+ )
+ .build();
+
+ p.cargo("build -v --target")
+ .arg(&target)
+ .with_stderr_contains(&"[COMPILING] d1 v0.0.0 ([CWD]/d1)")
+ .with_stderr_contains(
+ "[RUNNING] `rustc [..] d1/build.rs [..] --out-dir [CWD]/target/debug/build/d1-[..]`",
+ )
+ .with_stderr_contains("[RUNNING] `[CWD]/target/debug/build/d1-[..]/build-script-build`")
+ .with_stderr_contains("[RUNNING] `rustc [..] d1/src/lib.rs [..]`")
+ .with_stderr_contains("[COMPILING] d2 v0.0.0 ([CWD]/d2)")
+ .with_stderr_contains(&format!(
+ "[RUNNING] `rustc [..] d2/src/lib.rs [..] -L /path/to/{host}`",
+ host = host
+ ))
+ .with_stderr_contains("[COMPILING] foo v0.0.0 ([CWD])")
+ .with_stderr_contains(&format!(
+ "[RUNNING] `rustc [..] build.rs [..] --out-dir [CWD]/target/debug/build/foo-[..] \
+ -L /path/to/{host}`",
+ host = host
+ ))
+ .with_stderr_contains(&format!(
+ "[RUNNING] `rustc [..] src/main.rs [..] --target {target} [..] \
+ -L /path/to/{target}`",
+ target = target
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn build_deps_for_the_right_arch() {
+ if cross_compile::disabled() {
+ return;
+ }
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+
+ [dependencies.d2]
+ path = "d2"
+ "#,
+ )
+ .file("src/main.rs", "extern crate d2; fn main() {}")
+ .file("d1/Cargo.toml", &basic_manifest("d1", "0.0.0"))
+ .file("d1/src/lib.rs", "pub fn d1() {}")
+ .file(
+ "d2/Cargo.toml",
+ r#"
+ [package]
+ name = "d2"
+ version = "0.0.0"
+ authors = []
+ build = "build.rs"
+
+ [build-dependencies.d1]
+ path = "../d1"
+ "#,
+ )
+ .file("d2/build.rs", "extern crate d1; fn main() {}")
+ .file("d2/src/lib.rs", "")
+ .build();
+
+ let target = cross_compile::alternate();
+ p.cargo("build -v --target").arg(&target).run();
+}
+
+#[cargo_test]
+fn build_script_only_host() {
+ if cross_compile::disabled() {
+ return;
+ }
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ build = "build.rs"
+
+ [build-dependencies.d1]
+ path = "d1"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("build.rs", "extern crate d1; fn main() {}")
+ .file(
+ "d1/Cargo.toml",
+ r#"
+ [package]
+ name = "d1"
+ version = "0.0.0"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file("d1/src/lib.rs", "pub fn d1() {}")
+ .file(
+ "d1/build.rs",
+ r#"
+ use std::env;
+
+ fn main() {
+ assert!(env::var("OUT_DIR").unwrap().replace("\\", "/")
+ .contains("target/debug/build/d1-"),
+ "bad: {:?}", env::var("OUT_DIR"));
+ }
+ "#,
+ )
+ .build();
+
+ let target = cross_compile::alternate();
+ p.cargo("build -v --target").arg(&target).run();
+}
+
+#[cargo_test]
+fn plugin_build_script_right_arch() {
+ if cross_compile::disabled() {
+ return;
+ }
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+
+ [lib]
+ name = "foo"
+ plugin = true
+ "#,
+ )
+ .file("build.rs", "fn main() {}")
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build -v --target")
+ .arg(cross_compile::alternate())
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[RUNNING] `rustc [..] build.rs [..]`
+[RUNNING] `[..]/build-script-build`
+[RUNNING] `rustc [..] src/lib.rs [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_script_with_platform_specific_dependencies() {
+ if cross_compile::disabled() {
+ return;
+ }
+
+ let target = cross_compile::alternate();
+ let host = rustc_host();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+
+ [build-dependencies.d1]
+ path = "d1"
+ "#,
+ )
+ .file(
+ "build.rs",
+ "
+ #[allow(unused_extern_crates)]
+ extern crate d1;
+ fn main() {}
+ ",
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "d1/Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "d1"
+ version = "0.0.0"
+ authors = []
+
+ [target.{}.dependencies]
+ d2 = {{ path = "../d2" }}
+ "#,
+ host
+ ),
+ )
+ .file(
+ "d1/src/lib.rs",
+ "#[allow(unused_extern_crates)] extern crate d2;",
+ )
+ .file("d2/Cargo.toml", &basic_manifest("d2", "0.0.0"))
+ .file("d2/src/lib.rs", "")
+ .build();
+
+ p.cargo("build -v --target")
+ .arg(&target)
+ .with_stderr(&format!(
+ "\
+[COMPILING] d2 v0.0.0 ([..])
+[RUNNING] `rustc [..] d2/src/lib.rs [..]`
+[COMPILING] d1 v0.0.0 ([..])
+[RUNNING] `rustc [..] d1/src/lib.rs [..]`
+[COMPILING] foo v0.0.1 ([..])
+[RUNNING] `rustc [..] build.rs [..]`
+[RUNNING] `[CWD]/target/debug/build/foo-[..]/build-script-build`
+[RUNNING] `rustc [..] src/lib.rs [..] --target {target} [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ target = target
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn platform_specific_dependencies_do_not_leak() {
+ if cross_compile::disabled() {
+ return;
+ }
+
+ let target = cross_compile::alternate();
+ let host = rustc_host();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+
+ [dependencies.d1]
+ path = "d1"
+
+ [build-dependencies.d1]
+ path = "d1"
+ "#,
+ )
+ .file("build.rs", "extern crate d1; fn main() {}")
+ .file("src/lib.rs", "")
+ .file(
+ "d1/Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "d1"
+ version = "0.0.0"
+ authors = []
+
+ [target.{}.dependencies]
+ d2 = {{ path = "../d2" }}
+ "#,
+ host
+ ),
+ )
+ .file("d1/src/lib.rs", "extern crate d2;")
+ .file("d1/Cargo.toml", &basic_manifest("d1", "0.0.0"))
+ .file("d2/src/lib.rs", "")
+ .build();
+
+ p.cargo("build -v --target")
+ .arg(&target)
+ .with_status(101)
+ .with_stderr_contains("[..] can't find crate for `d2`[..]")
+ .run();
+}
+
+#[cargo_test]
+fn platform_specific_variables_reflected_in_build_scripts() {
+ if cross_compile::disabled() {
+ return;
+ }
+
+ let target = cross_compile::alternate();
+ let host = rustc_host();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+
+ [target.{host}.dependencies]
+ d1 = {{ path = "d1" }}
+
+ [target.{target}.dependencies]
+ d2 = {{ path = "d2" }}
+ "#,
+ host = host,
+ target = target
+ ),
+ )
+ .file(
+ "build.rs",
+ &format!(
+ r#"
+ use std::env;
+
+ fn main() {{
+ let platform = env::var("TARGET").unwrap();
+ let (expected, not_expected) = match &platform[..] {{
+ "{host}" => ("DEP_D1_VAL", "DEP_D2_VAL"),
+ "{target}" => ("DEP_D2_VAL", "DEP_D1_VAL"),
+ _ => panic!("unknown platform")
+ }};
+
+ env::var(expected).ok()
+ .expect(&format!("missing {{}}", expected));
+ env::var(not_expected).err()
+ .expect(&format!("found {{}}", not_expected));
+ }}
+ "#,
+ host = host,
+ target = target
+ ),
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "d1/Cargo.toml",
+ r#"
+ [package]
+ name = "d1"
+ version = "0.0.0"
+ authors = []
+ links = "d1"
+ build = "build.rs"
+ "#,
+ )
+ .file("d1/build.rs", r#"fn main() { println!("cargo:val=1") }"#)
+ .file("d1/src/lib.rs", "")
+ .file(
+ "d2/Cargo.toml",
+ r#"
+ [package]
+ name = "d2"
+ version = "0.0.0"
+ authors = []
+ links = "d2"
+ build = "build.rs"
+ "#,
+ )
+ .file("d2/build.rs", r#"fn main() { println!("cargo:val=1") }"#)
+ .file("d2/src/lib.rs", "")
+ .build();
+
+ p.cargo("build -v").run();
+ p.cargo("build -v --target").arg(&target).run();
+}
+
+#[cargo_test]
+#[cfg_attr(
+ target_os = "macos",
+ ignore = "don't have a dylib cross target on macos"
+)]
+fn cross_test_dylib() {
+ if cross_compile::disabled() {
+ return;
+ }
+
+ let target = cross_compile::alternate();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ name = "foo"
+ crate_type = ["dylib"]
+
+ [dependencies.bar]
+ path = "bar"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ extern crate bar as the_bar;
+
+ pub fn bar() { the_bar::baz(); }
+
+ #[test]
+ fn foo() { bar(); }
+ "#,
+ )
+ .file(
+ "tests/test.rs",
+ r#"
+ extern crate foo as the_foo;
+
+ #[test]
+ fn foo() { the_foo::bar(); }
+ "#,
+ )
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ name = "bar"
+ crate_type = ["dylib"]
+ "#,
+ )
+ .file(
+ "bar/src/lib.rs",
+ &format!(
+ r#"
+ use std::env;
+ pub fn baz() {{
+ assert_eq!(env::consts::ARCH, "{}");
+ }}
+ "#,
+ cross_compile::alternate_arch()
+ ),
+ )
+ .build();
+
+ p.cargo("test --target")
+ .arg(&target)
+ .with_stderr(&format!(
+ "\
+[COMPILING] bar v0.0.1 ([CWD]/bar)
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/{arch}/debug/deps/foo-[..][EXE])
+[RUNNING] [..] (target/{arch}/debug/deps/test-[..][EXE])",
+ arch = cross_compile::alternate()
+ ))
+ .with_stdout_contains_n("test foo ... ok", 2)
+ .run();
+}
+
+#[cargo_test(nightly, reason = "-Zdoctest-xcompile is unstable")]
+fn doctest_xcompile_linker() {
+ if cross_compile::disabled() {
+ return;
+ }
+
+ let target = cross_compile::alternate();
+ let p = project()
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [target.{}]
+ linker = "my-linker-tool"
+ "#,
+ target
+ ),
+ )
+ .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file(
+ "src/lib.rs",
+ r#"
+ /// ```
+ /// assert_eq!(1, 1);
+ /// ```
+ pub fn foo() {}
+ "#,
+ )
+ .build();
+
+ // Fails because `my-linker-tool` doesn't actually exist.
+ p.cargo("test --doc -v -Zdoctest-xcompile --target")
+ .arg(&target)
+ .with_status(101)
+ .masquerade_as_nightly_cargo(&["doctest-xcompile"])
+ .with_stderr_contains(&format!(
+ "\
+[RUNNING] `rustdoc --crate-type lib --crate-name foo --test [..]\
+ --target {target} [..] -C linker=my-linker-tool[..]
+",
+ target = target,
+ ))
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/cross_publish.rs b/src/tools/cargo/tests/testsuite/cross_publish.rs
new file mode 100644
index 000000000..83e0ecab7
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cross_publish.rs
@@ -0,0 +1,122 @@
+//! Tests for publishing using the `--target` flag.
+
+use std::fs::File;
+
+use cargo_test_support::{cross_compile, project, publish, registry};
+
+#[cargo_test]
+fn simple_cross_package() {
+ if cross_compile::disabled() {
+ return;
+ }
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ repository = "bar"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ &format!(
+ r#"
+ use std::env;
+ fn main() {{
+ assert_eq!(env::consts::ARCH, "{}");
+ }}
+ "#,
+ cross_compile::alternate_arch()
+ ),
+ )
+ .build();
+
+ let target = cross_compile::alternate();
+
+ p.cargo("package --target")
+ .arg(&target)
+ .with_stderr(
+ "\
+[PACKAGING] foo v0.0.0 ([CWD])
+[VERIFYING] foo v0.0.0 ([CWD])
+[COMPILING] foo v0.0.0 ([CWD]/target/package/foo-0.0.0)
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[PACKAGED] 4 files, [..] ([..] compressed)
+",
+ )
+ .run();
+
+ // Check that the tarball contains the files
+ let f = File::open(&p.root().join("target/package/foo-0.0.0.crate")).unwrap();
+ publish::validate_crate_contents(
+ f,
+ "foo-0.0.0.crate",
+ &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"],
+ &[],
+ );
+}
+
+#[cargo_test]
+fn publish_with_target() {
+ if cross_compile::disabled() {
+ return;
+ }
+
+ // `publish` generally requires a remote registry
+ let registry = registry::RegistryBuilder::new().http_api().build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ repository = "bar"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ &format!(
+ r#"
+ use std::env;
+ fn main() {{
+ assert_eq!(env::consts::ARCH, "{}");
+ }}
+ "#,
+ cross_compile::alternate_arch()
+ ),
+ )
+ .build();
+
+ let target = cross_compile::alternate();
+
+ p.cargo("publish")
+ .replace_crates_io(registry.index_url())
+ .arg("--target")
+ .arg(&target)
+ .with_stderr(
+ "\
+[UPDATING] crates.io index
+[PACKAGING] foo v0.0.0 ([CWD])
+[VERIFYING] foo v0.0.0 ([CWD])
+[COMPILING] foo v0.0.0 ([CWD]/target/package/foo-0.0.0)
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[PACKAGED] [..]
+[UPLOADING] foo v0.0.0 ([CWD])
+[UPLOADED] foo v0.0.0 to registry `crates-io`
+note: Waiting [..]
+You may press ctrl-c [..]
+[PUBLISHED] foo v0.0.0 at registry `crates-io`
+",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/custom_target.rs b/src/tools/cargo/tests/testsuite/custom_target.rs
new file mode 100644
index 000000000..b7ad4d835
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/custom_target.rs
@@ -0,0 +1,250 @@
+//! Tests for custom json target specifications.
+
+use cargo_test_support::{basic_manifest, project};
+use std::fs;
+
+const MINIMAL_LIB: &str = r#"
+#![feature(no_core)]
+#![feature(lang_items)]
+#![no_core]
+
+#[lang = "sized"]
+pub trait Sized {
+ // Empty.
+}
+#[lang = "copy"]
+pub trait Copy {
+ // Empty.
+}
+"#;
+
+const SIMPLE_SPEC: &str = r#"
+{
+ "llvm-target": "x86_64-unknown-none-gnu",
+ "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
+ "arch": "x86_64",
+ "target-endian": "little",
+ "target-pointer-width": "64",
+ "target-c-int-width": "32",
+ "os": "none",
+ "linker-flavor": "ld.lld",
+ "linker": "rust-lld",
+ "executables": true
+}
+"#;
+
+#[cargo_test(nightly, reason = "requires features no_core, lang_items")]
+fn custom_target_minimal() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ &"
+ __MINIMAL_LIB__
+
+ pub fn foo() -> u32 {
+ 42
+ }
+ "
+ .replace("__MINIMAL_LIB__", MINIMAL_LIB),
+ )
+ .file("custom-target.json", SIMPLE_SPEC)
+ .build();
+
+ p.cargo("build --lib --target custom-target.json -v").run();
+ p.cargo("build --lib --target src/../custom-target.json -v")
+ .run();
+
+ // Ensure that the correct style of flag is passed to --target with doc tests.
+ p.cargo("test --doc --target src/../custom-target.json -v -Zdoctest-xcompile")
+ .masquerade_as_nightly_cargo(&["doctest-xcompile", "no_core", "lang_items"])
+ .with_stderr_contains("[RUNNING] `rustdoc [..]--target [..]foo/custom-target.json[..]")
+ .run();
+}
+
+#[cargo_test(nightly, reason = "requires features no_core, lang_items, auto_traits")]
+fn custom_target_dependency() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.0.1"
+ authors = ["author@example.com"]
+
+ [dependencies]
+ bar = { path = "bar" }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ #![feature(no_core)]
+ #![feature(lang_items)]
+ #![feature(auto_traits)]
+ #![no_core]
+
+ extern crate bar;
+
+ pub fn foo() -> u32 {
+ bar::bar()
+ }
+
+ #[lang = "freeze"]
+ unsafe auto trait Freeze {}
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file(
+ "bar/src/lib.rs",
+ &"
+ __MINIMAL_LIB__
+
+ pub fn bar() -> u32 {
+ 42
+ }
+ "
+ .replace("__MINIMAL_LIB__", MINIMAL_LIB),
+ )
+ .file("custom-target.json", SIMPLE_SPEC)
+ .build();
+
+ p.cargo("build --lib --target custom-target.json -v").run();
+}
+
+#[cargo_test(nightly, reason = "requires features no_core, lang_items")]
+fn custom_bin_target() {
+ let p = project()
+ .file(
+ "src/main.rs",
+ &"
+ #![no_main]
+ __MINIMAL_LIB__
+ "
+ .replace("__MINIMAL_LIB__", MINIMAL_LIB),
+ )
+ .file("custom-bin-target.json", SIMPLE_SPEC)
+ .build();
+
+ p.cargo("build --target custom-bin-target.json -v").run();
+}
+
+#[cargo_test(nightly, reason = "requires features no_core, lang_items")]
+fn changing_spec_rebuilds() {
+ // Changing the .json file will trigger a rebuild.
+ let p = project()
+ .file(
+ "src/lib.rs",
+ &"
+ __MINIMAL_LIB__
+
+ pub fn foo() -> u32 {
+ 42
+ }
+ "
+ .replace("__MINIMAL_LIB__", MINIMAL_LIB),
+ )
+ .file("custom-target.json", SIMPLE_SPEC)
+ .build();
+
+ p.cargo("build --lib --target custom-target.json -v").run();
+ p.cargo("build --lib --target custom-target.json -v")
+ .with_stderr(
+ "\
+[FRESH] foo [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+ let spec_path = p.root().join("custom-target.json");
+ let spec = fs::read_to_string(&spec_path).unwrap();
+ // Some arbitrary change that I hope is safe.
+ let spec = spec.replace('{', "{\n\"vendor\": \"unknown\",\n");
+ fs::write(&spec_path, spec).unwrap();
+ p.cargo("build --lib --target custom-target.json -v")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 [..]
+[RUNNING] `rustc [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test(nightly, reason = "requires features no_core, lang_items")]
+fn changing_spec_relearns_crate_types() {
+ // Changing the .json file will invalidate the cache of crate types.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [lib]
+ crate-type = ["cdylib"]
+ "#,
+ )
+ .file("src/lib.rs", MINIMAL_LIB)
+ .file("custom-target.json", SIMPLE_SPEC)
+ .build();
+
+ p.cargo("build --lib --target custom-target.json -v")
+ .with_status(101)
+ .with_stderr("error: cannot produce cdylib for `foo [..]")
+ .run();
+
+ // Enable dynamic linking.
+ let spec_path = p.root().join("custom-target.json");
+ let spec = fs::read_to_string(&spec_path).unwrap();
+ let spec = spec.replace('{', "{\n\"dynamic-linking\": true,\n");
+ fs::write(&spec_path, spec).unwrap();
+
+ p.cargo("build --lib --target custom-target.json -v")
+ .with_stderr(
+ "\
+[COMPILING] foo [..]
+[RUNNING] `rustc [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test(nightly, reason = "requires features no_core, lang_items")]
+fn custom_target_ignores_filepath() {
+ // Changing the path of the .json file will not trigger a rebuild.
+ let p = project()
+ .file(
+ "src/lib.rs",
+ &"
+ __MINIMAL_LIB__
+
+ pub fn foo() -> u32 {
+ 42
+ }
+ "
+ .replace("__MINIMAL_LIB__", MINIMAL_LIB),
+ )
+ .file("b/custom-target.json", SIMPLE_SPEC)
+ .file("a/custom-target.json", SIMPLE_SPEC)
+ .build();
+
+ // Should build the library the first time.
+ p.cargo("build --lib --target a/custom-target.json")
+ .with_stderr(
+ "\
+[..]Compiling foo v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ // But not the second time, even though the path to the custom target is dfferent.
+ p.cargo("build --lib --target b/custom-target.json")
+ .with_stderr("[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]")
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/death.rs b/src/tools/cargo/tests/testsuite/death.rs
new file mode 100644
index 000000000..f0e182d01
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/death.rs
@@ -0,0 +1,101 @@
+//! Tests for ctrl-C handling.
+
+use std::fs;
+use std::io::{self, Read};
+use std::net::TcpListener;
+use std::process::{Child, Stdio};
+use std::thread;
+
+use cargo_test_support::{project, slow_cpu_multiplier};
+
+#[cargo_test]
+fn ctrl_c_kills_everyone() {
+ let listener = TcpListener::bind("127.0.0.1:0").unwrap();
+ let addr = listener.local_addr().unwrap();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ &format!(
+ r#"
+ use std::net::TcpStream;
+ use std::io::Read;
+
+ fn main() {{
+ let mut socket = TcpStream::connect("{}").unwrap();
+ let _ = socket.read(&mut [0; 10]);
+ panic!("that read should never return");
+ }}
+ "#,
+ addr
+ ),
+ )
+ .build();
+
+ let mut cargo = p.cargo("check").build_command();
+ cargo
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped())
+ .stderr(Stdio::piped())
+ .env("__CARGO_TEST_SETSID_PLEASE_DONT_USE_ELSEWHERE", "1");
+ let mut child = cargo.spawn().unwrap();
+
+ let mut sock = listener.accept().unwrap().0;
+ ctrl_c(&mut child);
+
+ assert!(!child.wait().unwrap().success());
+ match sock.read(&mut [0; 10]) {
+ Ok(n) => assert_eq!(n, 0),
+ Err(e) => assert_eq!(e.kind(), io::ErrorKind::ConnectionReset),
+ }
+
+ // Ok so what we just did was spawn cargo that spawned a build script, then
+ // we killed cargo in hopes of it killing the build script as well. If all
+ // went well the build script is now dead. On Windows, however, this is
+ // enforced with job objects which means that it may actually be in the
+ // *process* of being torn down at this point.
+ //
+ // Now on Windows we can't completely remove a file until all handles to it
+ // have been closed. Including those that represent running processes. So if
+ // we were to return here then there may still be an open reference to some
+ // file in the build directory. What we want to actually do is wait for the
+ // build script to *complete* exit. Take care of that by blowing away the
+ // build directory here, and panicking if we eventually spin too long
+ // without being able to.
+ for i in 0..10 {
+ match fs::remove_dir_all(&p.root().join("target")) {
+ Ok(()) => return,
+ Err(e) => println!("attempt {}: {}", i, e),
+ }
+ thread::sleep(slow_cpu_multiplier(100));
+ }
+
+ panic!(
+ "couldn't remove build directory after a few tries, seems like \
+ we won't be able to!"
+ );
+}
+
+#[cfg(unix)]
+pub fn ctrl_c(child: &mut Child) {
+ let r = unsafe { libc::kill(-(child.id() as i32), libc::SIGINT) };
+ if r < 0 {
+ panic!("failed to kill: {}", io::Error::last_os_error());
+ }
+}
+
+#[cfg(windows)]
+pub fn ctrl_c(child: &mut Child) {
+ child.kill().unwrap();
+}
diff --git a/src/tools/cargo/tests/testsuite/dep_info.rs b/src/tools/cargo/tests/testsuite/dep_info.rs
new file mode 100644
index 000000000..e9ea47792
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/dep_info.rs
@@ -0,0 +1,600 @@
+//! Tests for dep-info files. This includes the dep-info file Cargo creates in
+//! the output directory, and the ones stored in the fingerprint.
+
+use cargo_test_support::compare::assert_match_exact;
+use cargo_test_support::paths::{self, CargoPathExt};
+use cargo_test_support::registry::Package;
+use cargo_test_support::{
+ basic_bin_manifest, basic_manifest, main_file, project, rustc_host, Project,
+};
+use filetime::FileTime;
+use std::fs;
+use std::path::Path;
+use std::str;
+
+// Helper for testing dep-info files in the fingerprint dir.
+#[track_caller]
+fn assert_deps(project: &Project, fingerprint: &str, test_cb: impl Fn(&Path, &[(u8, &str)])) {
+ let mut files = project
+ .glob(fingerprint)
+ .map(|f| f.expect("unwrap glob result"))
+ // Filter out `.json` entries.
+ .filter(|f| f.extension().is_none());
+ let info_path = files
+ .next()
+ .unwrap_or_else(|| panic!("expected 1 dep-info file at {}, found 0", fingerprint));
+ assert!(files.next().is_none(), "expected only 1 dep-info file");
+ let dep_info = fs::read(&info_path).unwrap();
+ let dep_info = &mut &dep_info[..];
+ let deps = (0..read_usize(dep_info))
+ .map(|_| {
+ (
+ read_u8(dep_info),
+ str::from_utf8(read_bytes(dep_info)).unwrap(),
+ )
+ })
+ .collect::<Vec<_>>();
+ test_cb(&info_path, &deps);
+
+ fn read_usize(bytes: &mut &[u8]) -> usize {
+ let ret = &bytes[..4];
+ *bytes = &bytes[4..];
+
+ u32::from_le_bytes(ret.try_into().unwrap()) as usize
+ }
+
+ fn read_u8(bytes: &mut &[u8]) -> u8 {
+ let ret = bytes[0];
+ *bytes = &bytes[1..];
+ ret
+ }
+
+ fn read_bytes<'a>(bytes: &mut &'a [u8]) -> &'a [u8] {
+ let n = read_usize(bytes);
+ let ret = &bytes[..n];
+ *bytes = &bytes[n..];
+ ret
+ }
+}
+
+fn assert_deps_contains(project: &Project, fingerprint: &str, expected: &[(u8, &str)]) {
+ assert_deps(project, fingerprint, |info_path, entries| {
+ for (e_kind, e_path) in expected {
+ let pattern = glob::Pattern::new(e_path).unwrap();
+ let count = entries
+ .iter()
+ .filter(|(kind, path)| kind == e_kind && pattern.matches(path))
+ .count();
+ if count != 1 {
+ panic!(
+ "Expected 1 match of {} {} in {:?}, got {}:\n{:#?}",
+ e_kind, e_path, info_path, count, entries
+ );
+ }
+ }
+ })
+}
+
+#[cargo_test]
+fn build_dep_info() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .build();
+
+ p.cargo("build").run();
+
+ let depinfo_bin_path = &p.bin("foo").with_extension("d");
+
+ assert!(depinfo_bin_path.is_file());
+
+ let depinfo = p.read_file(depinfo_bin_path.to_str().unwrap());
+
+ let bin_path = p.bin("foo");
+ let src_path = p.root().join("src").join("foo.rs");
+ if !depinfo.lines().any(|line| {
+ line.starts_with(&format!("{}:", bin_path.display()))
+ && line.contains(src_path.to_str().unwrap())
+ }) {
+ panic!(
+ "Could not find {:?}: {:?} in {:?}",
+ bin_path, src_path, depinfo_bin_path
+ );
+ }
+}
+
+#[cargo_test]
+fn build_dep_info_lib() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [[example]]
+ name = "ex"
+ crate-type = ["lib"]
+ "#,
+ )
+ .file("build.rs", "fn main() {}")
+ .file("src/lib.rs", "")
+ .file("examples/ex.rs", "")
+ .build();
+
+ p.cargo("build --example=ex").run();
+ assert!(p.example_lib("ex", "lib").with_extension("d").is_file());
+}
+
+#[cargo_test]
+fn build_dep_info_rlib() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [[example]]
+ name = "ex"
+ crate-type = ["rlib"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("examples/ex.rs", "")
+ .build();
+
+ p.cargo("build --example=ex").run();
+ assert!(p.example_lib("ex", "rlib").with_extension("d").is_file());
+}
+
+#[cargo_test]
+fn build_dep_info_dylib() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [[example]]
+ name = "ex"
+ crate-type = ["dylib"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("examples/ex.rs", "")
+ .build();
+
+ p.cargo("build --example=ex").run();
+ assert!(p.example_lib("ex", "dylib").with_extension("d").is_file());
+}
+
+#[cargo_test]
+fn dep_path_inside_target_has_correct_path() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("a"))
+ .file("target/debug/blah", "")
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ let x = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/target/debug/blah"));
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build").run();
+
+ let depinfo_path = &p.bin("a").with_extension("d");
+
+ assert!(depinfo_path.is_file(), "{:?}", depinfo_path);
+
+ let depinfo = p.read_file(depinfo_path.to_str().unwrap());
+
+ let bin_path = p.bin("a");
+ let target_debug_blah = Path::new("target").join("debug").join("blah");
+ if !depinfo.lines().any(|line| {
+ line.starts_with(&format!("{}:", bin_path.display()))
+ && line.contains(target_debug_blah.to_str().unwrap())
+ }) {
+ panic!(
+ "Could not find {:?}: {:?} in {:?}",
+ bin_path, target_debug_blah, depinfo_path
+ );
+ }
+}
+
+#[cargo_test]
+fn no_rewrite_if_no_change() {
+ let p = project().file("src/lib.rs", "").build();
+
+ p.cargo("build").run();
+ let dep_info = p.root().join("target/debug/libfoo.d");
+ let metadata1 = dep_info.metadata().unwrap();
+ p.cargo("build").run();
+ let metadata2 = dep_info.metadata().unwrap();
+
+ assert_eq!(
+ FileTime::from_last_modification_time(&metadata1),
+ FileTime::from_last_modification_time(&metadata2),
+ );
+}
+
+#[cargo_test(nightly, reason = "-Z binary-dep-depinfo is unstable")]
+fn relative_depinfo_paths_ws() {
+ // Test relative dep-info paths in a workspace with --target with
+ // proc-macros and other dependency kinds.
+ Package::new("regdep", "0.1.0")
+ .file("src/lib.rs", "pub fn f() {}")
+ .publish();
+ Package::new("pmdep", "0.1.0")
+ .file("src/lib.rs", "pub fn f() {}")
+ .publish();
+ Package::new("bdep", "0.1.0")
+ .file("src/lib.rs", "pub fn f() {}")
+ .publish();
+
+ let p = project()
+ /*********** Workspace ***********/
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["foo"]
+ "#,
+ )
+ /*********** Main Project ***********/
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [dependencies]
+ pm = {path = "../pm"}
+ bar = {path = "../bar"}
+ regdep = "0.1"
+
+ [build-dependencies]
+ bdep = "0.1"
+ bar = {path = "../bar"}
+ "#,
+ )
+ .file(
+ "foo/src/main.rs",
+ r#"
+ pm::noop!{}
+
+ fn main() {
+ bar::f();
+ regdep::f();
+ }
+ "#,
+ )
+ .file("foo/build.rs", "fn main() { bdep::f(); }")
+ /*********** Proc Macro ***********/
+ .file(
+ "pm/Cargo.toml",
+ r#"
+ [package]
+ name = "pm"
+ version = "0.1.0"
+ edition = "2018"
+
+ [lib]
+ proc-macro = true
+
+ [dependencies]
+ pmdep = "0.1"
+ "#,
+ )
+ .file(
+ "pm/src/lib.rs",
+ r#"
+ extern crate proc_macro;
+ use proc_macro::TokenStream;
+
+ #[proc_macro]
+ pub fn noop(_item: TokenStream) -> TokenStream {
+ pmdep::f();
+ "".parse().unwrap()
+ }
+ "#,
+ )
+ /*********** Path Dependency `bar` ***********/
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn f() {}")
+ .build();
+
+ let host = rustc_host();
+ p.cargo("build -Z binary-dep-depinfo --target")
+ .arg(&host)
+ .masquerade_as_nightly_cargo(&["binary-dep-depinfo"])
+ .with_stderr_contains("[COMPILING] foo [..]")
+ .run();
+
+ assert_deps_contains(
+ &p,
+ "target/debug/.fingerprint/pm-*/dep-lib-pm",
+ &[(0, "src/lib.rs"), (1, "debug/deps/libpmdep-*.rlib")],
+ );
+
+ assert_deps_contains(
+ &p,
+ &format!("target/{}/debug/.fingerprint/foo-*/dep-bin-foo", host),
+ &[
+ (0, "src/main.rs"),
+ (
+ 1,
+ &format!(
+ "debug/deps/{}pm-*.{}",
+ paths::get_lib_prefix("proc-macro"),
+ paths::get_lib_extension("proc-macro")
+ ),
+ ),
+ (1, &format!("{}/debug/deps/libbar-*.rlib", host)),
+ (1, &format!("{}/debug/deps/libregdep-*.rlib", host)),
+ ],
+ );
+
+ assert_deps_contains(
+ &p,
+ "target/debug/.fingerprint/foo-*/dep-build-script-build-script-build",
+ &[(0, "build.rs"), (1, "debug/deps/libbdep-*.rlib")],
+ );
+
+ // Make sure it stays fresh.
+ p.cargo("build -Z binary-dep-depinfo --target")
+ .arg(&host)
+ .masquerade_as_nightly_cargo(&["binary-dep-depinfo"])
+ .with_stderr("[FINISHED] dev [..]")
+ .run();
+}
+
+#[cargo_test(nightly, reason = "-Z binary-dep-depinfo is unstable")]
+fn relative_depinfo_paths_no_ws() {
+ // Test relative dep-info paths without a workspace with proc-macros and
+ // other dependency kinds.
+ Package::new("regdep", "0.1.0")
+ .file("src/lib.rs", "pub fn f() {}")
+ .publish();
+ Package::new("pmdep", "0.1.0")
+ .file("src/lib.rs", "pub fn f() {}")
+ .publish();
+ Package::new("bdep", "0.1.0")
+ .file("src/lib.rs", "pub fn f() {}")
+ .publish();
+
+ let p = project()
+ /*********** Main Project ***********/
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [dependencies]
+ pm = {path = "pm"}
+ bar = {path = "bar"}
+ regdep = "0.1"
+
+ [build-dependencies]
+ bdep = "0.1"
+ bar = {path = "bar"}
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ pm::noop!{}
+
+ fn main() {
+ bar::f();
+ regdep::f();
+ }
+ "#,
+ )
+ .file("build.rs", "fn main() { bdep::f(); }")
+ /*********** Proc Macro ***********/
+ .file(
+ "pm/Cargo.toml",
+ r#"
+ [package]
+ name = "pm"
+ version = "0.1.0"
+ edition = "2018"
+
+ [lib]
+ proc-macro = true
+
+ [dependencies]
+ pmdep = "0.1"
+ "#,
+ )
+ .file(
+ "pm/src/lib.rs",
+ r#"
+ extern crate proc_macro;
+ use proc_macro::TokenStream;
+
+ #[proc_macro]
+ pub fn noop(_item: TokenStream) -> TokenStream {
+ pmdep::f();
+ "".parse().unwrap()
+ }
+ "#,
+ )
+ /*********** Path Dependency `bar` ***********/
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn f() {}")
+ .build();
+
+ p.cargo("build -Z binary-dep-depinfo")
+ .masquerade_as_nightly_cargo(&["binary-dep-depinfo"])
+ .with_stderr_contains("[COMPILING] foo [..]")
+ .run();
+
+ assert_deps_contains(
+ &p,
+ "target/debug/.fingerprint/pm-*/dep-lib-pm",
+ &[(0, "src/lib.rs"), (1, "debug/deps/libpmdep-*.rlib")],
+ );
+
+ assert_deps_contains(
+ &p,
+ "target/debug/.fingerprint/foo-*/dep-bin-foo",
+ &[
+ (0, "src/main.rs"),
+ (
+ 1,
+ &format!(
+ "debug/deps/{}pm-*.{}",
+ paths::get_lib_prefix("proc-macro"),
+ paths::get_lib_extension("proc-macro")
+ ),
+ ),
+ (1, "debug/deps/libbar-*.rlib"),
+ (1, "debug/deps/libregdep-*.rlib"),
+ ],
+ );
+
+ assert_deps_contains(
+ &p,
+ "target/debug/.fingerprint/foo-*/dep-build-script-build-script-build",
+ &[(0, "build.rs"), (1, "debug/deps/libbdep-*.rlib")],
+ );
+
+ // Make sure it stays fresh.
+ p.cargo("build -Z binary-dep-depinfo")
+ .masquerade_as_nightly_cargo(&["binary-dep-depinfo"])
+ .with_stderr("[FINISHED] dev [..]")
+ .run();
+}
+
+#[cargo_test]
+fn reg_dep_source_not_tracked() {
+ // Make sure source files in dep-info file are not tracked for registry dependencies.
+ Package::new("regdep", "0.1.0")
+ .file("src/lib.rs", "pub fn f() {}")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ regdep = "0.1"
+ "#,
+ )
+ .file("src/lib.rs", "pub fn f() { regdep::f(); }")
+ .build();
+
+ p.cargo("check").run();
+
+ assert_deps(
+ &p,
+ "target/debug/.fingerprint/regdep-*/dep-lib-regdep",
+ |info_path, entries| {
+ for (kind, path) in entries {
+ if *kind == 1 {
+ panic!(
+ "Did not expect package root relative path type: {:?} in {:?}",
+ path, info_path
+ );
+ }
+ }
+ },
+ );
+}
+
+#[cargo_test(nightly, reason = "-Z binary-dep-depinfo is unstable")]
+fn canonical_path() {
+ if !cargo_test_support::symlink_supported() {
+ return;
+ }
+ Package::new("regdep", "0.1.0")
+ .file("src/lib.rs", "pub fn f() {}")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ regdep = "0.1"
+ "#,
+ )
+ .file("src/lib.rs", "pub fn f() { regdep::f(); }")
+ .build();
+
+ let real = p.root().join("real_target");
+ real.mkdir_p();
+ p.symlink(real, "target");
+
+ p.cargo("check -Z binary-dep-depinfo")
+ .masquerade_as_nightly_cargo(&["binary-dep-depinfo"])
+ .run();
+
+ assert_deps_contains(
+ &p,
+ "target/debug/.fingerprint/foo-*/dep-lib-foo",
+ &[(0, "src/lib.rs"), (1, "debug/deps/libregdep-*.rmeta")],
+ );
+}
+
+#[cargo_test]
+fn non_local_build_script() {
+ // Non-local build script information is not included.
+ Package::new("bar", "1.0.0")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rerun-if-changed=build.rs");
+ }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "1.0"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build").run();
+ let contents = p.read_file("target/debug/foo.d");
+ assert_match_exact(
+ "[ROOT]/foo/target/debug/foo[EXE]: [ROOT]/foo/src/main.rs",
+ &contents,
+ );
+}
diff --git a/src/tools/cargo/tests/testsuite/direct_minimal_versions.rs b/src/tools/cargo/tests/testsuite/direct_minimal_versions.rs
new file mode 100644
index 000000000..0e62d6ce0
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/direct_minimal_versions.rs
@@ -0,0 +1,236 @@
+//! Tests for minimal-version resolution.
+//!
+//! Note: Some tests are located in the resolver-tests package.
+
+use cargo_test_support::project;
+use cargo_test_support::registry::Package;
+
+#[cargo_test]
+fn simple() {
+ Package::new("dep", "1.0.0").publish();
+ Package::new("dep", "1.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.1"
+
+ [dependencies]
+ dep = "1.0"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("generate-lockfile -Zdirect-minimal-versions")
+ .masquerade_as_nightly_cargo(&["direct-minimal-versions"])
+ .run();
+
+ let lock = p.read_lockfile();
+
+ assert!(
+ lock.contains("1.0.0"),
+ "dep minimal version must be present"
+ );
+ assert!(
+ !lock.contains("1.1.0"),
+ "dep maximimal version cannot be present"
+ );
+}
+
+#[cargo_test]
+fn mixed_dependencies() {
+ Package::new("dep", "1.0.0").publish();
+ Package::new("dep", "1.1.0").publish();
+ Package::new("dep", "1.2.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.1"
+
+ [dependencies]
+ dep = "1.0"
+
+ [dev-dependencies]
+ dep = "1.1"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("generate-lockfile -Zdirect-minimal-versions")
+ .masquerade_as_nightly_cargo(&["direct-minimal-versions"])
+ .with_status(101)
+ .with_stderr(
+ r#"[UPDATING] [..]
+[ERROR] failed to select a version for `dep`.
+ ... required by package `foo v0.0.1 ([CWD])`
+versions that meet the requirements `^1.1` are: 1.1.0
+
+all possible versions conflict with previously selected packages.
+
+ previously selected package `dep v1.0.0`
+ ... which satisfies dependency `dep = "^1.0"` of package `foo v0.0.1 ([CWD])`
+
+failed to select a version for `dep` which could resolve this conflict
+"#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn yanked() {
+ Package::new("dep", "1.0.0").yanked(true).publish();
+ Package::new("dep", "1.1.0").publish();
+ Package::new("dep", "1.2.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.1"
+
+ [dependencies]
+ dep = "1.0"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("generate-lockfile -Zdirect-minimal-versions")
+ .masquerade_as_nightly_cargo(&["direct-minimal-versions"])
+ .run();
+
+ let lock = p.read_lockfile();
+
+ assert!(
+ lock.contains("1.1.0"),
+ "dep minimal version must be present"
+ );
+ assert!(
+ !lock.contains("1.0.0"),
+ "yanked minimal version must be skipped"
+ );
+ assert!(
+ !lock.contains("1.2.0"),
+ "dep maximimal version cannot be present"
+ );
+}
+
+#[cargo_test]
+fn indirect() {
+ Package::new("indirect", "2.0.0").publish();
+ Package::new("indirect", "2.1.0").publish();
+ Package::new("indirect", "2.2.0").publish();
+ Package::new("direct", "1.0.0")
+ .dep("indirect", "2.1")
+ .publish();
+ Package::new("direct", "1.1.0")
+ .dep("indirect", "2.1")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.1"
+
+ [dependencies]
+ direct = "1.0"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("generate-lockfile -Zdirect-minimal-versions")
+ .masquerade_as_nightly_cargo(&["direct-minimal-versions"])
+ .run();
+
+ let lock = p.read_lockfile();
+
+ assert!(
+ lock.contains("1.0.0"),
+ "direct minimal version must be present"
+ );
+ assert!(
+ !lock.contains("1.1.0"),
+ "direct maximimal version cannot be present"
+ );
+ assert!(
+ !lock.contains("2.0.0"),
+ "indirect minimal version cannot be present"
+ );
+ assert!(
+ !lock.contains("2.1.0"),
+ "indirect minimal version cannot be present"
+ );
+ assert!(
+ lock.contains("2.2.0"),
+ "indirect maximal version must be present"
+ );
+}
+
+#[cargo_test]
+fn indirect_conflict() {
+ Package::new("indirect", "2.0.0").publish();
+ Package::new("indirect", "2.1.0").publish();
+ Package::new("indirect", "2.2.0").publish();
+ Package::new("direct", "1.0.0")
+ .dep("indirect", "2.1")
+ .publish();
+ Package::new("direct", "1.1.0")
+ .dep("indirect", "2.1")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.1"
+
+ [dependencies]
+ direct = "1.0"
+ indirect = "2.0"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("generate-lockfile -Zdirect-minimal-versions")
+ .masquerade_as_nightly_cargo(&["direct-minimal-versions"])
+ .with_status(101)
+ .with_stderr(
+ r#"[UPDATING] [..]
+[ERROR] failed to select a version for `indirect`.
+ ... required by package `direct v1.0.0`
+ ... which satisfies dependency `direct = "^1.0"` of package `foo v0.0.1 ([CWD])`
+versions that meet the requirements `^2.1` are: 2.2.0, 2.1.0
+
+all possible versions conflict with previously selected packages.
+
+ previously selected package `indirect v2.0.0`
+ ... which satisfies dependency `indirect = "^2.0"` of package `foo v0.0.1 ([CWD])`
+
+failed to select a version for `indirect` which could resolve this conflict
+"#,
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/directory.rs b/src/tools/cargo/tests/testsuite/directory.rs
new file mode 100644
index 000000000..0e28de039
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/directory.rs
@@ -0,0 +1,774 @@
+//! Tests for directory sources.
+
+use std::collections::HashMap;
+use std::fs;
+use std::str;
+
+use serde::Serialize;
+
+use cargo_test_support::cargo_process;
+use cargo_test_support::git;
+use cargo_test_support::paths;
+use cargo_test_support::registry::{cksum, Package};
+use cargo_test_support::{basic_manifest, project, t, ProjectBuilder};
+
+fn setup() {
+ let root = paths::root();
+ t!(fs::create_dir(&root.join(".cargo")));
+ t!(fs::write(
+ root.join(".cargo/config"),
+ r#"
+ [source.crates-io]
+ replace-with = 'my-awesome-local-registry'
+
+ [source.my-awesome-local-registry]
+ directory = 'index'
+ "#
+ ));
+}
+
+struct VendorPackage {
+ p: Option<ProjectBuilder>,
+ cksum: Checksum,
+}
+
+#[derive(Serialize)]
+struct Checksum {
+ package: Option<String>,
+ files: HashMap<String, String>,
+}
+
+impl VendorPackage {
+ fn new(name: &str) -> VendorPackage {
+ VendorPackage {
+ p: Some(project().at(&format!("index/{}", name))),
+ cksum: Checksum {
+ package: Some(String::new()),
+ files: HashMap::new(),
+ },
+ }
+ }
+
+ fn file(&mut self, name: &str, contents: &str) -> &mut VendorPackage {
+ self.p = Some(self.p.take().unwrap().file(name, contents));
+ self.cksum
+ .files
+ .insert(name.to_string(), cksum(contents.as_bytes()));
+ self
+ }
+
+ fn disable_checksum(&mut self) -> &mut VendorPackage {
+ self.cksum.package = None;
+ self
+ }
+
+ fn no_manifest(mut self) -> Self {
+ self.p = self.p.map(|pb| pb.no_manifest());
+ self
+ }
+
+ fn build(&mut self) {
+ let p = self.p.take().unwrap();
+ let json = serde_json::to_string(&self.cksum).unwrap();
+ let p = p.file(".cargo-checksum.json", &json);
+ let _ = p.build();
+ }
+}
+
+#[cargo_test]
+fn simple() {
+ setup();
+
+ VendorPackage::new("bar")
+ .file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "extern crate bar; pub fn foo() { bar::bar(); }",
+ )
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[CHECKING] bar v0.1.0
+[CHECKING] foo v0.1.0 ([CWD])
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn simple_install() {
+ setup();
+
+ VendorPackage::new("foo")
+ .file("src/lib.rs", "pub fn foo() {}")
+ .build();
+
+ VendorPackage::new("bar")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ foo = "0.0.1"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ "extern crate foo; pub fn main() { foo::foo(); }",
+ )
+ .build();
+
+ cargo_process("install bar")
+ .with_stderr(
+ "\
+[INSTALLING] bar v0.1.0
+[COMPILING] foo v0.0.1
+[COMPILING] bar v0.1.0
+[FINISHED] release [optimized] target(s) in [..]s
+[INSTALLING] [..]bar[..]
+[INSTALLED] package `bar v0.1.0` (executable `bar[EXE]`)
+[WARNING] be sure to add `[..]` to your PATH to be able to run the installed binaries
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn simple_install_fail() {
+ setup();
+
+ VendorPackage::new("foo")
+ .file("src/lib.rs", "pub fn foo() {}")
+ .build();
+
+ VendorPackage::new("bar")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ foo = "0.1.0"
+ baz = "9.8.7"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ "extern crate foo; pub fn main() { foo::foo(); }",
+ )
+ .build();
+
+ cargo_process("install bar")
+ .with_status(101)
+ .with_stderr(
+ " Installing bar v0.1.0
+error: failed to compile `bar v0.1.0`, intermediate artifacts can be found at `[..]`
+
+Caused by:
+ no matching package found
+ searched package name: `baz`
+ perhaps you meant: bar or foo
+ location searched: registry `crates-io`
+ required by package `bar v0.1.0`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn install_without_feature_dep() {
+ setup();
+
+ VendorPackage::new("foo")
+ .file("src/lib.rs", "pub fn foo() {}")
+ .build();
+
+ VendorPackage::new("bar")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ foo = "0.0.1"
+ baz = { version = "9.8.7", optional = true }
+
+ [features]
+ wantbaz = ["baz"]
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ "extern crate foo; pub fn main() { foo::foo(); }",
+ )
+ .build();
+
+ cargo_process("install bar")
+ .with_stderr(
+ "\
+[INSTALLING] bar v0.1.0
+[COMPILING] foo v0.0.1
+[COMPILING] bar v0.1.0
+[FINISHED] release [optimized] target(s) in [..]s
+[INSTALLING] [..]bar[..]
+[INSTALLED] package `bar v0.1.0` (executable `bar[EXE]`)
+[WARNING] be sure to add `[..]` to your PATH to be able to run the installed binaries
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn not_there() {
+ setup();
+
+ let _ = project().at("index").build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "extern crate bar; pub fn foo() { bar::bar(); }",
+ )
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: no matching package named `bar` found
+location searched: [..]
+required by package `foo v0.1.0 ([..])`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn multiple() {
+ setup();
+
+ VendorPackage::new("bar-0.1.0")
+ .file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("src/lib.rs", "pub fn bar() {}")
+ .file(".cargo-checksum", "")
+ .build();
+
+ VendorPackage::new("bar-0.2.0")
+ .file("Cargo.toml", &basic_manifest("bar", "0.2.0"))
+ .file("src/lib.rs", "pub fn bar() {}")
+ .file(".cargo-checksum", "")
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "extern crate bar; pub fn foo() { bar::bar(); }",
+ )
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[CHECKING] bar v0.1.0
+[CHECKING] foo v0.1.0 ([CWD])
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn crates_io_then_directory() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "extern crate bar; pub fn foo() { bar::bar(); }",
+ )
+ .build();
+
+ let cksum = Package::new("bar", "0.1.0")
+ .file("src/lib.rs", "pub fn bar() -> u32 { 0 }")
+ .publish();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.1.0 ([..])
+[CHECKING] bar v0.1.0
+[CHECKING] foo v0.1.0 ([CWD])
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ setup();
+
+ let mut v = VendorPackage::new("bar");
+ v.file("Cargo.toml", &basic_manifest("bar", "0.1.0"));
+ v.file("src/lib.rs", "pub fn bar() -> u32 { 1 }");
+ v.cksum.package = Some(cksum);
+ v.build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[CHECKING] bar v0.1.0
+[CHECKING] foo v0.1.0 ([CWD])
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn crates_io_then_bad_checksum() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ Package::new("bar", "0.1.0").publish();
+
+ p.cargo("check").run();
+ setup();
+
+ VendorPackage::new("bar")
+ .file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: checksum for `bar v0.1.0` changed between lock files
+
+this could be indicative of a few possible errors:
+
+ * the lock file is corrupt
+ * a replacement source in use (e.g., a mirror) returned a different checksum
+ * the source itself may be corrupt in one way or another
+
+unable to verify that `bar v0.1.0` is the same as when the lockfile was generated
+
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bad_file_checksum() {
+ setup();
+
+ VendorPackage::new("bar")
+ .file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("src/lib.rs", "")
+ .build();
+
+ t!(fs::write(
+ paths::root().join("index/bar/src/lib.rs"),
+ "fn bar() -> u32 { 0 }"
+ ));
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: the listed checksum of `[..]lib.rs` has changed:
+expected: [..]
+actual: [..]
+
+directory sources are not intended to be edited, if modifications are \
+required then it is recommended that `[patch]` is used with a forked copy of \
+the source
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn only_dot_files_ok() {
+ setup();
+
+ VendorPackage::new("bar")
+ .file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("src/lib.rs", "")
+ .build();
+ VendorPackage::new("foo")
+ .no_manifest()
+ .file(".bar", "")
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn random_files_ok() {
+ setup();
+
+ VendorPackage::new("bar")
+ .file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("src/lib.rs", "")
+ .build();
+ VendorPackage::new("foo")
+ .no_manifest()
+ .file("bar", "")
+ .file("../test", "")
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn git_lock_file_doesnt_change() {
+ let git = git::new("git", |p| {
+ p.file("Cargo.toml", &basic_manifest("git", "0.5.0"))
+ .file("src/lib.rs", "")
+ });
+
+ VendorPackage::new("git")
+ .file("Cargo.toml", &basic_manifest("git", "0.5.0"))
+ .file("src/lib.rs", "")
+ .disable_checksum()
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ git = {{ git = '{0}' }}
+ "#,
+ git.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check").run();
+
+ let lock1 = p.read_lockfile();
+
+ let root = paths::root();
+ t!(fs::create_dir(&root.join(".cargo")));
+ t!(fs::write(
+ root.join(".cargo/config"),
+ format!(
+ r#"
+ [source.my-git-repo]
+ git = '{}'
+ replace-with = 'my-awesome-local-registry'
+
+ [source.my-awesome-local-registry]
+ directory = 'index'
+ "#,
+ git.url()
+ )
+ ));
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[CHECKING] [..]
+[CHECKING] [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ let lock2 = p.read_lockfile();
+ assert_eq!(lock1, lock2, "lock files changed");
+}
+
+#[cargo_test]
+fn git_override_requires_lockfile() {
+ VendorPackage::new("git")
+ .file("Cargo.toml", &basic_manifest("git", "0.5.0"))
+ .file("src/lib.rs", "")
+ .disable_checksum()
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ git = { git = 'https://example.com/' }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ let root = paths::root();
+ t!(fs::create_dir(&root.join(".cargo")));
+ t!(fs::write(
+ root.join(".cargo/config"),
+ r#"
+ [source.my-git-repo]
+ git = 'https://example.com/'
+ replace-with = 'my-awesome-local-registry'
+
+ [source.my-awesome-local-registry]
+ directory = 'index'
+ "#
+ ));
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to get `git` as a dependency of package `foo v0.0.1 ([..])`
+
+Caused by:
+ failed to load source for dependency `git`
+
+Caused by:
+ Unable to update [..]
+
+Caused by:
+ the source my-git-repo requires a lock file to be present first before it can be
+ used against vendored source code
+
+ remove the source replacement configuration, generate a lock file, and then
+ restore the source replacement configuration to continue the build
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn workspace_different_locations() {
+ let p = project()
+ .no_manifest()
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = 'foo'
+ version = '0.1.0'
+
+ [dependencies]
+ baz = "*"
+ "#,
+ )
+ .file("foo/src/lib.rs", "")
+ .file("foo/vendor/baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("foo/vendor/baz/src/lib.rs", "")
+ .file("foo/vendor/baz/.cargo-checksum.json", "{\"files\":{}}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = 'bar'
+ version = '0.1.0'
+
+ [dependencies]
+ baz = "*"
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ r#"
+ [build]
+ target-dir = './target'
+
+ [source.crates-io]
+ replace-with = 'my-awesome-local-registry'
+
+ [source.my-awesome-local-registry]
+ directory = 'foo/vendor'
+ "#,
+ )
+ .build();
+
+ p.cargo("check").cwd("foo").run();
+ p.cargo("check")
+ .cwd("bar")
+ .with_stderr(
+ "\
+[CHECKING] bar [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn version_missing() {
+ setup();
+
+ VendorPackage::new("foo")
+ .file("src/lib.rs", "pub fn foo() {}")
+ .build();
+
+ VendorPackage::new("bar")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ foo = "2"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ cargo_process("install bar")
+ .with_stderr(
+ "\
+[INSTALLING] bar v0.1.0
+error: failed to compile [..]
+
+Caused by:
+ failed to select a version for the requirement `foo = \"^2\"`
+ candidate versions found which didn't match: 0.0.1
+ location searched: directory source `[..] (which is replacing registry `[..]`)
+ required by package `bar v0.1.0`
+ perhaps a crate was updated and forgotten to be re-vendored?
+",
+ )
+ .with_status(101)
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/doc.rs b/src/tools/cargo/tests/testsuite/doc.rs
new file mode 100644
index 000000000..739bcf376
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/doc.rs
@@ -0,0 +1,2503 @@
+//! Tests for the `cargo doc` command.
+
+use cargo::core::compiler::RustDocFingerprint;
+use cargo_test_support::paths::CargoPathExt;
+use cargo_test_support::registry::Package;
+use cargo_test_support::{basic_lib_manifest, basic_manifest, git, project};
+use cargo_test_support::{rustc_host, symlink_supported, tools};
+use std::fs;
+use std::str;
+
+#[cargo_test]
+fn simple() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file("build.rs", "fn main() {}")
+ .file("src/lib.rs", "pub fn foo() {}")
+ .build();
+
+ p.cargo("doc")
+ .with_stderr(
+ "\
+[..] foo v0.0.1 ([CWD])
+[..] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ assert!(p.root().join("target/doc").is_dir());
+ assert!(p.root().join("target/doc/foo/index.html").is_file());
+}
+
+#[cargo_test]
+fn doc_no_libs() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [[bin]]
+ name = "foo"
+ doc = false
+ "#,
+ )
+ .file("src/main.rs", "bad code")
+ .build();
+
+ p.cargo("doc").run();
+}
+
+#[cargo_test]
+fn doc_twice() {
+ let p = project().file("src/lib.rs", "pub fn foo() {}").build();
+
+ p.cargo("doc")
+ .with_stderr(
+ "\
+[DOCUMENTING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("doc").with_stdout("").run();
+}
+
+#[cargo_test]
+fn doc_deps() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "bar"
+ "#,
+ )
+ .file("src/lib.rs", "extern crate bar; pub fn foo() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ p.cargo("doc")
+ .with_stderr(
+ "\
+[..] bar v0.0.1 ([CWD]/bar)
+[..] bar v0.0.1 ([CWD]/bar)
+[DOCUMENTING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ assert!(p.root().join("target/doc").is_dir());
+ assert!(p.root().join("target/doc/foo/index.html").is_file());
+ assert!(p.root().join("target/doc/bar/index.html").is_file());
+
+ // Verify that it only emits rmeta for the dependency.
+ assert_eq!(p.glob("target/debug/**/*.rlib").count(), 0);
+ assert_eq!(p.glob("target/debug/deps/libbar-*.rmeta").count(), 1);
+
+ p.cargo("doc")
+ .env("CARGO_LOG", "cargo::ops::cargo_rustc::fingerprint")
+ .with_stdout("")
+ .run();
+
+ assert!(p.root().join("target/doc").is_dir());
+ assert!(p.root().join("target/doc/foo/index.html").is_file());
+ assert!(p.root().join("target/doc/bar/index.html").is_file());
+}
+
+#[cargo_test]
+fn doc_no_deps() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "bar"
+ "#,
+ )
+ .file("src/lib.rs", "extern crate bar; pub fn foo() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ p.cargo("doc --no-deps")
+ .with_stderr(
+ "\
+[CHECKING] bar v0.0.1 ([CWD]/bar)
+[DOCUMENTING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ assert!(p.root().join("target/doc").is_dir());
+ assert!(p.root().join("target/doc/foo/index.html").is_file());
+ assert!(!p.root().join("target/doc/bar/index.html").is_file());
+}
+
+#[cargo_test]
+fn doc_only_bin() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "bar"
+ "#,
+ )
+ .file("src/main.rs", "extern crate bar; pub fn foo() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ p.cargo("doc -v").run();
+
+ assert!(p.root().join("target/doc").is_dir());
+ assert!(p.root().join("target/doc/bar/index.html").is_file());
+ assert!(p.root().join("target/doc/foo/index.html").is_file());
+}
+
+#[cargo_test]
+fn doc_multiple_targets_same_name_lib() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["foo", "bar"]
+ "#,
+ )
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ [lib]
+ name = "foo_lib"
+ "#,
+ )
+ .file("foo/src/lib.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ [lib]
+ name = "foo_lib"
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("doc --workspace")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: document output filename collision
+The lib `foo_lib` in package `foo v0.1.0 ([ROOT]/foo/foo)` has the same name as \
+the lib `foo_lib` in package `bar v0.1.0 ([ROOT]/foo/bar)`.
+Only one may be documented at once since they output to the same path.
+Consider documenting only one, renaming one, or marking one with `doc = false` in Cargo.toml.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn doc_multiple_targets_same_name() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["foo", "bar"]
+ "#,
+ )
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ [[bin]]
+ name = "foo_lib"
+ path = "src/foo_lib.rs"
+ "#,
+ )
+ .file("foo/src/foo_lib.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ [lib]
+ name = "foo_lib"
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("doc --workspace")
+ .with_stderr_unordered(
+ "\
+warning: output filename collision.
+The bin target `foo_lib` in package `foo v0.1.0 ([ROOT]/foo/foo)` \
+has the same output filename as the lib target `foo_lib` in package \
+`bar v0.1.0 ([ROOT]/foo/bar)`.
+Colliding filename is: [ROOT]/foo/target/doc/foo_lib/index.html
+The targets should have unique names.
+This is a known bug where multiple crates with the same name use
+the same path; see <https://github.com/rust-lang/cargo/issues/6313>.
+[DOCUMENTING] bar v0.1.0 ([ROOT]/foo/bar)
+[DOCUMENTING] foo v0.1.0 ([ROOT]/foo/foo)
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn doc_multiple_targets_same_name_bin() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["foo", "bar"]
+ "#,
+ )
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ "#,
+ )
+ .file("foo/src/bin/foo-cli.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ "#,
+ )
+ .file("bar/src/bin/foo-cli.rs", "")
+ .build();
+
+ p.cargo("doc --workspace")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: document output filename collision
+The bin `foo-cli` in package `foo v0.1.0 ([ROOT]/foo/foo)` has the same name as \
+the bin `foo-cli` in package `bar v0.1.0 ([ROOT]/foo/bar)`.
+Only one may be documented at once since they output to the same path.
+Consider documenting only one, renaming one, or marking one with `doc = false` in Cargo.toml.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn doc_multiple_targets_same_name_undoced() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["foo", "bar"]
+ "#,
+ )
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ [[bin]]
+ name = "foo-cli"
+ "#,
+ )
+ .file("foo/src/foo-cli.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ [[bin]]
+ name = "foo-cli"
+ doc = false
+ "#,
+ )
+ .file("bar/src/foo-cli.rs", "")
+ .build();
+
+ p.cargo("doc --workspace").run();
+}
+
+#[cargo_test]
+fn doc_lib_bin_same_name_documents_lib() {
+ let p = project()
+ .file(
+ "src/main.rs",
+ r#"
+ //! Binary documentation
+ extern crate foo;
+ fn main() {
+ foo::foo();
+ }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ //! Library documentation
+ pub fn foo() {}
+ "#,
+ )
+ .build();
+
+ p.cargo("doc")
+ .with_stderr(
+ "\
+[DOCUMENTING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ let doc_html = p.read_file("target/doc/foo/index.html");
+ assert!(doc_html.contains("Library"));
+ assert!(!doc_html.contains("Binary"));
+}
+
+#[cargo_test]
+fn doc_lib_bin_same_name_documents_lib_when_requested() {
+ let p = project()
+ .file(
+ "src/main.rs",
+ r#"
+ //! Binary documentation
+ extern crate foo;
+ fn main() {
+ foo::foo();
+ }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ //! Library documentation
+ pub fn foo() {}
+ "#,
+ )
+ .build();
+
+ p.cargo("doc --lib")
+ .with_stderr(
+ "\
+[DOCUMENTING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ let doc_html = p.read_file("target/doc/foo/index.html");
+ assert!(doc_html.contains("Library"));
+ assert!(!doc_html.contains("Binary"));
+}
+
+#[cargo_test]
+fn doc_lib_bin_same_name_documents_named_bin_when_requested() {
+ let p = project()
+ .file(
+ "src/main.rs",
+ r#"
+ //! Binary documentation
+ extern crate foo;
+ fn main() {
+ foo::foo();
+ }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ //! Library documentation
+ pub fn foo() {}
+ "#,
+ )
+ .build();
+
+ p.cargo("doc --bin foo")
+ // The checking/documenting lines are sometimes swapped since they run
+ // concurrently.
+ .with_stderr_unordered(
+ "\
+warning: output filename collision.
+The bin target `foo` in package `foo v0.0.1 ([ROOT]/foo)` \
+has the same output filename as the lib target `foo` in package `foo v0.0.1 ([ROOT]/foo)`.
+Colliding filename is: [ROOT]/foo/target/doc/foo/index.html
+The targets should have unique names.
+This is a known bug where multiple crates with the same name use
+the same path; see <https://github.com/rust-lang/cargo/issues/6313>.
+[CHECKING] foo v0.0.1 ([CWD])
+[DOCUMENTING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ let doc_html = p.read_file("target/doc/foo/index.html");
+ assert!(!doc_html.contains("Library"));
+ assert!(doc_html.contains("Binary"));
+}
+
+#[cargo_test]
+fn doc_lib_bin_same_name_documents_bins_when_requested() {
+ let p = project()
+ .file(
+ "src/main.rs",
+ r#"
+ //! Binary documentation
+ extern crate foo;
+ fn main() {
+ foo::foo();
+ }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ //! Library documentation
+ pub fn foo() {}
+ "#,
+ )
+ .build();
+
+ p.cargo("doc --bins")
+ // The checking/documenting lines are sometimes swapped since they run
+ // concurrently.
+ .with_stderr_unordered(
+ "\
+warning: output filename collision.
+The bin target `foo` in package `foo v0.0.1 ([ROOT]/foo)` \
+has the same output filename as the lib target `foo` in package `foo v0.0.1 ([ROOT]/foo)`.
+Colliding filename is: [ROOT]/foo/target/doc/foo/index.html
+The targets should have unique names.
+This is a known bug where multiple crates with the same name use
+the same path; see <https://github.com/rust-lang/cargo/issues/6313>.
+[CHECKING] foo v0.0.1 ([CWD])
+[DOCUMENTING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ let doc_html = p.read_file("target/doc/foo/index.html");
+ assert!(!doc_html.contains("Library"));
+ assert!(doc_html.contains("Binary"));
+}
+
+#[cargo_test]
+fn doc_lib_bin_example_same_name_documents_named_example_when_requested() {
+ let p = project()
+ .file(
+ "src/main.rs",
+ r#"
+ //! Binary documentation
+ extern crate foo;
+ fn main() {
+ foo::foo();
+ }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ //! Library documentation
+ pub fn foo() {}
+ "#,
+ )
+ .file(
+ "examples/ex1.rs",
+ r#"
+ //! Example1 documentation
+ pub fn x() { f(); }
+ "#,
+ )
+ .build();
+
+ p.cargo("doc --example ex1")
+ // The checking/documenting lines are sometimes swapped since they run
+ // concurrently.
+ .with_stderr_unordered(
+ "\
+[CHECKING] foo v0.0.1 ([CWD])
+[DOCUMENTING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ )
+ .run();
+
+ let doc_html = p.read_file("target/doc/ex1/index.html");
+ assert!(!doc_html.contains("Library"));
+ assert!(!doc_html.contains("Binary"));
+ assert!(doc_html.contains("Example1"));
+}
+
+#[cargo_test]
+fn doc_lib_bin_example_same_name_documents_examples_when_requested() {
+ let p = project()
+ .file(
+ "src/main.rs",
+ r#"
+ //! Binary documentation
+ extern crate foo;
+ fn main() {
+ foo::foo();
+ }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ //! Library documentation
+ pub fn foo() {}
+ "#,
+ )
+ .file(
+ "examples/ex1.rs",
+ r#"
+ //! Example1 documentation
+ pub fn example1() { f(); }
+ "#,
+ )
+ .file(
+ "examples/ex2.rs",
+ r#"
+ //! Example2 documentation
+ pub fn example2() { f(); }
+ "#,
+ )
+ .build();
+
+ p.cargo("doc --examples")
+ // The checking/documenting lines are sometimes swapped since they run
+ // concurrently.
+ .with_stderr_unordered(
+ "\
+[CHECKING] foo v0.0.1 ([CWD])
+[DOCUMENTING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ )
+ .run();
+
+ let example_doc_html_1 = p.read_file("target/doc/ex1/index.html");
+ let example_doc_html_2 = p.read_file("target/doc/ex2/index.html");
+
+ assert!(!example_doc_html_1.contains("Library"));
+ assert!(!example_doc_html_1.contains("Binary"));
+
+ assert!(!example_doc_html_2.contains("Library"));
+ assert!(!example_doc_html_2.contains("Binary"));
+
+ assert!(example_doc_html_1.contains("Example1"));
+ assert!(example_doc_html_2.contains("Example2"));
+}
+
+#[cargo_test]
+fn doc_dash_p() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.a]
+ path = "a"
+ "#,
+ )
+ .file("src/lib.rs", "extern crate a;")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.b]
+ path = "../b"
+ "#,
+ )
+ .file("a/src/lib.rs", "extern crate b;")
+ .file("b/Cargo.toml", &basic_manifest("b", "0.0.1"))
+ .file("b/src/lib.rs", "")
+ .build();
+
+ p.cargo("doc -p a")
+ .with_stderr(
+ "\
+[..] b v0.0.1 ([CWD]/b)
+[..] b v0.0.1 ([CWD]/b)
+[DOCUMENTING] a v0.0.1 ([CWD]/a)
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn doc_all_exclude() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar", "baz"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("baz/src/lib.rs", "pub fn baz() { break_the_build(); }")
+ .build();
+
+ p.cargo("doc --workspace --exclude baz")
+ .with_stderr_does_not_contain("[DOCUMENTING] baz v0.1.0 [..]")
+ .with_stderr(
+ "\
+[DOCUMENTING] bar v0.1.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn doc_all_exclude_glob() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar", "baz"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("baz/src/lib.rs", "pub fn baz() { break_the_build(); }")
+ .build();
+
+ p.cargo("doc --workspace --exclude '*z'")
+ .with_stderr_does_not_contain("[DOCUMENTING] baz v0.1.0 [..]")
+ .with_stderr(
+ "\
+[DOCUMENTING] bar v0.1.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn doc_same_name() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file("src/bin/main.rs", "fn main() {}")
+ .file("examples/main.rs", "fn main() {}")
+ .file("tests/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("doc").run();
+}
+
+#[cargo_test(nightly, reason = "no_core, lang_items requires nightly")]
+fn doc_target() {
+ const TARGET: &str = "arm-unknown-linux-gnueabihf";
+
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ #![feature(no_core, lang_items)]
+ #![no_core]
+
+ #[lang = "sized"]
+ trait Sized {}
+
+ extern {
+ pub static A: u32;
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("doc --verbose --target").arg(TARGET).run();
+ assert!(p.root().join(&format!("target/{}/doc", TARGET)).is_dir());
+ assert!(p
+ .root()
+ .join(&format!("target/{}/doc/foo/index.html", TARGET))
+ .is_file());
+}
+
+#[cargo_test]
+fn target_specific_not_documented() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [target.foo.dependencies]
+ a = { path = "a" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("a/Cargo.toml", &basic_manifest("a", "0.0.1"))
+ .file("a/src/lib.rs", "not rust")
+ .build();
+
+ p.cargo("doc").run();
+}
+
+#[cargo_test]
+fn output_not_captured() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ a = { path = "a" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("a/Cargo.toml", &basic_manifest("a", "0.0.1"))
+ .file(
+ "a/src/lib.rs",
+ "
+ /// ```
+ /// `
+ /// ```
+ pub fn foo() {}
+ ",
+ )
+ .build();
+
+ p.cargo("doc")
+ .with_stderr_contains("[..]unknown start of token: `")
+ .run();
+}
+
+#[cargo_test]
+fn target_specific_documented() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [target.foo.dependencies]
+ a = {{ path = "a" }}
+ [target.{}.dependencies]
+ a = {{ path = "a" }}
+ "#,
+ rustc_host()
+ ),
+ )
+ .file(
+ "src/lib.rs",
+ "
+ extern crate a;
+
+ /// test
+ pub fn foo() {}
+ ",
+ )
+ .file("a/Cargo.toml", &basic_manifest("a", "0.0.1"))
+ .file(
+ "a/src/lib.rs",
+ "
+ /// test
+ pub fn foo() {}
+ ",
+ )
+ .build();
+
+ p.cargo("doc").run();
+}
+
+#[cargo_test]
+fn no_document_build_deps() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [build-dependencies]
+ a = { path = "a" }
+ "#,
+ )
+ .file("src/lib.rs", "pub fn foo() {}")
+ .file("a/Cargo.toml", &basic_manifest("a", "0.0.1"))
+ .file(
+ "a/src/lib.rs",
+ "
+ /// ```
+ /// ☃
+ /// ```
+ pub fn foo() {}
+ ",
+ )
+ .build();
+
+ p.cargo("doc").run();
+}
+
+#[cargo_test]
+fn doc_release() {
+ let p = project().file("src/lib.rs", "").build();
+
+ p.cargo("check --release").run();
+ p.cargo("doc --release -v")
+ .with_stderr(
+ "\
+[DOCUMENTING] foo v0.0.1 ([..])
+[RUNNING] `rustdoc [..] src/lib.rs [..]`
+[FINISHED] release [optimized] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn doc_multiple_deps() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "bar"
+
+ [dependencies.baz]
+ path = "baz"
+ "#,
+ )
+ .file("src/lib.rs", "extern crate bar; pub fn foo() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.0.1"))
+ .file("baz/src/lib.rs", "pub fn baz() {}")
+ .build();
+
+ p.cargo("doc -p bar -p baz -v").run();
+
+ assert!(p.root().join("target/doc").is_dir());
+ assert!(p.root().join("target/doc/bar/index.html").is_file());
+ assert!(p.root().join("target/doc/baz/index.html").is_file());
+}
+
+#[cargo_test]
+fn features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "bar"
+
+ [features]
+ foo = ["bar/bar"]
+ "#,
+ )
+ .file("src/lib.rs", r#"#[cfg(feature = "foo")] pub fn foo() {}"#)
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ bar = []
+ "#,
+ )
+ .file(
+ "bar/build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rustc-cfg=bar");
+ }
+ "#,
+ )
+ .file(
+ "bar/src/lib.rs",
+ r#"#[cfg(feature = "bar")] pub fn bar() {}"#,
+ )
+ .build();
+ p.cargo("doc --features foo")
+ .with_stderr(
+ "\
+[COMPILING] bar v0.0.1 [..]
+[DOCUMENTING] bar v0.0.1 [..]
+[DOCUMENTING] foo v0.0.1 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+ assert!(p.root().join("target/doc").is_dir());
+ assert!(p.root().join("target/doc/foo/fn.foo.html").is_file());
+ assert!(p.root().join("target/doc/bar/fn.bar.html").is_file());
+ // Check that turning the feature off will remove the files.
+ p.cargo("doc")
+ .with_stderr(
+ "\
+[COMPILING] bar v0.0.1 [..]
+[DOCUMENTING] bar v0.0.1 [..]
+[DOCUMENTING] foo v0.0.1 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+ assert!(!p.root().join("target/doc/foo/fn.foo.html").is_file());
+ assert!(!p.root().join("target/doc/bar/fn.bar.html").is_file());
+ // And switching back will rebuild and bring them back.
+ p.cargo("doc --features foo")
+ .with_stderr(
+ "\
+[DOCUMENTING] bar v0.0.1 [..]
+[DOCUMENTING] foo v0.0.1 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+ assert!(p.root().join("target/doc/foo/fn.foo.html").is_file());
+ assert!(p.root().join("target/doc/bar/fn.bar.html").is_file());
+}
+
+#[cargo_test]
+fn rerun_when_dir_removed() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ /// dox
+ pub fn foo() {}
+ "#,
+ )
+ .build();
+
+ p.cargo("doc").run();
+ assert!(p.root().join("target/doc/foo/index.html").is_file());
+
+ fs::remove_dir_all(p.root().join("target/doc/foo")).unwrap();
+
+ p.cargo("doc").run();
+ assert!(p.root().join("target/doc/foo/index.html").is_file());
+}
+
+#[cargo_test]
+fn document_only_lib() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ /// dox
+ pub fn foo() {}
+ "#,
+ )
+ .file(
+ "src/bin/bar.rs",
+ r#"
+ /// ```
+ /// ☃
+ /// ```
+ pub fn foo() {}
+ fn main() { foo(); }
+ "#,
+ )
+ .build();
+ p.cargo("doc --lib").run();
+ assert!(p.root().join("target/doc/foo/index.html").is_file());
+}
+
+#[cargo_test]
+fn plugins_no_use_target() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ proc-macro = true
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("doc --target=x86_64-unknown-openbsd -v").run();
+}
+
+#[cargo_test]
+fn doc_all_workspace() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { path = "bar" }
+
+ [workspace]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ // The order in which bar is compiled or documented is not deterministic
+ p.cargo("doc --workspace")
+ .with_stderr_contains("[..] Documenting bar v0.1.0 ([..])")
+ .with_stderr_contains("[..] Checking bar v0.1.0 ([..])")
+ .with_stderr_contains("[..] Documenting foo v0.1.0 ([..])")
+ .run();
+}
+
+#[cargo_test]
+fn doc_all_virtual_manifest() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar", "baz"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("baz/src/lib.rs", "pub fn baz() {}")
+ .build();
+
+ // The order in which bar and baz are documented is not guaranteed
+ p.cargo("doc --workspace")
+ .with_stderr_contains("[..] Documenting baz v0.1.0 ([..])")
+ .with_stderr_contains("[..] Documenting bar v0.1.0 ([..])")
+ .run();
+}
+
+#[cargo_test]
+fn doc_virtual_manifest_all_implied() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar", "baz"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("baz/src/lib.rs", "pub fn baz() {}")
+ .build();
+
+ // The order in which bar and baz are documented is not guaranteed
+ p.cargo("doc")
+ .with_stderr_contains("[..] Documenting baz v0.1.0 ([..])")
+ .with_stderr_contains("[..] Documenting bar v0.1.0 ([..])")
+ .run();
+}
+
+#[cargo_test]
+fn doc_virtual_manifest_one_project() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar", "baz"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("baz/src/lib.rs", "pub fn baz() { break_the_build(); }")
+ .build();
+
+ p.cargo("doc -p bar")
+ .with_stderr_does_not_contain("[DOCUMENTING] baz v0.1.0 [..]")
+ .with_stderr(
+ "\
+[DOCUMENTING] bar v0.1.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn doc_virtual_manifest_glob() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar", "baz"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() { break_the_build(); }")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("baz/src/lib.rs", "pub fn baz() {}")
+ .build();
+
+ p.cargo("doc -p '*z'")
+ .with_stderr_does_not_contain("[DOCUMENTING] bar v0.1.0 [..]")
+ .with_stderr(
+ "\
+[DOCUMENTING] baz v0.1.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn doc_all_member_dependency_same_name() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar"]
+ "#,
+ )
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ Package::new("bar", "0.1.0").publish();
+
+ p.cargo("doc --workspace")
+ .with_stderr_unordered(
+ "\
+[UPDATING] [..]
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.1.0 (registry `dummy-registry`)
+warning: output filename collision.
+The lib target `bar` in package `bar v0.1.0` has the same output filename as \
+the lib target `bar` in package `bar v0.1.0 ([ROOT]/foo/bar)`.
+Colliding filename is: [ROOT]/foo/target/doc/bar/index.html
+The targets should have unique names.
+This is a known bug where multiple crates with the same name use
+the same path; see <https://github.com/rust-lang/cargo/issues/6313>.
+[DOCUMENTING] bar v0.1.0
+[CHECKING] bar v0.1.0
+[DOCUMENTING] bar v0.1.0 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn doc_workspace_open_help_message() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["foo", "bar"]
+ "#,
+ )
+ .file("foo/Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("foo/src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ // The order in which bar is compiled or documented is not deterministic
+ p.cargo("doc --workspace --open")
+ .env("BROWSER", tools::echo())
+ .with_stderr_contains("[..] Documenting bar v0.1.0 ([..])")
+ .with_stderr_contains("[..] Documenting foo v0.1.0 ([..])")
+ .with_stderr_contains("[..] Opening [..]/bar/index.html")
+ .run();
+}
+
+#[cargo_test(nightly, reason = "-Zextern-html-root-url is unstable")]
+fn doc_extern_map_local() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(".cargo/config.toml", "doc.extern-map.std = 'local'")
+ .build();
+
+ p.cargo("doc -v --no-deps -Zrustdoc-map --open")
+ .env("BROWSER", tools::echo())
+ .masquerade_as_nightly_cargo(&["rustdoc-map"])
+ .with_stderr(
+ "\
+[DOCUMENTING] foo v0.1.0 [..]
+[RUNNING] `rustdoc --crate-type lib --crate-name foo src/lib.rs [..]--crate-version 0.1.0`
+[FINISHED] [..]
+ Opening [CWD]/target/doc/foo/index.html
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn open_no_doc_crate() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ doc = false
+ "#,
+ )
+ .file("src/lib.rs", "#[cfg(feature)] pub fn f();")
+ .build();
+
+ p.cargo("doc --open")
+ .env("BROWSER", "do_not_run_me")
+ .with_status(101)
+ .with_stderr_contains("error: no crates with documentation")
+ .run();
+}
+
+#[cargo_test]
+fn doc_workspace_open_different_library_and_package_names() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["foo"]
+ "#,
+ )
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ [lib]
+ name = "foolib"
+ "#,
+ )
+ .file("foo/src/lib.rs", "")
+ .build();
+
+ p.cargo("doc --open")
+ .env("BROWSER", tools::echo())
+ .with_stderr_contains("[..] Documenting foo v0.1.0 ([..])")
+ .with_stderr_contains("[..] [CWD]/target/doc/foolib/index.html")
+ .with_stdout_contains("[CWD]/target/doc/foolib/index.html")
+ .run();
+
+ p.change_file(
+ ".cargo/config.toml",
+ &format!(
+ r#"
+ [doc]
+ browser = ["{}", "a"]
+ "#,
+ tools::echo().display().to_string().replace('\\', "\\\\")
+ ),
+ );
+
+ // check that the cargo config overrides the browser env var
+ p.cargo("doc --open")
+ .env("BROWSER", "do_not_run_me")
+ .with_stdout_contains("a [CWD]/target/doc/foolib/index.html")
+ .run();
+}
+
+#[cargo_test]
+fn doc_workspace_open_binary() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["foo"]
+ "#,
+ )
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ [[bin]]
+ name = "foobin"
+ path = "src/main.rs"
+ "#,
+ )
+ .file("foo/src/main.rs", "")
+ .build();
+
+ p.cargo("doc --open")
+ .env("BROWSER", tools::echo())
+ .with_stderr_contains("[..] Documenting foo v0.1.0 ([..])")
+ .with_stderr_contains("[..] Opening [CWD]/target/doc/foobin/index.html")
+ .run();
+}
+
+#[cargo_test]
+fn doc_workspace_open_binary_and_library() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["foo"]
+ "#,
+ )
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ [lib]
+ name = "foolib"
+ [[bin]]
+ name = "foobin"
+ path = "src/main.rs"
+ "#,
+ )
+ .file("foo/src/lib.rs", "")
+ .file("foo/src/main.rs", "")
+ .build();
+
+ p.cargo("doc --open")
+ .env("BROWSER", tools::echo())
+ .with_stderr_contains("[..] Documenting foo v0.1.0 ([..])")
+ .with_stderr_contains("[..] Opening [CWD]/target/doc/foolib/index.html")
+ .run();
+}
+
+#[cargo_test]
+fn doc_edition() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ edition = "2018"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("doc -v")
+ .with_stderr_contains("[RUNNING] `rustdoc [..]--edition=2018[..]")
+ .run();
+
+ p.cargo("test -v")
+ .with_stderr_contains("[RUNNING] `rustdoc [..]--edition=2018[..]")
+ .run();
+}
+
+#[cargo_test]
+fn doc_target_edition() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ edition = "2018"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("doc -v")
+ .with_stderr_contains("[RUNNING] `rustdoc [..]--edition=2018[..]")
+ .run();
+
+ p.cargo("test -v")
+ .with_stderr_contains("[RUNNING] `rustdoc [..]--edition=2018[..]")
+ .run();
+}
+
+// Tests an issue where depending on different versions of the same crate depending on `cfg`s
+// caused `cargo doc` to fail.
+#[cargo_test]
+fn issue_5345() {
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [target.'cfg(all(windows, target_arch = "x86"))'.dependencies]
+ bar = "0.1"
+
+ [target.'cfg(not(all(windows, target_arch = "x86")))'.dependencies]
+ bar = "0.2"
+ "#,
+ )
+ .file("src/lib.rs", "extern crate bar;")
+ .build();
+ Package::new("bar", "0.1.0").publish();
+ Package::new("bar", "0.2.0").publish();
+
+ foo.cargo("check").run();
+ foo.cargo("doc").run();
+}
+
+#[cargo_test]
+fn doc_private_items() {
+ let foo = project()
+ .file("src/lib.rs", "mod private { fn private_item() {} }")
+ .build();
+ foo.cargo("doc --document-private-items").run();
+
+ assert!(foo.root().join("target/doc").is_dir());
+ assert!(foo
+ .root()
+ .join("target/doc/foo/private/index.html")
+ .is_file());
+}
+
+#[cargo_test]
+fn doc_private_ws() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["a", "b"]
+ "#,
+ )
+ .file("a/Cargo.toml", &basic_manifest("a", "0.0.1"))
+ .file("a/src/lib.rs", "fn p() {}")
+ .file("b/Cargo.toml", &basic_manifest("b", "0.0.1"))
+ .file("b/src/lib.rs", "fn p2() {}")
+ .file("b/src/bin/b-cli.rs", "fn main() {}")
+ .build();
+ p.cargo("doc --workspace --bins --lib --document-private-items -v")
+ .with_stderr_contains(
+ "[RUNNING] `rustdoc [..] a/src/lib.rs [..]--document-private-items[..]",
+ )
+ .with_stderr_contains(
+ "[RUNNING] `rustdoc [..] b/src/lib.rs [..]--document-private-items[..]",
+ )
+ .with_stderr_contains(
+ "[RUNNING] `rustdoc [..] b/src/bin/b-cli.rs [..]--document-private-items[..]",
+ )
+ .run();
+}
+
+const BAD_INTRA_LINK_LIB: &str = r#"
+#![deny(broken_intra_doc_links)]
+
+/// [bad_link]
+pub fn foo() {}
+"#;
+
+#[cargo_test]
+fn doc_cap_lints() {
+ let a = git::new("a", |p| {
+ p.file("Cargo.toml", &basic_lib_manifest("a"))
+ .file("src/lib.rs", BAD_INTRA_LINK_LIB)
+ });
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ a = {{ git = '{}' }}
+ "#,
+ a.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("doc")
+ .with_stderr_unordered(
+ "\
+[UPDATING] git repository `[..]`
+[DOCUMENTING] a v0.5.0 ([..])
+[CHECKING] a v0.5.0 ([..])
+[DOCUMENTING] foo v0.0.1 ([..])
+[FINISHED] dev [..]
+",
+ )
+ .run();
+
+ p.root().join("target").rm_rf();
+
+ p.cargo("doc -vv")
+ .with_stderr_contains("[WARNING] [..]`bad_link`[..]")
+ .run();
+}
+
+#[cargo_test]
+fn doc_message_format() {
+ let p = project().file("src/lib.rs", BAD_INTRA_LINK_LIB).build();
+
+ p.cargo("doc --message-format=json")
+ .with_status(101)
+ .with_json_contains_unordered(
+ r#"
+ {
+ "message": {
+ "children": "{...}",
+ "code": "{...}",
+ "level": "error",
+ "message": "{...}",
+ "rendered": "{...}",
+ "spans": "{...}"
+ },
+ "package_id": "foo [..]",
+ "manifest_path": "[..]",
+ "reason": "compiler-message",
+ "target": "{...}"
+ }
+ "#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn doc_json_artifacts() {
+ // Checks the output of json artifact messages.
+ let p = project()
+ .file("src/lib.rs", "")
+ .file("src/bin/somebin.rs", "fn main() {}")
+ .build();
+
+ p.cargo("doc --message-format=json")
+ .with_json_contains_unordered(
+ r#"
+{
+ "reason": "compiler-artifact",
+ "package_id": "foo 0.0.1 [..]",
+ "manifest_path": "[ROOT]/foo/Cargo.toml",
+ "target":
+ {
+ "kind": ["lib"],
+ "crate_types": ["lib"],
+ "name": "foo",
+ "src_path": "[ROOT]/foo/src/lib.rs",
+ "edition": "2015",
+ "doc": true,
+ "doctest": true,
+ "test": true
+ },
+ "profile": "{...}",
+ "features": [],
+ "filenames": ["[ROOT]/foo/target/debug/deps/libfoo-[..].rmeta"],
+ "executable": null,
+ "fresh": false
+}
+
+{
+ "reason": "compiler-artifact",
+ "package_id": "foo 0.0.1 [..]",
+ "manifest_path": "[ROOT]/foo/Cargo.toml",
+ "target":
+ {
+ "kind": ["lib"],
+ "crate_types": ["lib"],
+ "name": "foo",
+ "src_path": "[ROOT]/foo/src/lib.rs",
+ "edition": "2015",
+ "doc": true,
+ "doctest": true,
+ "test": true
+ },
+ "profile": "{...}",
+ "features": [],
+ "filenames": ["[ROOT]/foo/target/doc/foo/index.html"],
+ "executable": null,
+ "fresh": false
+}
+
+{
+ "reason": "compiler-artifact",
+ "package_id": "foo 0.0.1 [..]",
+ "manifest_path": "[ROOT]/foo/Cargo.toml",
+ "target":
+ {
+ "kind": ["bin"],
+ "crate_types": ["bin"],
+ "name": "somebin",
+ "src_path": "[ROOT]/foo/src/bin/somebin.rs",
+ "edition": "2015",
+ "doc": true,
+ "doctest": false,
+ "test": true
+ },
+ "profile": "{...}",
+ "features": [],
+ "filenames": ["[ROOT]/foo/target/doc/somebin/index.html"],
+ "executable": null,
+ "fresh": false
+}
+
+{"reason":"build-finished","success":true}
+"#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn short_message_format() {
+ let p = project().file("src/lib.rs", BAD_INTRA_LINK_LIB).build();
+ p.cargo("doc --message-format=short")
+ .with_status(101)
+ .with_stderr_contains("src/lib.rs:4:6: error: [..]`bad_link`[..]")
+ .run();
+}
+
+#[cargo_test]
+fn doc_example() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [[example]]
+ crate-type = ["lib"]
+ name = "ex1"
+ doc = true
+ "#,
+ )
+ .file("src/lib.rs", "pub fn f() {}")
+ .file(
+ "examples/ex1.rs",
+ r#"
+ use foo::f;
+
+ /// Example
+ pub fn x() { f(); }
+ "#,
+ )
+ .build();
+
+ p.cargo("doc").run();
+ assert!(p
+ .build_dir()
+ .join("doc")
+ .join("ex1")
+ .join("fn.x.html")
+ .exists());
+}
+
+#[cargo_test]
+fn doc_example_with_deps() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [[example]]
+ crate-type = ["lib"]
+ name = "ex"
+ doc = true
+
+ [dev-dependencies]
+ a = {path = "a"}
+ b = {path = "b"}
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "examples/ex.rs",
+ r#"
+ use a::fun;
+
+ /// Example
+ pub fn x() { fun(); }
+ "#,
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+
+ [dependencies]
+ b = {path = "../b"}
+ "#,
+ )
+ .file("a/src/fun.rs", "pub fn fun() {}")
+ .file("a/src/lib.rs", "pub mod fun;")
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.0.1"
+ "#,
+ )
+ .file("b/src/lib.rs", "")
+ .build();
+
+ p.cargo("doc --examples").run();
+ assert!(p
+ .build_dir()
+ .join("doc")
+ .join("ex")
+ .join("fn.x.html")
+ .exists());
+}
+
+#[cargo_test]
+fn bin_private_items() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ "
+ pub fn foo_pub() {}
+ fn foo_priv() {}
+ struct FooStruct;
+ enum FooEnum {}
+ trait FooTrait {}
+ type FooType = u32;
+ mod foo_mod {}
+
+ ",
+ )
+ .build();
+
+ p.cargo("doc")
+ .with_stderr(
+ "\
+[DOCUMENTING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ assert!(p.root().join("target/doc/foo/index.html").is_file());
+ assert!(p.root().join("target/doc/foo/fn.foo_pub.html").is_file());
+ assert!(p.root().join("target/doc/foo/fn.foo_priv.html").is_file());
+ assert!(p
+ .root()
+ .join("target/doc/foo/struct.FooStruct.html")
+ .is_file());
+ assert!(p.root().join("target/doc/foo/enum.FooEnum.html").is_file());
+ assert!(p
+ .root()
+ .join("target/doc/foo/trait.FooTrait.html")
+ .is_file());
+ assert!(p.root().join("target/doc/foo/type.FooType.html").is_file());
+ assert!(p.root().join("target/doc/foo/foo_mod/index.html").is_file());
+}
+
+#[cargo_test]
+fn bin_private_items_deps() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "bar"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ "
+ fn foo_priv() {}
+ pub fn foo_pub() {}
+ ",
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file(
+ "bar/src/lib.rs",
+ "
+ #[allow(dead_code)]
+ fn bar_priv() {}
+ pub fn bar_pub() {}
+ ",
+ )
+ .build();
+
+ p.cargo("doc")
+ .with_stderr_unordered(
+ "\
+[DOCUMENTING] bar v0.0.1 ([..])
+[CHECKING] bar v0.0.1 ([..])
+[DOCUMENTING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ assert!(p.root().join("target/doc/foo/index.html").is_file());
+ assert!(p.root().join("target/doc/foo/fn.foo_pub.html").is_file());
+ assert!(p.root().join("target/doc/foo/fn.foo_priv.html").is_file());
+
+ assert!(p.root().join("target/doc/bar/index.html").is_file());
+ assert!(p.root().join("target/doc/bar/fn.bar_pub.html").is_file());
+ assert!(!p.root().join("target/doc/bar/fn.bar_priv.html").exists());
+}
+
+#[cargo_test]
+fn crate_versions() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "1.2.4"
+ authors = []
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("doc -v")
+ .with_stderr(
+ "\
+[DOCUMENTING] foo v1.2.4 [..]
+[RUNNING] `rustdoc --crate-type lib --crate-name foo src/lib.rs [..]--crate-version 1.2.4`
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ let output_path = p.root().join("target/doc/foo/index.html");
+ let output_documentation = fs::read_to_string(&output_path).unwrap();
+
+ assert!(output_documentation.contains("Version 1.2.4"));
+}
+
+#[cargo_test]
+fn crate_versions_flag_is_overridden() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "1.2.4"
+ authors = []
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ let output_documentation = || {
+ let output_path = p.root().join("target/doc/foo/index.html");
+ fs::read_to_string(&output_path).unwrap()
+ };
+ let asserts = |html: String| {
+ assert!(!html.contains("1.2.4"));
+ assert!(html.contains("Version 2.0.3"));
+ };
+
+ p.cargo("doc")
+ .env("RUSTDOCFLAGS", "--crate-version 2.0.3")
+ .run();
+ asserts(output_documentation());
+
+ p.build_dir().rm_rf();
+
+ p.cargo("rustdoc -- --crate-version 2.0.3").run();
+ asserts(output_documentation());
+}
+
+#[cargo_test(nightly, reason = "-Zdoctest-in-workspace is unstable")]
+fn doc_test_in_workspace() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = [
+ "crate-a",
+ "crate-b",
+ ]
+ "#,
+ )
+ .file(
+ "crate-a/Cargo.toml",
+ r#"
+ [package]
+ name = "crate-a"
+ version = "0.1.0"
+ "#,
+ )
+ .file(
+ "crate-a/src/lib.rs",
+ "\
+ //! ```
+ //! assert_eq!(1, 1);
+ //! ```
+ ",
+ )
+ .file(
+ "crate-b/Cargo.toml",
+ r#"
+ [package]
+ name = "crate-b"
+ version = "0.1.0"
+ "#,
+ )
+ .file(
+ "crate-b/src/lib.rs",
+ "\
+ //! ```
+ //! assert_eq!(1, 1);
+ //! ```
+ ",
+ )
+ .build();
+ p.cargo("test -Zdoctest-in-workspace --doc -vv")
+ .masquerade_as_nightly_cargo(&["doctest-in-workspace"])
+ .with_stderr_contains("[DOCTEST] crate-a")
+ .with_stdout_contains(
+ "
+running 1 test
+test crate-a/src/lib.rs - (line 1) ... ok
+
+test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out[..]
+
+",
+ )
+ .with_stderr_contains("[DOCTEST] crate-b")
+ .with_stdout_contains(
+ "
+running 1 test
+test crate-b/src/lib.rs - (line 1) ... ok
+
+test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out[..]
+
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn doc_fingerprint_is_versioning_consistent() {
+ // Random rustc verbose version
+ let old_rustc_verbose_version = format!(
+ "\
+rustc 1.41.1 (f3e1a954d 2020-02-24)
+binary: rustc
+commit-hash: f3e1a954d2ead4e2fc197c7da7d71e6c61bad196
+commit-date: 2020-02-24
+host: {}
+release: 1.41.1
+LLVM version: 9.0
+",
+ rustc_host()
+ );
+
+ // Create the dummy project.
+ let dummy_project = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "1.2.4"
+ authors = []
+ "#,
+ )
+ .file("src/lib.rs", "//! These are the docs!")
+ .build();
+
+ dummy_project.cargo("doc").run();
+
+ let fingerprint: RustDocFingerprint =
+ serde_json::from_str(&dummy_project.read_file("target/.rustdoc_fingerprint.json"))
+ .expect("JSON Serde fail");
+
+ // Check that the fingerprint contains the actual rustc version
+ // which has been used to compile the docs.
+ let output = std::process::Command::new("rustc")
+ .arg("-vV")
+ .output()
+ .expect("Failed to get actual rustc verbose version");
+ assert_eq!(
+ fingerprint.rustc_vv,
+ (String::from_utf8_lossy(&output.stdout).as_ref())
+ );
+
+ // As the test shows above. Now we have generated the `doc/` folder and inside
+ // the rustdoc fingerprint file is located with the correct rustc version.
+ // So we will remove it and create a new fingerprint with an old rustc version
+ // inside it. We will also place a bogus file inside of the `doc/` folder to ensure
+ // it gets removed as we expect on the next doc compilation.
+ dummy_project.change_file(
+ "target/.rustdoc_fingerprint.json",
+ &old_rustc_verbose_version,
+ );
+
+ fs::write(
+ dummy_project.build_dir().join("doc/bogus_file"),
+ String::from("This is a bogus file and should be removed!"),
+ )
+ .expect("Error writing test bogus file");
+
+ // Now if we trigger another compilation, since the fingerprint contains an old version
+ // of rustc, cargo should remove the entire `/doc` folder (including the fingerprint)
+ // and generating another one with the actual version.
+ // It should also remove the bogus file we created above.
+ dummy_project.cargo("doc").run();
+
+ assert!(!dummy_project.build_dir().join("doc/bogus_file").exists());
+
+ let fingerprint: RustDocFingerprint =
+ serde_json::from_str(&dummy_project.read_file("target/.rustdoc_fingerprint.json"))
+ .expect("JSON Serde fail");
+
+ // Check that the fingerprint contains the actual rustc version
+ // which has been used to compile the docs.
+ assert_eq!(
+ fingerprint.rustc_vv,
+ (String::from_utf8_lossy(&output.stdout).as_ref())
+ );
+}
+
+#[cargo_test]
+fn doc_fingerprint_respects_target_paths() {
+ // Random rustc verbose version
+ let old_rustc_verbose_version = format!(
+ "\
+rustc 1.41.1 (f3e1a954d 2020-02-24)
+binary: rustc
+commit-hash: f3e1a954d2ead4e2fc197c7da7d71e6c61bad196
+commit-date: 2020-02-24
+host: {}
+release: 1.41.1
+LLVM version: 9.0
+",
+ rustc_host()
+ );
+
+ // Create the dummy project.
+ let dummy_project = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "1.2.4"
+ authors = []
+ "#,
+ )
+ .file("src/lib.rs", "//! These are the docs!")
+ .build();
+
+ dummy_project.cargo("doc --target").arg(rustc_host()).run();
+
+ let fingerprint: RustDocFingerprint =
+ serde_json::from_str(&dummy_project.read_file("target/.rustdoc_fingerprint.json"))
+ .expect("JSON Serde fail");
+
+ // Check that the fingerprint contains the actual rustc version
+ // which has been used to compile the docs.
+ let output = std::process::Command::new("rustc")
+ .arg("-vV")
+ .output()
+ .expect("Failed to get actual rustc verbose version");
+ assert_eq!(
+ fingerprint.rustc_vv,
+ (String::from_utf8_lossy(&output.stdout).as_ref())
+ );
+
+ // As the test shows above. Now we have generated the `doc/` folder and inside
+ // the rustdoc fingerprint file is located with the correct rustc version.
+ // So we will remove it and create a new fingerprint with an old rustc version
+ // inside it. We will also place a bogus file inside of the `doc/` folder to ensure
+ // it gets removed as we expect on the next doc compilation.
+ dummy_project.change_file(
+ "target/.rustdoc_fingerprint.json",
+ &old_rustc_verbose_version,
+ );
+
+ fs::write(
+ dummy_project
+ .build_dir()
+ .join(rustc_host())
+ .join("doc/bogus_file"),
+ String::from("This is a bogus file and should be removed!"),
+ )
+ .expect("Error writing test bogus file");
+
+ // Now if we trigger another compilation, since the fingerprint contains an old version
+ // of rustc, cargo should remove the entire `/doc` folder (including the fingerprint)
+ // and generating another one with the actual version.
+ // It should also remove the bogus file we created above.
+ dummy_project.cargo("doc --target").arg(rustc_host()).run();
+
+ assert!(!dummy_project
+ .build_dir()
+ .join(rustc_host())
+ .join("doc/bogus_file")
+ .exists());
+
+ let fingerprint: RustDocFingerprint =
+ serde_json::from_str(&dummy_project.read_file("target/.rustdoc_fingerprint.json"))
+ .expect("JSON Serde fail");
+
+ // Check that the fingerprint contains the actual rustc version
+ // which has been used to compile the docs.
+ assert_eq!(
+ fingerprint.rustc_vv,
+ (String::from_utf8_lossy(&output.stdout).as_ref())
+ );
+}
+
+#[cargo_test]
+fn doc_fingerprint_unusual_behavior() {
+ // Checks for some unusual circumstances with clearing the doc directory.
+ if !symlink_supported() {
+ return;
+ }
+ let p = project().file("src/lib.rs", "").build();
+ p.build_dir().mkdir_p();
+ let real_doc = p.root().join("doc");
+ real_doc.mkdir_p();
+ let build_doc = p.build_dir().join("doc");
+ p.symlink(&real_doc, &build_doc);
+ fs::write(real_doc.join("somefile"), "test").unwrap();
+ fs::write(real_doc.join(".hidden"), "test").unwrap();
+ p.cargo("doc").run();
+ // Make sure for the first run, it does not delete any files and does not
+ // break the symlink.
+ assert!(build_doc.join("somefile").exists());
+ assert!(real_doc.join("somefile").exists());
+ assert!(real_doc.join(".hidden").exists());
+ assert!(real_doc.join("foo/index.html").exists());
+ // Pretend that the last build was generated by an older version.
+ p.change_file(
+ "target/.rustdoc_fingerprint.json",
+ "{\"rustc_vv\": \"I am old\"}",
+ );
+ // Change file to trigger a new build.
+ p.change_file("src/lib.rs", "// changed");
+ p.cargo("doc")
+ .with_stderr(
+ "[DOCUMENTING] foo [..]\n\
+ [FINISHED] [..]",
+ )
+ .run();
+ // This will delete somefile, but not .hidden.
+ assert!(!real_doc.join("somefile").exists());
+ assert!(real_doc.join(".hidden").exists());
+ assert!(real_doc.join("foo/index.html").exists());
+ // And also check the -Z flag behavior.
+ p.change_file(
+ "target/.rustdoc_fingerprint.json",
+ "{\"rustc_vv\": \"I am old\"}",
+ );
+ // Change file to trigger a new build.
+ p.change_file("src/lib.rs", "// changed2");
+ fs::write(real_doc.join("somefile"), "test").unwrap();
+ p.cargo("doc -Z skip-rustdoc-fingerprint")
+ .masquerade_as_nightly_cargo(&["skip-rustdoc-fingerprint"])
+ .with_stderr(
+ "[DOCUMENTING] foo [..]\n\
+ [FINISHED] [..]",
+ )
+ .run();
+ // Should not have deleted anything.
+ assert!(build_doc.join("somefile").exists());
+ assert!(real_doc.join("somefile").exists());
+}
+
+#[cargo_test]
+fn lib_before_bin() {
+ // Checks that the library is documented before the binary.
+ // Previously they were built concurrently, which can cause issues
+ // if the bin has intra-doc links to the lib.
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ /// Hi
+ pub fn abc() {}
+ "#,
+ )
+ .file(
+ "src/bin/somebin.rs",
+ r#"
+ //! See [`foo::abc`]
+ fn main() {}
+ "#,
+ )
+ .build();
+
+ // Run check first. This just helps ensure that the test clearly shows the
+ // order of the rustdoc commands.
+ p.cargo("check").run();
+
+ // The order of output here should be deterministic.
+ p.cargo("doc -v")
+ .with_stderr(
+ "\
+[DOCUMENTING] foo [..]
+[RUNNING] `rustdoc --crate-type lib --crate-name foo src/lib.rs [..]
+[RUNNING] `rustdoc --crate-type bin --crate-name somebin src/bin/somebin.rs [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ // And the link should exist.
+ let bin_html = p.read_file("target/doc/somebin/index.html");
+ assert!(bin_html.contains("../foo/fn.abc.html"));
+}
+
+#[cargo_test]
+fn doc_lib_false() {
+ // doc = false for a library
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [lib]
+ doc = false
+
+ [dependencies]
+ bar = {path = "bar"}
+ "#,
+ )
+ .file("src/lib.rs", "extern crate bar;")
+ .file("src/bin/some-bin.rs", "fn main() {}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+
+ [lib]
+ doc = false
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("doc")
+ .with_stderr(
+ "\
+[CHECKING] bar v0.1.0 [..]
+[CHECKING] foo v0.1.0 [..]
+[DOCUMENTING] foo v0.1.0 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ assert!(!p.build_dir().join("doc/foo").exists());
+ assert!(!p.build_dir().join("doc/bar").exists());
+ assert!(p.build_dir().join("doc/some_bin").exists());
+}
+
+#[cargo_test]
+fn doc_lib_false_dep() {
+ // doc = false for a dependency
+ // Ensures that the rmeta gets produced
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { path = "bar" }
+ "#,
+ )
+ .file("src/lib.rs", "extern crate bar;")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+
+ [lib]
+ doc = false
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("doc")
+ .with_stderr(
+ "\
+[CHECKING] bar v0.1.0 [..]
+[DOCUMENTING] foo v0.1.0 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ assert!(p.build_dir().join("doc/foo").exists());
+ assert!(!p.build_dir().join("doc/bar").exists());
+}
+
+#[cargo_test]
+fn link_to_private_item() {
+ let main = r#"
+ //! [bar]
+ #[allow(dead_code)]
+ fn bar() {}
+ "#;
+ let p = project().file("src/lib.rs", main).build();
+ p.cargo("doc")
+ .with_stderr_contains("[..] documentation for `foo` links to private item `bar`")
+ .run();
+ // Check that binaries don't emit a private_intra_doc_links warning.
+ fs::rename(p.root().join("src/lib.rs"), p.root().join("src/main.rs")).unwrap();
+ p.cargo("doc")
+ .with_stderr(
+ "[DOCUMENTING] foo [..]\n\
+ [FINISHED] [..]",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/docscrape.rs b/src/tools/cargo/tests/testsuite/docscrape.rs
new file mode 100644
index 000000000..c536a6738
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/docscrape.rs
@@ -0,0 +1,637 @@
+//! Tests for the `cargo doc` command with `-Zrustdoc-scrape-examples`.
+
+use cargo_test_support::project;
+
+#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
+fn basic() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ "#,
+ )
+ .file("examples/ex.rs", "fn main() { foo::foo(); }")
+ .file("src/lib.rs", "pub fn foo() {}\npub fn bar() { foo(); }")
+ .build();
+
+ p.cargo("doc -Zunstable-options -Zrustdoc-scrape-examples")
+ .masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
+ .with_stderr(
+ "\
+[CHECKING] foo v0.0.1 ([CWD])
+[SCRAPING] foo v0.0.1 ([CWD])
+[DOCUMENTING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("doc -Zunstable-options -Z rustdoc-scrape-examples")
+ .masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
+ .with_stderr("[FINISHED] [..]")
+ .run();
+
+ let doc_html = p.read_file("target/doc/foo/fn.foo.html");
+ assert!(doc_html.contains("Examples found in repository"));
+ assert!(!doc_html.contains("More examples"));
+
+ // Ensure that the reverse-dependency has its sources generated
+ assert!(p.build_dir().join("doc/src/ex/ex.rs.html").exists());
+}
+
+#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
+fn avoid_build_script_cycle() {
+ let p = project()
+ // package with build dependency
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ links = "foo"
+
+ [workspace]
+ members = ["bar"]
+
+ [build-dependencies]
+ bar = {path = "bar"}
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("build.rs", "fn main(){}")
+ // dependency
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+ links = "bar"
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .file("bar/build.rs", "fn main(){}")
+ .build();
+
+ p.cargo("doc --workspace -Zunstable-options -Zrustdoc-scrape-examples")
+ .masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
+ .run();
+}
+
+#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
+fn complex_reverse_dependencies() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dev-dependencies]
+ a = {path = "a", features = ["feature"]}
+ b = {path = "b"}
+
+ [workspace]
+ members = ["b"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("examples/ex.rs", "fn main() {}")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ proc-macro = true
+
+ [dependencies]
+ b = {path = "../b"}
+
+ [features]
+ feature = []
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.0.1"
+ authors = []
+ "#,
+ )
+ .file("b/src/lib.rs", "")
+ .build();
+
+ p.cargo("doc --workspace --examples -Zunstable-options -Zrustdoc-scrape-examples")
+ .masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
+ .run();
+}
+
+#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
+fn crate_with_dash() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "da-sh"
+ version = "0.0.1"
+ authors = []
+ "#,
+ )
+ .file("src/lib.rs", "pub fn foo() {}")
+ .file("examples/a.rs", "fn main() { da_sh::foo(); }")
+ .build();
+
+ p.cargo("doc -Zunstable-options -Zrustdoc-scrape-examples")
+ .masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
+ .run();
+
+ let doc_html = p.read_file("target/doc/da_sh/fn.foo.html");
+ assert!(doc_html.contains("Examples found in repository"));
+}
+
+#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
+fn configure_target() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ doc-scrape-examples = true
+
+ [[bin]]
+ name = "a_bin"
+ doc-scrape-examples = true
+
+ [[example]]
+ name = "a"
+ doc-scrape-examples = false
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "pub fn foo() {} fn lib_must_appear() { foo(); }",
+ )
+ .file(
+ "examples/a.rs",
+ "fn example_must_not_appear() { foo::foo(); }",
+ )
+ .file(
+ "src/bin/a_bin.rs",
+ "fn bin_must_appear() { foo::foo(); } fn main(){}",
+ )
+ .build();
+
+ p.cargo("doc -Zunstable-options -Zrustdoc-scrape-examples")
+ .masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
+ .run();
+
+ let doc_html = p.read_file("target/doc/foo/fn.foo.html");
+ assert!(doc_html.contains("lib_must_appear"));
+ assert!(doc_html.contains("bin_must_appear"));
+ assert!(!doc_html.contains("example_must_not_appear"));
+}
+
+#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
+fn configure_profile_issue_10500() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [profile.dev]
+ panic = "abort"
+ "#,
+ )
+ .file("examples/ex.rs", "fn main() { foo::foo(); }")
+ .file("src/lib.rs", "pub fn foo() {}\npub fn bar() { foo(); }")
+ .build();
+
+ p.cargo("doc -Zunstable-options -Zrustdoc-scrape-examples")
+ .masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
+ .run();
+
+ let doc_html = p.read_file("target/doc/foo/fn.foo.html");
+ assert!(doc_html.contains("Examples found in repository"));
+}
+
+#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
+fn issue_10545() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ resolver = "2"
+ members = ["a", "b"]
+ "#,
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+ edition = "2021"
+
+ [features]
+ default = ["foo"]
+ foo = []
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.0.1"
+ authors = []
+ edition = "2021"
+
+ [lib]
+ proc-macro = true
+ "#,
+ )
+ .file("b/src/lib.rs", "")
+ .build();
+
+ p.cargo("doc --workspace -Zunstable-options -Zrustdoc-scrape-examples")
+ .masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
+ .run();
+}
+
+#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
+fn cache() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ "#,
+ )
+ .file("examples/ex.rs", "fn main() { foo::foo(); }")
+ .file("src/lib.rs", "pub fn foo() {}\npub fn bar() { foo(); }")
+ .build();
+
+ p.cargo("doc -Zunstable-options -Zrustdoc-scrape-examples")
+ .masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
+ .with_stderr(
+ "\
+[CHECKING] foo v0.0.1 ([CWD])
+[SCRAPING] foo v0.0.1 ([CWD])
+[DOCUMENTING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("doc -Zunstable-options -Zrustdoc-scrape-examples")
+ .masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
+ .with_stderr(
+ "\
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
+fn no_fail_bad_lib() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ "#,
+ )
+ .file("src/lib.rs", "pub fn foo() { CRASH_THE_BUILD() }")
+ .file("examples/ex.rs", "fn main() { foo::foo(); }")
+ .file("examples/ex2.rs", "fn main() { foo::foo(); }")
+ .build();
+
+ p.cargo("doc -Zunstable-options -Z rustdoc-scrape-examples")
+ .masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
+ .with_stderr_unordered(
+ "\
+[CHECKING] foo v0.0.1 ([CWD])
+[SCRAPING] foo v0.0.1 ([CWD])
+warning: failed to check lib in package `foo` as a prerequisite for scraping examples from: example \"ex\", example \"ex2\"
+ Try running with `--verbose` to see the error message.
+ If an example should not be scanned, then consider adding `doc-scrape-examples = false` to its `[[example]]` definition in Cargo.toml
+warning: `foo` (lib) generated 1 warning
+warning: failed to scan example \"ex\" in package `foo` for example code usage
+ Try running with `--verbose` to see the error message.
+ If an example should not be scanned, then consider adding `doc-scrape-examples = false` to its `[[example]]` definition in Cargo.toml
+warning: `foo` (example \"ex\") generated 1 warning
+warning: failed to scan example \"ex2\" in package `foo` for example code usage
+ Try running with `--verbose` to see the error message.
+ If an example should not be scanned, then consider adding `doc-scrape-examples = false` to its `[[example]]` definition in Cargo.toml
+warning: `foo` (example \"ex2\") generated 1 warning
+[DOCUMENTING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ )
+ .run();
+}
+
+#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
+fn fail_bad_build_script() {
+ // See rust-lang/cargo#11623
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("build.rs", "fn main() { panic!(\"You shall not pass\")}")
+ .file("examples/ex.rs", "fn main() {}")
+ .build();
+
+ // `cargo doc` fails
+ p.cargo("doc")
+ .with_status(101)
+ .with_stderr_contains("[..]You shall not pass[..]")
+ .run();
+
+ // scrape examples should fail whenever `cargo doc` fails.
+ p.cargo("doc -Zunstable-options -Z rustdoc-scrape-examples")
+ .masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
+ .with_status(101)
+ .with_stderr_contains("[..]You shall not pass[..]")
+ .run();
+}
+
+#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
+fn no_fail_bad_example() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ "#,
+ )
+ .file("examples/ex1.rs", "DOES NOT COMPILE")
+ .file("examples/ex2.rs", "fn main() { foo::foo(); }")
+ .file("src/lib.rs", "pub fn foo(){}")
+ .build();
+
+ p.cargo("doc -Zunstable-options -Z rustdoc-scrape-examples")
+ .masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
+ .with_stderr(
+ "\
+[CHECKING] foo v0.0.1 ([CWD])
+[SCRAPING] foo v0.0.1 ([CWD])
+warning: failed to scan example \"ex1\" in package `foo` for example code usage
+ Try running with `--verbose` to see the error message.
+ If an example should not be scanned, then consider adding `doc-scrape-examples = false` to its `[[example]]` definition in Cargo.toml
+warning: `foo` (example \"ex1\") generated 1 warning
+[DOCUMENTING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ )
+ .run();
+
+ p.cargo("clean").run();
+
+ p.cargo("doc -v -Zunstable-options -Z rustdoc-scrape-examples")
+ .masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
+ .with_stderr_unordered(
+ "\
+[CHECKING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name foo[..]
+[SCRAPING] foo v0.0.1 ([CWD])
+[RUNNING] `rustdoc[..] --crate-name ex1[..]
+[RUNNING] `rustdoc[..] --crate-name ex2[..]
+[RUNNING] `rustdoc[..] --crate-name foo[..]
+error: expected one of `!` or `::`, found `NOT`
+ --> examples/ex1.rs:1:6
+ |
+1 | DOES NOT COMPILE
+ | ^^^ expected one of `!` or `::`
+
+[DOCUMENTING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ )
+ .run();
+
+ let doc_html = p.read_file("target/doc/foo/fn.foo.html");
+ assert!(doc_html.contains("Examples found in repository"));
+}
+
+#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
+fn no_scrape_with_dev_deps() {
+ // Tests that a crate with dev-dependencies does not have its examples
+ // scraped unless explicitly prompted to check them. See
+ // `UnitGenerator::create_docscrape_proposals` for details on why.
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dev-dependencies]
+ a = {path = "a"}
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("examples/ex.rs", "fn main() { a::f(); }")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+ "#,
+ )
+ .file("a/src/lib.rs", "pub fn f() {}")
+ .build();
+
+ // If --examples is not provided, then the example is not scanned, and a warning
+ // should be raised.
+ p.cargo("doc -Zunstable-options -Z rustdoc-scrape-examples")
+ .masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
+ .with_stderr(
+ "\
+warning: Rustdoc did not scrape the following examples because they require dev-dependencies: ex
+ If you want Rustdoc to scrape these examples, then add `doc-scrape-examples = true`
+ to the [[example]] target configuration of at least one example.
+[DOCUMENTING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ )
+ .run();
+
+ // If --examples is provided, then the example is scanned.
+ p.cargo("doc --examples -Zunstable-options -Z rustdoc-scrape-examples")
+ .masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
+ .with_stderr_unordered(
+ "\
+[CHECKING] a v0.0.1 ([CWD]/a)
+[CHECKING] foo v0.0.1 ([CWD])
+[DOCUMENTING] a v0.0.1 ([CWD]/a)
+[SCRAPING] foo v0.0.1 ([CWD])
+[DOCUMENTING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ )
+ .run();
+}
+
+#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
+fn use_dev_deps_if_explicitly_enabled() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [[example]]
+ name = "ex"
+ doc-scrape-examples = true
+
+ [dev-dependencies]
+ a = {path = "a"}
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("examples/ex.rs", "fn main() { a::f(); }")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+ "#,
+ )
+ .file("a/src/lib.rs", "pub fn f() {}")
+ .build();
+
+ // If --examples is not provided, then the example is never scanned.
+ p.cargo("doc -Zunstable-options -Z rustdoc-scrape-examples")
+ .masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
+ .with_stderr_unordered(
+ "\
+[CHECKING] foo v0.0.1 ([CWD])
+[CHECKING] a v0.0.1 ([CWD]/a)
+[SCRAPING] foo v0.0.1 ([CWD])
+[DOCUMENTING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ )
+ .run();
+}
+
+#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
+fn only_scrape_documented_targets() {
+ // package bar has doc = false and should not be eligible for documtation.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ doc = false
+
+ [workspace]
+ members = ["foo"]
+
+ [dependencies]
+ foo = {{ path = "foo" }}
+ "#
+ ),
+ )
+ .file("src/lib.rs", "")
+ .file("examples/ex.rs", "pub fn main() { foo::foo(); }")
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ "#,
+ )
+ .file("foo/src/lib.rs", "pub fn foo() {}")
+ .build();
+
+ p.cargo("doc --workspace -Zunstable-options -Zrustdoc-scrape-examples")
+ .masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
+ .run();
+
+ let doc_html = p.read_file("target/doc/foo/fn.foo.html");
+ let example_found = doc_html.contains("Examples found in repository");
+ assert!(!example_found);
+}
+
+#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
+fn issue_11496() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "repro"
+ version = "0.1.0"
+ edition = "2021"
+
+ [lib]
+ proc-macro = true
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("examples/ex.rs", "fn main(){}")
+ .build();
+
+ p.cargo("doc -Zunstable-options -Zrustdoc-scrape-examples")
+ .masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/edition.rs b/src/tools/cargo/tests/testsuite/edition.rs
new file mode 100644
index 000000000..377a86ec0
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/edition.rs
@@ -0,0 +1,124 @@
+//! Tests for edition setting.
+
+use cargo::core::Edition;
+use cargo_test_support::{basic_lib_manifest, project};
+
+#[cargo_test]
+fn edition_works_for_build_script() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = 'foo'
+ version = '0.1.0'
+ edition = '2018'
+
+ [build-dependencies]
+ a = { path = 'a' }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ a::foo();
+ }
+ "#,
+ )
+ .file("a/Cargo.toml", &basic_lib_manifest("a"))
+ .file("a/src/lib.rs", "pub fn foo() {}")
+ .build();
+
+ p.cargo("check -v").run();
+}
+
+#[cargo_test]
+fn edition_unstable_gated() {
+ // During the period where a new edition is coming up, but not yet stable,
+ // this test will verify that it cannot be used on stable. If there is no
+ // next edition, it does nothing.
+ let next = match Edition::LATEST_UNSTABLE {
+ Some(next) => next,
+ None => {
+ eprintln!("Next edition is currently not available, skipping test.");
+ return;
+ }
+ };
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "{}"
+ "#,
+ next
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(&format!(
+ "\
+[ERROR] failed to parse manifest at `[..]/foo/Cargo.toml`
+
+Caused by:
+ feature `edition{next}` is required
+
+ The package requires the Cargo feature called `edition{next}`, \
+ but that feature is not stabilized in this version of Cargo (1.[..]).
+ Consider trying a newer version of Cargo (this may require the nightly release).
+ See https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#edition-{next} \
+ for more information about the status of this feature.
+",
+ next = next
+ ))
+ .run();
+}
+
+#[cargo_test(nightly, reason = "fundamentally always nightly")]
+fn edition_unstable() {
+ // During the period where a new edition is coming up, but not yet stable,
+ // this test will verify that it can be used with `cargo-features`. If
+ // there is no next edition, it does nothing.
+ let next = match Edition::LATEST_UNSTABLE {
+ Some(next) => next,
+ None => {
+ eprintln!("Next edition is currently not available, skipping test.");
+ return;
+ }
+ };
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ cargo-features = ["edition{next}"]
+
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "{next}"
+ "#,
+ next = next
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .masquerade_as_nightly_cargo(&["always_nightly"])
+ .with_stderr(
+ "\
+[CHECKING] foo [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/error.rs b/src/tools/cargo/tests/testsuite/error.rs
new file mode 100644
index 000000000..410902c21
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/error.rs
@@ -0,0 +1,19 @@
+//! General error tests that don't belong anywhere else.
+
+use cargo_test_support::cargo_process;
+
+#[cargo_test]
+fn internal_error() {
+ cargo_process("init")
+ .env("__CARGO_TEST_INTERNAL_ERROR", "1")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] internal error test
+[NOTE] this is an unexpected cargo internal error
+[NOTE] we would appreciate a bug report: https://github.com/rust-lang/cargo/issues/
+[NOTE] cargo [..]
+",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/features.rs b/src/tools/cargo/tests/testsuite/features.rs
new file mode 100644
index 000000000..848e05677
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/features.rs
@@ -0,0 +1,2084 @@
+//! Tests for `[features]` table.
+
+use cargo_test_support::paths::CargoPathExt;
+use cargo_test_support::registry::{Dependency, Package};
+use cargo_test_support::{basic_manifest, project};
+
+#[cargo_test]
+fn invalid1() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ bar = ["baz"]
+ "#,
+ )
+ .file("src/main.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ feature `bar` includes `baz` which is neither a dependency nor another feature
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn same_name() {
+ // Feature with the same name as a dependency.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ bar = ["baz"]
+ baz = []
+
+ [dependencies.bar]
+ path = "bar"
+ "#,
+ )
+ .file("src/main.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "1.0.0"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("tree -f")
+ .arg("{p} [{f}]")
+ .with_stderr("")
+ .with_stdout(
+ "\
+foo v0.0.1 ([..]) []
+└── bar v1.0.0 ([..]) []
+",
+ )
+ .run();
+
+ p.cargo("tree --features bar -f")
+ .arg("{p} [{f}]")
+ .with_stderr("")
+ .with_stdout(
+ "\
+foo v0.0.1 ([..]) [bar,baz]
+└── bar v1.0.0 ([..]) []
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn invalid3() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ bar = ["baz"]
+
+ [dependencies.baz]
+ path = "foo"
+ "#,
+ )
+ .file("src/main.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ feature `bar` includes `baz`, but `baz` is not an optional dependency
+ A non-optional dependency of the same name is defined; consider adding `optional = true` to its definition.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn invalid4() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "bar"
+ features = ["bar"]
+ "#,
+ )
+ .file("src/main.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to select a version for `bar`.
+ ... required by package `foo v0.0.1 ([..])`
+versions that meet the requirements `*` are: 0.0.1
+
+the package `foo` depends on `bar`, with features: `bar` but `bar` does not have these features.
+
+
+failed to select a version for `bar` which could resolve this conflict",
+ )
+ .run();
+
+ p.change_file("Cargo.toml", &basic_manifest("foo", "0.0.1"));
+
+ p.cargo("check --features test")
+ .with_status(101)
+ .with_stderr("error: Package `foo v0.0.1 ([..])` does not have the feature `test`")
+ .run();
+}
+
+#[cargo_test]
+fn invalid5() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dev-dependencies.bar]
+ path = "bar"
+ optional = true
+ "#,
+ )
+ .file("src/main.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ dev-dependencies are not allowed to be optional: `bar`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn invalid6() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ foo = ["bar/baz"]
+ "#,
+ )
+ .file("src/main.rs", "")
+ .build();
+
+ p.cargo("check --features foo")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ feature `foo` includes `bar/baz`, but `bar` is not a dependency
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn invalid7() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ foo = ["bar/baz"]
+ bar = []
+ "#,
+ )
+ .file("src/main.rs", "")
+ .build();
+
+ p.cargo("check --features foo")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ feature `foo` includes `bar/baz`, but `bar` is not a dependency
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn invalid8() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "bar"
+ features = ["foo/bar"]
+ "#,
+ )
+ .file("src/main.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("check --features foo")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[CWD]/Cargo.toml`
+
+Caused by:
+ feature `foo/bar` in dependency `bar` is not allowed to contain slashes
+ If you want to enable features [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn invalid9() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "bar"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("check --features bar")
+ .with_stderr(
+ "\
+error: Package `foo v0.0.1 ([..])` does not have feature `bar`. It has a required dependency with that name, but only optional dependencies can be used as features.
+",
+ ).with_status(101).run();
+}
+
+#[cargo_test]
+fn invalid10() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "bar"
+ features = ["baz"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.baz]
+ path = "baz"
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .file("bar/baz/Cargo.toml", &basic_manifest("baz", "0.0.1"))
+ .file("bar/baz/src/lib.rs", "")
+ .build();
+
+ p.cargo("check").with_stderr("\
+error: failed to select a version for `bar`.
+ ... required by package `foo v0.0.1 ([..])`
+versions that meet the requirements `*` are: 0.0.1
+
+the package `foo` depends on `bar`, with features: `baz` but `bar` does not have these features.
+ It has a required dependency with that name, but only optional dependencies can be used as features.
+
+
+failed to select a version for `bar` which could resolve this conflict
+").with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn no_transitive_dep_feature_requirement() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.derived]
+ path = "derived"
+
+ [features]
+ default = ["derived/bar/qux"]
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ extern crate derived;
+ fn main() { derived::test(); }
+ "#,
+ )
+ .file(
+ "derived/Cargo.toml",
+ r#"
+ [package]
+ name = "derived"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "../bar"
+ "#,
+ )
+ .file("derived/src/lib.rs", "extern crate bar; pub use bar::test;")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ qux = []
+ "#,
+ )
+ .file(
+ "bar/src/lib.rs",
+ r#"
+ #[cfg(feature = "qux")]
+ pub fn test() { print!("test"); }
+ "#,
+ )
+ .build();
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[CWD]/Cargo.toml`
+
+Caused by:
+ multiple slashes in feature `derived/bar/qux` (included by feature `default`) are not allowed
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn no_feature_doesnt_build() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "bar"
+ optional = true
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ #[cfg(feature = "bar")]
+ extern crate bar;
+ #[cfg(feature = "bar")]
+ fn main() { bar::bar(); println!("bar") }
+ #[cfg(not(feature = "bar"))]
+ fn main() {}
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ p.cargo("build")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ p.process(&p.bin("foo")).with_stdout("").run();
+
+ p.cargo("build --features bar -v")
+ .with_stderr(
+ "\
+[COMPILING] bar v0.0.1 ([CWD]/bar)
+[RUNNING] `rustc --crate-name bar [..]
+[DIRTY-MSVC] foo v0.0.1 ([CWD]): the list of features changed
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name foo [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ p.process(&p.bin("foo")).with_stdout("bar\n").run();
+}
+
+#[cargo_test]
+fn default_feature_pulled_in() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ default = ["bar"]
+
+ [dependencies.bar]
+ path = "bar"
+ optional = true
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ #[cfg(feature = "bar")]
+ extern crate bar;
+ #[cfg(feature = "bar")]
+ fn main() { bar::bar(); println!("bar") }
+ #[cfg(not(feature = "bar"))]
+ fn main() {}
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ p.cargo("build")
+ .with_stderr(
+ "\
+[COMPILING] bar v0.0.1 ([CWD]/bar)
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ p.process(&p.bin("foo")).with_stdout("bar\n").run();
+
+ p.cargo("build --no-default-features -v")
+ .with_stderr(
+ "\
+[DIRTY-MSVC] foo v0.0.1 ([CWD]): the list of features changed
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name foo [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ p.process(&p.bin("foo")).with_stdout("").run();
+}
+
+#[cargo_test]
+fn cyclic_feature() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ default = ["default"]
+ "#,
+ )
+ .file("src/main.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr("[ERROR] cyclic feature dependency: feature `default` depends on itself")
+ .run();
+}
+
+#[cargo_test]
+fn cyclic_feature2() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ foo = ["bar"]
+ bar = ["foo"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check").with_stdout("").run();
+}
+
+#[cargo_test]
+fn groups_on_groups_on_groups() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ default = ["f1"]
+ f1 = ["f2", "bar"]
+ f2 = ["f3", "f4"]
+ f3 = ["f5", "f6", "baz"]
+ f4 = ["f5", "f7"]
+ f5 = ["f6"]
+ f6 = ["f7"]
+ f7 = ["bar"]
+
+ [dependencies.bar]
+ path = "bar"
+ optional = true
+
+ [dependencies.baz]
+ path = "baz"
+ optional = true
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ #[allow(unused_extern_crates)]
+ extern crate bar;
+ #[allow(unused_extern_crates)]
+ extern crate baz;
+ fn main() {}
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.0.1"))
+ .file("baz/src/lib.rs", "pub fn baz() {}")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[CHECKING] ba[..] v0.0.1 ([CWD]/ba[..])
+[CHECKING] ba[..] v0.0.1 ([CWD]/ba[..])
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn many_cli_features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "bar"
+ optional = true
+
+ [dependencies.baz]
+ path = "baz"
+ optional = true
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ #[allow(unused_extern_crates)]
+ extern crate bar;
+ #[allow(unused_extern_crates)]
+ extern crate baz;
+ fn main() {}
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.0.1"))
+ .file("baz/src/lib.rs", "pub fn baz() {}")
+ .build();
+
+ p.cargo("check --features")
+ .arg("bar baz")
+ .with_stderr(
+ "\
+[CHECKING] ba[..] v0.0.1 ([CWD]/ba[..])
+[CHECKING] ba[..] v0.0.1 ([CWD]/ba[..])
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn union_features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.d1]
+ path = "d1"
+ features = ["f1"]
+ [dependencies.d2]
+ path = "d2"
+ features = ["f2"]
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ #[allow(unused_extern_crates)]
+ extern crate d1;
+ extern crate d2;
+ fn main() {
+ d2::f1();
+ d2::f2();
+ }
+ "#,
+ )
+ .file(
+ "d1/Cargo.toml",
+ r#"
+ [package]
+ name = "d1"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ f1 = ["d2"]
+
+ [dependencies.d2]
+ path = "../d2"
+ features = ["f1"]
+ optional = true
+ "#,
+ )
+ .file("d1/src/lib.rs", "")
+ .file(
+ "d2/Cargo.toml",
+ r#"
+ [package]
+ name = "d2"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ f1 = []
+ f2 = []
+ "#,
+ )
+ .file(
+ "d2/src/lib.rs",
+ r#"
+ #[cfg(feature = "f1")] pub fn f1() {}
+ #[cfg(feature = "f2")] pub fn f2() {}
+ "#,
+ )
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[CHECKING] d2 v0.0.1 ([CWD]/d2)
+[CHECKING] d1 v0.0.1 ([CWD]/d1)
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn many_features_no_rebuilds() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies.a]
+ path = "a"
+ features = ["fall"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+ authors = []
+
+ [features]
+ ftest = []
+ ftest2 = []
+ fall = ["ftest", "ftest2"]
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[CHECKING] a v0.1.0 ([CWD]/a)
+[CHECKING] b v0.1.0 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ p.root().move_into_the_past();
+
+ p.cargo("check -v")
+ .with_stderr(
+ "\
+[FRESH] a v0.1.0 ([..]/a)
+[FRESH] b v0.1.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+// Tests that all cmd lines work with `--features ""`
+#[cargo_test]
+fn empty_features() {
+ let p = project().file("src/main.rs", "fn main() {}").build();
+
+ p.cargo("check --features").arg("").run();
+}
+
+// Tests that all cmd lines work with `--features ""`
+#[cargo_test]
+fn transitive_features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ foo = ["bar/baz"]
+
+ [dependencies.bar]
+ path = "bar"
+ "#,
+ )
+ .file("src/main.rs", "extern crate bar; fn main() { bar::baz(); }")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ baz = []
+ "#,
+ )
+ .file(
+ "bar/src/lib.rs",
+ r#"#[cfg(feature = "baz")] pub fn baz() {}"#,
+ )
+ .build();
+
+ p.cargo("check --features foo").run();
+}
+
+#[cargo_test]
+fn everything_in_the_lockfile() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ f1 = ["d1/f1"]
+ f2 = ["d2"]
+
+ [dependencies.d1]
+ path = "d1"
+ [dependencies.d2]
+ path = "d2"
+ optional = true
+ [dependencies.d3]
+ path = "d3"
+ optional = true
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "d1/Cargo.toml",
+ r#"
+ [package]
+ name = "d1"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ f1 = []
+ "#,
+ )
+ .file("d1/src/lib.rs", "")
+ .file("d2/Cargo.toml", &basic_manifest("d2", "0.0.2"))
+ .file("d2/src/lib.rs", "")
+ .file(
+ "d3/Cargo.toml",
+ r#"
+ [package]
+ name = "d3"
+ version = "0.0.3"
+ authors = []
+
+ [features]
+ f3 = []
+ "#,
+ )
+ .file("d3/src/lib.rs", "")
+ .build();
+
+ p.cargo("fetch").run();
+ let lockfile = p.read_lockfile();
+ assert!(
+ lockfile.contains(r#"name = "d1""#),
+ "d1 not found\n{}",
+ lockfile
+ );
+ assert!(
+ lockfile.contains(r#"name = "d2""#),
+ "d2 not found\n{}",
+ lockfile
+ );
+ assert!(
+ lockfile.contains(r#"name = "d3""#),
+ "d3 not found\n{}",
+ lockfile
+ );
+}
+
+#[cargo_test]
+fn no_rebuild_when_frobbing_default_feature() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ a = { path = "a" }
+ b = { path = "b" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ a = { path = "../a", features = ["f1"], default-features = false }
+ "#,
+ )
+ .file("b/src/lib.rs", "")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+ authors = []
+
+ [features]
+ default = ["f1"]
+ f1 = []
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .build();
+
+ p.cargo("check").run();
+ p.cargo("check").with_stdout("").run();
+ p.cargo("check").with_stdout("").run();
+}
+
+#[cargo_test]
+fn unions_work_with_no_default_features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ a = { path = "a" }
+ b = { path = "b" }
+ "#,
+ )
+ .file("src/lib.rs", "extern crate a; pub fn foo() { a::a(); }")
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ a = { path = "../a", features = [], default-features = false }
+ "#,
+ )
+ .file("b/src/lib.rs", "")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+ authors = []
+
+ [features]
+ default = ["f1"]
+ f1 = []
+ "#,
+ )
+ .file("a/src/lib.rs", r#"#[cfg(feature = "f1")] pub fn a() {}"#)
+ .build();
+
+ p.cargo("check").run();
+ p.cargo("check").with_stdout("").run();
+ p.cargo("check").with_stdout("").run();
+}
+
+#[cargo_test]
+fn optional_and_dev_dep() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "test"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ foo = { path = "foo", optional = true }
+ [dev-dependencies]
+ foo = { path = "foo" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("foo/Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("foo/src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[CHECKING] test v0.1.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn activating_feature_activates_dep() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "test"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ foo = { path = "foo", optional = true }
+
+ [features]
+ a = ["foo/a"]
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "extern crate foo; pub fn bar() { foo::bar(); }",
+ )
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [features]
+ a = []
+ "#,
+ )
+ .file("foo/src/lib.rs", r#"#[cfg(feature = "a")] pub fn bar() {}"#)
+ .build();
+
+ p.cargo("check --features a -v").run();
+}
+
+#[cargo_test]
+fn dep_feature_in_cmd_line() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.derived]
+ path = "derived"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ extern crate derived;
+ fn main() { derived::test(); }
+ "#,
+ )
+ .file(
+ "derived/Cargo.toml",
+ r#"
+ [package]
+ name = "derived"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "../bar"
+
+ [features]
+ default = []
+ derived-feat = ["bar/some-feat"]
+ "#,
+ )
+ .file("derived/src/lib.rs", "extern crate bar; pub use bar::test;")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ some-feat = []
+ "#,
+ )
+ .file(
+ "bar/src/lib.rs",
+ r#"
+ #[cfg(feature = "some-feat")]
+ pub fn test() { print!("test"); }
+ "#,
+ )
+ .build();
+
+ // The foo project requires that feature "some-feat" in "bar" is enabled.
+ // Building without any features enabled should fail:
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr_contains("[..]unresolved import `bar::test`")
+ .run();
+
+ // We should be able to enable the feature "derived-feat", which enables "some-feat",
+ // on the command line. The feature is enabled, thus building should be successful:
+ p.cargo("check --features derived/derived-feat").run();
+
+ // Trying to enable features of transitive dependencies is an error
+ p.cargo("check --features bar/some-feat")
+ .with_status(101)
+ .with_stderr("error: package `foo v0.0.1 ([..])` does not have a dependency named `bar`")
+ .run();
+
+ // Hierarchical feature specification should still be disallowed
+ p.cargo("check --features derived/bar/some-feat")
+ .with_status(101)
+ .with_stderr("[ERROR] multiple slashes in feature `derived/bar/some-feat` is not allowed")
+ .run();
+}
+
+#[cargo_test]
+fn all_features_flag_enables_all_features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ foo = []
+ bar = []
+
+ [dependencies.baz]
+ path = "baz"
+ optional = true
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ #[cfg(feature = "foo")]
+ pub fn foo() {}
+
+ #[cfg(feature = "bar")]
+ pub fn bar() {
+ extern crate baz;
+ baz::baz();
+ }
+
+ fn main() {
+ foo();
+ bar();
+ }
+ "#,
+ )
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.0.1"))
+ .file("baz/src/lib.rs", "pub fn baz() {}")
+ .build();
+
+ p.cargo("check --all-features").run();
+}
+
+#[cargo_test]
+fn many_cli_features_comma_delimited() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "bar"
+ optional = true
+
+ [dependencies.baz]
+ path = "baz"
+ optional = true
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ #[allow(unused_extern_crates)]
+ extern crate bar;
+ #[allow(unused_extern_crates)]
+ extern crate baz;
+ fn main() {}
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.0.1"))
+ .file("baz/src/lib.rs", "pub fn baz() {}")
+ .build();
+
+ p.cargo("check --features bar,baz")
+ .with_stderr(
+ "\
+[CHECKING] ba[..] v0.0.1 ([CWD]/ba[..])
+[CHECKING] ba[..] v0.0.1 ([CWD]/ba[..])
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn many_cli_features_comma_and_space_delimited() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "bar"
+ optional = true
+
+ [dependencies.baz]
+ path = "baz"
+ optional = true
+
+ [dependencies.bam]
+ path = "bam"
+ optional = true
+
+ [dependencies.bap]
+ path = "bap"
+ optional = true
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ #[allow(unused_extern_crates)]
+ extern crate bar;
+ #[allow(unused_extern_crates)]
+ extern crate baz;
+ #[allow(unused_extern_crates)]
+ extern crate bam;
+ #[allow(unused_extern_crates)]
+ extern crate bap;
+ fn main() {}
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.0.1"))
+ .file("baz/src/lib.rs", "pub fn baz() {}")
+ .file("bam/Cargo.toml", &basic_manifest("bam", "0.0.1"))
+ .file("bam/src/lib.rs", "pub fn bam() {}")
+ .file("bap/Cargo.toml", &basic_manifest("bap", "0.0.1"))
+ .file("bap/src/lib.rs", "pub fn bap() {}")
+ .build();
+
+ p.cargo("check --features")
+ .arg("bar,baz bam bap")
+ .with_stderr(
+ "\
+[CHECKING] ba[..] v0.0.1 ([CWD]/ba[..])
+[CHECKING] ba[..] v0.0.1 ([CWD]/ba[..])
+[CHECKING] ba[..] v0.0.1 ([CWD]/ba[..])
+[CHECKING] ba[..] v0.0.1 ([CWD]/ba[..])
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn only_dep_is_optional() {
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ foo = ['bar']
+
+ [dependencies]
+ bar = { version = "0.1", optional = true }
+
+ [dev-dependencies]
+ bar = "0.1"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn all_features_all_crates() {
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [workspace]
+ members = ['bar']
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ foo = []
+ "#,
+ )
+ .file("bar/src/main.rs", "#[cfg(feature = \"foo\")] fn main() {}")
+ .build();
+
+ p.cargo("check --all-features --workspace").run();
+}
+
+#[cargo_test]
+fn feature_off_dylib() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar"]
+
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [lib]
+ crate-type = ["dylib"]
+
+ [features]
+ f1 = []
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn hello() -> &'static str {
+ if cfg!(feature = "f1") {
+ "f1"
+ } else {
+ "no f1"
+ }
+ }
+ "#,
+ )
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+
+ [dependencies]
+ foo = { path = ".." }
+ "#,
+ )
+ .file(
+ "bar/src/main.rs",
+ r#"
+ extern crate foo;
+
+ fn main() {
+ assert_eq!(foo::hello(), "no f1");
+ }
+ "#,
+ )
+ .build();
+
+ // Build the dylib with `f1` feature.
+ p.cargo("check --features f1").run();
+ // Check that building without `f1` uses a dylib without `f1`.
+ p.cargo("run -p bar").run();
+}
+
+#[cargo_test]
+fn warn_if_default_features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "bar"
+ optional = true
+
+ [features]
+ default-features = ["bar"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ r#"
+[WARNING] `default-features = [".."]` was found in [features]. Did you mean to use `default = [".."]`?
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+ "#.trim(),
+ ).run();
+}
+
+#[cargo_test]
+fn no_feature_for_non_optional_dep() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = { path = "bar" }
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ #[cfg(not(feature = "bar"))]
+ fn main() {
+ }
+ "#,
+ )
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ a = []
+ "#,
+ )
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ p.cargo("check --features bar/a").run();
+}
+
+#[cargo_test]
+fn features_option_given_twice() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ a = []
+ b = []
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ #[cfg(all(feature = "a", feature = "b"))]
+ fn main() {}
+ "#,
+ )
+ .build();
+
+ p.cargo("check --features a --features b").run();
+}
+
+#[cargo_test]
+fn multi_multi_features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ a = []
+ b = []
+ c = []
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ #[cfg(all(feature = "a", feature = "b", feature = "c"))]
+ fn main() {}
+ "#,
+ )
+ .build();
+
+ p.cargo("check --features a --features").arg("b c").run();
+}
+
+#[cargo_test]
+fn cli_parse_ok() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ a = []
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ #[cfg(feature = "a")]
+ fn main() {
+ assert_eq!(std::env::args().nth(1).unwrap(), "b");
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("run --features a b").run();
+}
+
+#[cargo_test]
+fn all_features_virtual_ws() {
+ // What happens with `--all-features` in the root of a virtual workspace.
+ // Some of this behavior is a little strange (member dependencies also
+ // have all features enabled, one might expect `f4` to be disabled).
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["a", "b"]
+ "#,
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+ edition = "2018"
+
+ [dependencies]
+ b = {path="../b", optional=true}
+
+ [features]
+ default = ["f1"]
+ f1 = []
+ f2 = []
+ "#,
+ )
+ .file(
+ "a/src/main.rs",
+ r#"
+ fn main() {
+ if cfg!(feature="f1") {
+ println!("f1");
+ }
+ if cfg!(feature="f2") {
+ println!("f2");
+ }
+ #[cfg(feature="b")]
+ b::f();
+ }
+ "#,
+ )
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.1.0"
+
+ [features]
+ default = ["f3"]
+ f3 = []
+ f4 = []
+ "#,
+ )
+ .file(
+ "b/src/lib.rs",
+ r#"
+ pub fn f() {
+ if cfg!(feature="f3") {
+ println!("f3");
+ }
+ if cfg!(feature="f4") {
+ println!("f4");
+ }
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("run").with_stdout("f1\n").run();
+ p.cargo("run --all-features")
+ .with_stdout("f1\nf2\nf3\nf4\n")
+ .run();
+ // In `a`, it behaves differently. :(
+ p.cargo("run --all-features")
+ .cwd("a")
+ .with_stdout("f1\nf2\nf3\n")
+ .run();
+}
+
+#[cargo_test]
+fn slash_optional_enables() {
+ // --features dep/feat will enable `dep` and set its feature.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ dep = {path="dep", optional=true}
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ #[cfg(not(feature="dep"))]
+ compile_error!("dep not set");
+ "#,
+ )
+ .file(
+ "dep/Cargo.toml",
+ r#"
+ [package]
+ name = "dep"
+ version = "0.1.0"
+
+ [features]
+ feat = []
+ "#,
+ )
+ .file(
+ "dep/src/lib.rs",
+ r#"
+ #[cfg(not(feature="feat"))]
+ compile_error!("feat not set");
+ "#,
+ )
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr_contains("[..]dep not set[..]")
+ .run();
+
+ p.cargo("check --features dep/feat").run();
+}
+
+#[cargo_test]
+fn registry_summary_order_doesnt_matter() {
+ // Checks for an issue where the resolver depended on the order of entries
+ // in the registry summary. If there was a non-optional dev-dependency
+ // that appeared before an optional normal dependency, then the resolver
+ // would not activate the optional dependency with a pkg/featname feature
+ // syntax.
+ Package::new("dep", "0.1.0")
+ .feature("feat1", &[])
+ .file(
+ "src/lib.rs",
+ r#"
+ #[cfg(feature="feat1")]
+ pub fn work() {
+ println!("it works");
+ }
+ "#,
+ )
+ .publish();
+ Package::new("bar", "0.1.0")
+ .feature("bar_feat", &["dep/feat1"])
+ .add_dep(Dependency::new("dep", "0.1.0").dev())
+ .add_dep(Dependency::new("dep", "0.1.0").optional(true))
+ .file(
+ "src/lib.rs",
+ r#"
+ // This will fail to compile without `dep` optional dep activated.
+ extern crate dep;
+
+ pub fn doit() {
+ dep::work();
+ }
+ "#,
+ )
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [dependencies]
+ bar = { version="0.1", features = ["bar_feat"] }
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ bar::doit();
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("run")
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[DOWNLOADING] crates ...
+[DOWNLOADED] [..]
+[DOWNLOADED] [..]
+[COMPILING] dep v0.1.0
+[COMPILING] bar v0.1.0
+[COMPILING] foo v0.1.0 [..]
+[FINISHED] [..]
+[RUNNING] `target/debug/foo[EXE]`
+",
+ )
+ .with_stdout("it works")
+ .run();
+}
+
+#[cargo_test]
+fn nonexistent_required_features() {
+ Package::new("required_dependency", "0.1.0")
+ .feature("simple", &[])
+ .publish();
+ Package::new("optional_dependency", "0.2.0")
+ .feature("optional", &[])
+ .publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ [features]
+ existing = []
+ fancy = ["optional_dependency"]
+ [dependencies]
+ required_dependency = { version = "0.1", optional = false}
+ optional_dependency = { version = "0.2", optional = true}
+ [[example]]
+ name = "ololo"
+ required-features = ["not_present",
+ "existing",
+ "fancy",
+ "required_dependency/not_existing",
+ "required_dependency/simple",
+ "optional_dependency/optional",
+ "not_specified_dependency/some_feature"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("examples/ololo.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check --examples")
+ .with_stderr_contains(
+ "\
+[WARNING] invalid feature `not_present` in required-features of target `ololo`: \
+ `not_present` is not present in [features] section
+[WARNING] invalid feature `required_dependency/not_existing` in required-features \
+ of target `ololo`: feature `not_existing` does not exist in package \
+ `required_dependency v0.1.0`
+[WARNING] invalid feature `not_specified_dependency/some_feature` in required-features \
+ of target `ololo`: dependency `not_specified_dependency` does not exist
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn invalid_feature_names_warning() {
+ // Warnings for more restricted feature syntax.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [features]
+ # Some valid, but unusual names, shouldn't warn.
+ "c++17" = []
+ "128bit" = []
+ "_foo" = []
+ "feat-name" = []
+ "feat_name" = []
+ "foo.bar" = []
+
+ # Invalid names.
+ "+foo" = []
+ "-foo" = []
+ ".foo" = []
+ "foo:bar" = []
+ "foo?" = []
+ "?foo" = []
+ "ⒶⒷⒸ" = []
+ "a¼" = []
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ // Unfortunately the warnings are duplicated due to the Summary being
+ // loaded twice (once in the Workspace, and once in PackageRegistry) and
+ // Cargo does not have a de-duplication system. This should probably be
+ // OK, since I'm not expecting this to affect anyone.
+ p.cargo("check")
+ .with_stderr("\
+[WARNING] invalid character `+` in feature `+foo` in package foo v0.1.0 ([ROOT]/foo), the first character must be a Unicode XID start character or digit (most letters or `_` or `0` to `9`)
+This was previously accepted but is being phased out; it will become a hard error in a future release.
+For more information, see issue #8813 <https://github.com/rust-lang/cargo/issues/8813>, and please leave a comment if this will be a problem for your project.
+[WARNING] invalid character `-` in feature `-foo` in package foo v0.1.0 ([ROOT]/foo), the first character must be a Unicode XID start character or digit (most letters or `_` or `0` to `9`)
+This was previously accepted but is being phased out; it will become a hard error in a future release.
+For more information, see issue #8813 <https://github.com/rust-lang/cargo/issues/8813>, and please leave a comment if this will be a problem for your project.
+[WARNING] invalid character `.` in feature `.foo` in package foo v0.1.0 ([ROOT]/foo), the first character must be a Unicode XID start character or digit (most letters or `_` or `0` to `9`)
+This was previously accepted but is being phased out; it will become a hard error in a future release.
+For more information, see issue #8813 <https://github.com/rust-lang/cargo/issues/8813>, and please leave a comment if this will be a problem for your project.
+[WARNING] invalid character `?` in feature `?foo` in package foo v0.1.0 ([ROOT]/foo), the first character must be a Unicode XID start character or digit (most letters or `_` or `0` to `9`)
+This was previously accepted but is being phased out; it will become a hard error in a future release.
+For more information, see issue #8813 <https://github.com/rust-lang/cargo/issues/8813>, and please leave a comment if this will be a problem for your project.
+[WARNING] invalid character `¼` in feature `a¼` in package foo v0.1.0 ([ROOT]/foo), characters must be Unicode XID characters, `+`, or `.` (numbers, `+`, `-`, `_`, `.`, or most letters)
+This was previously accepted but is being phased out; it will become a hard error in a future release.
+For more information, see issue #8813 <https://github.com/rust-lang/cargo/issues/8813>, and please leave a comment if this will be a problem for your project.
+[WARNING] invalid character `:` in feature `foo:bar` in package foo v0.1.0 ([ROOT]/foo), characters must be Unicode XID characters, `+`, or `.` (numbers, `+`, `-`, `_`, `.`, or most letters)
+This was previously accepted but is being phased out; it will become a hard error in a future release.
+For more information, see issue #8813 <https://github.com/rust-lang/cargo/issues/8813>, and please leave a comment if this will be a problem for your project.
+[WARNING] invalid character `?` in feature `foo?` in package foo v0.1.0 ([ROOT]/foo), characters must be Unicode XID characters, `+`, or `.` (numbers, `+`, `-`, `_`, `.`, or most letters)
+This was previously accepted but is being phased out; it will become a hard error in a future release.
+For more information, see issue #8813 <https://github.com/rust-lang/cargo/issues/8813>, and please leave a comment if this will be a problem for your project.
+[WARNING] invalid character `Ⓐ` in feature `ⒶⒷⒸ` in package foo v0.1.0 ([ROOT]/foo), the first character must be a Unicode XID start character or digit (most letters or `_` or `0` to `9`)
+This was previously accepted but is being phased out; it will become a hard error in a future release.
+For more information, see issue #8813 <https://github.com/rust-lang/cargo/issues/8813>, and please leave a comment if this will be a problem for your project.
+[WARNING] invalid character `Ⓑ` in feature `ⒶⒷⒸ` in package foo v0.1.0 ([ROOT]/foo), characters must be Unicode XID characters, `+`, or `.` (numbers, `+`, `-`, `_`, `.`, or most letters)
+This was previously accepted but is being phased out; it will become a hard error in a future release.
+For more information, see issue #8813 <https://github.com/rust-lang/cargo/issues/8813>, and please leave a comment if this will be a problem for your project.
+[WARNING] invalid character `Ⓒ` in feature `ⒶⒷⒸ` in package foo v0.1.0 ([ROOT]/foo), characters must be Unicode XID characters, `+`, or `.` (numbers, `+`, `-`, `_`, `.`, or most letters)
+This was previously accepted but is being phased out; it will become a hard error in a future release.
+For more information, see issue #8813 <https://github.com/rust-lang/cargo/issues/8813>, and please leave a comment if this will be a problem for your project.
+[CHECKING] foo v0.1.0 [..]
+[FINISHED] [..]
+")
+ .run();
+}
+
+#[cargo_test]
+fn invalid_feature_names_error() {
+ // Errors for more restricted feature syntax.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [features]
+ "foo/bar" = []
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[CWD]/Cargo.toml`
+
+Caused by:
+ feature named `foo/bar` is not allowed to contain slashes
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn default_features_conflicting_warning() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ a = { path = "a", features = ["f1"], default-features = false, default_features = false }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+ authors = []
+
+ [features]
+ default = ["f1"]
+ f1 = []
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_stderr_contains(
+"[WARNING] conflicting between `default-features` and `default_features` in the `a` dependency.\n
+ `default_features` is ignored and not recommended for use in the future"
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/features2.rs b/src/tools/cargo/tests/testsuite/features2.rs
new file mode 100644
index 000000000..494c83f1e
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/features2.rs
@@ -0,0 +1,2553 @@
+//! Tests for the new feature resolver.
+
+use cargo_test_support::cross_compile::{self, alternate};
+use cargo_test_support::install::cargo_home;
+use cargo_test_support::paths::CargoPathExt;
+use cargo_test_support::publish::validate_crate_contents;
+use cargo_test_support::registry::{Dependency, Package};
+use cargo_test_support::{basic_manifest, cargo_process, project, rustc_host, Project};
+use std::fs::File;
+
+/// Switches Cargo.toml to use `resolver = "2"`.
+pub fn switch_to_resolver_2(p: &Project) {
+ let mut manifest = p.read_file("Cargo.toml");
+ if manifest.contains("resolver =") {
+ panic!("did not expect manifest to already contain a resolver setting");
+ }
+ if let Some(index) = manifest.find("[workspace]\n") {
+ manifest.insert_str(index + 12, "resolver = \"2\"\n");
+ } else if let Some(index) = manifest.find("[package]\n") {
+ manifest.insert_str(index + 10, "resolver = \"2\"\n");
+ } else {
+ panic!("expected [package] or [workspace] in manifest");
+ }
+ p.change_file("Cargo.toml", &manifest);
+}
+
+#[cargo_test]
+fn inactivate_targets() {
+ // Basic test of `itarget`. A shared dependency where an inactive [target]
+ // changes the features.
+ Package::new("common", "1.0.0")
+ .feature("f1", &[])
+ .file(
+ "src/lib.rs",
+ r#"
+ #[cfg(feature = "f1")]
+ compile_error!("f1 should not activate");
+ "#,
+ )
+ .publish();
+
+ Package::new("bar", "1.0.0")
+ .add_dep(
+ Dependency::new("common", "1.0")
+ .target("cfg(whatever)")
+ .enable_features(&["f1"]),
+ )
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ common = "1.0"
+ bar = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr_contains("[..]f1 should not activate[..]")
+ .run();
+
+ switch_to_resolver_2(&p);
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn inactive_target_optional() {
+ // Activating optional [target] dependencies for inactivate target.
+ Package::new("common", "1.0.0")
+ .feature("f1", &[])
+ .feature("f2", &[])
+ .feature("f3", &[])
+ .feature("f4", &[])
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn f() {
+ if cfg!(feature="f1") { println!("f1"); }
+ if cfg!(feature="f2") { println!("f2"); }
+ if cfg!(feature="f3") { println!("f3"); }
+ if cfg!(feature="f4") { println!("f4"); }
+ }
+ "#,
+ )
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [dependencies]
+ common = "1.0"
+
+ [target.'cfg(whatever)'.dependencies]
+ dep1 = {path='dep1', optional=true}
+ dep2 = {path='dep2', optional=true, features=["f3"]}
+ common = {version="1.0", optional=true, features=["f4"]}
+
+ [features]
+ foo1 = ["dep1/f2"]
+ foo2 = ["dep2"]
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ if cfg!(feature="foo1") { println!("foo1"); }
+ if cfg!(feature="foo2") { println!("foo2"); }
+ if cfg!(feature="dep1") { println!("dep1"); }
+ if cfg!(feature="dep2") { println!("dep2"); }
+ if cfg!(feature="common") { println!("common"); }
+ common::f();
+ }
+ "#,
+ )
+ .file(
+ "dep1/Cargo.toml",
+ r#"
+ [package]
+ name = "dep1"
+ version = "0.1.0"
+
+ [dependencies]
+ common = {version="1.0", features=["f1"]}
+
+ [features]
+ f2 = ["common/f2"]
+ "#,
+ )
+ .file(
+ "dep1/src/lib.rs",
+ r#"compile_error!("dep1 should not build");"#,
+ )
+ .file(
+ "dep2/Cargo.toml",
+ r#"
+ [package]
+ name = "dep2"
+ version = "0.1.0"
+
+ [dependencies]
+ common = "1.0"
+
+ [features]
+ f3 = ["common/f3"]
+ "#,
+ )
+ .file(
+ "dep2/src/lib.rs",
+ r#"compile_error!("dep2 should not build");"#,
+ )
+ .build();
+
+ p.cargo("run --all-features")
+ .with_stdout("foo1\nfoo2\ndep1\ndep2\ncommon\nf1\nf2\nf3\nf4\n")
+ .run();
+ p.cargo("run --features dep1")
+ .with_stdout("dep1\nf1\n")
+ .run();
+ p.cargo("run --features foo1")
+ .with_stdout("foo1\ndep1\nf1\nf2\n")
+ .run();
+ p.cargo("run --features dep2")
+ .with_stdout("dep2\nf3\n")
+ .run();
+ p.cargo("run --features common")
+ .with_stdout("common\nf4\n")
+ .run();
+
+ switch_to_resolver_2(&p);
+ p.cargo("run --all-features")
+ .with_stdout("foo1\nfoo2\ndep1\ndep2\ncommon")
+ .run();
+ p.cargo("run --features dep1").with_stdout("dep1\n").run();
+ p.cargo("run --features foo1").with_stdout("foo1\n").run();
+ p.cargo("run --features dep2").with_stdout("dep2\n").run();
+ p.cargo("run --features common").with_stdout("common").run();
+}
+
+#[cargo_test]
+fn itarget_proc_macro() {
+ // itarget inside a proc-macro while cross-compiling
+ if cross_compile::disabled() {
+ return;
+ }
+ Package::new("hostdep", "1.0.0").publish();
+ Package::new("pm", "1.0.0")
+ .proc_macro(true)
+ .target_dep("hostdep", "1.0", rustc_host())
+ .file("src/lib.rs", "extern crate hostdep;")
+ .publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ pm = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ // Old behavior
+ p.cargo("check").run();
+ p.cargo("check --target").arg(alternate()).run();
+
+ // New behavior
+ switch_to_resolver_2(&p);
+ p.cargo("check").run();
+ p.cargo("check --target").arg(alternate()).run();
+ // For good measure, just make sure things don't break.
+ p.cargo("check --target").arg(alternate()).run();
+}
+
+#[cargo_test]
+fn decouple_host_deps() {
+ // Basic test for `host_dep` decouple.
+ Package::new("common", "1.0.0")
+ .feature("f1", &[])
+ .file(
+ "src/lib.rs",
+ r#"
+ #[cfg(feature = "f1")]
+ pub fn foo() {}
+ #[cfg(not(feature = "f1"))]
+ pub fn bar() {}
+ "#,
+ )
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [build-dependencies]
+ common = {version="1.0", features=["f1"]}
+
+ [dependencies]
+ common = "1.0"
+ "#,
+ )
+ .file(
+ "build.rs",
+ r#"
+ use common::foo;
+ fn main() {}
+ "#,
+ )
+ .file("src/lib.rs", "use common::bar;")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr_contains("[..]unresolved import `common::bar`[..]")
+ .run();
+
+ switch_to_resolver_2(&p);
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn decouple_host_deps_nested() {
+ // `host_dep` decouple of transitive dependencies.
+ Package::new("common", "1.0.0")
+ .feature("f1", &[])
+ .file(
+ "src/lib.rs",
+ r#"
+ #[cfg(feature = "f1")]
+ pub fn foo() {}
+ #[cfg(not(feature = "f1"))]
+ pub fn bar() {}
+ "#,
+ )
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [build-dependencies]
+ bdep = {path="bdep"}
+
+ [dependencies]
+ common = "1.0"
+ "#,
+ )
+ .file(
+ "build.rs",
+ r#"
+ use bdep::foo;
+ fn main() {}
+ "#,
+ )
+ .file("src/lib.rs", "use common::bar;")
+ .file(
+ "bdep/Cargo.toml",
+ r#"
+ [package]
+ name = "bdep"
+ version = "0.1.0"
+ edition = "2018"
+
+ [dependencies]
+ common = {version="1.0", features=["f1"]}
+ "#,
+ )
+ .file("bdep/src/lib.rs", "pub use common::foo;")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr_contains("[..]unresolved import `common::bar`[..]")
+ .run();
+
+ switch_to_resolver_2(&p);
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn decouple_dev_deps() {
+ // Basic test for `dev_dep` decouple.
+ Package::new("common", "1.0.0")
+ .feature("f1", &[])
+ .feature("f2", &[])
+ .file(
+ "src/lib.rs",
+ r#"
+ // const ensures it uses the correct dependency at *build time*
+ // compared to *link time*.
+ #[cfg(all(feature="f1", not(feature="f2")))]
+ pub const X: u32 = 1;
+
+ #[cfg(all(feature="f1", feature="f2"))]
+ pub const X: u32 = 3;
+
+ pub fn foo() -> u32 {
+ let mut res = 0;
+ if cfg!(feature = "f1") {
+ res |= 1;
+ }
+ if cfg!(feature = "f2") {
+ res |= 2;
+ }
+ res
+ }
+ "#,
+ )
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [dependencies]
+ common = {version="1.0", features=["f1"]}
+
+ [dev-dependencies]
+ common = {version="1.0", features=["f2"]}
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ let expected: u32 = std::env::args().skip(1).next().unwrap().parse().unwrap();
+ assert_eq!(foo::foo(), expected);
+ assert_eq!(foo::build_time(), expected);
+ assert_eq!(common::foo(), expected);
+ assert_eq!(common::X, expected);
+ }
+
+ #[test]
+ fn test_bin() {
+ assert_eq!(foo::foo(), 3);
+ assert_eq!(common::foo(), 3);
+ assert_eq!(common::X, 3);
+ assert_eq!(foo::build_time(), 3);
+ }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn foo() -> u32 {
+ common::foo()
+ }
+
+ pub fn build_time() -> u32 {
+ common::X
+ }
+
+ #[test]
+ fn test_lib() {
+ assert_eq!(foo(), 3);
+ assert_eq!(common::foo(), 3);
+ assert_eq!(common::X, 3);
+ }
+ "#,
+ )
+ .file(
+ "tests/t1.rs",
+ r#"
+ #[test]
+ fn test_t1() {
+ assert_eq!(foo::foo(), 3);
+ assert_eq!(common::foo(), 3);
+ assert_eq!(common::X, 3);
+ assert_eq!(foo::build_time(), 3);
+ }
+
+ #[test]
+ fn test_main() {
+ // Features are unified for main when run with `cargo test`,
+ // even with the new resolver.
+ let s = std::process::Command::new("target/debug/foo")
+ .arg("3")
+ .status().unwrap();
+ assert!(s.success());
+ }
+ "#,
+ )
+ .build();
+
+ // Old behavior
+ p.cargo("run 3").run();
+ p.cargo("test").run();
+
+ // New behavior
+ switch_to_resolver_2(&p);
+ p.cargo("run 1").run();
+ p.cargo("test").run();
+}
+
+#[cargo_test]
+fn build_script_runtime_features() {
+ // Check that the CARGO_FEATURE_* environment variable is set correctly.
+ //
+ // This has a common dependency between build/normal/dev-deps, and it
+ // queries which features it was built with in different circumstances.
+ Package::new("common", "1.0.0")
+ .feature("normal", &[])
+ .feature("dev", &[])
+ .feature("build", &[])
+ .file(
+ "build.rs",
+ r#"
+ fn is_set(name: &str) -> bool {
+ std::env::var(name) == Ok("1".to_string())
+ }
+
+ fn main() {
+ let mut res = 0;
+ if is_set("CARGO_FEATURE_NORMAL") {
+ res |= 1;
+ }
+ if is_set("CARGO_FEATURE_DEV") {
+ res |= 2;
+ }
+ if is_set("CARGO_FEATURE_BUILD") {
+ res |= 4;
+ }
+ println!("cargo:rustc-cfg=RunCustomBuild=\"{}\"", res);
+
+ let mut res = 0;
+ if cfg!(feature = "normal") {
+ res |= 1;
+ }
+ if cfg!(feature = "dev") {
+ res |= 2;
+ }
+ if cfg!(feature = "build") {
+ res |= 4;
+ }
+ println!("cargo:rustc-cfg=CustomBuild=\"{}\"", res);
+ }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn foo() -> u32 {
+ let mut res = 0;
+ if cfg!(feature = "normal") {
+ res |= 1;
+ }
+ if cfg!(feature = "dev") {
+ res |= 2;
+ }
+ if cfg!(feature = "build") {
+ res |= 4;
+ }
+ res
+ }
+
+ pub fn build_time() -> u32 {
+ #[cfg(RunCustomBuild="1")] return 1;
+ #[cfg(RunCustomBuild="3")] return 3;
+ #[cfg(RunCustomBuild="4")] return 4;
+ #[cfg(RunCustomBuild="5")] return 5;
+ #[cfg(RunCustomBuild="7")] return 7;
+ }
+ "#,
+ )
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [build-dependencies]
+ common = {version="1.0", features=["build"]}
+
+ [dependencies]
+ common = {version="1.0", features=["normal"]}
+
+ [dev-dependencies]
+ common = {version="1.0", features=["dev"]}
+ "#,
+ )
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ assert_eq!(common::foo(), common::build_time());
+ println!("cargo:rustc-cfg=from_build=\"{}\"", common::foo());
+ }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn foo() -> u32 {
+ common::foo()
+ }
+
+ pub fn build_time() -> u32 {
+ common::build_time()
+ }
+
+ #[test]
+ fn test_lib() {
+ assert_eq!(common::foo(), common::build_time());
+ assert_eq!(common::foo(),
+ std::env::var("CARGO_FEATURE_EXPECT").unwrap().parse().unwrap());
+ }
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ assert_eq!(common::foo(), common::build_time());
+ assert_eq!(common::foo(),
+ std::env::var("CARGO_FEATURE_EXPECT").unwrap().parse().unwrap());
+ }
+
+ #[test]
+ fn test_bin() {
+ assert_eq!(common::foo(), common::build_time());
+ assert_eq!(common::foo(),
+ std::env::var("CARGO_FEATURE_EXPECT").unwrap().parse().unwrap());
+ }
+ "#,
+ )
+ .file(
+ "tests/t1.rs",
+ r#"
+ #[test]
+ fn test_t1() {
+ assert_eq!(common::foo(), common::build_time());
+ assert_eq!(common::foo(),
+ std::env::var("CARGO_FEATURE_EXPECT").unwrap().parse().unwrap());
+ }
+
+ #[test]
+ fn test_main() {
+ // Features are unified for main when run with `cargo test`,
+ // even with the new resolver.
+ let s = std::process::Command::new("target/debug/foo")
+ .status().unwrap();
+ assert!(s.success());
+ }
+ "#,
+ )
+ .build();
+
+ // Old way, unifies all 3.
+ p.cargo("run").env("CARGO_FEATURE_EXPECT", "7").run();
+ p.cargo("test").env("CARGO_FEATURE_EXPECT", "7").run();
+
+ // New behavior.
+ switch_to_resolver_2(&p);
+
+ // normal + build unify
+ p.cargo("run").env("CARGO_FEATURE_EXPECT", "1").run();
+
+ // dev_deps are still unified with `cargo test`
+ p.cargo("test").env("CARGO_FEATURE_EXPECT", "3").run();
+}
+
+#[cargo_test]
+fn cyclical_dev_dep() {
+ // Check how a cyclical dev-dependency will work.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [features]
+ dev = []
+
+ [dev-dependencies]
+ foo = { path = '.', features = ["dev"] }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn assert_dev(enabled: bool) {
+ assert_eq!(enabled, cfg!(feature="dev"));
+ }
+
+ #[test]
+ fn test_in_lib() {
+ assert_dev(true);
+ }
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ let expected: bool = std::env::args().skip(1).next().unwrap().parse().unwrap();
+ foo::assert_dev(expected);
+ }
+ "#,
+ )
+ .file(
+ "tests/t1.rs",
+ r#"
+ #[test]
+ fn integration_links() {
+ foo::assert_dev(true);
+ // The lib linked with main.rs will also be unified.
+ let s = std::process::Command::new("target/debug/foo")
+ .arg("true")
+ .status().unwrap();
+ assert!(s.success());
+ }
+ "#,
+ )
+ .build();
+
+ // Old way unifies features.
+ p.cargo("run true").run();
+ // dev feature should always be enabled in tests.
+ p.cargo("test").run();
+
+ // New behavior.
+ switch_to_resolver_2(&p);
+ // Should decouple main.
+ p.cargo("run false").run();
+
+ // And this should be no different.
+ p.cargo("test").run();
+}
+
+#[cargo_test]
+fn all_feature_opts() {
+ // All feature options at once.
+ Package::new("common", "1.0.0")
+ .feature("normal", &[])
+ .feature("build", &[])
+ .feature("dev", &[])
+ .feature("itarget", &[])
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn feats() -> u32 {
+ let mut res = 0;
+ if cfg!(feature="normal") { res |= 1; }
+ if cfg!(feature="build") { res |= 2; }
+ if cfg!(feature="dev") { res |= 4; }
+ if cfg!(feature="itarget") { res |= 8; }
+ res
+ }
+ "#,
+ )
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [dependencies]
+ common = {version = "1.0", features=["normal"]}
+
+ [dev-dependencies]
+ common = {version = "1.0", features=["dev"]}
+
+ [build-dependencies]
+ common = {version = "1.0", features=["build"]}
+
+ [target.'cfg(whatever)'.dependencies]
+ common = {version = "1.0", features=["itarget"]}
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ expect();
+ }
+
+ fn expect() {
+ let expected: u32 = std::env::var("EXPECTED_FEATS").unwrap().parse().unwrap();
+ assert_eq!(expected, common::feats());
+ }
+
+ #[test]
+ fn from_test() {
+ expect();
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("run").env("EXPECTED_FEATS", "15").run();
+ p.cargo("test").env("EXPECTED_FEATS", "15").run();
+
+ // New behavior.
+ switch_to_resolver_2(&p);
+ // Only normal feature.
+ p.cargo("run").env("EXPECTED_FEATS", "1").run();
+
+ // only normal+dev
+ p.cargo("test").env("EXPECTED_FEATS", "5").run();
+}
+
+#[cargo_test]
+fn required_features_host_dep() {
+ // Check that required-features handles build-dependencies correctly.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [[bin]]
+ name = "x"
+ required-features = ["bdep/f1"]
+
+ [build-dependencies]
+ bdep = {path="bdep"}
+ "#,
+ )
+ .file("build.rs", "fn main() {}")
+ .file(
+ "src/bin/x.rs",
+ r#"
+ fn main() {}
+ "#,
+ )
+ .file(
+ "bdep/Cargo.toml",
+ r#"
+ [package]
+ name = "bdep"
+ version = "0.1.0"
+
+ [features]
+ f1 = []
+ "#,
+ )
+ .file("bdep/src/lib.rs", "")
+ .build();
+
+ p.cargo("run")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] target `x` in package `foo` requires the features: `bdep/f1`
+Consider enabling them by passing, e.g., `--features=\"bdep/f1\"`
+",
+ )
+ .run();
+
+ // New behavior.
+ switch_to_resolver_2(&p);
+ p.cargo("run --features bdep/f1").run();
+}
+
+#[cargo_test]
+fn disabled_shared_host_dep() {
+ // Check for situation where an optional dep of a shared dep is enabled in
+ // a normal dependency, but disabled in an optional one. The unit tree is:
+ // foo
+ // ├── foo build.rs
+ // | └── common (BUILD dependency, NO FEATURES)
+ // └── common (Normal dependency, default features)
+ // └── somedep
+ Package::new("somedep", "1.0.0")
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn f() { println!("hello from somedep"); }
+ "#,
+ )
+ .publish();
+ Package::new("common", "1.0.0")
+ .feature("default", &["somedep"])
+ .add_dep(Dependency::new("somedep", "1.0").optional(true))
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn check_somedep() -> bool {
+ #[cfg(feature="somedep")]
+ {
+ extern crate somedep;
+ somedep::f();
+ true
+ }
+ #[cfg(not(feature="somedep"))]
+ {
+ println!("no somedep");
+ false
+ }
+ }
+ "#,
+ )
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "1.0.0"
+ edition = "2018"
+ resolver = "2"
+
+ [dependencies]
+ common = "1.0"
+
+ [build-dependencies]
+ common = {version = "1.0", default-features = false}
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ "fn main() { assert!(common::check_somedep()); }",
+ )
+ .file(
+ "build.rs",
+ "fn main() { assert!(!common::check_somedep()); }",
+ )
+ .build();
+
+ p.cargo("run -v").with_stdout("hello from somedep").run();
+}
+
+#[cargo_test]
+fn required_features_inactive_dep() {
+ // required-features with an inactivated dep.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ resolver = "2"
+
+ [target.'cfg(whatever)'.dependencies]
+ bar = {path="bar"}
+
+ [[bin]]
+ name = "foo"
+ required-features = ["feat1"]
+
+ [features]
+ feat1 = []
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("check").with_stderr("[FINISHED] [..]").run();
+
+ p.cargo("check --features=feat1")
+ .with_stderr("[CHECKING] foo[..]\n[FINISHED] [..]")
+ .run();
+}
+
+#[cargo_test]
+fn decouple_proc_macro() {
+ // proc macro features are not shared
+ Package::new("common", "1.0.0")
+ .feature("somefeat", &[])
+ .file(
+ "src/lib.rs",
+ r#"
+ pub const fn foo() -> bool { cfg!(feature="somefeat") }
+ #[cfg(feature="somefeat")]
+ pub const FEAT_ONLY_CONST: bool = true;
+ "#,
+ )
+ .publish();
+ Package::new("pm", "1.0.0")
+ .proc_macro(true)
+ .feature_dep("common", "1.0", &["somefeat"])
+ .file(
+ "src/lib.rs",
+ r#"
+ extern crate proc_macro;
+ extern crate common;
+ #[proc_macro]
+ pub fn foo(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+ assert!(common::foo());
+ "".parse().unwrap()
+ }
+ "#,
+ )
+ .publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "1.0.0"
+ edition = "2018"
+
+ [dependencies]
+ pm = "1.0"
+ common = "1.0"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ //! Test with docs.
+ //!
+ //! ```rust
+ //! pm::foo!{}
+ //! fn main() {
+ //! let expected = std::env::var_os("TEST_EXPECTS_ENABLED").is_some();
+ //! assert_eq!(expected, common::foo(), "common is wrong");
+ //! }
+ //! ```
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ pm::foo!{}
+ fn main() {
+ println!("it is {}", common::foo());
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("run")
+ .env("TEST_EXPECTS_ENABLED", "1")
+ .with_stdout("it is true")
+ .run();
+ // Make sure the test is fallible.
+ p.cargo("test --doc")
+ .with_status(101)
+ .with_stdout_contains("[..]common is wrong[..]")
+ .run();
+ p.cargo("test --doc").env("TEST_EXPECTS_ENABLED", "1").run();
+ p.cargo("doc").run();
+ assert!(p
+ .build_dir()
+ .join("doc/common/constant.FEAT_ONLY_CONST.html")
+ .exists());
+ // cargo doc should clean in-between runs, but it doesn't, and leaves stale files.
+ // https://github.com/rust-lang/cargo/issues/6783 (same for removed items)
+ p.build_dir().join("doc").rm_rf();
+
+ // New behavior.
+ switch_to_resolver_2(&p);
+ p.cargo("run").with_stdout("it is false").run();
+
+ p.cargo("test --doc").run();
+ p.cargo("doc").run();
+ assert!(!p
+ .build_dir()
+ .join("doc/common/constant.FEAT_ONLY_CONST.html")
+ .exists());
+}
+
+#[cargo_test]
+fn proc_macro_ws() {
+ // Checks for bug with proc-macro in a workspace with dependency (shouldn't panic).
+ //
+ // Note, debuginfo is explicitly requested here to preserve the intent of this non-regression
+ // test: that will disable the debuginfo build dependencies optimization. Otherwise, it would
+ // initially trigger when the crates are built independently, but rebuild them with debuginfo
+ // when it sees the shared build/runtime dependency when checking the complete workspace.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["foo", "pm"]
+ resolver = "2"
+
+ [profile.dev.build-override]
+ debug = true
+ "#,
+ )
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [features]
+ feat1 = []
+ "#,
+ )
+ .file("foo/src/lib.rs", "")
+ .file(
+ "pm/Cargo.toml",
+ r#"
+ [package]
+ name = "pm"
+ version = "0.1.0"
+
+ [lib]
+ proc-macro = true
+
+ [dependencies]
+ foo = { path = "../foo", features=["feat1"] }
+ "#,
+ )
+ .file("pm/src/lib.rs", "")
+ .build();
+
+ p.cargo("check -p pm -v")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name foo [..]--cfg[..]feat1[..]")
+ .run();
+ // This may be surprising that `foo` doesn't get built separately. It is
+ // because pm might have other units (binaries, tests, etc.), and so the
+ // feature resolver must assume that normal deps get unified with it. This
+ // is related to the bigger issue where the features selected in a
+ // workspace depend on which packages are selected.
+ p.cargo("check --workspace -v")
+ .with_stderr(
+ "\
+[FRESH] foo v0.1.0 [..]
+[FRESH] pm v0.1.0 [..]
+[FINISHED] dev [..]
+",
+ )
+ .run();
+ // Selecting just foo will build without unification.
+ p.cargo("check -p foo -v")
+ // Make sure `foo` is built without feat1
+ .with_stderr_line_without(&["[RUNNING] `rustc --crate-name foo"], &["--cfg[..]feat1"])
+ .run();
+}
+
+#[cargo_test]
+fn has_dev_dep_for_test() {
+ // Check for a bug where the decision on whether or not "dev dependencies"
+ // should be used did not consider `check --profile=test`.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dev-dependencies]
+ dep = { path = 'dep', features = ['f1'] }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ #[test]
+ fn t1() {
+ dep::f();
+ }
+ "#,
+ )
+ .file(
+ "dep/Cargo.toml",
+ r#"
+ [package]
+ name = "dep"
+ version = "0.1.0"
+
+ [features]
+ f1 = []
+ "#,
+ )
+ .file(
+ "dep/src/lib.rs",
+ r#"
+ #[cfg(feature = "f1")]
+ pub fn f() {}
+ "#,
+ )
+ .build();
+
+ p.cargo("check -v")
+ .with_stderr(
+ "\
+[CHECKING] foo v0.1.0 [..]
+[RUNNING] `rustc --crate-name foo [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+ p.cargo("check -v --profile=test")
+ .with_stderr(
+ "\
+[CHECKING] dep v0.1.0 [..]
+[RUNNING] `rustc --crate-name dep [..]
+[CHECKING] foo v0.1.0 [..]
+[RUNNING] `rustc --crate-name foo [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ // New resolver should not be any different.
+ switch_to_resolver_2(&p);
+ p.cargo("check -v --profile=test")
+ .with_stderr(
+ "\
+[FRESH] dep [..]
+[FRESH] foo [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_dep_activated() {
+ // Build dependencies always match the host for [target.*.build-dependencies].
+ if cross_compile::disabled() {
+ return;
+ }
+ Package::new("somedep", "1.0.0")
+ .file("src/lib.rs", "")
+ .publish();
+ Package::new("targetdep", "1.0.0").publish();
+ Package::new("hostdep", "1.0.0")
+ // Check that "for_host" is sticky.
+ .target_dep("somedep", "1.0", rustc_host())
+ .feature("feat1", &[])
+ .file(
+ "src/lib.rs",
+ r#"
+ extern crate somedep;
+
+ #[cfg(not(feature="feat1"))]
+ compile_error!{"feat1 missing"}
+ "#,
+ )
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ # This should never be selected.
+ [target.'{}'.build-dependencies]
+ targetdep = "1.0"
+
+ [target.'{}'.build-dependencies]
+ hostdep = {{version="1.0", features=["feat1"]}}
+ "#,
+ alternate(),
+ rustc_host()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .file("build.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check").run();
+ p.cargo("check --target").arg(alternate()).run();
+
+ // New behavior.
+ switch_to_resolver_2(&p);
+ p.cargo("check").run();
+ p.cargo("check --target").arg(alternate()).run();
+}
+
+#[cargo_test]
+fn resolver_bad_setting() {
+ // Unknown setting in `resolver`
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ resolver = "foo"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[..]/foo/Cargo.toml`
+
+Caused by:
+ `resolver` setting `foo` is not valid, valid options are \"1\" or \"2\"
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn resolver_original() {
+ // resolver="1" uses old unification behavior.
+ Package::new("common", "1.0.0")
+ .feature("f1", &[])
+ .file(
+ "src/lib.rs",
+ r#"
+ #[cfg(feature = "f1")]
+ compile_error!("f1 should not activate");
+ "#,
+ )
+ .publish();
+
+ Package::new("bar", "1.0.0")
+ .add_dep(
+ Dependency::new("common", "1.0")
+ .target("cfg(whatever)")
+ .enable_features(&["f1"]),
+ )
+ .publish();
+
+ let manifest = |resolver| {
+ format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ resolver = "{}"
+
+ [dependencies]
+ common = "1.0"
+ bar = "1.0"
+ "#,
+ resolver
+ )
+ };
+
+ let p = project()
+ .file("Cargo.toml", &manifest("1"))
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr_contains("[..]f1 should not activate[..]")
+ .run();
+
+ p.change_file("Cargo.toml", &manifest("2"));
+
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn resolver_not_both() {
+ // Can't specify resolver in both workspace and package.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ resolver = "2"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ resolver = "2"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[..]/foo/Cargo.toml`
+
+Caused by:
+ cannot specify `resolver` field in both `[workspace]` and `[package]`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn resolver_ws_member() {
+ // Can't specify `resolver` in a ws member.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["a"]
+ "#,
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+ resolver = "2"
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+warning: resolver for the non root package will be ignored, specify resolver at the workspace root:
+package: [..]/foo/a/Cargo.toml
+workspace: [..]/foo/Cargo.toml
+[CHECKING] a v0.1.0 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn resolver_ws_root_and_member() {
+ // Check when specified in both ws root and member.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["a"]
+ resolver = "2"
+ "#,
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+ resolver = "2"
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .build();
+
+ // Ignores if they are the same.
+ p.cargo("check")
+ .with_stderr(
+ "\
+[CHECKING] a v0.1.0 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn resolver_enables_new_features() {
+ // resolver="2" enables all the things.
+ Package::new("common", "1.0.0")
+ .feature("normal", &[])
+ .feature("build", &[])
+ .feature("dev", &[])
+ .feature("itarget", &[])
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn feats() -> u32 {
+ let mut res = 0;
+ if cfg!(feature="normal") { res |= 1; }
+ if cfg!(feature="build") { res |= 2; }
+ if cfg!(feature="dev") { res |= 4; }
+ if cfg!(feature="itarget") { res |= 8; }
+ res
+ }
+ "#,
+ )
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["a", "b"]
+ resolver = "2"
+ "#,
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+ edition = "2018"
+
+ [dependencies]
+ common = {version = "1.0", features=["normal"]}
+
+ [dev-dependencies]
+ common = {version = "1.0", features=["dev"]}
+
+ [build-dependencies]
+ common = {version = "1.0", features=["build"]}
+
+ [target.'cfg(whatever)'.dependencies]
+ common = {version = "1.0", features=["itarget"]}
+ "#,
+ )
+ .file(
+ "a/src/main.rs",
+ r#"
+ fn main() {
+ expect();
+ }
+
+ fn expect() {
+ let expected: u32 = std::env::var("EXPECTED_FEATS").unwrap().parse().unwrap();
+ assert_eq!(expected, common::feats());
+ }
+
+ #[test]
+ fn from_test() {
+ expect();
+ }
+ "#,
+ )
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.1.0"
+
+ [features]
+ ping = []
+ "#,
+ )
+ .file(
+ "b/src/main.rs",
+ r#"
+ fn main() {
+ if cfg!(feature="ping") {
+ println!("pong");
+ }
+ }
+ "#,
+ )
+ .build();
+
+ // Only normal.
+ p.cargo("run --bin a")
+ .env("EXPECTED_FEATS", "1")
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[DOWNLOADING] crates ...
+[DOWNLOADED] common [..]
+[COMPILING] common v1.0.0
+[COMPILING] a v0.1.0 [..]
+[FINISHED] [..]
+[RUNNING] `target/debug/a[EXE]`
+",
+ )
+ .run();
+
+ // only normal+dev
+ p.cargo("test").cwd("a").env("EXPECTED_FEATS", "5").run();
+
+ // Can specify features of packages from a different directory.
+ p.cargo("run -p b --features=ping")
+ .cwd("a")
+ .with_stdout("pong")
+ .run();
+}
+
+#[cargo_test]
+fn install_resolve_behavior() {
+ // install honors the resolver behavior.
+ Package::new("common", "1.0.0")
+ .feature("f1", &[])
+ .file(
+ "src/lib.rs",
+ r#"
+ #[cfg(feature = "f1")]
+ compile_error!("f1 should not activate");
+ "#,
+ )
+ .publish();
+
+ Package::new("bar", "1.0.0").dep("common", "1.0").publish();
+
+ Package::new("foo", "1.0.0")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "1.0.0"
+ resolver = "2"
+
+ [target.'cfg(whatever)'.dependencies]
+ common = {version="1.0", features=["f1"]}
+
+ [dependencies]
+ bar = "1.0"
+
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .publish();
+
+ cargo_process("install foo").run();
+}
+
+#[cargo_test]
+fn package_includes_resolve_behavior() {
+ // `cargo package` will inherit the correct resolve behavior.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["a"]
+ resolver = "2"
+ "#,
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+ authors = ["Zzz"]
+ description = "foo"
+ license = "MIT"
+ homepage = "https://example.com/"
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .build();
+
+ p.cargo("package").cwd("a").run();
+
+ let rewritten_toml = format!(
+ r#"{}
+[package]
+name = "a"
+version = "0.1.0"
+authors = ["Zzz"]
+description = "foo"
+homepage = "https://example.com/"
+license = "MIT"
+resolver = "2"
+"#,
+ cargo::core::package::MANIFEST_PREAMBLE
+ );
+
+ let f = File::open(&p.root().join("target/package/a-0.1.0.crate")).unwrap();
+ validate_crate_contents(
+ f,
+ "a-0.1.0.crate",
+ &["Cargo.toml", "Cargo.toml.orig", "src/lib.rs"],
+ &[("Cargo.toml", &rewritten_toml)],
+ );
+}
+
+#[cargo_test]
+fn tree_all() {
+ // `cargo tree` with the new feature resolver.
+ Package::new("log", "0.4.8").feature("serde", &[]).publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ resolver = "2"
+
+ [target.'cfg(whatever)'.dependencies]
+ log = {version="*", features=["serde"]}
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("tree --target=all")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+└── log v0.4.8
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn shared_dep_same_but_dependencies() {
+ // Checks for a bug of nondeterminism. This scenario creates a shared
+ // dependency `dep` which needs to be built twice (once as normal, and
+ // once as a build dep). However, in both cases the flags to `dep` are the
+ // same, the only difference is what it links to. The normal dependency
+ // should link to `subdep` with the feature disabled, and the build
+ // dependency should link to it with it enabled. Crucially, the `--target`
+ // flag should not be specified, otherwise Unit.kind would be different
+ // and avoid the collision, and this bug won't manifest.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bin1", "bin2"]
+ resolver = "2"
+ "#,
+ )
+ .file(
+ "bin1/Cargo.toml",
+ r#"
+ [package]
+ name = "bin1"
+ version = "0.1.0"
+
+ [dependencies]
+ dep = { path = "../dep" }
+ "#,
+ )
+ .file("bin1/src/main.rs", "fn main() { dep::feat_func(); }")
+ .file(
+ "bin2/Cargo.toml",
+ r#"
+ [package]
+ name = "bin2"
+ version = "0.1.0"
+
+ [build-dependencies]
+ dep = { path = "../dep" }
+ subdep = { path = "../subdep", features = ["feat"] }
+ "#,
+ )
+ .file("bin2/build.rs", "fn main() { dep::feat_func(); }")
+ .file("bin2/src/main.rs", "fn main() {}")
+ .file(
+ "dep/Cargo.toml",
+ r#"
+ [package]
+ name = "dep"
+ version = "0.1.0"
+
+ [dependencies]
+ subdep = { path = "../subdep" }
+ "#,
+ )
+ .file(
+ "dep/src/lib.rs",
+ "pub fn feat_func() { subdep::feat_func(); }",
+ )
+ .file(
+ "subdep/Cargo.toml",
+ r#"
+ [package]
+ name = "subdep"
+ version = "0.1.0"
+
+ [features]
+ feat = []
+ "#,
+ )
+ .file(
+ "subdep/src/lib.rs",
+ r#"
+ pub fn feat_func() {
+ #[cfg(feature = "feat")] println!("cargo:warning=feat: enabled");
+ #[cfg(not(feature = "feat"))] println!("cargo:warning=feat: not enabled");
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build --bin bin1 --bin bin2")
+ // unordered because bin1 and bin2 build at the same time
+ .with_stderr_unordered(
+ "\
+[COMPILING] subdep [..]
+[COMPILING] dep [..]
+[COMPILING] bin2 [..]
+[COMPILING] bin1 [..]
+warning: feat: enabled
+[FINISHED] [..]
+",
+ )
+ .run();
+ p.process(p.bin("bin1"))
+ .with_stdout("cargo:warning=feat: not enabled")
+ .run();
+
+ // Make sure everything stays cached.
+ p.cargo("build -v --bin bin1 --bin bin2")
+ .with_stderr_unordered(
+ "\
+[FRESH] subdep [..]
+[FRESH] dep [..]
+[FRESH] bin1 [..]
+warning: feat: enabled
+[FRESH] bin2 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn test_proc_macro() {
+ // Running `cargo test` on a proc-macro, with a shared dependency that has
+ // different features.
+ //
+ // There was a bug where `shared` was built twice (once with feature "B"
+ // and once without), and both copies linked into the unit test. This
+ // would cause a type failure when used in an intermediate dependency
+ // (the-macro-support).
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "runtime"
+ version = "0.1.0"
+ resolver = "2"
+
+ [dependencies]
+ the-macro = { path = "the-macro", features = ['a'] }
+ [build-dependencies]
+ shared = { path = "shared", features = ['b'] }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "the-macro/Cargo.toml",
+ r#"
+ [package]
+ name = "the-macro"
+ version = "0.1.0"
+ [lib]
+ proc-macro = true
+ test = false
+ [dependencies]
+ the-macro-support = { path = "../the-macro-support" }
+ shared = { path = "../shared" }
+ [dev-dependencies]
+ runtime = { path = ".." }
+ [features]
+ a = []
+ "#,
+ )
+ .file(
+ "the-macro/src/lib.rs",
+ "
+ fn _test() {
+ the_macro_support::foo(shared::Foo);
+ }
+ ",
+ )
+ .file(
+ "the-macro-support/Cargo.toml",
+ r#"
+ [package]
+ name = "the-macro-support"
+ version = "0.1.0"
+ [dependencies]
+ shared = { path = "../shared" }
+ "#,
+ )
+ .file(
+ "the-macro-support/src/lib.rs",
+ "
+ pub fn foo(_: shared::Foo) {}
+ ",
+ )
+ .file(
+ "shared/Cargo.toml",
+ r#"
+ [package]
+ name = "shared"
+ version = "0.1.0"
+ [features]
+ b = []
+ "#,
+ )
+ .file("shared/src/lib.rs", "pub struct Foo;")
+ .build();
+ p.cargo("test --manifest-path the-macro/Cargo.toml").run();
+}
+
+#[cargo_test]
+fn doc_optional() {
+ // Checks for a bug where `cargo doc` was failing with an inactive target
+ // that enables a shared optional dependency.
+ Package::new("spin", "1.0.0").publish();
+ Package::new("bar", "1.0.0")
+ .add_dep(Dependency::new("spin", "1.0").optional(true))
+ .publish();
+ // The enabler package enables the `spin` feature, which we don't want.
+ Package::new("enabler", "1.0.0")
+ .feature_dep("bar", "1.0", &["spin"])
+ .publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ resolver = "2"
+
+ [target.'cfg(whatever)'.dependencies]
+ enabler = "1.0"
+
+ [dependencies]
+ bar = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("doc")
+ .with_stderr_unordered(
+ "\
+[UPDATING] [..]
+[DOWNLOADING] crates ...
+[DOWNLOADED] spin v1.0.0 [..]
+[DOWNLOADED] bar v1.0.0 [..]
+[DOCUMENTING] bar v1.0.0
+[CHECKING] bar v1.0.0
+[DOCUMENTING] foo v0.1.0 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn minimal_download() {
+ // Various checks that it only downloads the minimum set of dependencies
+ // needed in various situations.
+ //
+ // This checks several permutations of the different
+ // host_dep/dev_dep/itarget settings. These 3 are planned to be stabilized
+ // together, so there isn't much need to be concerned about how the behave
+ // independently. However, there are some cases where they do behave
+ // independently. Specifically:
+ //
+ // * `cargo test` forces dev_dep decoupling to be disabled.
+ // * `cargo tree --target=all` forces ignore_inactive_targets off and decouple_dev_deps off.
+ // * `cargo tree --target=all -e normal` forces ignore_inactive_targets off.
+ //
+ // However, `cargo tree` is a little weird because it downloads everything
+ // anyways.
+ //
+ // So to summarize the different permutations:
+ //
+ // dev_dep | host_dep | itarget | Notes
+ // --------|----------|---------|----------------------------
+ // | | | -Zfeatures=compare (new resolver should behave same as old)
+ // | | ✓ | This scenario should not happen.
+ // | ✓ | | `cargo tree --target=all -Zfeatures=all`†
+ // | ✓ | ✓ | `cargo test`
+ // ✓ | | | This scenario should not happen.
+ // ✓ | | ✓ | This scenario should not happen.
+ // ✓ | ✓ | | `cargo tree --target=all -e normal -Z features=all`†
+ // ✓ | ✓ | ✓ | A normal build.
+ //
+ // † — However, `cargo tree` downloads everything.
+ Package::new("normal", "1.0.0").publish();
+ Package::new("normal_pm", "1.0.0").publish();
+ Package::new("normal_opt", "1.0.0").publish();
+ Package::new("dev_dep", "1.0.0").publish();
+ Package::new("dev_dep_pm", "1.0.0").publish();
+ Package::new("build_dep", "1.0.0").publish();
+ Package::new("build_dep_pm", "1.0.0").publish();
+ Package::new("build_dep_opt", "1.0.0").publish();
+
+ Package::new("itarget_normal", "1.0.0").publish();
+ Package::new("itarget_normal_pm", "1.0.0").publish();
+ Package::new("itarget_dev_dep", "1.0.0").publish();
+ Package::new("itarget_dev_dep_pm", "1.0.0").publish();
+ Package::new("itarget_build_dep", "1.0.0").publish();
+ Package::new("itarget_build_dep_pm", "1.0.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ normal = "1.0"
+ normal_pm = "1.0"
+ normal_opt = { version = "1.0", optional = true }
+
+ [dev-dependencies]
+ dev_dep = "1.0"
+ dev_dep_pm = "1.0"
+
+ [build-dependencies]
+ build_dep = "1.0"
+ build_dep_pm = "1.0"
+ build_dep_opt = { version = "1.0", optional = true }
+
+ [target.'cfg(whatever)'.dependencies]
+ itarget_normal = "1.0"
+ itarget_normal_pm = "1.0"
+
+ [target.'cfg(whatever)'.dev-dependencies]
+ itarget_dev_dep = "1.0"
+ itarget_dev_dep_pm = "1.0"
+
+ [target.'cfg(whatever)'.build-dependencies]
+ itarget_build_dep = "1.0"
+ itarget_build_dep_pm = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("build.rs", "fn main() {}")
+ .build();
+
+ let clear = || {
+ cargo_home().join("registry/cache").rm_rf();
+ cargo_home().join("registry/src").rm_rf();
+ p.build_dir().rm_rf();
+ };
+
+ // none
+ // Should be the same as `-Zfeatures=all`
+ p.cargo("check -Zfeatures=compare")
+ .masquerade_as_nightly_cargo(&["features=compare"])
+ .with_stderr_unordered(
+ "\
+[UPDATING] [..]
+[DOWNLOADING] crates ...
+[DOWNLOADED] normal_pm v1.0.0 [..]
+[DOWNLOADED] normal v1.0.0 [..]
+[DOWNLOADED] build_dep_pm v1.0.0 [..]
+[DOWNLOADED] build_dep v1.0.0 [..]
+[COMPILING] build_dep v1.0.0
+[COMPILING] build_dep_pm v1.0.0
+[CHECKING] normal_pm v1.0.0
+[CHECKING] normal v1.0.0
+[COMPILING] foo v0.1.0 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+ clear();
+
+ // New behavior
+ switch_to_resolver_2(&p);
+
+ // all
+ p.cargo("check")
+ .with_stderr_unordered(
+ "\
+[DOWNLOADING] crates ...
+[DOWNLOADED] normal_pm v1.0.0 [..]
+[DOWNLOADED] normal v1.0.0 [..]
+[DOWNLOADED] build_dep_pm v1.0.0 [..]
+[DOWNLOADED] build_dep v1.0.0 [..]
+[COMPILING] build_dep v1.0.0
+[COMPILING] build_dep_pm v1.0.0
+[CHECKING] normal v1.0.0
+[CHECKING] normal_pm v1.0.0
+[COMPILING] foo v0.1.0 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+ clear();
+
+ // This disables decouple_dev_deps.
+ p.cargo("test --no-run")
+ .with_stderr_unordered(
+ "\
+[DOWNLOADING] crates ...
+[DOWNLOADED] normal_pm v1.0.0 [..]
+[DOWNLOADED] normal v1.0.0 [..]
+[DOWNLOADED] dev_dep_pm v1.0.0 [..]
+[DOWNLOADED] dev_dep v1.0.0 [..]
+[DOWNLOADED] build_dep_pm v1.0.0 [..]
+[DOWNLOADED] build_dep v1.0.0 [..]
+[COMPILING] build_dep v1.0.0
+[COMPILING] build_dep_pm v1.0.0
+[COMPILING] normal_pm v1.0.0
+[COMPILING] normal v1.0.0
+[COMPILING] dev_dep_pm v1.0.0
+[COMPILING] dev_dep v1.0.0
+[COMPILING] foo v0.1.0 [..]
+[FINISHED] [..]
+[EXECUTABLE] unittests src/lib.rs (target/debug/deps/foo-[..][EXE])
+",
+ )
+ .run();
+ clear();
+
+ // This disables itarget, but leaves decouple_dev_deps enabled.
+ p.cargo("tree -e normal --target=all")
+ .with_stderr_unordered(
+ "\
+[DOWNLOADING] crates ...
+[DOWNLOADED] normal v1.0.0 [..]
+[DOWNLOADED] normal_pm v1.0.0 [..]
+[DOWNLOADED] build_dep v1.0.0 [..]
+[DOWNLOADED] build_dep_pm v1.0.0 [..]
+[DOWNLOADED] itarget_normal v1.0.0 [..]
+[DOWNLOADED] itarget_normal_pm v1.0.0 [..]
+[DOWNLOADED] itarget_build_dep v1.0.0 [..]
+[DOWNLOADED] itarget_build_dep_pm v1.0.0 [..]
+",
+ )
+ .with_stdout(
+ "\
+foo v0.1.0 ([ROOT]/foo)
+├── itarget_normal v1.0.0
+├── itarget_normal_pm v1.0.0
+├── normal v1.0.0
+└── normal_pm v1.0.0
+",
+ )
+ .run();
+ clear();
+
+ // This disables itarget and decouple_dev_deps.
+ p.cargo("tree --target=all")
+ .with_stderr_unordered(
+ "\
+[DOWNLOADING] crates ...
+[DOWNLOADED] normal_pm v1.0.0 [..]
+[DOWNLOADED] normal v1.0.0 [..]
+[DOWNLOADED] itarget_normal_pm v1.0.0 [..]
+[DOWNLOADED] itarget_normal v1.0.0 [..]
+[DOWNLOADED] itarget_dev_dep_pm v1.0.0 [..]
+[DOWNLOADED] itarget_dev_dep v1.0.0 [..]
+[DOWNLOADED] itarget_build_dep_pm v1.0.0 [..]
+[DOWNLOADED] itarget_build_dep v1.0.0 [..]
+[DOWNLOADED] dev_dep_pm v1.0.0 [..]
+[DOWNLOADED] dev_dep v1.0.0 [..]
+[DOWNLOADED] build_dep_pm v1.0.0 [..]
+[DOWNLOADED] build_dep v1.0.0 [..]
+",
+ )
+ .with_stdout(
+ "\
+foo v0.1.0 ([ROOT]/foo)
+├── itarget_normal v1.0.0
+├── itarget_normal_pm v1.0.0
+├── normal v1.0.0
+└── normal_pm v1.0.0
+[build-dependencies]
+├── build_dep v1.0.0
+├── build_dep_pm v1.0.0
+├── itarget_build_dep v1.0.0
+└── itarget_build_dep_pm v1.0.0
+[dev-dependencies]
+├── dev_dep v1.0.0
+├── dev_dep_pm v1.0.0
+├── itarget_dev_dep v1.0.0
+└── itarget_dev_dep_pm v1.0.0
+",
+ )
+ .run();
+ clear();
+}
+
+#[cargo_test]
+fn pm_with_int_shared() {
+ // This is a somewhat complex scenario of a proc-macro in a workspace with
+ // an integration test where the proc-macro is used for other things, and
+ // *everything* is built at once (`--workspace --all-targets
+ // --all-features`). There was a bug where the UnitFor settings were being
+ // incorrectly computed based on the order that the graph was traversed.
+ //
+ // There are some uncertainties about exactly how proc-macros should behave
+ // with `--workspace`, see https://github.com/rust-lang/cargo/issues/8312.
+ //
+ // This uses a const-eval hack to do compile-time feature checking.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["foo", "pm", "shared"]
+ resolver = "2"
+ "#,
+ )
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [dependencies]
+ pm = { path = "../pm" }
+ shared = { path = "../shared", features = ["norm-feat"] }
+ "#,
+ )
+ .file(
+ "foo/src/lib.rs",
+ r#"
+ // foo->shared always has both features set
+ const _CHECK: [(); 0] = [(); 0-!(shared::FEATS==3) as usize];
+ "#,
+ )
+ .file(
+ "pm/Cargo.toml",
+ r#"
+ [package]
+ name = "pm"
+ version = "0.1.0"
+
+ [lib]
+ proc-macro = true
+
+ [dependencies]
+ shared = { path = "../shared", features = ["host-feat"] }
+ "#,
+ )
+ .file(
+ "pm/src/lib.rs",
+ r#"
+ // pm->shared always has just host
+ const _CHECK: [(); 0] = [(); 0-!(shared::FEATS==1) as usize];
+ "#,
+ )
+ .file(
+ "pm/tests/pm_test.rs",
+ r#"
+ // integration test gets both set
+ const _CHECK: [(); 0] = [(); 0-!(shared::FEATS==3) as usize];
+ "#,
+ )
+ .file(
+ "shared/Cargo.toml",
+ r#"
+ [package]
+ name = "shared"
+ version = "0.1.0"
+
+ [features]
+ norm-feat = []
+ host-feat = []
+ "#,
+ )
+ .file(
+ "shared/src/lib.rs",
+ r#"
+ pub const FEATS: u32 = {
+ if cfg!(feature="norm-feat") && cfg!(feature="host-feat") {
+ 3
+ } else if cfg!(feature="norm-feat") {
+ 2
+ } else if cfg!(feature="host-feat") {
+ 1
+ } else {
+ 0
+ }
+ };
+ "#,
+ )
+ .build();
+
+ p.cargo("build --workspace --all-targets --all-features -v")
+ .with_stderr_unordered(
+ "\
+[COMPILING] shared [..]
+[RUNNING] `rustc --crate-name shared [..]--crate-type lib [..]
+[RUNNING] `rustc --crate-name shared [..]--crate-type lib [..]
+[RUNNING] `rustc --crate-name shared [..]--test[..]
+[COMPILING] pm [..]
+[RUNNING] `rustc --crate-name pm [..]--crate-type proc-macro[..]
+[RUNNING] `rustc --crate-name pm [..]--test[..]
+[COMPILING] foo [..]
+[RUNNING] `rustc --crate-name foo [..]--test[..]
+[RUNNING] `rustc --crate-name pm_test [..]--test[..]
+[RUNNING] `rustc --crate-name foo [..]--crate-type lib[..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ // And again, should stay fresh.
+ p.cargo("build --workspace --all-targets --all-features -v")
+ .with_stderr_unordered(
+ "\
+[FRESH] shared [..]
+[FRESH] pm [..]
+[FRESH] foo [..]
+[FINISHED] [..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn doc_proc_macro() {
+ // Checks for a bug when documenting a proc-macro with a dependency. The
+ // doc unit builder was not carrying the "for host" setting through the
+ // dependencies, and the `pm-dep` dependency was causing a panic because
+ // it was looking for target features instead of host features.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ resolver = "2"
+
+ [dependencies]
+ pm = { path = "pm" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "pm/Cargo.toml",
+ r#"
+ [package]
+ name = "pm"
+ version = "0.1.0"
+
+ [lib]
+ proc-macro = true
+
+ [dependencies]
+ pm-dep = { path = "../pm-dep" }
+ "#,
+ )
+ .file("pm/src/lib.rs", "")
+ .file("pm-dep/Cargo.toml", &basic_manifest("pm-dep", "0.1.0"))
+ .file("pm-dep/src/lib.rs", "")
+ .build();
+
+ // Unfortunately this cannot check the output because what it prints is
+ // nondeterministic. Sometimes it says "Compiling pm-dep" and sometimes
+ // "Checking pm-dep". This is because it is both building it and checking
+ // it in parallel (building so it can build the proc-macro, and checking
+ // so rustdoc can load it).
+ p.cargo("doc").run();
+}
+
+#[cargo_test]
+fn edition_2021_default_2() {
+ // edition = 2021 defaults to v2 resolver.
+ Package::new("common", "1.0.0")
+ .feature("f1", &[])
+ .file("src/lib.rs", "")
+ .publish();
+
+ Package::new("bar", "1.0.0")
+ .add_dep(
+ Dependency::new("common", "1.0")
+ .target("cfg(whatever)")
+ .enable_features(&["f1"]),
+ )
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ common = "1.0"
+ bar = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ // First without edition.
+ p.cargo("tree -f")
+ .arg("{p} feats:{f}")
+ .with_stdout(
+ "\
+foo v0.1.0 [..]
+├── bar v1.0.0 feats:
+└── common v1.0.0 feats:f1
+",
+ )
+ .run();
+
+ p.change_file(
+ "Cargo.toml",
+ r#"
+ cargo-features = ["edition2021"]
+
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2021"
+
+ [dependencies]
+ common = "1.0"
+ bar = "1.0"
+ "#,
+ );
+
+ // Importantly, this does not include `f1` on `common`.
+ p.cargo("tree -f")
+ .arg("{p} feats:{f}")
+ .with_stdout(
+ "\
+foo v0.1.0 [..]
+├── bar v1.0.0 feats:
+└── common v1.0.0 feats:
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn all_features_merges_with_features() {
+ Package::new("dep", "0.1.0")
+ .feature("feat1", &[])
+ .file(
+ "src/lib.rs",
+ r#"
+ #[cfg(feature="feat1")]
+ pub fn work() {
+ println!("it works");
+ }
+ "#,
+ )
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [features]
+ a = []
+
+ [dependencies]
+ dep = "0.1"
+
+ [[example]]
+ name = "ex"
+ required-features = ["a", "dep/feat1"]
+ "#,
+ )
+ .file(
+ "examples/ex.rs",
+ r#"
+ fn main() {
+ dep::work();
+ }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("run --example ex --all-features --features dep/feat1")
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[DOWNLOADING] crates ...
+[DOWNLOADED] [..]
+[COMPILING] dep v0.1.0
+[COMPILING] foo v0.1.0 [..]
+[FINISHED] [..]
+[RUNNING] `target/debug/examples/ex[EXE]`
+",
+ )
+ .with_stdout("it works")
+ .run();
+
+ switch_to_resolver_2(&p);
+
+ p.cargo("run --example ex --all-features --features dep/feat1")
+ .with_stderr(
+ "\
+[FINISHED] [..]
+[RUNNING] `target/debug/examples/ex[EXE]`
+",
+ )
+ .with_stdout("it works")
+ .run();
+}
+
+#[cargo_test]
+fn dep_with_optional_host_deps_activated() {
+ // To prevent regression like rust-lang/cargo#11330
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2021"
+
+ [dependencies]
+ serde = { path = "serde", features = ["derive", "build"] }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "serde/Cargo.toml",
+ r#"
+ [package]
+ name = "serde"
+ version = "0.1.0"
+ edition = "2021"
+
+ [dependencies]
+ serde_derive = { path = "../serde_derive", optional = true }
+
+ [build-dependencies]
+ serde_build = { path = "../serde_build", optional = true }
+
+ [features]
+ derive = ["dep:serde_derive"]
+ build = ["dep:serde_build"]
+ "#,
+ )
+ .file("serde/src/lib.rs", "")
+ .file("serde/build.rs", "fn main() {}")
+ .file(
+ "serde_derive/Cargo.toml",
+ r#"
+ [package]
+ name = "serde_derive"
+ version = "0.1.0"
+ edition = "2021"
+
+ [lib]
+ proc-macro = true
+ "#,
+ )
+ .file("serde_derive/src/lib.rs", "")
+ .file(
+ "serde_build/Cargo.toml",
+ &basic_manifest("serde_build", "0.1.0"),
+ )
+ .file("serde_build/src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[COMPILING] serde_build v0.1.0 ([CWD]/serde_build)
+[COMPILING] serde_derive v0.1.0 ([CWD]/serde_derive)
+[COMPILING] serde v0.1.0 ([CWD]/serde)
+[CHECKING] foo v0.1.0 ([CWD])
+[FINISHED] dev [..]
+",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/features_namespaced.rs b/src/tools/cargo/tests/testsuite/features_namespaced.rs
new file mode 100644
index 000000000..8ec2fc2e3
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/features_namespaced.rs
@@ -0,0 +1,1215 @@
+//! Tests for namespaced features.
+
+use super::features2::switch_to_resolver_2;
+use cargo_test_support::registry::{Dependency, Package, RegistryBuilder};
+use cargo_test_support::{project, publish};
+
+#[cargo_test]
+fn dependency_with_crate_syntax() {
+ // Registry dependency uses dep: syntax.
+ Package::new("baz", "1.0.0").publish();
+ Package::new("bar", "1.0.0")
+ .add_dep(Dependency::new("baz", "1.0").optional(true))
+ .feature("feat", &["dep:baz"])
+ .publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = {version="1.0", features=["feat"]}
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[DOWNLOADING] crates ...
+[DOWNLOADED] [..]
+[DOWNLOADED] [..]
+[CHECKING] baz v1.0.0
+[CHECKING] bar v1.0.0
+[CHECKING] foo v0.1.0 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn namespaced_invalid_feature() {
+ // Specifies a feature that doesn't exist.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ bar = ["baz"]
+ "#,
+ )
+ .file("src/main.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ feature `bar` includes `baz` which is neither a dependency nor another feature
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn namespaced_invalid_dependency() {
+ // Specifies a dep:name that doesn't exist.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [features]
+ bar = ["dep:baz"]
+ "#,
+ )
+ .file("src/main.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ feature `bar` includes `dep:baz`, but `baz` is not listed as a dependency
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn namespaced_non_optional_dependency() {
+ // Specifies a dep:name for a dependency that is not optional.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [features]
+ bar = ["dep:baz"]
+
+ [dependencies]
+ baz = "0.1"
+ "#,
+ )
+ .file("src/main.rs", "")
+ .build();
+
+ p.cargo("check")
+
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ feature `bar` includes `dep:baz`, but `baz` is not an optional dependency
+ A non-optional dependency of the same name is defined; consider adding `optional = true` to its definition.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn namespaced_implicit_feature() {
+ // Backwards-compatible with old syntax.
+ Package::new("baz", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [features]
+ bar = ["baz"]
+
+ [dependencies]
+ baz = { version = "0.1", optional = true }
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[CHECKING] foo v0.0.1 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+ p.cargo("check --features baz")
+ .with_stderr(
+ "\
+[DOWNLOADING] crates ...
+[DOWNLOADED] baz v0.1.0 [..]
+[CHECKING] baz v0.1.0
+[CHECKING] foo v0.0.1 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn namespaced_shadowed_dep() {
+ // An optional dependency is not listed in the features table, and its
+ // implicit feature is overridden.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [features]
+ baz = []
+
+ [dependencies]
+ baz = { version = "0.1", optional = true }
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ optional dependency `baz` is not included in any feature
+ Make sure that `dep:baz` is included in one of features in the [features] table.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn namespaced_shadowed_non_optional() {
+ // Able to specify a feature with the same name as a required dependency.
+ Package::new("baz", "0.1.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [features]
+ baz = []
+
+ [dependencies]
+ baz = "0.1"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn namespaced_implicit_non_optional() {
+ // Includes a non-optional dependency in [features] table.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [features]
+ bar = ["baz"]
+
+ [dependencies]
+ baz = "0.1"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check").with_status(101).with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ feature `bar` includes `baz`, but `baz` is not an optional dependency
+ A non-optional dependency of the same name is defined; consider adding `optional = true` to its definition.
+",
+ ).run();
+}
+
+#[cargo_test]
+fn namespaced_same_name() {
+ // Explicitly listing an optional dependency in the [features] table.
+ Package::new("baz", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [features]
+ baz = ["dep:baz"]
+
+ [dependencies]
+ baz = { version = "0.1", optional = true }
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ if cfg!(feature="baz") { println!("baz"); }
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("run")
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[COMPILING] foo v0.0.1 [..]
+[FINISHED] [..]
+[RUNNING] [..]
+",
+ )
+ .with_stdout("")
+ .run();
+
+ p.cargo("run --features baz")
+ .with_stderr(
+ "\
+[DOWNLOADING] crates ...
+[DOWNLOADED] baz v0.1.0 [..]
+[COMPILING] baz v0.1.0
+[COMPILING] foo v0.0.1 [..]
+[FINISHED] [..]
+[RUNNING] [..]
+",
+ )
+ .with_stdout("baz")
+ .run();
+}
+
+#[cargo_test]
+fn no_implicit_feature() {
+ // Using `dep:` will not create an implicit feature.
+ Package::new("regex", "1.0.0").publish();
+ Package::new("lazy_static", "1.0.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ regex = { version = "1.0", optional = true }
+ lazy_static = { version = "1.0", optional = true }
+
+ [features]
+ regex = ["dep:regex", "dep:lazy_static"]
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ if cfg!(feature = "regex") { println!("regex"); }
+ if cfg!(feature = "lazy_static") { println!("lazy_static"); }
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("run")
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[COMPILING] foo v0.1.0 [..]
+[FINISHED] [..]
+[RUNNING] `target/debug/foo[EXE]`
+",
+ )
+ .with_stdout("")
+ .run();
+
+ p.cargo("run --features regex")
+ .with_stderr_unordered(
+ "\
+[DOWNLOADING] crates ...
+[DOWNLOADED] regex v1.0.0 [..]
+[DOWNLOADED] lazy_static v1.0.0 [..]
+[COMPILING] regex v1.0.0
+[COMPILING] lazy_static v1.0.0
+[COMPILING] foo v0.1.0 [..]
+[FINISHED] [..]
+[RUNNING] `target/debug/foo[EXE]`
+",
+ )
+ .with_stdout("regex")
+ .run();
+
+ p.cargo("run --features lazy_static")
+ .with_stderr(
+ "\
+[ERROR] Package `foo v0.1.0 [..]` does not have feature `lazy_static`. \
+It has an optional dependency with that name, but that dependency uses the \"dep:\" \
+syntax in the features table, so it does not have an implicit feature with that name.
+",
+ )
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn crate_syntax_bad_name() {
+ // "dep:bar" = []
+ Package::new("bar", "1.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { version="1.0", optional=true }
+
+ [features]
+ "dep:bar" = []
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check --features dep:bar")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at [..]/foo/Cargo.toml`
+
+Caused by:
+ feature named `dep:bar` is not allowed to start with `dep:`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn crate_syntax_in_dep() {
+ // features = ["dep:baz"]
+ Package::new("baz", "1.0.0").publish();
+ Package::new("bar", "1.0.0")
+ .add_dep(Dependency::new("baz", "1.0").optional(true))
+ .publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { version = "1.0", features = ["dep:baz"] }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[CWD]/Cargo.toml`
+
+Caused by:
+ feature `dep:baz` in dependency `bar` is not allowed to use explicit `dep:` syntax
+ If you want to enable [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn crate_syntax_cli() {
+ // --features dep:bar
+ Package::new("bar", "1.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { version = "1.0", optional=true }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check --features dep:bar")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] feature `dep:bar` is not allowed to use explicit `dep:` syntax
+",
+ )
+ .run();
+
+ switch_to_resolver_2(&p);
+ p.cargo("check --features dep:bar")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] feature `dep:bar` is not allowed to use explicit `dep:` syntax
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn crate_required_features() {
+ // required-features = ["dep:bar"]
+ Package::new("bar", "1.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { version = "1.0", optional=true }
+
+ [[bin]]
+ name = "foo"
+ required-features = ["dep:bar"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[ERROR] invalid feature `dep:bar` in required-features of target `foo`: \
+`dep:` prefixed feature values are not allowed in required-features
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn json_exposed() {
+ // Checks that the implicit dep: values are exposed in JSON.
+ Package::new("bar", "1.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { version = "1.0", optional=true }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("metadata --no-deps")
+ .with_json(
+ r#"
+ {
+ "packages": [
+ {
+ "name": "foo",
+ "version": "0.1.0",
+ "id": "foo 0.1.0 [..]",
+ "license": null,
+ "license_file": null,
+ "description": null,
+ "homepage": null,
+ "documentation": null,
+ "source": null,
+ "dependencies": "{...}",
+ "targets": "{...}",
+ "features": {
+ "bar": ["dep:bar"]
+ },
+ "manifest_path": "[..]foo/Cargo.toml",
+ "metadata": null,
+ "publish": null,
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "keywords": [],
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "edition": "2015",
+ "links": null
+ }
+ ],
+ "workspace_members": "{...}",
+ "resolve": null,
+ "target_directory": "[..]foo/target",
+ "version": 1,
+ "workspace_root": "[..]foo",
+ "metadata": null
+ }
+ "#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn crate_feature_with_explicit() {
+ // crate_name/feat_name syntax where crate_name already has a feature defined.
+ // NOTE: I don't know if this is actually ideal behavior.
+ Package::new("bar", "1.0.0")
+ .feature("bar_feat", &[])
+ .file(
+ "src/lib.rs",
+ r#"
+ #[cfg(not(feature="bar_feat"))]
+ compile_error!("bar_feat is not enabled");
+ "#,
+ )
+ .publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { version="1.0", optional = true }
+
+ [features]
+ f1 = ["bar/bar_feat"]
+ bar = ["dep:bar", "f2"]
+ f2 = []
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ #[cfg(not(feature="bar"))]
+ compile_error!("bar should be enabled");
+
+ #[cfg(not(feature="f2"))]
+ compile_error!("f2 should be enabled");
+ "#,
+ )
+ .build();
+
+ p.cargo("check --features f1")
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v1.0.0 [..]
+[CHECKING] bar v1.0.0
+[CHECKING] foo v0.1.0 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn optional_explicit_without_crate() {
+ // "feat" syntax when there is no implicit "feat" feature because it is
+ // explicitly listed elsewhere.
+ Package::new("bar", "1.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { version = "1.0", optional = true }
+
+ [features]
+ feat1 = ["dep:bar"]
+ feat2 = ["bar"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at [..]
+
+Caused by:
+ feature `feat2` includes `bar`, but `bar` is an optional dependency without an implicit feature
+ Use `dep:bar` to enable the dependency.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn tree() {
+ Package::new("baz", "1.0.0").publish();
+ Package::new("bar", "1.0.0")
+ .add_dep(Dependency::new("baz", "1.0").optional(true))
+ .feature("feat1", &["dep:baz"])
+ .feature("feat2", &[])
+ .publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { version = "1.0", features = ["feat1"], optional=true }
+
+ [features]
+ a = ["bar/feat2"]
+ bar = ["dep:bar"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("tree -e features")
+ .with_stdout("foo v0.1.0 ([ROOT]/foo)")
+ .run();
+
+ p.cargo("tree -e features --features a")
+ .with_stdout(
+ "\
+foo v0.1.0 ([ROOT]/foo)
+├── bar feature \"default\"
+│ └── bar v1.0.0
+│ └── baz feature \"default\"
+│ └── baz v1.0.0
+└── bar feature \"feat1\"
+ └── bar v1.0.0 (*)
+",
+ )
+ .run();
+
+ p.cargo("tree -e features --features a -i bar")
+ .with_stdout(
+ "\
+bar v1.0.0
+├── bar feature \"default\"
+│ └── foo v0.1.0 ([ROOT]/foo)
+│ ├── foo feature \"a\" (command-line)
+│ ├── foo feature \"bar\"
+│ │ └── foo feature \"a\" (command-line)
+│ └── foo feature \"default\" (command-line)
+├── bar feature \"feat1\"
+│ └── foo v0.1.0 ([ROOT]/foo) (*)
+└── bar feature \"feat2\"
+ └── foo feature \"a\" (command-line)
+",
+ )
+ .run();
+
+ p.cargo("tree -e features --features bar")
+ .with_stdout(
+ "\
+foo v0.1.0 ([ROOT]/foo)
+├── bar feature \"default\"
+│ └── bar v1.0.0
+│ └── baz feature \"default\"
+│ └── baz v1.0.0
+└── bar feature \"feat1\"
+ └── bar v1.0.0 (*)
+",
+ )
+ .run();
+
+ p.cargo("tree -e features --features bar -i bar")
+ .with_stdout(
+ "\
+bar v1.0.0
+├── bar feature \"default\"
+│ └── foo v0.1.0 ([ROOT]/foo)
+│ ├── foo feature \"bar\" (command-line)
+│ └── foo feature \"default\" (command-line)
+└── bar feature \"feat1\"
+ └── foo v0.1.0 ([ROOT]/foo) (*)
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn tree_no_implicit() {
+ // tree without an implicit feature
+ Package::new("bar", "1.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { version = "1.0", optional=true }
+
+ [features]
+ a = ["dep:bar"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("tree -e features")
+ .with_stdout("foo v0.1.0 ([ROOT]/foo)")
+ .run();
+
+ p.cargo("tree -e features --all-features")
+ .with_stdout(
+ "\
+foo v0.1.0 ([ROOT]/foo)
+└── bar feature \"default\"
+ └── bar v1.0.0
+",
+ )
+ .run();
+
+ p.cargo("tree -e features -i bar --all-features")
+ .with_stdout(
+ "\
+bar v1.0.0
+└── bar feature \"default\"
+ └── foo v0.1.0 ([ROOT]/foo)
+ ├── foo feature \"a\" (command-line)
+ └── foo feature \"default\" (command-line)
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn publish_no_implicit() {
+ let registry = RegistryBuilder::new().http_api().http_index().build();
+
+ // Does not include implicit features or dep: syntax on publish.
+ Package::new("opt-dep1", "1.0.0").publish();
+ Package::new("opt-dep2", "1.0.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ description = "foo"
+ license = "MIT"
+ homepage = "https://example.com/"
+
+ [dependencies]
+ opt-dep1 = { version = "1.0", optional = true }
+ opt-dep2 = { version = "1.0", optional = true }
+
+ [features]
+ feat = ["opt-dep1"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("publish --no-verify")
+ .replace_crates_io(registry.index_url())
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[PACKAGING] foo v0.1.0 [..]
+[PACKAGED] [..]
+[UPLOADING] foo v0.1.0 [..]
+[UPLOADED] foo v0.1.0 [..]
+note: Waiting [..]
+You may press ctrl-c [..]
+[PUBLISHED] foo v0.1.0 [..]
+",
+ )
+ .run();
+
+ publish::validate_upload_with_contents(
+ r#"
+ {
+ "authors": [],
+ "badges": {},
+ "categories": [],
+ "deps": [
+ {
+ "default_features": true,
+ "features": [],
+ "kind": "normal",
+ "name": "opt-dep1",
+ "optional": true,
+ "target": null,
+ "version_req": "^1.0"
+ },
+ {
+ "default_features": true,
+ "features": [],
+ "kind": "normal",
+ "name": "opt-dep2",
+ "optional": true,
+ "target": null,
+ "version_req": "^1.0"
+ }
+ ],
+ "description": "foo",
+ "documentation": null,
+ "features": {
+ "feat": ["opt-dep1"]
+ },
+ "homepage": "https://example.com/",
+ "keywords": [],
+ "license": "MIT",
+ "license_file": null,
+ "links": null,
+ "name": "foo",
+ "readme": null,
+ "readme_file": null,
+ "repository": null,
+ "vers": "0.1.0"
+ }
+ "#,
+ "foo-0.1.0.crate",
+ &["Cargo.toml", "Cargo.toml.orig", "src/lib.rs"],
+ &[(
+ "Cargo.toml",
+ &format!(
+ r#"{}
+[package]
+name = "foo"
+version = "0.1.0"
+description = "foo"
+homepage = "https://example.com/"
+license = "MIT"
+
+[dependencies.opt-dep1]
+version = "1.0"
+optional = true
+
+[dependencies.opt-dep2]
+version = "1.0"
+optional = true
+
+[features]
+feat = ["opt-dep1"]
+"#,
+ cargo::core::package::MANIFEST_PREAMBLE
+ ),
+ )],
+ );
+}
+
+#[cargo_test]
+fn publish() {
+ let registry = RegistryBuilder::new().http_api().http_index().build();
+
+ // Publish behavior with explicit dep: syntax.
+ Package::new("bar", "1.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ description = "foo"
+ license = "MIT"
+ homepage = "https://example.com/"
+
+ [dependencies]
+ bar = { version = "1.0", optional = true }
+
+ [features]
+ feat1 = []
+ feat2 = ["dep:bar"]
+ feat3 = ["feat2"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("publish")
+ .replace_crates_io(registry.index_url())
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[PACKAGING] foo v0.1.0 [..]
+[VERIFYING] foo v0.1.0 [..]
+[UPDATING] [..]
+[COMPILING] foo v0.1.0 [..]
+[FINISHED] [..]
+[PACKAGED] [..]
+[UPLOADING] foo v0.1.0 [..]
+[UPLOADED] foo v0.1.0 [..]
+note: Waiting [..]
+You may press ctrl-c [..]
+[PUBLISHED] foo v0.1.0 [..]
+",
+ )
+ .run();
+
+ publish::validate_upload_with_contents(
+ r#"
+ {
+ "authors": [],
+ "badges": {},
+ "categories": [],
+ "deps": [
+ {
+ "default_features": true,
+ "features": [],
+ "kind": "normal",
+ "name": "bar",
+ "optional": true,
+ "target": null,
+ "version_req": "^1.0"
+ }
+ ],
+ "description": "foo",
+ "documentation": null,
+ "features": {
+ "feat1": [],
+ "feat2": ["dep:bar"],
+ "feat3": ["feat2"]
+ },
+ "homepage": "https://example.com/",
+ "keywords": [],
+ "license": "MIT",
+ "license_file": null,
+ "links": null,
+ "name": "foo",
+ "readme": null,
+ "readme_file": null,
+ "repository": null,
+ "vers": "0.1.0"
+ }
+ "#,
+ "foo-0.1.0.crate",
+ &["Cargo.toml", "Cargo.toml.orig", "src/lib.rs"],
+ &[(
+ "Cargo.toml",
+ &format!(
+ r#"{}
+[package]
+name = "foo"
+version = "0.1.0"
+description = "foo"
+homepage = "https://example.com/"
+license = "MIT"
+
+[dependencies.bar]
+version = "1.0"
+optional = true
+
+[features]
+feat1 = []
+feat2 = ["dep:bar"]
+feat3 = ["feat2"]
+"#,
+ cargo::core::package::MANIFEST_PREAMBLE
+ ),
+ )],
+ );
+}
+
+#[cargo_test]
+fn namespaced_feature_together() {
+ // Check for an error when `dep:` is used with `/`
+ Package::new("bar", "1.0.0")
+ .feature("bar-feat", &[])
+ .publish();
+
+ // Non-optional shouldn't have extra err.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "1.0"
+
+ [features]
+ f1 = ["dep:bar/bar-feat"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[ROOT]/foo/Cargo.toml`
+
+Caused by:
+ feature `f1` includes `dep:bar/bar-feat` with both `dep:` and `/`
+ To fix this, remove the `dep:` prefix.
+",
+ )
+ .run();
+
+ // Weak dependency shouldn't have extra err.
+ p.change_file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = {version = "1.0", optional = true }
+
+ [features]
+ f1 = ["dep:bar?/bar-feat"]
+ "#,
+ );
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[ROOT]/foo/Cargo.toml`
+
+Caused by:
+ feature `f1` includes `dep:bar?/bar-feat` with both `dep:` and `/`
+ To fix this, remove the `dep:` prefix.
+",
+ )
+ .run();
+
+ // If dep: is already specified, shouldn't have extra err.
+ p.change_file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = {version = "1.0", optional = true }
+
+ [features]
+ f1 = ["dep:bar", "dep:bar/bar-feat"]
+ "#,
+ );
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[ROOT]/foo/Cargo.toml`
+
+Caused by:
+ feature `f1` includes `dep:bar/bar-feat` with both `dep:` and `/`
+ To fix this, remove the `dep:` prefix.
+",
+ )
+ .run();
+
+ // Only when the other 3 cases aren't true should it give some extra help.
+ p.change_file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = {version = "1.0", optional = true }
+
+ [features]
+ f1 = ["dep:bar/bar-feat"]
+ "#,
+ );
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[ROOT]/foo/Cargo.toml`
+
+Caused by:
+ feature `f1` includes `dep:bar/bar-feat` with both `dep:` and `/`
+ To fix this, remove the `dep:` prefix.
+ If the intent is to avoid creating an implicit feature `bar` for an optional \
+ dependency, then consider replacing this with two values:
+ \"dep:bar\", \"bar/bar-feat\"
+",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/fetch.rs b/src/tools/cargo/tests/testsuite/fetch.rs
new file mode 100644
index 000000000..f90131a59
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/fetch.rs
@@ -0,0 +1,135 @@
+//! Tests for the `cargo fetch` command.
+
+use cargo_test_support::registry::Package;
+use cargo_test_support::rustc_host;
+use cargo_test_support::{basic_manifest, cross_compile, project};
+
+#[cargo_test]
+fn no_deps() {
+ let p = project()
+ .file("src/main.rs", "mod a; fn main() {}")
+ .file("src/a.rs", "")
+ .build();
+
+ p.cargo("fetch").with_stdout("").run();
+}
+
+#[cargo_test]
+fn fetch_all_platform_dependencies_when_no_target_is_given() {
+ if cross_compile::disabled() {
+ return;
+ }
+
+ Package::new("d1", "1.2.3")
+ .file("Cargo.toml", &basic_manifest("d1", "1.2.3"))
+ .file("src/lib.rs", "")
+ .publish();
+
+ Package::new("d2", "0.1.2")
+ .file("Cargo.toml", &basic_manifest("d2", "0.1.2"))
+ .file("src/lib.rs", "")
+ .publish();
+
+ let target = cross_compile::alternate();
+ let host = rustc_host();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [target.{host}.dependencies]
+ d1 = "1.2.3"
+
+ [target.{target}.dependencies]
+ d2 = "0.1.2"
+ "#,
+ host = host,
+ target = target
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("fetch")
+ .with_stderr_contains("[DOWNLOADED] d1 v1.2.3 [..]")
+ .with_stderr_contains("[DOWNLOADED] d2 v0.1.2 [..]")
+ .run();
+}
+
+#[cargo_test]
+fn fetch_platform_specific_dependencies() {
+ if cross_compile::disabled() {
+ return;
+ }
+
+ Package::new("d1", "1.2.3")
+ .file("Cargo.toml", &basic_manifest("d1", "1.2.3"))
+ .file("src/lib.rs", "")
+ .publish();
+
+ Package::new("d2", "0.1.2")
+ .file("Cargo.toml", &basic_manifest("d2", "0.1.2"))
+ .file("src/lib.rs", "")
+ .publish();
+
+ let target = cross_compile::alternate();
+ let host = rustc_host();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [target.{host}.dependencies]
+ d1 = "1.2.3"
+
+ [target.{target}.dependencies]
+ d2 = "0.1.2"
+ "#,
+ host = host,
+ target = target
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("fetch --target")
+ .arg(&host)
+ .with_stderr_contains("[DOWNLOADED] d1 v1.2.3 [..]")
+ .with_stderr_does_not_contain("[DOWNLOADED] d2 v0.1.2 [..]")
+ .run();
+
+ p.cargo("fetch --target")
+ .arg(&target)
+ .with_stderr_contains("[DOWNLOADED] d2 v0.1.2[..]")
+ .with_stderr_does_not_contain("[DOWNLOADED] d1 v1.2.3 [..]")
+ .run();
+}
+
+#[cargo_test]
+fn fetch_warning() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "1.0.0"
+ misspelled = "wut"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("fetch")
+ .with_stderr("[WARNING] unused manifest key: package.misspelled")
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/fix.rs b/src/tools/cargo/tests/testsuite/fix.rs
new file mode 100644
index 000000000..54a021c03
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/fix.rs
@@ -0,0 +1,1855 @@
+//! Tests for the `cargo fix` command.
+
+use cargo::core::Edition;
+use cargo_test_support::compare::assert_match_exact;
+use cargo_test_support::git::{self, init};
+use cargo_test_support::paths::{self, CargoPathExt};
+use cargo_test_support::registry::{Dependency, Package};
+use cargo_test_support::tools;
+use cargo_test_support::{basic_manifest, is_nightly, project};
+
+#[cargo_test]
+fn do_not_fix_broken_builds() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn foo() {
+ let mut x = 3;
+ drop(x);
+ }
+
+ pub fn foo2() {
+ let _x: u32 = "a";
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("fix --allow-no-vcs")
+ .env("__CARGO_FIX_YOLO", "1")
+ .with_status(101)
+ .with_stderr_contains("[ERROR] could not compile `foo` (lib) due to previous error")
+ .run();
+ assert!(p.read_file("src/lib.rs").contains("let mut x = 3;"));
+}
+
+#[cargo_test]
+fn fix_broken_if_requested() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ fn foo(a: &u32) -> u32 { a + 1 }
+ pub fn bar() {
+ foo(1);
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("fix --allow-no-vcs --broken-code")
+ .env("__CARGO_FIX_YOLO", "1")
+ .run();
+}
+
+#[cargo_test]
+fn broken_fixes_backed_out() {
+ // This works as follows:
+ // - Create a `rustc` shim (the "foo" project) which will pretend that the
+ // verification step fails.
+ // - There is an empty build script so `foo` has `OUT_DIR` to track the steps.
+ // - The first "check", `foo` creates a file in OUT_DIR, and it completes
+ // successfully with a warning diagnostic to remove unused `mut`.
+ // - rustfix removes the `mut`.
+ // - The second "check" to verify the changes, `foo` swaps out the content
+ // with something that fails to compile. It creates a second file so it
+ // won't do anything in the third check.
+ // - cargo fix discovers that the fix failed, and it backs out the changes.
+ // - The third "check" is done to display the original diagnostics of the
+ // original code.
+ let p = project()
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = 'foo'
+ version = '0.1.0'
+ [workspace]
+ "#,
+ )
+ .file(
+ "foo/src/main.rs",
+ r#"
+ use std::env;
+ use std::fs;
+ use std::io::Write;
+ use std::path::{Path, PathBuf};
+ use std::process::{self, Command};
+
+ fn main() {
+ // Ignore calls to things like --print=file-names and compiling build.rs.
+ // Also compatible for rustc invocations with `@path` argfile.
+ let is_lib_rs = env::args_os()
+ .map(PathBuf::from)
+ .flat_map(|p| if let Some(p) = p.to_str().unwrap_or_default().strip_prefix("@") {
+ fs::read_to_string(p).unwrap().lines().map(PathBuf::from).collect()
+ } else {
+ vec![p]
+ })
+ .any(|l| l == Path::new("src/lib.rs"));
+ if is_lib_rs {
+ let path = PathBuf::from(env::var_os("OUT_DIR").unwrap());
+ let first = path.join("first");
+ let second = path.join("second");
+ if first.exists() && !second.exists() {
+ fs::write("src/lib.rs", b"not rust code").unwrap();
+ fs::File::create(&second).unwrap();
+ } else {
+ fs::File::create(&first).unwrap();
+ }
+ }
+
+ let status = Command::new("rustc")
+ .args(env::args().skip(1))
+ .status()
+ .expect("failed to run rustc");
+ process::exit(status.code().unwrap_or(2));
+ }
+ "#,
+ )
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = 'bar'
+ version = '0.1.0'
+ [workspace]
+ "#,
+ )
+ .file("bar/build.rs", "fn main() {}")
+ .file(
+ "bar/src/lib.rs",
+ r#"
+ pub fn foo() {
+ let mut x = 3;
+ drop(x);
+ }
+ "#,
+ )
+ .build();
+
+ // Build our rustc shim
+ p.cargo("build").cwd("foo").run();
+
+ // Attempt to fix code, but our shim will always fail the second compile
+ p.cargo("fix --allow-no-vcs --lib")
+ .cwd("bar")
+ .env("__CARGO_FIX_YOLO", "1")
+ .env("RUSTC", p.root().join("foo/target/debug/foo"))
+ .with_stderr_contains(
+ "warning: failed to automatically apply fixes suggested by rustc \
+ to crate `bar`\n\
+ \n\
+ after fixes were automatically applied the compiler reported \
+ errors within these files:\n\
+ \n \
+ * src/lib.rs\n\
+ \n\
+ This likely indicates a bug in either rustc or cargo itself,\n\
+ and we would appreciate a bug report! You're likely to see \n\
+ a number of compiler warnings after this message which cargo\n\
+ attempted to fix but failed. If you could open an issue at\n\
+ [..]\n\
+ quoting the full output of this command we'd be very appreciative!\n\
+ Note that you may be able to make some more progress in the near-term\n\
+ fixing code with the `--broken-code` flag\n\
+ \n\
+ The following errors were reported:\n\
+ error: expected one of `!` or `::`, found `rust`\n\
+ ",
+ )
+ .with_stderr_contains("Original diagnostics will follow.")
+ .with_stderr_contains("[WARNING] variable does not need to be mutable")
+ .with_stderr_does_not_contain("[..][FIXED][..]")
+ .run();
+
+ // Make sure the fix which should have been applied was backed out
+ assert!(p.read_file("bar/src/lib.rs").contains("let mut x = 3;"));
+}
+
+#[cargo_test]
+fn fix_path_deps() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { path = 'bar' }
+
+ [workspace]
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ extern crate bar;
+
+ pub fn foo() -> u32 {
+ let mut x = 3;
+ x
+ }
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file(
+ "bar/src/lib.rs",
+ r#"
+ pub fn foo() -> u32 {
+ let mut x = 3;
+ x
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("fix --allow-no-vcs -p foo -p bar")
+ .env("__CARGO_FIX_YOLO", "1")
+ .with_stdout("")
+ .with_stderr_unordered(
+ "\
+[CHECKING] bar v0.1.0 ([..])
+[FIXED] bar/src/lib.rs (1 fix)
+[CHECKING] foo v0.1.0 ([..])
+[FIXED] src/lib.rs (1 fix)
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn do_not_fix_non_relevant_deps() {
+ let p = project()
+ .no_manifest()
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { path = '../bar' }
+
+ [workspace]
+ "#,
+ )
+ .file("foo/src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file(
+ "bar/src/lib.rs",
+ r#"
+ pub fn foo() -> u32 {
+ let mut x = 3;
+ x
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("fix --allow-no-vcs")
+ .env("__CARGO_FIX_YOLO", "1")
+ .cwd("foo")
+ .run();
+
+ assert!(p.read_file("bar/src/lib.rs").contains("mut"));
+}
+
+#[cargo_test]
+fn prepare_for_2018() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ #![allow(unused)]
+
+ mod foo {
+ pub const FOO: &str = "fooo";
+ }
+
+ mod bar {
+ use ::foo::FOO;
+ }
+
+ fn main() {
+ let x = ::foo::FOO;
+ }
+ "#,
+ )
+ .build();
+
+ let stderr = "\
+[CHECKING] foo v0.0.1 ([..])
+[MIGRATING] src/lib.rs from 2015 edition to 2018
+[FIXED] src/lib.rs (2 fixes)
+[FINISHED] [..]
+";
+ p.cargo("fix --edition --allow-no-vcs")
+ .with_stderr(stderr)
+ .with_stdout("")
+ .run();
+
+ println!("{}", p.read_file("src/lib.rs"));
+ assert!(p.read_file("src/lib.rs").contains("use crate::foo::FOO;"));
+ assert!(p
+ .read_file("src/lib.rs")
+ .contains("let x = crate::foo::FOO;"));
+}
+
+#[cargo_test]
+fn local_paths() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ use test::foo;
+
+ mod test {
+ pub fn foo() {}
+ }
+
+ pub fn f() {
+ foo();
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("fix --edition --allow-no-vcs")
+ .with_stderr(
+ "\
+[CHECKING] foo v0.0.1 ([..])
+[MIGRATING] src/lib.rs from 2015 edition to 2018
+[FIXED] src/lib.rs (1 fix)
+[FINISHED] [..]
+",
+ )
+ .with_stdout("")
+ .run();
+
+ println!("{}", p.read_file("src/lib.rs"));
+ assert!(p.read_file("src/lib.rs").contains("use crate::test::foo;"));
+}
+
+#[cargo_test]
+fn upgrade_extern_crate() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = '2018'
+
+ [workspace]
+
+ [dependencies]
+ bar = { path = 'bar' }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ #![warn(rust_2018_idioms)]
+ extern crate bar;
+
+ use bar::bar;
+
+ pub fn foo() {
+ ::bar::bar();
+ bar();
+ }
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ let stderr = "\
+[CHECKING] bar v0.1.0 ([..])
+[CHECKING] foo v0.1.0 ([..])
+[FIXED] src/lib.rs (1 fix)
+[FINISHED] [..]
+";
+ p.cargo("fix --allow-no-vcs")
+ .env("__CARGO_FIX_YOLO", "1")
+ .with_stderr(stderr)
+ .with_stdout("")
+ .run();
+ println!("{}", p.read_file("src/lib.rs"));
+ assert!(!p.read_file("src/lib.rs").contains("extern crate"));
+}
+
+#[cargo_test]
+fn specify_rustflags() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ #![allow(unused)]
+
+ mod foo {
+ pub const FOO: &str = "fooo";
+ }
+
+ fn main() {
+ let x = ::foo::FOO;
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("fix --edition --allow-no-vcs")
+ .env("RUSTFLAGS", "-C linker=cc")
+ .with_stderr(
+ "\
+[CHECKING] foo v0.0.1 ([..])
+[MIGRATING] src/lib.rs from 2015 edition to 2018
+[FIXED] src/lib.rs (1 fix)
+[FINISHED] [..]
+",
+ )
+ .with_stdout("")
+ .run();
+}
+
+#[cargo_test]
+fn no_changes_necessary() {
+ let p = project().file("src/lib.rs", "").build();
+
+ let stderr = "\
+[CHECKING] foo v0.0.1 ([..])
+[FINISHED] [..]
+";
+ p.cargo("fix --allow-no-vcs")
+ .with_stderr(stderr)
+ .with_stdout("")
+ .run();
+}
+
+#[cargo_test]
+fn fixes_extra_mut() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn foo() -> u32 {
+ let mut x = 3;
+ x
+ }
+ "#,
+ )
+ .build();
+
+ let stderr = "\
+[CHECKING] foo v0.0.1 ([..])
+[FIXED] src/lib.rs (1 fix)
+[FINISHED] [..]
+";
+ p.cargo("fix --allow-no-vcs")
+ .env("__CARGO_FIX_YOLO", "1")
+ .with_stderr(stderr)
+ .with_stdout("")
+ .run();
+}
+
+#[cargo_test]
+fn fixes_two_missing_ampersands() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn foo() -> u32 {
+ let mut x = 3;
+ let mut y = 3;
+ x + y
+ }
+ "#,
+ )
+ .build();
+
+ let stderr = "\
+[CHECKING] foo v0.0.1 ([..])
+[FIXED] src/lib.rs (2 fixes)
+[FINISHED] [..]
+";
+ p.cargo("fix --allow-no-vcs")
+ .env("__CARGO_FIX_YOLO", "1")
+ .with_stderr(stderr)
+ .with_stdout("")
+ .run();
+}
+
+#[cargo_test]
+fn tricky() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn foo() -> u32 {
+ let mut x = 3; let mut y = 3;
+ x + y
+ }
+ "#,
+ )
+ .build();
+
+ let stderr = "\
+[CHECKING] foo v0.0.1 ([..])
+[FIXED] src/lib.rs (2 fixes)
+[FINISHED] [..]
+";
+ p.cargo("fix --allow-no-vcs")
+ .env("__CARGO_FIX_YOLO", "1")
+ .with_stderr(stderr)
+ .with_stdout("")
+ .run();
+}
+
+#[cargo_test]
+fn preserve_line_endings() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ "fn add(a: &u32) -> u32 { a + 1 }\r\n\
+ pub fn foo() -> u32 { let mut x = 3; add(&x) }\r\n\
+ ",
+ )
+ .build();
+
+ p.cargo("fix --allow-no-vcs")
+ .env("__CARGO_FIX_YOLO", "1")
+ .run();
+ assert!(p.read_file("src/lib.rs").contains("\r\n"));
+}
+
+#[cargo_test]
+fn fix_deny_warnings() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ "#![deny(warnings)]
+ pub fn foo() { let mut x = 3; drop(x); }
+ ",
+ )
+ .build();
+
+ p.cargo("fix --allow-no-vcs")
+ .env("__CARGO_FIX_YOLO", "1")
+ .run();
+}
+
+#[cargo_test]
+fn fix_deny_warnings_but_not_others() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ "
+ #![deny(unused_mut)]
+
+ pub fn foo() -> u32 {
+ let mut x = 3;
+ x
+ }
+
+ pub fn bar() {
+ #[allow(unused_mut)]
+ let mut _y = 4;
+ }
+ ",
+ )
+ .build();
+
+ p.cargo("fix --allow-no-vcs")
+ .env("__CARGO_FIX_YOLO", "1")
+ .run();
+ assert!(!p.read_file("src/lib.rs").contains("let mut x = 3;"));
+ assert!(p.read_file("src/lib.rs").contains("let mut _y = 4;"));
+}
+
+#[cargo_test]
+fn fix_two_files() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ "
+ pub mod bar;
+
+ pub fn foo() -> u32 {
+ let mut x = 3;
+ x
+ }
+ ",
+ )
+ .file(
+ "src/bar.rs",
+ "
+ pub fn foo() -> u32 {
+ let mut x = 3;
+ x
+ }
+
+ ",
+ )
+ .build();
+
+ p.cargo("fix --allow-no-vcs")
+ .env("__CARGO_FIX_YOLO", "1")
+ .with_stderr_contains("[FIXED] src/bar.rs (1 fix)")
+ .with_stderr_contains("[FIXED] src/lib.rs (1 fix)")
+ .run();
+ assert!(!p.read_file("src/lib.rs").contains("let mut x = 3;"));
+ assert!(!p.read_file("src/bar.rs").contains("let mut x = 3;"));
+}
+
+#[cargo_test]
+fn fixes_missing_ampersand() {
+ let p = project()
+ .file("src/main.rs", "fn main() { let mut x = 3; drop(x); }")
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn foo() { let mut x = 3; drop(x); }
+
+ #[test]
+ pub fn foo2() { let mut x = 3; drop(x); }
+ "#,
+ )
+ .file(
+ "tests/a.rs",
+ r#"
+ #[test]
+ pub fn foo() { let mut x = 3; drop(x); }
+ "#,
+ )
+ .file("examples/foo.rs", "fn main() { let mut x = 3; drop(x); }")
+ .file("build.rs", "fn main() { let mut x = 3; drop(x); }")
+ .build();
+
+ p.cargo("fix --all-targets --allow-no-vcs")
+ .env("__CARGO_FIX_YOLO", "1")
+ .with_stdout("")
+ .with_stderr_contains("[COMPILING] foo v0.0.1 ([..])")
+ .with_stderr_contains("[FIXED] build.rs (1 fix)")
+ // Don't assert number of fixes for this one, as we don't know if we're
+ // fixing it once or twice! We run this all concurrently, and if we
+ // compile (and fix) in `--test` mode first, we get two fixes. Otherwise
+ // we'll fix one non-test thing, and then fix another one later in
+ // test mode.
+ .with_stderr_contains("[FIXED] src/lib.rs[..]")
+ .with_stderr_contains("[FIXED] src/main.rs (1 fix)")
+ .with_stderr_contains("[FIXED] examples/foo.rs (1 fix)")
+ .with_stderr_contains("[FIXED] tests/a.rs (1 fix)")
+ .with_stderr_contains("[FINISHED] [..]")
+ .run();
+ p.cargo("check").run();
+ p.cargo("test").run();
+}
+
+#[cargo_test]
+fn fix_features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [features]
+ bar = []
+
+ [workspace]
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ #[cfg(feature = "bar")]
+ pub fn foo() -> u32 { let mut x = 3; x }
+ "#,
+ )
+ .build();
+
+ p.cargo("fix --allow-no-vcs").run();
+ p.cargo("check").run();
+ p.cargo("fix --features bar --allow-no-vcs").run();
+ p.cargo("check --features bar").run();
+}
+
+#[cargo_test]
+fn shows_warnings() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ "#[deprecated] fn bar() {} pub fn foo() { let _ = bar(); }",
+ )
+ .build();
+
+ p.cargo("fix --allow-no-vcs")
+ .with_stderr_contains("[..]warning: use of deprecated[..]")
+ .run();
+}
+
+#[cargo_test]
+fn warns_if_no_vcs_detected() {
+ let p = project().file("src/lib.rs", "pub fn foo() {}").build();
+
+ p.cargo("fix")
+ .with_status(101)
+ .with_stderr(
+ "error: no VCS found for this package and `cargo fix` can potentially perform \
+ destructive changes; if you'd like to suppress this error pass `--allow-no-vcs`\
+ ",
+ )
+ .run();
+ p.cargo("fix --allow-no-vcs").run();
+}
+
+#[cargo_test]
+fn warns_about_dirty_working_directory() {
+ let p = git::new("foo", |p| p.file("src/lib.rs", "pub fn foo() {}"));
+
+ p.change_file("src/lib.rs", "");
+
+ p.cargo("fix")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: the working directory of this package has uncommitted changes, \
+and `cargo fix` can potentially perform destructive changes; if you'd \
+like to suppress this error pass `--allow-dirty`, `--allow-staged`, or \
+commit the changes to these files:
+
+ * src/lib.rs (dirty)
+
+
+",
+ )
+ .run();
+ p.cargo("fix --allow-dirty").run();
+}
+
+#[cargo_test]
+fn warns_about_staged_working_directory() {
+ let (p, repo) = git::new_repo("foo", |p| p.file("src/lib.rs", "pub fn foo() {}"));
+
+ p.change_file("src/lib.rs", "pub fn bar() {}");
+ git::add(&repo);
+
+ p.cargo("fix")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: the working directory of this package has uncommitted changes, \
+and `cargo fix` can potentially perform destructive changes; if you'd \
+like to suppress this error pass `--allow-dirty`, `--allow-staged`, or \
+commit the changes to these files:
+
+ * src/lib.rs (staged)
+
+
+",
+ )
+ .run();
+ p.cargo("fix --allow-staged").run();
+}
+
+#[cargo_test]
+fn errors_about_untracked_files() {
+ let mut git_project = project().at("foo");
+ git_project = git_project.file("src/lib.rs", "pub fn foo() {}");
+ let p = git_project.build();
+ let _ = init(&p.root());
+
+ p.cargo("fix")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: the working directory of this package has uncommitted changes, \
+and `cargo fix` can potentially perform destructive changes; if you'd \
+like to suppress this error pass `--allow-dirty`, `--allow-staged`, or \
+commit the changes to these files:
+
+ * Cargo.toml (dirty)
+ * src/ (dirty)
+
+
+",
+ )
+ .run();
+ p.cargo("fix --allow-dirty").run();
+}
+
+#[cargo_test]
+fn does_not_warn_about_clean_working_directory() {
+ let p = git::new("foo", |p| p.file("src/lib.rs", "pub fn foo() {}"));
+ p.cargo("fix").run();
+}
+
+#[cargo_test]
+fn does_not_warn_about_dirty_ignored_files() {
+ let p = git::new("foo", |p| {
+ p.file("src/lib.rs", "pub fn foo() {}")
+ .file(".gitignore", "bar\n")
+ });
+
+ p.change_file("bar", "");
+
+ p.cargo("fix").run();
+}
+
+#[cargo_test]
+fn fix_all_targets_by_default() {
+ let p = project()
+ .file("src/lib.rs", "pub fn foo() { let mut x = 3; drop(x); }")
+ .file("tests/foo.rs", "pub fn foo() { let mut x = 3; drop(x); }")
+ .build();
+ p.cargo("fix --allow-no-vcs")
+ .env("__CARGO_FIX_YOLO", "1")
+ .run();
+ assert!(!p.read_file("src/lib.rs").contains("let mut x"));
+ assert!(!p.read_file("tests/foo.rs").contains("let mut x"));
+}
+
+#[cargo_test]
+fn prepare_for_unstable() {
+ // During the period where a new edition is coming up, but not yet stable,
+ // this test will verify that it cannot be migrated to on stable. If there
+ // is no next edition, it does nothing.
+ let next = match Edition::LATEST_UNSTABLE {
+ Some(next) => next,
+ None => {
+ eprintln!("Next edition is currently not available, skipping test.");
+ return;
+ }
+ };
+ let latest_stable = Edition::LATEST_STABLE;
+ let prev = latest_stable.previous().unwrap();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "{}"
+ "#,
+ latest_stable
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ // -j1 to make the error more deterministic (otherwise there can be
+ // multiple errors since they run in parallel).
+ p.cargo("fix --edition --allow-no-vcs -j1")
+ .with_stderr(&format_args!("\
+[CHECKING] foo [..]
+[WARNING] `src/lib.rs` is on the latest edition, but trying to migrate to edition {next}.
+Edition {next} is unstable and not allowed in this release, consider trying the nightly release channel.
+
+If you are trying to migrate from the previous edition ({prev}), the
+process requires following these steps:
+
+1. Start with `edition = \"{prev}\"` in `Cargo.toml`
+2. Run `cargo fix --edition`
+3. Modify `Cargo.toml` to set `edition = \"{latest_stable}\"`
+4. Run `cargo build` or `cargo test` to verify the fixes worked
+
+More details may be found at
+https://doc.rust-lang.org/edition-guide/editions/transitioning-an-existing-project-to-a-new-edition.html
+
+[FINISHED] [..]
+", next=next, latest_stable=latest_stable, prev=prev))
+ .run();
+
+ if !is_nightly() {
+ // The rest of this test is fundamentally always nightly.
+ return;
+ }
+
+ p.cargo("fix --edition --allow-no-vcs")
+ .masquerade_as_nightly_cargo(&["always_nightly"])
+ .with_stderr(&format!(
+ "\
+[CHECKING] foo [..]
+[MIGRATING] src/lib.rs from {latest_stable} edition to {next}
+[FINISHED] [..]
+",
+ latest_stable = latest_stable,
+ next = next,
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn prepare_for_latest_stable() {
+ // This is the stable counterpart of prepare_for_unstable.
+ let latest_stable = Edition::LATEST_STABLE;
+ let previous = latest_stable.previous().unwrap();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = 'foo'
+ version = '0.1.0'
+ edition = '{}'
+ "#,
+ previous
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("fix --edition --allow-no-vcs")
+ .with_stderr(&format!(
+ "\
+[CHECKING] foo [..]
+[MIGRATING] src/lib.rs from {} edition to {}
+[FINISHED] [..]
+",
+ previous, latest_stable
+ ))
+ .run();
+}
+
+#[cargo_test(nightly, reason = "fundamentally always nightly")]
+fn prepare_for_already_on_latest_unstable() {
+ // During the period where a new edition is coming up, but not yet stable,
+ // this test will check what happens if you are already on the latest. If
+ // there is no next edition, it does nothing.
+ let next_edition = match Edition::LATEST_UNSTABLE {
+ Some(next) => next,
+ None => {
+ eprintln!("Next edition is currently not available, skipping test.");
+ return;
+ }
+ };
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ cargo-features = ["edition{}"]
+
+ [package]
+ name = 'foo'
+ version = '0.1.0'
+ edition = '{}'
+ "#,
+ next_edition, next_edition
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("fix --edition --allow-no-vcs")
+ .masquerade_as_nightly_cargo(&["always_nightly"])
+ .with_stderr_contains("[CHECKING] foo [..]")
+ .with_stderr_contains(&format!(
+ "\
+[WARNING] `src/lib.rs` is already on the latest edition ({next_edition}), unable to migrate further
+",
+ next_edition = next_edition
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn prepare_for_already_on_latest_stable() {
+ // Stable counterpart of prepare_for_already_on_latest_unstable.
+ if Edition::LATEST_UNSTABLE.is_some() {
+ eprintln!("This test cannot run while the latest edition is unstable, skipping.");
+ return;
+ }
+ let latest_stable = Edition::LATEST_STABLE;
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = 'foo'
+ version = '0.1.0'
+ edition = '{}'
+ "#,
+ latest_stable
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("fix --edition --allow-no-vcs")
+ .with_stderr_contains("[CHECKING] foo [..]")
+ .with_stderr_contains(&format!(
+ "\
+[WARNING] `src/lib.rs` is already on the latest edition ({latest_stable}), unable to migrate further
+",
+ latest_stable = latest_stable
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn fix_overlapping() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn foo<T>() {}
+ pub struct A;
+
+ pub mod bar {
+ pub fn baz() {
+ ::foo::<::A>();
+ }
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("fix --allow-no-vcs --edition --lib")
+ .with_stderr(
+ "\
+[CHECKING] foo [..]
+[MIGRATING] src/lib.rs from 2015 edition to 2018
+[FIXED] src/lib.rs (2 fixes)
+[FINISHED] dev [..]
+",
+ )
+ .run();
+
+ let contents = p.read_file("src/lib.rs");
+ println!("{}", contents);
+ assert!(contents.contains("crate::foo::<crate::A>()"));
+}
+
+#[cargo_test]
+fn fix_idioms() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = 'foo'
+ version = '0.1.0'
+ edition = '2018'
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ use std::any::Any;
+ pub fn foo() {
+ let _x: Box<Any> = Box::new(3);
+ }
+ "#,
+ )
+ .build();
+
+ let stderr = "\
+[CHECKING] foo [..]
+[FIXED] src/lib.rs (1 fix)
+[FINISHED] [..]
+";
+ p.cargo("fix --edition-idioms --allow-no-vcs")
+ .with_stderr(stderr)
+ .run();
+
+ assert!(p.read_file("src/lib.rs").contains("Box<dyn Any>"));
+}
+
+#[cargo_test]
+fn idioms_2015_ok() {
+ let p = project().file("src/lib.rs", "").build();
+
+ p.cargo("fix --edition-idioms --allow-no-vcs").run();
+}
+
+#[cargo_test]
+fn shows_warnings_on_second_run_without_changes() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ #[deprecated]
+ fn bar() {}
+
+ pub fn foo() {
+ let _ = bar();
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("fix --allow-no-vcs")
+ .with_stderr_contains("[..]warning: use of deprecated[..]")
+ .run();
+
+ p.cargo("fix --allow-no-vcs")
+ .with_stderr_contains("[..]warning: use of deprecated[..]")
+ .run();
+}
+
+#[cargo_test]
+fn shows_warnings_on_second_run_without_changes_on_multiple_targets() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ #[deprecated]
+ fn bar() {}
+
+ pub fn foo() {
+ let _ = bar();
+ }
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ #[deprecated]
+ fn bar() {}
+
+ fn main() {
+ let _ = bar();
+ }
+ "#,
+ )
+ .file(
+ "tests/foo.rs",
+ r#"
+ #[deprecated]
+ fn bar() {}
+
+ #[test]
+ fn foo_test() {
+ let _ = bar();
+ }
+ "#,
+ )
+ .file(
+ "tests/bar.rs",
+ r#"
+ #[deprecated]
+ fn bar() {}
+
+ #[test]
+ fn foo_test() {
+ let _ = bar();
+ }
+ "#,
+ )
+ .file(
+ "examples/fooxample.rs",
+ r#"
+ #[deprecated]
+ fn bar() {}
+
+ fn main() {
+ let _ = bar();
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("fix --allow-no-vcs --all-targets")
+ .with_stderr_contains(" --> examples/fooxample.rs:6:29")
+ .with_stderr_contains(" --> src/lib.rs:6:29")
+ .with_stderr_contains(" --> src/main.rs:6:29")
+ .with_stderr_contains(" --> tests/bar.rs:7:29")
+ .with_stderr_contains(" --> tests/foo.rs:7:29")
+ .run();
+
+ p.cargo("fix --allow-no-vcs --all-targets")
+ .with_stderr_contains(" --> examples/fooxample.rs:6:29")
+ .with_stderr_contains(" --> src/lib.rs:6:29")
+ .with_stderr_contains(" --> src/main.rs:6:29")
+ .with_stderr_contains(" --> tests/bar.rs:7:29")
+ .with_stderr_contains(" --> tests/foo.rs:7:29")
+ .run();
+}
+
+#[cargo_test]
+fn doesnt_rebuild_dependencies() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { path = 'bar' }
+
+ [workspace]
+ "#,
+ )
+ .file("src/lib.rs", "extern crate bar;")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("fix --allow-no-vcs -p foo")
+ .env("__CARGO_FIX_YOLO", "1")
+ .with_stdout("")
+ .with_stderr(
+ "\
+[CHECKING] bar v0.1.0 ([..])
+[CHECKING] foo v0.1.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("fix --allow-no-vcs -p foo")
+ .env("__CARGO_FIX_YOLO", "1")
+ .with_stdout("")
+ .with_stderr(
+ "\
+[CHECKING] foo v0.1.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn does_not_crash_with_rustc_wrapper() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("fix --allow-no-vcs")
+ .env("RUSTC_WRAPPER", tools::echo_wrapper())
+ .run();
+ p.build_dir().rm_rf();
+ p.cargo("fix --allow-no-vcs --verbose")
+ .env("RUSTC_WORKSPACE_WRAPPER", tools::echo_wrapper())
+ .run();
+}
+
+#[cargo_test]
+fn uses_workspace_wrapper_and_primary_wrapper_override() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("fix --allow-no-vcs --verbose")
+ .env("RUSTC_WORKSPACE_WRAPPER", tools::echo_wrapper())
+ .with_stderr_contains("WRAPPER CALLED: rustc src/lib.rs --crate-name foo [..]")
+ .run();
+}
+
+#[cargo_test]
+fn only_warn_for_relevant_crates() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ a = { path = 'a' }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+ "#,
+ )
+ .file(
+ "a/src/lib.rs",
+ "
+ pub fn foo() {}
+ pub mod bar {
+ use foo;
+ pub fn baz() { foo() }
+ }
+ ",
+ )
+ .build();
+
+ p.cargo("fix --allow-no-vcs --edition")
+ .with_stderr(
+ "\
+[CHECKING] a v0.1.0 ([..])
+[CHECKING] foo v0.1.0 ([..])
+[MIGRATING] src/lib.rs from 2015 edition to 2018
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn fix_to_broken_code() {
+ let p = project()
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = 'foo'
+ version = '0.1.0'
+ [workspace]
+ "#,
+ )
+ .file(
+ "foo/src/main.rs",
+ r#"
+ use std::env;
+ use std::fs;
+ use std::io::Write;
+ use std::path::{Path, PathBuf};
+ use std::process::{self, Command};
+
+ fn main() {
+ // Ignore calls to things like --print=file-names and compiling build.rs.
+ // Also compatible for rustc invocations with `@path` argfile.
+ let is_lib_rs = env::args_os()
+ .map(PathBuf::from)
+ .flat_map(|p| if let Some(p) = p.to_str().unwrap_or_default().strip_prefix("@") {
+ fs::read_to_string(p).unwrap().lines().map(PathBuf::from).collect()
+ } else {
+ vec![p]
+ })
+ .any(|l| l == Path::new("src/lib.rs"));
+ if is_lib_rs {
+ let path = PathBuf::from(env::var_os("OUT_DIR").unwrap());
+ let path = path.join("foo");
+ if path.exists() {
+ panic!()
+ } else {
+ fs::File::create(&path).unwrap();
+ }
+ }
+
+ let status = Command::new("rustc")
+ .args(env::args().skip(1))
+ .status()
+ .expect("failed to run rustc");
+ process::exit(status.code().unwrap_or(2));
+ }
+ "#,
+ )
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = 'bar'
+ version = '0.1.0'
+ [workspace]
+ "#,
+ )
+ .file("bar/build.rs", "fn main() {}")
+ .file("bar/src/lib.rs", "pub fn foo() { let mut x = 3; drop(x); }")
+ .build();
+
+ // Build our rustc shim
+ p.cargo("build").cwd("foo").run();
+
+ // Attempt to fix code, but our shim will always fail the second compile
+ p.cargo("fix --allow-no-vcs --broken-code")
+ .cwd("bar")
+ .env("RUSTC", p.root().join("foo/target/debug/foo"))
+ .with_status(101)
+ .with_stderr_contains("[WARNING] failed to automatically apply fixes [..]")
+ .run();
+
+ assert_eq!(
+ p.read_file("bar/src/lib.rs"),
+ "pub fn foo() { let x = 3; drop(x); }"
+ );
+}
+
+#[cargo_test]
+fn fix_with_common() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(
+ "tests/t1.rs",
+ "mod common; #[test] fn t1() { common::try(); }",
+ )
+ .file(
+ "tests/t2.rs",
+ "mod common; #[test] fn t2() { common::try(); }",
+ )
+ .file("tests/common/mod.rs", "pub fn try() {}")
+ .build();
+
+ p.cargo("fix --edition --allow-no-vcs").run();
+
+ assert_eq!(p.read_file("tests/common/mod.rs"), "pub fn r#try() {}");
+}
+
+#[cargo_test]
+fn fix_in_existing_repo_weird_ignore() {
+ // Check that ignore doesn't ignore the repo itself.
+ let p = git::new("foo", |project| {
+ project
+ .file("src/lib.rs", "")
+ .file(".gitignore", "foo\ninner\nCargo.lock\ntarget\n")
+ .file("inner/file", "")
+ });
+
+ p.cargo("fix").run();
+ // This is questionable about whether it is the right behavior. It should
+ // probably be checking if any source file for the current project is
+ // ignored.
+ p.cargo("fix")
+ .cwd("inner")
+ .with_stderr_contains("[ERROR] no VCS found[..]")
+ .with_status(101)
+ .run();
+ p.cargo("fix").cwd("src").run();
+}
+
+#[cargo_test]
+fn fix_color_message() {
+ // Check that color appears in diagnostics.
+ let p = project()
+ .file("src/lib.rs", "std::compile_error!{\"color test\"}")
+ .build();
+
+ p.cargo("fix --allow-no-vcs --color=always")
+ .with_stderr_contains("[..]\x1b[[..]")
+ .with_status(101)
+ .run();
+
+ p.cargo("fix --allow-no-vcs --color=never")
+ .with_stderr_contains("error: color test")
+ .with_stderr_does_not_contain("[..]\x1b[[..]")
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn edition_v2_resolver_report() {
+ // Show a report if the V2 resolver shows differences.
+ Package::new("common", "1.0.0")
+ .feature("f1", &[])
+ .feature("dev-feat", &[])
+ .add_dep(Dependency::new("opt_dep", "1.0").optional(true))
+ .publish();
+ Package::new("opt_dep", "1.0.0").publish();
+
+ Package::new("bar", "1.0.0")
+ .add_dep(
+ Dependency::new("common", "1.0")
+ .target("cfg(whatever)")
+ .enable_features(&["f1"]),
+ )
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [dependencies]
+ common = "1.0"
+ bar = "1.0"
+
+ [build-dependencies]
+ common = { version = "1.0", features = ["opt_dep"] }
+
+ [dev-dependencies]
+ common = { version="1.0", features=["dev-feat"] }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("fix --edition --allow-no-vcs")
+ .with_stderr_unordered("\
+[UPDATING] [..]
+[DOWNLOADING] crates ...
+[DOWNLOADED] common v1.0.0 [..]
+[DOWNLOADED] bar v1.0.0 [..]
+[DOWNLOADED] opt_dep v1.0.0 [..]
+note: Switching to Edition 2021 will enable the use of the version 2 feature resolver in Cargo.
+This may cause some dependencies to be built with fewer features enabled than previously.
+More information about the resolver changes may be found at https://doc.rust-lang.org/nightly/edition-guide/rust-2021/default-cargo-resolver.html
+When building the following dependencies, the given features will no longer be used:
+
+ common v1.0.0 removed features: dev-feat, f1, opt_dep
+ common v1.0.0 (as host dependency) removed features: dev-feat, f1
+
+The following differences only apply when building with dev-dependencies:
+
+ common v1.0.0 removed features: f1, opt_dep
+
+[CHECKING] opt_dep v1.0.0
+[CHECKING] common v1.0.0
+[CHECKING] bar v1.0.0
+[CHECKING] foo v0.1.0 [..]
+[MIGRATING] src/lib.rs from 2018 edition to 2021
+[FINISHED] [..]
+")
+ .run();
+}
+
+#[cargo_test]
+fn rustfix_handles_multi_spans() {
+ // Checks that rustfix handles a single diagnostic with multiple
+ // suggestion spans (non_fmt_panic in this case).
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn foo() {
+ panic!(format!("hey"));
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("fix --allow-no-vcs").run();
+ assert!(p.read_file("src/lib.rs").contains(r#"panic!("hey");"#));
+}
+
+#[cargo_test]
+fn fix_edition_2021() {
+ // Can migrate 2021, even when lints are allowed.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ #![allow(ellipsis_inclusive_range_patterns)]
+
+ pub fn f() -> bool {
+ let x = 123;
+ match x {
+ 0...100 => true,
+ _ => false,
+ }
+ }
+ "#,
+ )
+ .build();
+ p.cargo("fix --edition --allow-no-vcs")
+ .with_stderr(
+ "\
+[CHECKING] foo v0.1.0 [..]
+[MIGRATING] src/lib.rs from 2018 edition to 2021
+[FIXED] src/lib.rs (1 fix)
+[FINISHED] [..]
+",
+ )
+ .run();
+ assert!(p.read_file("src/lib.rs").contains(r#"0..=100 => true,"#));
+}
+
+#[cargo_test]
+fn fix_shared_cross_workspace() {
+ // Fixing a file that is shared between multiple packages in the same workspace.
+ // Make sure two processes don't try to fix the same file at the same time.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["foo", "bar"]
+ "#,
+ )
+ .file("foo/Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("foo/src/lib.rs", "pub mod shared;")
+ // This will fix both unused and bare trait.
+ .file("foo/src/shared.rs", "pub fn fixme(x: Box<&Fn() -> ()>) {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file(
+ "bar/src/lib.rs",
+ r#"
+ #[path="../../foo/src/shared.rs"]
+ pub mod shared;
+ "#,
+ )
+ .build();
+
+ // The output here can be either of these two, depending on who runs first:
+ // [FIXED] bar/src/../../foo/src/shared.rs (2 fixes)
+ // [FIXED] foo/src/shared.rs (2 fixes)
+ p.cargo("fix --allow-no-vcs")
+ .with_stderr_unordered(
+ "\
+[CHECKING] foo v0.1.0 [..]
+[CHECKING] bar v0.1.0 [..]
+[FIXED] [..]foo/src/shared.rs (2 fixes)
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ assert_match_exact(
+ "pub fn fixme(_x: Box<&dyn Fn() -> ()>) {}",
+ &p.read_file("foo/src/shared.rs"),
+ );
+}
+
+#[cargo_test]
+fn abnormal_exit() {
+ // rustc fails unexpectedly after applying fixes, should show some error information.
+ //
+ // This works with a proc-macro that runs three times:
+ // - First run (collect diagnostics pass): writes a file, exits normally.
+ // - Second run (verify diagnostics work): it detects the presence of the
+ // file, removes the file, and aborts the process.
+ // - Third run (collecting messages to display): file not found, exits normally.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ pm = {path="pm"}
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn f() {
+ let mut x = 1;
+ pm::crashme!();
+ }
+ "#,
+ )
+ .file(
+ "pm/Cargo.toml",
+ r#"
+ [package]
+ name = "pm"
+ version = "0.1.0"
+ edition = "2018"
+
+ [lib]
+ proc-macro = true
+ "#,
+ )
+ .file(
+ "pm/src/lib.rs",
+ r#"
+ use proc_macro::TokenStream;
+ #[proc_macro]
+ pub fn crashme(_input: TokenStream) -> TokenStream {
+ // Use a file to succeed on the first pass, and fail on the second.
+ let p = std::env::var_os("ONCE_PATH").unwrap();
+ let check_path = std::path::Path::new(&p);
+ if check_path.exists() {
+ eprintln!("I'm not a diagnostic.");
+ std::fs::remove_file(check_path).unwrap();
+ std::process::abort();
+ } else {
+ std::fs::write(check_path, "").unwrap();
+ "".parse().unwrap()
+ }
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("fix --lib --allow-no-vcs")
+ .env(
+ "ONCE_PATH",
+ paths::root().join("proc-macro-run-once").to_str().unwrap(),
+ )
+ .with_stderr_contains(
+ "[WARNING] failed to automatically apply fixes suggested by rustc to crate `foo`",
+ )
+ .with_stderr_contains("I'm not a diagnostic.")
+ // "signal: 6, SIGABRT: process abort signal" on some platforms
+ .with_stderr_contains("rustc exited abnormally: [..]")
+ .with_stderr_contains("Original diagnostics will follow.")
+ .run();
+}
+
+#[cargo_test]
+fn fix_with_run_cargo_in_proc_macros() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [lib]
+ proc-macro = true
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ use proc_macro::*;
+
+ #[proc_macro]
+ pub fn foo(_input: TokenStream) -> TokenStream {
+ let output = std::process::Command::new(env!("CARGO"))
+ .args(&["metadata", "--format-version=1"])
+ .output()
+ .unwrap();
+ eprintln!("{}", std::str::from_utf8(&output.stderr).unwrap());
+ println!("{}", std::str::from_utf8(&output.stdout).unwrap());
+ "".parse().unwrap()
+ }
+ "#,
+ )
+ .file(
+ "src/bin/main.rs",
+ r#"
+ use foo::foo;
+
+ fn main() {
+ foo!("bar")
+ }
+ "#,
+ )
+ .build();
+ p.cargo("fix --allow-no-vcs")
+ .with_stderr_does_not_contain("error: could not find .rs file in rustc args")
+ .run();
+}
+
+#[cargo_test]
+fn non_edition_lint_migration() {
+ // Migrating to a new edition where a non-edition lint causes problems.
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file(
+ "src/lib.rs",
+ r#"
+ // This is only used in a test.
+ // To be correct, this should be gated on #[cfg(test)], but
+ // sometimes people don't do that. If the unused_imports
+ // lint removes this, then the unittest will fail to compile.
+ use std::str::from_utf8;
+
+ pub mod foo {
+ pub const FOO: &[u8] = &[102, 111, 111];
+ }
+
+ #[test]
+ fn example() {
+ assert_eq!(
+ from_utf8(::foo::FOO), Ok("foo")
+ );
+ }
+ "#,
+ )
+ .build();
+ // Check that it complains about an unused import.
+ p.cargo("check --lib")
+ .with_stderr_contains("[..]unused_imports[..]")
+ .with_stderr_contains("[..]std::str::from_utf8[..]")
+ .run();
+ p.cargo("fix --edition --allow-no-vcs").run();
+ let contents = p.read_file("src/lib.rs");
+ // Check it does not remove the "unused" import.
+ assert!(contents.contains("use std::str::from_utf8;"));
+ // Check that it made the edition migration.
+ assert!(contents.contains("from_utf8(crate::foo::FOO)"));
+}
+
+// For rust-lang/cargo#9857
+#[cargo_test]
+fn fix_in_dependency() {
+ Package::new("bar", "1.0.0")
+ .file(
+ "src/lib.rs",
+ r#"
+ #[macro_export]
+ macro_rules! m {
+ ($i:tt) => {
+ let $i = 1;
+ };
+ }
+ "#,
+ )
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "1.0"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn foo() {
+ bar::m!(abc);
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("fix --allow-no-vcs")
+ .with_stderr_does_not_contain("[FIXED] [..]")
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/freshness.rs b/src/tools/cargo/tests/testsuite/freshness.rs
new file mode 100644
index 000000000..86b186af8
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/freshness.rs
@@ -0,0 +1,2816 @@
+//! Tests for fingerprinting (rebuild detection).
+
+use filetime::FileTime;
+use std::fs::{self, OpenOptions};
+use std::io;
+use std::io::prelude::*;
+use std::net::TcpListener;
+use std::path::{Path, PathBuf};
+use std::process::Stdio;
+use std::thread;
+use std::time::SystemTime;
+
+use super::death;
+use cargo_test_support::paths::{self, CargoPathExt};
+use cargo_test_support::registry::Package;
+use cargo_test_support::{
+ basic_manifest, is_coarse_mtime, project, rustc_host, rustc_host_env, sleep_ms,
+};
+
+#[cargo_test]
+fn modifying_and_moving() {
+ let p = project()
+ .file("src/main.rs", "mod a; fn main() {}")
+ .file("src/a.rs", "")
+ .build();
+
+ p.cargo("build")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("build").with_stdout("").run();
+ p.root().move_into_the_past();
+ p.root().join("target").move_into_the_past();
+
+ p.change_file("src/a.rs", "#[allow(unused)]fn main() {}");
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[DIRTY] foo v0.0.1 ([CWD]): the file `src/a.rs` has changed ([..])
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name foo [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ fs::rename(&p.root().join("src/a.rs"), &p.root().join("src/b.rs")).unwrap();
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr_contains("[..]file not found[..]")
+ .run();
+}
+
+#[cargo_test]
+fn modify_only_some_files() {
+ let p = project()
+ .file("src/lib.rs", "mod a;")
+ .file("src/a.rs", "")
+ .file("src/main.rs", "mod b; fn main() {}")
+ .file("src/b.rs", "")
+ .file("tests/test.rs", "")
+ .build();
+
+ p.cargo("build")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ p.cargo("test").run();
+ sleep_ms(1000);
+
+ assert!(p.bin("foo").is_file());
+
+ let lib = p.root().join("src/lib.rs");
+ p.change_file("src/lib.rs", "invalid rust code");
+ p.change_file("src/b.rs", "#[allow(unused)]fn foo() {}");
+ lib.move_into_the_past();
+
+ // Make sure the binary is rebuilt, not the lib
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[DIRTY] foo v0.0.1 ([CWD]): the file `src/b.rs` has changed ([..])
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name foo [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ assert!(p.bin("foo").is_file());
+}
+
+#[cargo_test]
+fn rebuild_sub_package_then_while_package() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.1"
+
+ [dependencies.a]
+ path = "a"
+ [dependencies.b]
+ path = "b"
+ "#,
+ )
+ .file("src/lib.rs", "extern crate a; extern crate b;")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ authors = []
+ version = "0.0.1"
+ [dependencies.b]
+ path = "../b"
+ "#,
+ )
+ .file("a/src/lib.rs", "extern crate b;")
+ .file("b/Cargo.toml", &basic_manifest("b", "0.0.1"))
+ .file("b/src/lib.rs", "")
+ .build();
+
+ p.cargo("build")
+ .with_stderr(
+ "\
+[COMPILING] b [..]
+[COMPILING] a [..]
+[COMPILING] foo [..]
+[FINISHED] dev [..]
+",
+ )
+ .run();
+
+ if is_coarse_mtime() {
+ sleep_ms(1000);
+ }
+ p.change_file("b/src/lib.rs", "pub fn b() {}");
+
+ p.cargo("build -pb -v")
+ .with_stderr(
+ "\
+[DIRTY] b v0.0.1 ([..]): the file `b/src/lib.rs` has changed ([..])
+[COMPILING] b [..]
+[RUNNING] `rustc --crate-name b [..]
+[FINISHED] dev [..]
+",
+ )
+ .run();
+
+ p.change_file(
+ "src/lib.rs",
+ "extern crate a; extern crate b; pub fn toplevel() {}",
+ );
+
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[FRESH] b [..]
+[DIRTY] a [..]: the dependency b was rebuilt ([..])
+[COMPILING] a [..]
+[RUNNING] `rustc --crate-name a [..]
+[DIRTY] foo [..]: the dependency b was rebuilt ([..])
+[COMPILING] foo [..]
+[RUNNING] `rustc --crate-name foo [..]
+[FINISHED] dev [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn changing_lib_features_caches_targets() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.1"
+
+ [features]
+ foo = []
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build")
+ .with_stderr(
+ "\
+[..]Compiling foo v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("build --features foo")
+ .with_stderr(
+ "\
+[..]Compiling foo v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ /* Targets should be cached from the first build */
+
+ p.cargo("build")
+ .with_stderr("[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]")
+ .run();
+
+ p.cargo("build").with_stdout("").run();
+
+ p.cargo("build --features foo")
+ .with_stderr("[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]")
+ .run();
+}
+
+#[cargo_test]
+fn changing_profiles_caches_targets() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.1"
+
+ [profile.dev]
+ panic = "abort"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build")
+ .with_stderr(
+ "\
+[..]Compiling foo v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("test")
+ .with_stderr(
+ "\
+[..]Compiling foo v0.0.1 ([..])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target[..]debug[..]deps[..]foo-[..][EXE])
+[DOCTEST] foo
+",
+ )
+ .run();
+
+ /* Targets should be cached from the first build */
+
+ p.cargo("build")
+ .with_stderr("[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]")
+ .run();
+
+ p.cargo("test foo")
+ .with_stderr(
+ "\
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target[..]debug[..]deps[..]foo-[..][EXE])
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn changing_bin_paths_common_target_features_caches_targets() {
+ // Make sure dep_cache crate is built once per feature
+ let p = project()
+ .no_manifest()
+ .file(
+ ".cargo/config",
+ r#"
+ [build]
+ target-dir = "./target"
+ "#,
+ )
+ .file(
+ "dep_crate/Cargo.toml",
+ r#"
+ [package]
+ name = "dep_crate"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ ftest = []
+ "#,
+ )
+ .file(
+ "dep_crate/src/lib.rs",
+ r#"
+ #[cfg(feature = "ftest")]
+ pub fn yo() {
+ println!("ftest on")
+ }
+ #[cfg(not(feature = "ftest"))]
+ pub fn yo() {
+ println!("ftest off")
+ }
+ "#,
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ dep_crate = {path = "../dep_crate", features = []}
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .file(
+ "a/src/main.rs",
+ r#"
+ extern crate dep_crate;
+ use dep_crate::yo;
+ fn main() {
+ yo();
+ }
+ "#,
+ )
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ dep_crate = {path = "../dep_crate", features = ["ftest"]}
+ "#,
+ )
+ .file("b/src/lib.rs", "")
+ .file(
+ "b/src/main.rs",
+ r#"
+ extern crate dep_crate;
+ use dep_crate::yo;
+ fn main() {
+ yo();
+ }
+ "#,
+ )
+ .build();
+
+ /* Build and rebuild a/. Ensure dep_crate only builds once */
+ p.cargo("run")
+ .cwd("a")
+ .with_stdout("ftest off")
+ .with_stderr(
+ "\
+[..]Compiling dep_crate v0.0.1 ([..])
+[..]Compiling a v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `[..]target/debug/a[EXE]`
+",
+ )
+ .run();
+ p.cargo("clean -p a").cwd("a").run();
+ p.cargo("run")
+ .cwd("a")
+ .with_stdout("ftest off")
+ .with_stderr(
+ "\
+[..]Compiling a v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `[..]target/debug/a[EXE]`
+",
+ )
+ .run();
+
+ /* Build and rebuild b/. Ensure dep_crate only builds once */
+ p.cargo("run")
+ .cwd("b")
+ .with_stdout("ftest on")
+ .with_stderr(
+ "\
+[..]Compiling dep_crate v0.0.1 ([..])
+[..]Compiling b v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `[..]target/debug/b[EXE]`
+",
+ )
+ .run();
+ p.cargo("clean -p b").cwd("b").run();
+ p.cargo("run")
+ .cwd("b")
+ .with_stdout("ftest on")
+ .with_stderr(
+ "\
+[..]Compiling b v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `[..]target/debug/b[EXE]`
+",
+ )
+ .run();
+
+ /* Build a/ package again. If we cache different feature dep builds correctly,
+ * this should not cause a rebuild of dep_crate */
+ p.cargo("clean -p a").cwd("a").run();
+ p.cargo("run")
+ .cwd("a")
+ .with_stdout("ftest off")
+ .with_stderr(
+ "\
+[..]Compiling a v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `[..]target/debug/a[EXE]`
+",
+ )
+ .run();
+
+ /* Build b/ package again. If we cache different feature dep builds correctly,
+ * this should not cause a rebuild */
+ p.cargo("clean -p b").cwd("b").run();
+ p.cargo("run")
+ .cwd("b")
+ .with_stdout("ftest on")
+ .with_stderr(
+ "\
+[..]Compiling b v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `[..]target/debug/b[EXE]`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn changing_bin_features_caches_targets() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.1"
+
+ [features]
+ foo = []
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ let msg = if cfg!(feature = "foo") { "feature on" } else { "feature off" };
+ println!("{}", msg);
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ p.rename_run("foo", "off1").with_stdout("feature off").run();
+
+ p.cargo("build --features foo")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ p.rename_run("foo", "on1").with_stdout("feature on").run();
+
+ /* Targets should be cached from the first build */
+
+ let mut e = p.cargo("build -v");
+
+ // MSVC does not include hash in binary filename, so it gets recompiled.
+ if cfg!(target_env = "msvc") {
+ e.with_stderr(
+ "\
+[DIRTY] foo v0.0.1 ([..]): the list of features changed
+[COMPILING] foo[..]
+[RUNNING] `rustc [..]
+[FINISHED] dev[..]",
+ );
+ } else {
+ e.with_stderr("[FRESH] foo v0.0.1 ([..])\n[FINISHED] dev[..]");
+ }
+ e.run();
+ p.rename_run("foo", "off2").with_stdout("feature off").run();
+
+ let mut e = p.cargo("build --features foo -v");
+ if cfg!(target_env = "msvc") {
+ e.with_stderr(
+ "\
+[DIRTY] foo v0.0.1 ([..]): the list of features changed
+[COMPILING] foo[..]
+[RUNNING] `rustc [..]
+[FINISHED] dev[..]",
+ );
+ } else {
+ e.with_stderr(
+ "\
+[FRESH] foo v0.0.1 ([..])
+[FINISHED] dev[..]",
+ );
+ }
+ e.run();
+ p.rename_run("foo", "on2").with_stdout("feature on").run();
+}
+
+#[cargo_test]
+fn rebuild_tests_if_lib_changes() {
+ let p = project()
+ .file("src/lib.rs", "pub fn foo() {}")
+ .file(
+ "tests/foo.rs",
+ r#"
+ extern crate foo;
+ #[test]
+ fn test() { foo::foo(); }
+ "#,
+ )
+ .build();
+
+ p.cargo("build").run();
+ p.cargo("test").run();
+
+ sleep_ms(1000);
+ p.change_file("src/lib.rs", "");
+
+ p.cargo("build -v").run();
+ p.cargo("test -v")
+ .with_status(101)
+ .with_stderr_contains("[..]cannot find function `foo`[..]")
+ .run();
+}
+
+#[cargo_test]
+fn no_rebuild_transitive_target_deps() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ a = { path = "a" }
+ [dev-dependencies]
+ b = { path = "b" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("tests/foo.rs", "")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+
+ [target.foo.dependencies]
+ c = { path = "../c" }
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ c = { path = "../c" }
+ "#,
+ )
+ .file("b/src/lib.rs", "")
+ .file("c/Cargo.toml", &basic_manifest("c", "0.0.1"))
+ .file("c/src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+ p.cargo("test --no-run")
+ .with_stderr(
+ "\
+[COMPILING] c v0.0.1 ([..])
+[COMPILING] b v0.0.1 ([..])
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[EXECUTABLE] unittests src/lib.rs (target/debug/deps/foo-[..][EXE])
+[EXECUTABLE] tests/foo.rs (target/debug/deps/foo-[..][EXE])
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rerun_if_changed_in_dep() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ a = { path = "a" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file(
+ "a/build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rerun-if-changed=build.rs");
+ }
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+ p.cargo("build").with_stdout("").run();
+}
+
+#[cargo_test]
+fn same_build_dir_cached_packages() {
+ let p = project()
+ .no_manifest()
+ .file(
+ "a1/Cargo.toml",
+ r#"
+ [package]
+ name = "a1"
+ version = "0.0.1"
+ authors = []
+ [dependencies]
+ b = { path = "../b" }
+ "#,
+ )
+ .file("a1/src/lib.rs", "")
+ .file(
+ "a2/Cargo.toml",
+ r#"
+ [package]
+ name = "a2"
+ version = "0.0.1"
+ authors = []
+ [dependencies]
+ b = { path = "../b" }
+ "#,
+ )
+ .file("a2/src/lib.rs", "")
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.0.1"
+ authors = []
+ [dependencies]
+ c = { path = "../c" }
+ "#,
+ )
+ .file("b/src/lib.rs", "")
+ .file(
+ "c/Cargo.toml",
+ r#"
+ [package]
+ name = "c"
+ version = "0.0.1"
+ authors = []
+ [dependencies]
+ d = { path = "../d" }
+ "#,
+ )
+ .file("c/src/lib.rs", "")
+ .file("d/Cargo.toml", &basic_manifest("d", "0.0.1"))
+ .file("d/src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ r#"
+ [build]
+ target-dir = "./target"
+ "#,
+ )
+ .build();
+
+ p.cargo("build")
+ .cwd("a1")
+ .with_stderr(&format!(
+ "\
+[COMPILING] d v0.0.1 ({dir}/d)
+[COMPILING] c v0.0.1 ({dir}/c)
+[COMPILING] b v0.0.1 ({dir}/b)
+[COMPILING] a1 v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ dir = p.url().to_file_path().unwrap().to_str().unwrap()
+ ))
+ .run();
+ p.cargo("build")
+ .cwd("a2")
+ .with_stderr(
+ "\
+[COMPILING] a2 v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn no_rebuild_if_build_artifacts_move_backwards_in_time() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ a = { path = "a" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("a/Cargo.toml", &basic_manifest("a", "0.0.1"))
+ .file("a/src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+
+ p.root().move_into_the_past();
+
+ p.cargo("build")
+ .with_stdout("")
+ .with_stderr("[FINISHED] [..]")
+ .run();
+}
+
+#[cargo_test]
+fn rebuild_if_build_artifacts_move_forward_in_time() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ a = { path = "a" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("a/Cargo.toml", &basic_manifest("a", "0.0.1"))
+ .file("a/src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+
+ p.root().move_into_the_future();
+
+ p.cargo("build")
+ .env("CARGO_LOG", "")
+ .with_stdout("")
+ .with_stderr(
+ "\
+[COMPILING] a v0.0.1 ([..])
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rebuild_if_environment_changes() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ description = "old desc"
+ version = "0.0.1"
+ authors = []
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ println!("{}", env!("CARGO_PKG_DESCRIPTION"));
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("run")
+ .with_stdout("old desc")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `target/debug/foo[EXE]`
+",
+ )
+ .run();
+
+ p.change_file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ description = "new desc"
+ version = "0.0.1"
+ authors = []
+ "#,
+ );
+
+ p.cargo("run -v")
+ .with_stdout("new desc")
+ .with_stderr(
+ "\
+[DIRTY] foo v0.0.1 ([CWD]): the metadata changed
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `target/debug/foo[EXE]`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn no_rebuild_when_rename_dir() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [workspace]
+
+ [dependencies]
+ foo = { path = "foo" }
+ "#,
+ )
+ .file("src/_unused.rs", "")
+ .file("build.rs", "fn main() {}")
+ .file("foo/Cargo.toml", &basic_manifest("foo", "0.0.1"))
+ .file("foo/src/lib.rs", "")
+ .file("foo/build.rs", "fn main() {}")
+ .build();
+
+ // make sure the most recently modified file is `src/lib.rs`, not
+ // `Cargo.toml`, to expose a historical bug where we forgot to strip the
+ // `Cargo.toml` path from looking for the package root.
+ cargo_test_support::sleep_ms(100);
+ fs::write(p.root().join("src/lib.rs"), "").unwrap();
+
+ p.cargo("build").run();
+ let mut new = p.root();
+ new.pop();
+ new.push("bar");
+ fs::rename(p.root(), &new).unwrap();
+
+ p.cargo("build")
+ .cwd(&new)
+ .with_stderr("[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]")
+ .run();
+}
+
+#[cargo_test]
+fn unused_optional_dep() {
+ Package::new("registry1", "0.1.0").publish();
+ Package::new("registry2", "0.1.0").publish();
+ Package::new("registry3", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "p"
+ authors = []
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { path = "bar" }
+ baz = { path = "baz" }
+ registry1 = "*"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.1"
+ authors = []
+
+ [dev-dependencies]
+ registry2 = "*"
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .file(
+ "baz/Cargo.toml",
+ r#"
+ [package]
+ name = "baz"
+ version = "0.1.1"
+ authors = []
+
+ [dependencies]
+ registry3 = { version = "*", optional = true }
+ "#,
+ )
+ .file("baz/src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+ p.cargo("build").with_stderr("[FINISHED] [..]").run();
+}
+
+#[cargo_test]
+fn path_dev_dep_registry_updates() {
+ Package::new("registry1", "0.1.0").publish();
+ Package::new("registry2", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "p"
+ authors = []
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { path = "bar" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.1"
+ authors = []
+
+ [dependencies]
+ registry1 = "*"
+
+ [dev-dependencies]
+ baz = { path = "../baz"}
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .file(
+ "baz/Cargo.toml",
+ r#"
+ [package]
+ name = "baz"
+ version = "0.1.1"
+ authors = []
+
+ [dependencies]
+ registry2 = "*"
+ "#,
+ )
+ .file("baz/src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+ p.cargo("build").with_stderr("[FINISHED] [..]").run();
+}
+
+#[cargo_test]
+fn change_panic_mode() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ['bar', 'baz']
+ [profile.dev]
+ panic = 'abort'
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.1"))
+ .file("bar/src/lib.rs", "")
+ .file(
+ "baz/Cargo.toml",
+ r#"
+ [package]
+ name = "baz"
+ version = "0.1.1"
+ authors = []
+
+ [lib]
+ proc-macro = true
+
+ [dependencies]
+ bar = { path = '../bar' }
+ "#,
+ )
+ .file("baz/src/lib.rs", "extern crate bar;")
+ .build();
+
+ p.cargo("build -p bar").run();
+ p.cargo("build -p baz").run();
+}
+
+#[cargo_test]
+fn dont_rebuild_based_on_plugins() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.1"
+
+ [workspace]
+ members = ['baz']
+
+ [dependencies]
+ proc-macro-thing = { path = 'proc-macro-thing' }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "proc-macro-thing/Cargo.toml",
+ r#"
+ [package]
+ name = "proc-macro-thing"
+ version = "0.1.1"
+
+ [lib]
+ proc-macro = true
+
+ [dependencies]
+ qux = { path = '../qux' }
+ "#,
+ )
+ .file("proc-macro-thing/src/lib.rs", "")
+ .file(
+ "baz/Cargo.toml",
+ r#"
+ [package]
+ name = "baz"
+ version = "0.1.1"
+
+ [dependencies]
+ qux = { path = '../qux' }
+ "#,
+ )
+ .file("baz/src/main.rs", "fn main() {}")
+ .file("qux/Cargo.toml", &basic_manifest("qux", "0.1.1"))
+ .file("qux/src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+ p.cargo("build -p baz").run();
+ p.cargo("build").with_stderr("[FINISHED] [..]\n").run();
+ p.cargo("build -p bar")
+ .with_stderr("[FINISHED] [..]\n")
+ .run();
+}
+
+#[cargo_test]
+fn reuse_workspace_lib() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.1"
+
+ [workspace]
+
+ [dependencies]
+ baz = { path = 'baz' }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.1"))
+ .file("baz/src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+ p.cargo("test -p baz -v --no-run")
+ .with_stderr(
+ "\
+[COMPILING] baz v0.1.1 ([..])
+[RUNNING] `rustc[..] --test [..]`
+[FINISHED] [..]
+[EXECUTABLE] `[..]/target/debug/deps/baz-[..][EXE]`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn reuse_shared_build_dep() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies]
+ shared = {path = "shared"}
+
+ [workspace]
+ members = ["shared", "bar"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("shared/Cargo.toml", &basic_manifest("shared", "0.0.1"))
+ .file("shared/src/lib.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+
+ [build-dependencies]
+ shared = { path = "../shared" }
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .file("bar/build.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build --workspace").run();
+ // This should not recompile!
+ p.cargo("build -p foo -v")
+ .with_stderr(
+ "\
+[FRESH] shared [..]
+[FRESH] foo [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn changing_rustflags_is_cached() {
+ let p = project().file("src/lib.rs", "").build();
+
+ // This isn't ever cached, we always have to recompile
+ p.cargo("build")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ )
+ .run();
+ p.cargo("build -v")
+ .env("RUSTFLAGS", "-C linker=cc")
+ .with_stderr(
+ "\
+[DIRTY] foo v0.0.1 ([..]): the rustflags changed
+[COMPILING] foo v0.0.1 ([..])
+[RUNNING] `rustc [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ )
+ .run();
+
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[DIRTY] foo v0.0.1 ([..]): the rustflags changed
+[COMPILING] foo v0.0.1 ([..])
+[RUNNING] `rustc [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ )
+ .run();
+ p.cargo("build -v")
+ .env("RUSTFLAGS", "-C linker=cc")
+ .with_stderr(
+ "\
+[DIRTY] foo v0.0.1 ([..]): the rustflags changed
+[COMPILING] foo v0.0.1 ([..])
+[RUNNING] `rustc [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn update_dependency_mtime_does_not_rebuild() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies]
+ bar = { path = "bar" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("build -Z mtime-on-use")
+ .masquerade_as_nightly_cargo(&["mtime-on-use"])
+ .env("RUSTFLAGS", "-C linker=cc")
+ .with_stderr(
+ "\
+[COMPILING] bar v0.0.1 ([..])
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ )
+ .run();
+ // This does not make new files, but it does update the mtime of the dependency.
+ p.cargo("build -p bar -Z mtime-on-use")
+ .masquerade_as_nightly_cargo(&["mtime-on-use"])
+ .env("RUSTFLAGS", "-C linker=cc")
+ .with_stderr("[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]")
+ .run();
+ // This should not recompile!
+ p.cargo("build -Z mtime-on-use")
+ .masquerade_as_nightly_cargo(&["mtime-on-use"])
+ .env("RUSTFLAGS", "-C linker=cc")
+ .with_stderr("[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]")
+ .run();
+}
+
+fn fingerprint_cleaner(mut dir: PathBuf, timestamp: filetime::FileTime) {
+ // Cargo is experimenting with letting outside projects develop some
+ // limited forms of GC for target_dir. This is one of the forms.
+ // Specifically, Cargo is updating the mtime of a file in
+ // target/profile/.fingerprint each time it uses the fingerprint.
+ // So a cleaner can remove files associated with a fingerprint
+ // if all the files in the fingerprint's folder are older then a time stamp without
+ // effecting any builds that happened since that time stamp.
+ let mut cleaned = false;
+ dir.push(".fingerprint");
+ for fing in fs::read_dir(&dir).unwrap() {
+ let fing = fing.unwrap();
+
+ let outdated = |f: io::Result<fs::DirEntry>| {
+ filetime::FileTime::from_last_modification_time(&f.unwrap().metadata().unwrap())
+ <= timestamp
+ };
+ if fs::read_dir(fing.path()).unwrap().all(outdated) {
+ fs::remove_dir_all(fing.path()).unwrap();
+ println!("remove: {:?}", fing.path());
+ // a real cleaner would remove the big files in deps and build as well
+ // but fingerprint is sufficient for our tests
+ cleaned = true;
+ } else {
+ }
+ }
+ assert!(
+ cleaned,
+ "called fingerprint_cleaner, but there was nothing to remove"
+ );
+}
+
+#[cargo_test]
+fn fingerprint_cleaner_does_not_rebuild() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies]
+ bar = { path = "bar" }
+
+ [features]
+ a = []
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("build -Z mtime-on-use")
+ .masquerade_as_nightly_cargo(&["mtime-on-use"])
+ .run();
+ p.cargo("build -Z mtime-on-use --features a")
+ .masquerade_as_nightly_cargo(&["mtime-on-use"])
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ )
+ .run();
+ if is_coarse_mtime() {
+ sleep_ms(1000);
+ }
+ let timestamp = filetime::FileTime::from_system_time(SystemTime::now());
+ if is_coarse_mtime() {
+ sleep_ms(1000);
+ }
+ // This does not make new files, but it does update the mtime.
+ p.cargo("build -Z mtime-on-use --features a")
+ .masquerade_as_nightly_cargo(&["mtime-on-use"])
+ .with_stderr("[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]")
+ .run();
+ fingerprint_cleaner(p.target_debug_dir(), timestamp);
+ // This should not recompile!
+ p.cargo("build -Z mtime-on-use --features a")
+ .masquerade_as_nightly_cargo(&["mtime-on-use"])
+ .with_stderr("[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]")
+ .run();
+ // But this should be cleaned and so need a rebuild
+ p.cargo("build -Z mtime-on-use")
+ .masquerade_as_nightly_cargo(&["mtime-on-use"])
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn reuse_panic_build_dep_test() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [build-dependencies]
+ bar = { path = "bar" }
+
+ [dev-dependencies]
+ bar = { path = "bar" }
+
+ [profile.dev]
+ panic = "abort"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("build.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ // Check that `bar` is not built twice. It is only needed once (without `panic`).
+ p.cargo("test --lib --no-run -v")
+ .with_stderr(
+ "\
+[COMPILING] bar [..]
+[RUNNING] `rustc --crate-name bar [..]
+[COMPILING] foo [..]
+[RUNNING] `rustc --crate-name build_script_build [..]
+[RUNNING] [..]build-script-build`
+[RUNNING] `rustc --crate-name foo src/lib.rs [..]--test[..]
+[FINISHED] [..]
+[EXECUTABLE] `[..]/target/debug/deps/foo-[..][EXE]`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn reuse_panic_pm() {
+ // foo(panic) -> bar(panic)
+ // somepm(nopanic) -> bar(nopanic)
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies]
+ bar = { path = "bar" }
+ somepm = { path = "somepm" }
+
+ [profile.dev]
+ panic = "abort"
+ "#,
+ )
+ .file("src/lib.rs", "extern crate bar;")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "")
+ .file(
+ "somepm/Cargo.toml",
+ r#"
+ [package]
+ name = "somepm"
+ version = "0.0.1"
+
+ [lib]
+ proc-macro = true
+
+ [dependencies]
+ bar = { path = "../bar" }
+ "#,
+ )
+ .file("somepm/src/lib.rs", "extern crate bar;")
+ .build();
+
+ // bar is built once without panic (for proc-macro) and once with (for the
+ // normal dependency).
+ p.cargo("build -v")
+ .with_stderr_unordered(
+ "\
+[COMPILING] bar [..]
+[RUNNING] `rustc --crate-name bar bar/src/lib.rs [..]--crate-type lib --emit=[..]link[..]
+[RUNNING] `rustc --crate-name bar bar/src/lib.rs [..]--crate-type lib --emit=[..]link -C panic=abort[..]-C debuginfo=2 [..]
+[COMPILING] somepm [..]
+[RUNNING] `rustc --crate-name somepm [..]
+[COMPILING] foo [..]
+[RUNNING] `rustc --crate-name foo src/lib.rs [..]-C panic=abort[..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bust_patched_dep() {
+ Package::new("registry1", "0.1.0").publish();
+ Package::new("registry2", "0.1.0")
+ .dep("registry1", "0.1.0")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies]
+ registry2 = "0.1.0"
+
+ [patch.crates-io]
+ registry1 = { path = "reg1new" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("reg1new/Cargo.toml", &basic_manifest("registry1", "0.1.0"))
+ .file("reg1new/src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+ if is_coarse_mtime() {
+ sleep_ms(1000);
+ }
+
+ p.change_file("reg1new/src/lib.rs", "");
+ if is_coarse_mtime() {
+ sleep_ms(1000);
+ }
+
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[DIRTY] registry1 v0.1.0 ([..]): the file `reg1new/src/lib.rs` has changed ([..])
+[COMPILING] registry1 v0.1.0 ([..])
+[RUNNING] `rustc [..]
+[DIRTY] registry2 v0.1.0: the dependency registry1 was rebuilt
+[COMPILING] registry2 v0.1.0
+[RUNNING] `rustc [..]
+[DIRTY] foo v0.0.1 ([..]): the dependency registry2 was rebuilt
+[COMPILING] foo v0.0.1 ([..])
+[RUNNING] `rustc [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[FRESH] registry1 v0.1.0 ([..])
+[FRESH] registry2 v0.1.0
+[FRESH] foo v0.0.1 ([..])
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rebuild_on_mid_build_file_modification() {
+ let server = TcpListener::bind("127.0.0.1:0").unwrap();
+ let addr = server.local_addr().unwrap();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["root", "proc_macro_dep"]
+ "#,
+ )
+ .file(
+ "root/Cargo.toml",
+ r#"
+ [package]
+ name = "root"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ proc_macro_dep = { path = "../proc_macro_dep" }
+ "#,
+ )
+ .file(
+ "root/src/lib.rs",
+ r#"
+ #[macro_use]
+ extern crate proc_macro_dep;
+
+ #[derive(Noop)]
+ pub struct X;
+ "#,
+ )
+ .file(
+ "proc_macro_dep/Cargo.toml",
+ r#"
+ [package]
+ name = "proc_macro_dep"
+ version = "0.1.0"
+ authors = []
+
+ [lib]
+ proc-macro = true
+ "#,
+ )
+ .file(
+ "proc_macro_dep/src/lib.rs",
+ &format!(
+ r#"
+ extern crate proc_macro;
+
+ use std::io::Read;
+ use std::net::TcpStream;
+ use proc_macro::TokenStream;
+
+ #[proc_macro_derive(Noop)]
+ pub fn noop(_input: TokenStream) -> TokenStream {{
+ let mut stream = TcpStream::connect("{}").unwrap();
+ let mut v = Vec::new();
+ stream.read_to_end(&mut v).unwrap();
+ "".parse().unwrap()
+ }}
+ "#,
+ addr
+ ),
+ )
+ .build();
+ let root = p.root();
+
+ let t = thread::spawn(move || {
+ let socket = server.accept().unwrap().0;
+ sleep_ms(1000);
+ let mut file = OpenOptions::new()
+ .write(true)
+ .append(true)
+ .open(root.join("root/src/lib.rs"))
+ .unwrap();
+ writeln!(file, "// modified").expect("Failed to append to root sources");
+ drop(file);
+ drop(socket);
+ drop(server.accept().unwrap());
+ });
+
+ p.cargo("build")
+ .with_stderr(
+ "\
+[COMPILING] proc_macro_dep v0.1.0 ([..]/proc_macro_dep)
+[COMPILING] root v0.1.0 ([..]/root)
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[FRESH] proc_macro_dep v0.1.0 ([..]/proc_macro_dep)
+[DIRTY] root v0.1.0 ([..]/root): the file `root/src/lib.rs` has changed ([..])
+[COMPILING] root v0.1.0 ([..]/root)
+[RUNNING] `rustc [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ t.join().ok().unwrap();
+}
+
+#[cargo_test]
+fn dirty_both_lib_and_test() {
+ // This tests that all artifacts that depend on the results of a build
+ // script will get rebuilt when the build script reruns, even for separate
+ // commands. It does the following:
+ //
+ // 1. Project "foo" has a build script which will compile a small
+ // staticlib to link against. Normally this would use the `cc` crate,
+ // but here we just use rustc to avoid the `cc` dependency.
+ // 2. Build the library.
+ // 3. Build the unit test. The staticlib intentionally has a bad value.
+ // 4. Rewrite the staticlib with the correct value.
+ // 5. Build the library again.
+ // 6. Build the unit test. This should recompile.
+
+ let slib = |n| {
+ format!(
+ r#"
+ #[no_mangle]
+ pub extern "C" fn doit() -> i32 {{
+ return {};
+ }}
+ "#,
+ n
+ )
+ };
+
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ extern "C" {
+ fn doit() -> i32;
+ }
+
+ #[test]
+ fn t1() {
+ assert_eq!(unsafe { doit() }, 1, "doit assert failure");
+ }
+ "#,
+ )
+ .file(
+ "build.rs",
+ r#"
+ use std::env;
+ use std::path::PathBuf;
+ use std::process::Command;
+
+ fn main() {
+ let rustc = env::var_os("RUSTC").unwrap();
+ let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
+ assert!(
+ Command::new(rustc)
+ .args(&[
+ "--crate-type=staticlib",
+ "--out-dir",
+ out_dir.to_str().unwrap(),
+ "slib.rs"
+ ])
+ .status()
+ .unwrap()
+ .success(),
+ "slib build failed"
+ );
+ println!("cargo:rustc-link-lib=slib");
+ println!("cargo:rustc-link-search={}", out_dir.display());
+ }
+ "#,
+ )
+ .file("slib.rs", &slib(2))
+ .build();
+
+ p.cargo("build").run();
+
+ // 2 != 1
+ p.cargo("test --lib")
+ .with_status(101)
+ .with_stdout_contains("[..]doit assert failure[..]")
+ .run();
+
+ if is_coarse_mtime() {
+ // #5918
+ sleep_ms(1000);
+ }
+ // Fix the mistake.
+ p.change_file("slib.rs", &slib(1));
+
+ p.cargo("build").run();
+ // This should recompile with the new static lib, and the test should pass.
+ p.cargo("test --lib").run();
+}
+
+#[cargo_test]
+fn script_fails_stay_dirty() {
+ // Check if a script is aborted (such as hitting Ctrl-C) that it will re-run.
+ // Steps:
+ // 1. Build to establish fingerprints.
+ // 2. Make a change that triggers the build script to re-run. Abort the
+ // script while it is running.
+ // 3. Run the build again and make sure it re-runs the script.
+ let p = project()
+ .file(
+ "build.rs",
+ r#"
+ mod helper;
+ fn main() {
+ println!("cargo:rerun-if-changed=build.rs");
+ helper::doit();
+ }
+ "#,
+ )
+ .file("helper.rs", "pub fn doit() {}")
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+ if is_coarse_mtime() {
+ sleep_ms(1000);
+ }
+ p.change_file("helper.rs", r#"pub fn doit() {panic!("Crash!");}"#);
+ p.cargo("build")
+ .with_stderr_contains("[..]Crash![..]")
+ .with_status(101)
+ .run();
+ // There was a bug where this second call would be "fresh".
+ p.cargo("build")
+ .with_stderr_contains("[..]Crash![..]")
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn simulated_docker_deps_stay_cached() {
+ // Test what happens in docker where the nanoseconds are zeroed out.
+ Package::new("regdep", "1.0.0").publish();
+ Package::new("regdep_old_style", "1.0.0")
+ .file("build.rs", "fn main() {}")
+ .file("src/lib.rs", "")
+ .publish();
+ Package::new("regdep_env", "1.0.0")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rerun-if-env-changed=SOMEVAR");
+ }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .publish();
+ Package::new("regdep_rerun", "1.0.0")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rerun-if-changed=build.rs");
+ }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ pathdep = { path = "pathdep" }
+ regdep = "1.0"
+ regdep_old_style = "1.0"
+ regdep_env = "1.0"
+ regdep_rerun = "1.0"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "
+ extern crate pathdep;
+ extern crate regdep;
+ extern crate regdep_old_style;
+ extern crate regdep_env;
+ extern crate regdep_rerun;
+ ",
+ )
+ .file("build.rs", "fn main() {}")
+ .file("pathdep/Cargo.toml", &basic_manifest("pathdep", "1.0.0"))
+ .file("pathdep/src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+
+ let already_zero = {
+ // This happens on HFS with 1-second timestamp resolution,
+ // or other filesystems where it just so happens to write exactly on a
+ // 1-second boundary.
+ let metadata = fs::metadata(p.root().join("src/lib.rs")).unwrap();
+ let mtime = FileTime::from_last_modification_time(&metadata);
+ mtime.nanoseconds() == 0
+ };
+
+ // Recursively remove `nanoseconds` from every path.
+ fn zeropath(path: &Path) {
+ for entry in walkdir::WalkDir::new(path)
+ .into_iter()
+ .filter_map(|e| e.ok())
+ {
+ let metadata = fs::metadata(entry.path()).unwrap();
+ let mtime = metadata.modified().unwrap();
+ let mtime_duration = mtime.duration_since(SystemTime::UNIX_EPOCH).unwrap();
+ let trunc_mtime = FileTime::from_unix_time(mtime_duration.as_secs() as i64, 0);
+ let atime = metadata.accessed().unwrap();
+ let atime_duration = atime.duration_since(SystemTime::UNIX_EPOCH).unwrap();
+ let trunc_atime = FileTime::from_unix_time(atime_duration.as_secs() as i64, 0);
+ if let Err(e) = filetime::set_file_times(entry.path(), trunc_atime, trunc_mtime) {
+ // Windows doesn't allow changing filetimes on some things
+ // (directories, other random things I'm not sure why). Just
+ // ignore them.
+ if e.kind() == std::io::ErrorKind::PermissionDenied {
+ println!("PermissionDenied filetime on {:?}", entry.path());
+ } else {
+ panic!("FileTime error on {:?}: {:?}", entry.path(), e);
+ }
+ }
+ }
+ }
+ zeropath(&p.root());
+ zeropath(&paths::home());
+
+ if already_zero {
+ println!("already zero");
+ // If it was already truncated, then everything stays fresh.
+ p.cargo("build -v")
+ .with_stderr_unordered(
+ "\
+[FRESH] pathdep [..]
+[FRESH] regdep [..]
+[FRESH] regdep_env [..]
+[FRESH] regdep_old_style [..]
+[FRESH] regdep_rerun [..]
+[FRESH] foo [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+ } else {
+ println!("not already zero");
+ // It is not ideal that `foo` gets recompiled, but that is the current
+ // behavior. Currently mtimes are ignored for registry deps.
+ //
+ // Note that this behavior is due to the fact that `foo` has a build
+ // script in "old" mode where it doesn't print `rerun-if-*`. In this
+ // mode we use `Precalculated` to fingerprint a path dependency, where
+ // `Precalculated` is an opaque string which has the most recent mtime
+ // in it. It differs between builds because one has nsec=0 and the other
+ // likely has a nonzero nsec. Hence, the rebuild.
+ p.cargo("build -v")
+ .with_stderr_unordered(
+ "\
+[FRESH] pathdep [..]
+[FRESH] regdep [..]
+[FRESH] regdep_env [..]
+[FRESH] regdep_old_style [..]
+[FRESH] regdep_rerun [..]
+[DIRTY] foo [..]: the precalculated components changed
+[COMPILING] foo [..]
+[RUNNING] [..]/foo-[..]/build-script-build[..]
+[RUNNING] `rustc --crate-name foo[..]
+[FINISHED] [..]
+",
+ )
+ .run();
+ }
+}
+
+#[cargo_test]
+fn metadata_change_invalidates() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+
+ for attr in &[
+ "authors = [\"foo\"]",
+ "description = \"desc\"",
+ "homepage = \"https://example.com\"",
+ "repository =\"https://example.com\"",
+ ] {
+ let mut file = OpenOptions::new()
+ .write(true)
+ .append(true)
+ .open(p.root().join("Cargo.toml"))
+ .unwrap();
+ writeln!(file, "{}", attr).unwrap();
+ p.cargo("build")
+ .with_stderr_contains("[COMPILING] foo [..]")
+ .run();
+ }
+ p.cargo("build -v")
+ .with_stderr_contains("[FRESH] foo[..]")
+ .run();
+ assert_eq!(p.glob("target/debug/deps/libfoo-*.rlib").count(), 1);
+}
+
+#[cargo_test]
+fn edition_change_invalidates() {
+ const MANIFEST: &str = r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ "#;
+ let p = project()
+ .file("Cargo.toml", MANIFEST)
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("build").run();
+ p.change_file("Cargo.toml", &format!("{}edition = \"2018\"", MANIFEST));
+ p.cargo("build")
+ .with_stderr_contains("[COMPILING] foo [..]")
+ .run();
+ p.change_file(
+ "Cargo.toml",
+ &format!(
+ r#"{}edition = "2018"
+ [lib]
+ edition = "2015"
+ "#,
+ MANIFEST
+ ),
+ );
+ p.cargo("build")
+ .with_stderr_contains("[COMPILING] foo [..]")
+ .run();
+ p.cargo("build -v")
+ .with_stderr_contains("[FRESH] foo[..]")
+ .run();
+ assert_eq!(p.glob("target/debug/deps/libfoo-*.rlib").count(), 1);
+}
+
+#[cargo_test]
+fn rename_with_path_deps() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ a = { path = 'a' }
+ "#,
+ )
+ .file("src/lib.rs", "extern crate a; pub fn foo() { a::foo(); }")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ b = { path = 'b' }
+ "#,
+ )
+ .file("a/src/lib.rs", "extern crate b; pub fn foo() { b::foo() }")
+ .file(
+ "a/b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.5.0"
+ authors = []
+ "#,
+ )
+ .file("a/b/src/lib.rs", "pub fn foo() { }");
+ let p = p.build();
+
+ p.cargo("build").run();
+
+ // Now rename the root directory and rerun `cargo run`. Not only should we
+ // not build anything but we also shouldn't crash.
+ let mut new = p.root();
+ new.pop();
+ new.push("foo2");
+
+ fs::rename(p.root(), &new).unwrap();
+
+ p.cargo("build")
+ .cwd(&new)
+ .with_stderr("[FINISHED] [..]")
+ .run();
+}
+
+#[cargo_test]
+fn move_target_directory_with_path_deps() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ a = { path = "a" }
+ "#,
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.5.0"
+ authors = []
+ "#,
+ )
+ .file("src/lib.rs", "extern crate a; pub use a::print_msg;")
+ .file(
+ "a/build.rs",
+ r###"
+ use std::env;
+ use std::fs;
+ use std::path::Path;
+
+ fn main() {
+ println!("cargo:rerun-if-changed=build.rs");
+ let out_dir = env::var("OUT_DIR").unwrap();
+ let dest_path = Path::new(&out_dir).join("hello.rs");
+ fs::write(&dest_path, r#"
+ pub fn message() -> &'static str {
+ "Hello, World!"
+ }
+ "#).unwrap();
+ }
+ "###,
+ )
+ .file(
+ "a/src/lib.rs",
+ r#"
+ include!(concat!(env!("OUT_DIR"), "/hello.rs"));
+ pub fn print_msg() { message(); }
+ "#,
+ );
+ let p = p.build();
+
+ let mut parent = p.root();
+ parent.pop();
+
+ p.cargo("build").run();
+
+ let new_target = p.root().join("target2");
+ fs::rename(p.root().join("target"), &new_target).unwrap();
+
+ p.cargo("build")
+ .env("CARGO_TARGET_DIR", &new_target)
+ .with_stderr("[FINISHED] [..]")
+ .run();
+}
+
+#[cargo_test]
+fn rerun_if_changes() {
+ let p = project()
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rerun-if-env-changed=FOO");
+ if std::env::var("FOO").is_ok() {
+ println!("cargo:rerun-if-env-changed=BAR");
+ }
+ }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+ p.cargo("build").with_stderr("[FINISHED] [..]").run();
+
+ p.cargo("build -v")
+ .env("FOO", "1")
+ .with_stderr(
+ "\
+[DIRTY] foo [..]: the env variable FOO changed
+[COMPILING] foo [..]
+[RUNNING] `[..]build-script-build`
+[RUNNING] `rustc [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+ p.cargo("build")
+ .env("FOO", "1")
+ .with_stderr("[FINISHED] [..]")
+ .run();
+
+ p.cargo("build -v")
+ .env("FOO", "1")
+ .env("BAR", "1")
+ .with_stderr(
+ "\
+[DIRTY] foo [..]: the env variable BAR changed
+[COMPILING] foo [..]
+[RUNNING] `[..]build-script-build`
+[RUNNING] `rustc [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+ p.cargo("build")
+ .env("FOO", "1")
+ .env("BAR", "1")
+ .with_stderr("[FINISHED] [..]")
+ .run();
+
+ p.cargo("build -v")
+ .env("BAR", "2")
+ .with_stderr(
+ "\
+[DIRTY] foo [..]: the env variable FOO changed
+[COMPILING] foo [..]
+[RUNNING] `[..]build-script-build`
+[RUNNING] `rustc [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+ p.cargo("build")
+ .env("BAR", "2")
+ .with_stderr("[FINISHED] [..]")
+ .run();
+}
+
+#[cargo_test]
+fn channel_shares_filenames() {
+ // Test that different "nightly" releases use the same output filename.
+
+ // Create separate rustc binaries to emulate running different toolchains.
+ let nightly1 = format!(
+ "\
+rustc 1.44.0-nightly (38114ff16 2020-03-21)
+binary: rustc
+commit-hash: 38114ff16e7856f98b2b4be7ab4cd29b38bed59a
+commit-date: 2020-03-21
+host: {}
+release: 1.44.0-nightly
+LLVM version: 9.0
+",
+ rustc_host()
+ );
+
+ let nightly2 = format!(
+ "\
+rustc 1.44.0-nightly (a5b09d354 2020-03-31)
+binary: rustc
+commit-hash: a5b09d35473615e7142f5570f5c5fad0caf68bd2
+commit-date: 2020-03-31
+host: {}
+release: 1.44.0-nightly
+LLVM version: 9.0
+",
+ rustc_host()
+ );
+
+ let beta1 = format!(
+ "\
+rustc 1.43.0-beta.3 (4c587bbda 2020-03-25)
+binary: rustc
+commit-hash: 4c587bbda04ab55aaf56feab11dfdfe387a85d7a
+commit-date: 2020-03-25
+host: {}
+release: 1.43.0-beta.3
+LLVM version: 9.0
+",
+ rustc_host()
+ );
+
+ let beta2 = format!(
+ "\
+rustc 1.42.0-beta.5 (4e1c5f0e9 2020-02-28)
+binary: rustc
+commit-hash: 4e1c5f0e9769a588b91c977e3d81e140209ef3a2
+commit-date: 2020-02-28
+host: {}
+release: 1.42.0-beta.5
+LLVM version: 9.0
+",
+ rustc_host()
+ );
+
+ let stable1 = format!(
+ "\
+rustc 1.42.0 (b8cedc004 2020-03-09)
+binary: rustc
+commit-hash: b8cedc00407a4c56a3bda1ed605c6fc166655447
+commit-date: 2020-03-09
+host: {}
+release: 1.42.0
+LLVM version: 9.0
+",
+ rustc_host()
+ );
+
+ let stable2 = format!(
+ "\
+rustc 1.41.1 (f3e1a954d 2020-02-24)
+binary: rustc
+commit-hash: f3e1a954d2ead4e2fc197c7da7d71e6c61bad196
+commit-date: 2020-02-24
+host: {}
+release: 1.41.1
+LLVM version: 9.0
+",
+ rustc_host()
+ );
+
+ let compiler = project()
+ .at("compiler")
+ .file("Cargo.toml", &basic_manifest("compiler", "0.1.0"))
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ if std::env::args_os().any(|a| a == "-vV") {
+ print!("{}", env!("FUNKY_VERSION_TEST"));
+ return;
+ }
+ let mut cmd = std::process::Command::new("rustc");
+ cmd.args(std::env::args_os().skip(1));
+ assert!(cmd.status().unwrap().success());
+ }
+ "#,
+ )
+ .build();
+
+ let makeit = |version, vv| {
+ // Force a rebuild.
+ compiler.target_debug_dir().join("deps").rm_rf();
+ compiler.cargo("build").env("FUNKY_VERSION_TEST", vv).run();
+ fs::rename(compiler.bin("compiler"), compiler.bin(version)).unwrap();
+ };
+ makeit("nightly1", nightly1);
+ makeit("nightly2", nightly2);
+ makeit("beta1", beta1);
+ makeit("beta2", beta2);
+ makeit("stable1", stable1);
+ makeit("stable2", stable2);
+
+ // Run `cargo check` with different rustc versions to observe its behavior.
+ let p = project().file("src/lib.rs", "").build();
+
+ // Runs `cargo check` and returns the rmeta filename created.
+ // Checks that the freshness matches the given value.
+ let check = |version, fresh| -> String {
+ let output = p
+ .cargo("check --message-format=json")
+ .env("RUSTC", compiler.bin(version))
+ .exec_with_output()
+ .unwrap();
+ // Collect the filenames generated.
+ let mut artifacts: Vec<_> = std::str::from_utf8(&output.stdout)
+ .unwrap()
+ .lines()
+ .filter_map(|line| {
+ let value: serde_json::Value = serde_json::from_str(line).unwrap();
+ if value["reason"].as_str().unwrap() == "compiler-artifact" {
+ assert_eq!(value["fresh"].as_bool().unwrap(), fresh);
+ let filenames = value["filenames"].as_array().unwrap();
+ assert_eq!(filenames.len(), 1);
+ Some(filenames[0].to_string())
+ } else {
+ None
+ }
+ })
+ .collect();
+ // Should only generate one rmeta file.
+ assert_eq!(artifacts.len(), 1);
+ artifacts.pop().unwrap()
+ };
+
+ let nightly1_name = check("nightly1", false);
+ assert_eq!(check("nightly1", true), nightly1_name);
+ assert_eq!(check("nightly2", false), nightly1_name); // same as before
+ assert_eq!(check("nightly2", true), nightly1_name);
+ // Should rebuild going back to nightly1.
+ assert_eq!(check("nightly1", false), nightly1_name);
+
+ let beta1_name = check("beta1", false);
+ assert_ne!(beta1_name, nightly1_name);
+ assert_eq!(check("beta1", true), beta1_name);
+ assert_eq!(check("beta2", false), beta1_name); // same as before
+ assert_eq!(check("beta2", true), beta1_name);
+ // Should rebuild going back to beta1.
+ assert_eq!(check("beta1", false), beta1_name);
+
+ let stable1_name = check("stable1", false);
+ assert_ne!(stable1_name, nightly1_name);
+ assert_ne!(stable1_name, beta1_name);
+ let stable2_name = check("stable2", false);
+ assert_ne!(stable1_name, stable2_name);
+ // Check everything is fresh.
+ assert_eq!(check("stable1", true), stable1_name);
+ assert_eq!(check("stable2", true), stable2_name);
+ assert_eq!(check("beta1", true), beta1_name);
+ assert_eq!(check("nightly1", true), nightly1_name);
+}
+
+#[cargo_test]
+fn linking_interrupted() {
+ // Interrupt during the linking phase shouldn't leave test executable as "fresh".
+
+ // This is used to detect when linking starts, then to pause the linker so
+ // that the test can kill cargo.
+ let link_listener = TcpListener::bind("127.0.0.1:0").unwrap();
+ let link_addr = link_listener.local_addr().unwrap();
+
+ // This is used to detect when rustc exits.
+ let rustc_listener = TcpListener::bind("127.0.0.1:0").unwrap();
+ let rustc_addr = rustc_listener.local_addr().unwrap();
+
+ // Create a linker that we can interrupt.
+ let linker = project()
+ .at("linker")
+ .file("Cargo.toml", &basic_manifest("linker", "1.0.0"))
+ .file(
+ "src/main.rs",
+ &r#"
+ fn main() {
+ // Figure out the output filename.
+ let output = match std::env::args().find(|a| a.starts_with("/OUT:")) {
+ Some(s) => s[5..].to_string(),
+ None => {
+ let mut args = std::env::args();
+ loop {
+ if args.next().unwrap() == "-o" {
+ break;
+ }
+ }
+ args.next().unwrap()
+ }
+ };
+ std::fs::remove_file(&output).unwrap();
+ std::fs::write(&output, "").unwrap();
+ // Tell the test that we are ready to be interrupted.
+ let mut socket = std::net::TcpStream::connect("__ADDR__").unwrap();
+ // Wait for the test to kill us.
+ std::thread::sleep(std::time::Duration::new(60, 0));
+ }
+ "#
+ .replace("__ADDR__", &link_addr.to_string()),
+ )
+ .build();
+ linker.cargo("build").run();
+
+ // Create a wrapper around rustc that will tell us when rustc is finished.
+ let rustc = project()
+ .at("rustc-waiter")
+ .file("Cargo.toml", &basic_manifest("rustc-waiter", "1.0.0"))
+ .file(
+ "src/main.rs",
+ &r#"
+ fn main() {
+ let mut conn = None;
+ // Check for a normal build (not -vV or --print).
+ if std::env::args().any(|arg| arg == "t1") {
+ // Tell the test that rustc has started.
+ conn = Some(std::net::TcpStream::connect("__ADDR__").unwrap());
+ }
+ let status = std::process::Command::new("rustc")
+ .args(std::env::args().skip(1))
+ .status()
+ .expect("rustc to run");
+ std::process::exit(status.code().unwrap_or(1));
+ }
+ "#
+ .replace("__ADDR__", &rustc_addr.to_string()),
+ )
+ .build();
+ rustc.cargo("build").run();
+
+ // Build it once so that the fingerprint gets saved to disk.
+ let p = project()
+ .file("src/lib.rs", "")
+ .file("tests/t1.rs", "")
+ .build();
+ p.cargo("test --test t1 --no-run").run();
+
+ // Make a change, start a build, then interrupt it.
+ p.change_file("src/lib.rs", "// modified");
+ let linker_env = format!("CARGO_TARGET_{}_LINKER", rustc_host_env());
+ // NOTE: This assumes that the paths to the linker or rustc are not in the
+ // fingerprint. But maybe they should be?
+ let mut cmd = p
+ .cargo("test --test t1 --no-run")
+ .env(&linker_env, linker.bin("linker"))
+ .env("RUSTC", rustc.bin("rustc-waiter"))
+ .build_command();
+ let mut child = cmd
+ .stdout(Stdio::null())
+ .stderr(Stdio::null())
+ .env("__CARGO_TEST_SETSID_PLEASE_DONT_USE_ELSEWHERE", "1")
+ .spawn()
+ .unwrap();
+ // Wait for rustc to start.
+ let mut rustc_conn = rustc_listener.accept().unwrap().0;
+ // Wait for linking to start.
+ drop(link_listener.accept().unwrap());
+
+ // Interrupt the child.
+ death::ctrl_c(&mut child);
+ assert!(!child.wait().unwrap().success());
+ // Wait for rustc to exit. If we don't wait, then the command below could
+ // start while rustc is still being torn down.
+ let mut buf = [0];
+ drop(rustc_conn.read_exact(&mut buf));
+
+ // Build again, shouldn't be fresh.
+ p.cargo("test --test t1 -v")
+ .with_stderr(
+ "\
+[DIRTY] foo v0.0.1 ([..]): the config settings changed
+[COMPILING] foo [..]
+[RUNNING] `rustc --crate-name foo [..]
+[RUNNING] `rustc --crate-name t1 [..]
+[FINISHED] [..]
+[RUNNING] `[..]target/debug/deps/t1-[..][EXE]`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+#[cfg_attr(
+ not(all(target_arch = "x86_64", target_os = "windows", target_env = "msvc")),
+ ignore
+)]
+fn lld_is_fresh() {
+ // Check for bug when using lld linker that it remains fresh with dylib.
+ let p = project()
+ .file(
+ ".cargo/config",
+ r#"
+ [target.x86_64-pc-windows-msvc]
+ linker = "rust-lld"
+ rustflags = ["-C", "link-arg=-fuse-ld=lld"]
+ "#,
+ )
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [lib]
+ crate-type = ["dylib"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+ p.cargo("build -v")
+ .with_stderr("[FRESH] foo [..]\n[FINISHED] [..]")
+ .run();
+}
+
+#[cargo_test]
+fn env_in_code_causes_rebuild() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ println!("{:?}", option_env!("FOO"));
+ println!("{:?}", option_env!("FOO\nBAR"));
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build").env_remove("FOO").run();
+ p.cargo("build")
+ .env_remove("FOO")
+ .with_stderr("[FINISHED] [..]")
+ .run();
+ p.cargo("build -v")
+ .env("FOO", "bar")
+ .with_stderr(
+ "\
+[DIRTY] foo [..]: the environment variable FOO changed
+[COMPILING][..]
+[RUNNING] `rustc [..]
+[FINISHED][..]",
+ )
+ .run();
+ p.cargo("build")
+ .env("FOO", "bar")
+ .with_stderr("[FINISHED][..]")
+ .run();
+ p.cargo("build -v")
+ .env("FOO", "baz")
+ .with_stderr(
+ "\
+[DIRTY] foo [..]: the environment variable FOO changed
+[COMPILING][..]
+[RUNNING] `rustc [..]
+[FINISHED][..]",
+ )
+ .run();
+ p.cargo("build")
+ .env("FOO", "baz")
+ .with_stderr("[FINISHED][..]")
+ .run();
+ p.cargo("build -v")
+ .env_remove("FOO")
+ .with_stderr(
+ "\
+[DIRTY] foo [..]: the environment variable FOO changed
+[COMPILING][..]
+[RUNNING] `rustc [..]
+[FINISHED][..]",
+ )
+ .run();
+ p.cargo("build")
+ .env_remove("FOO")
+ .with_stderr("[FINISHED][..]")
+ .run();
+
+ let interesting = " #!$\nabc\r\\\t\u{8}\r\n";
+ p.cargo("build").env("FOO", interesting).run();
+ p.cargo("build")
+ .env("FOO", interesting)
+ .with_stderr("[FINISHED][..]")
+ .run();
+
+ p.cargo("build").env("FOO\nBAR", interesting).run();
+ p.cargo("build")
+ .env("FOO\nBAR", interesting)
+ .with_stderr("[FINISHED][..]")
+ .run();
+}
+
+#[cargo_test]
+fn env_build_script_no_rebuild() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ "#,
+ )
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rustc-env=FOO=bar");
+ }
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ println!("{:?}", env!("FOO"));
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build").run();
+ p.cargo("build").with_stderr("[FINISHED] [..]").run();
+}
+
+#[cargo_test]
+fn cargo_env_changes() {
+ // Checks that changes to the env var CARGO in the dep-info file triggers
+ // a rebuild.
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo", "1.0.0"))
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ println!("{:?}", env!("CARGO"));
+ }
+ "#,
+ )
+ .build();
+
+ let cargo_exe = cargo_test_support::cargo_exe();
+ let other_cargo_path = p.root().join(cargo_exe.file_name().unwrap());
+ std::fs::hard_link(&cargo_exe, &other_cargo_path).unwrap();
+ let other_cargo = || {
+ let mut pb = cargo_test_support::process(&other_cargo_path);
+ pb.cwd(p.root());
+ cargo_test_support::execs().with_process_builder(pb)
+ };
+
+ p.cargo("check").run();
+ other_cargo()
+ .arg("check")
+ .arg("-v")
+ .with_stderr(
+ "\
+[DIRTY] foo v1.0.0 ([..]): the environment variable CARGO changed
+[CHECKING] foo [..]
+[RUNNING] `rustc [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ // And just to confirm that without using env! it doesn't rebuild.
+ p.change_file("src/main.rs", "fn main() {}");
+ p.cargo("check")
+ .with_stderr(
+ "\
+[CHECKING] foo [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+ other_cargo()
+ .arg("check")
+ .arg("-v")
+ .with_stderr(
+ "\
+[FRESH] foo [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn changing_linker() {
+ // Changing linker should rebuild.
+ let p = project().file("src/main.rs", "fn main() {}").build();
+ p.cargo("build").run();
+ let linker_env = format!("CARGO_TARGET_{}_LINKER", rustc_host_env());
+ p.cargo("build --verbose")
+ .env(&linker_env, "nonexistent-linker")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[RUNNING] `rustc [..] -C linker=nonexistent-linker [..]`
+[ERROR] [..]linker[..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn verify_source_before_recompile() {
+ Package::new("bar", "0.1.0")
+ .file("src/lib.rs", "")
+ .publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("vendor --respect-source-config").run();
+ p.change_file(
+ ".cargo/config.toml",
+ r#"
+ [source.crates-io]
+ replace-with = 'vendor'
+
+ [source.vendor]
+ directory = 'vendor'
+ "#,
+ );
+ // Sanity check: vendoring works correctly.
+ p.cargo("check --verbose")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name bar [CWD]/vendor/bar/src/lib.rs[..]")
+ .run();
+ // Now modify vendored crate.
+ p.change_file(
+ "vendor/bar/src/lib.rs",
+ r#"compile_error!("You shall not pass!");"#,
+ );
+ // Should ignore modifed sources without any recompile.
+ p.cargo("check --verbose")
+ .with_stderr(
+ "\
+[FRESH] bar v0.1.0
+[FRESH] foo v0.1.0 ([CWD])
+[FINISHED] dev [..]
+",
+ )
+ .run();
+
+ // Add a `RUSTFLAGS` to trigger a recompile.
+ //
+ // Cargo should refuse to build because of checksum verfication failure.
+ // Cargo shouldn't recompile dependency `bar`.
+ p.cargo("check --verbose")
+ .env("RUSTFLAGS", "-W warnings")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: the listed checksum of `[CWD]/vendor/bar/src/lib.rs` has changed:
+expected: [..]
+actual: [..]
+
+directory sources are not [..]
+",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/future_incompat_report.rs b/src/tools/cargo/tests/testsuite/future_incompat_report.rs
new file mode 100644
index 000000000..9f451a64c
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/future_incompat_report.rs
@@ -0,0 +1,391 @@
+//! Tests for future-incompat-report messages
+//!
+//! Note that these tests use the -Zfuture-incompat-test for rustc.
+//! This causes rustc to treat *every* lint as future-incompatible.
+//! This is done because future-incompatible lints are inherently
+//! ephemeral, but we don't want to continually update these tests.
+//! So we pick some random lint that will likely always be the same
+//! over time.
+
+use super::config::write_config_toml;
+use cargo_test_support::registry::Package;
+use cargo_test_support::{basic_manifest, project, Project};
+
+// An arbitrary lint (unused_variables) that triggers a lint.
+// We use a special flag to force it to generate a report.
+const FUTURE_EXAMPLE: &'static str = "fn main() { let x = 1; }";
+// Some text that will be displayed when the lint fires.
+const FUTURE_OUTPUT: &'static str = "[..]unused_variables[..]";
+
+fn simple_project() -> Project {
+ project()
+ .file("Cargo.toml", &basic_manifest("foo", "0.0.0"))
+ .file("src/main.rs", FUTURE_EXAMPLE)
+ .build()
+}
+
+#[cargo_test(
+ nightly,
+ reason = "-Zfuture-incompat-test requires nightly (permanently)"
+)]
+fn output_on_stable() {
+ let p = simple_project();
+
+ p.cargo("check")
+ .env("RUSTFLAGS", "-Zfuture-incompat-test")
+ .with_stderr_contains(FUTURE_OUTPUT)
+ .with_stderr_contains("[..]cargo report[..]")
+ .run();
+}
+
+// This feature is stable, and should not be gated
+#[cargo_test]
+fn no_gate_future_incompat_report() {
+ let p = simple_project();
+
+ p.cargo("check --future-incompat-report")
+ .with_status(0)
+ .run();
+
+ p.cargo("report future-incompatibilities --id foo")
+ .with_stderr_contains("error: no reports are currently available")
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test(
+ nightly,
+ reason = "-Zfuture-incompat-test requires nightly (permanently)"
+)]
+fn test_zero_future_incompat() {
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo", "0.0.0"))
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ // No note if --future-incompat-report is not specified.
+ p.cargo("check")
+ .env("RUSTFLAGS", "-Zfuture-incompat-test")
+ .with_stderr(
+ "\
+[CHECKING] foo v0.0.0 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ p.cargo("check --future-incompat-report")
+ .env("RUSTFLAGS", "-Zfuture-incompat-test")
+ .with_stderr(
+ "\
+[FINISHED] [..]
+note: 0 dependencies had future-incompatible warnings
+",
+ )
+ .run();
+}
+
+#[cargo_test(
+ nightly,
+ reason = "-Zfuture-incompat-test requires nightly (permanently)"
+)]
+fn test_single_crate() {
+ let p = simple_project();
+
+ for command in &["build", "check", "rustc", "test"] {
+ let check_has_future_compat = || {
+ p.cargo(command)
+ .env("RUSTFLAGS", "-Zfuture-incompat-test")
+ .with_stderr_contains(FUTURE_OUTPUT)
+ .with_stderr_contains("warning: the following packages contain code that will be rejected by a future version of Rust: foo v0.0.0 [..]")
+ .with_stderr_does_not_contain("[..]incompatibility[..]")
+ .run();
+ };
+
+ // Check that we show a message with no [future-incompat-report] config section
+ write_config_toml("");
+ check_has_future_compat();
+
+ // Check that we show a message with `frequency = "always"`
+ write_config_toml(
+ "\
+[future-incompat-report]
+frequency = 'always'
+",
+ );
+ check_has_future_compat();
+
+ // Check that we do not show a message with `frequency = "never"`
+ write_config_toml(
+ "\
+[future-incompat-report]
+frequency = 'never'
+",
+ );
+ p.cargo(command)
+ .env("RUSTFLAGS", "-Zfuture-incompat-test")
+ .with_stderr_contains(FUTURE_OUTPUT)
+ .with_stderr_does_not_contain("[..]rejected[..]")
+ .with_stderr_does_not_contain("[..]incompatibility[..]")
+ .run();
+
+ // Check that passing `--future-incompat-report` overrides `frequency = 'never'`
+ p.cargo(command).arg("--future-incompat-report")
+ .env("RUSTFLAGS", "-Zfuture-incompat-test")
+ .with_stderr_contains(FUTURE_OUTPUT)
+ .with_stderr_contains("warning: the following packages contain code that will be rejected by a future version of Rust: foo v0.0.0 [..]")
+ .with_stderr_contains(" - foo@0.0.0[..]")
+ .run();
+ }
+}
+
+#[cargo_test(
+ nightly,
+ reason = "-Zfuture-incompat-test requires nightly (permanently)"
+)]
+fn test_multi_crate() {
+ Package::new("first-dep", "0.0.1")
+ .file("src/lib.rs", FUTURE_EXAMPLE)
+ .publish();
+ Package::new("second-dep", "0.0.2")
+ .file("src/lib.rs", FUTURE_EXAMPLE)
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+
+ [dependencies]
+ first-dep = "*"
+ second-dep = "*"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ for command in &["build", "check", "rustc", "test"] {
+ p.cargo(command)
+ .env("RUSTFLAGS", "-Zfuture-incompat-test")
+ .with_stderr_does_not_contain(FUTURE_OUTPUT)
+ .with_stderr_contains("warning: the following packages contain code that will be rejected by a future version of Rust: first-dep v0.0.1, second-dep v0.0.2")
+ // Check that we don't have the 'triggers' message shown at the bottom of this loop,
+ // and that we don't explain how to show a per-package report
+ .with_stderr_does_not_contain("[..]triggers[..]")
+ .with_stderr_does_not_contain("[..]--package[..]")
+ .with_stderr_does_not_contain("[..]-p[..]")
+ .run();
+
+ p.cargo(command).arg("--future-incompat-report")
+ .env("RUSTFLAGS", "-Zfuture-incompat-test")
+ .with_stderr_contains("warning: the following packages contain code that will be rejected by a future version of Rust: first-dep v0.0.1, second-dep v0.0.2")
+ .with_stderr_contains(" - first-dep@0.0.1")
+ .with_stderr_contains(" - second-dep@0.0.2")
+ .run();
+
+ p.cargo("report future-incompatibilities").arg("--package").arg("first-dep@0.0.1")
+ .with_stdout_contains("The package `first-dep v0.0.1` currently triggers the following future incompatibility lints:")
+ .with_stdout_contains(FUTURE_OUTPUT)
+ .with_stdout_does_not_contain("[..]second-dep-0.0.2/src[..]")
+ .run();
+
+ p.cargo("report future-incompatibilities").arg("--package").arg("second-dep@0.0.2")
+ .with_stdout_contains("The package `second-dep v0.0.2` currently triggers the following future incompatibility lints:")
+ .with_stdout_contains(FUTURE_OUTPUT)
+ .with_stdout_does_not_contain("[..]first-dep-0.0.1/src[..]")
+ .run();
+ }
+
+ // Test that passing the correct id via '--id' doesn't generate a warning message
+ let output = p
+ .cargo("check")
+ .env("RUSTFLAGS", "-Zfuture-incompat-test")
+ .exec_with_output()
+ .unwrap();
+
+ // Extract the 'id' from the stdout. We are looking
+ // for the id in a line of the form "run `cargo report future-incompatibilities --id yZ7S`"
+ // which is generated by Cargo to tell the user what command to run
+ // This is just to test that passing the id suppresses the warning mesasge. Any users needing
+ // access to the report from a shell script should use the `--future-incompat-report` flag
+ let stderr = std::str::from_utf8(&output.stderr).unwrap();
+
+ // Find '--id <ID>' in the output
+ let mut iter = stderr.split(' ');
+ iter.find(|w| *w == "--id").unwrap();
+ let id = iter
+ .next()
+ .unwrap_or_else(|| panic!("Unexpected output:\n{}", stderr));
+ // Strip off the trailing '`' included in the output
+ let id: String = id.chars().take_while(|c| *c != '`').collect();
+
+ p.cargo(&format!("report future-incompatibilities --id {}", id))
+ .with_stdout_contains("The package `first-dep v0.0.1` currently triggers the following future incompatibility lints:")
+ .with_stdout_contains("The package `second-dep v0.0.2` currently triggers the following future incompatibility lints:")
+ .run();
+
+ // Test without --id, and also the full output of the report.
+ let output = p
+ .cargo("report future-incompat")
+ .exec_with_output()
+ .unwrap();
+ let output = std::str::from_utf8(&output.stdout).unwrap();
+ assert!(output.starts_with("The following warnings were discovered"));
+ let mut lines = output
+ .lines()
+ // Skip the beginning of the per-package information.
+ .skip_while(|line| !line.starts_with("The package"));
+ for expected in &["first-dep v0.0.1", "second-dep v0.0.2"] {
+ assert_eq!(
+ &format!(
+ "The package `{}` currently triggers the following future incompatibility lints:",
+ expected
+ ),
+ lines.next().unwrap(),
+ "Bad output:\n{}",
+ output
+ );
+ let mut count = 0;
+ while let Some(line) = lines.next() {
+ if line.is_empty() {
+ break;
+ }
+ count += 1;
+ }
+ assert!(count > 0);
+ }
+ assert_eq!(lines.next(), None);
+}
+
+#[cargo_test(
+ nightly,
+ reason = "-Zfuture-incompat-test requires nightly (permanently)"
+)]
+fn color() {
+ let p = simple_project();
+
+ p.cargo("check")
+ .env("RUSTFLAGS", "-Zfuture-incompat-test")
+ .masquerade_as_nightly_cargo(&["future-incompat-test"])
+ .run();
+
+ p.cargo("report future-incompatibilities")
+ .with_stdout_does_not_contain("[..]\x1b[[..]")
+ .run();
+
+ p.cargo("report future-incompatibilities")
+ .env("CARGO_TERM_COLOR", "always")
+ .with_stdout_contains("[..]\x1b[[..]")
+ .run();
+}
+
+#[cargo_test(
+ nightly,
+ reason = "-Zfuture-incompat-test requires nightly (permanently)"
+)]
+fn bad_ids() {
+ let p = simple_project();
+
+ p.cargo("report future-incompatibilities --id 1")
+ .with_status(101)
+ .with_stderr("error: no reports are currently available")
+ .run();
+
+ p.cargo("check")
+ .env("RUSTFLAGS", "-Zfuture-incompat-test")
+ .masquerade_as_nightly_cargo(&["future-incompat-test"])
+ .run();
+
+ p.cargo("report future-incompatibilities --id foo")
+ .with_status(1)
+ .with_stderr("error: Invalid value: could not parse `foo` as a number")
+ .run();
+
+ p.cargo("report future-incompatibilities --id 7")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: could not find report with ID 7
+Available IDs are: 1
+",
+ )
+ .run();
+}
+
+#[cargo_test(
+ nightly,
+ reason = "-Zfuture-incompat-test requires nightly (permanently)"
+)]
+fn suggestions_for_updates() {
+ Package::new("with_updates", "1.0.0")
+ .file("src/lib.rs", FUTURE_EXAMPLE)
+ .publish();
+ Package::new("big_update", "1.0.0")
+ .file("src/lib.rs", FUTURE_EXAMPLE)
+ .publish();
+ Package::new("without_updates", "1.0.0")
+ .file("src/lib.rs", FUTURE_EXAMPLE)
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ with_updates = "1"
+ big_update = "1"
+ without_updates = "1"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("generate-lockfile").run();
+
+ Package::new("with_updates", "1.0.1")
+ .file("src/lib.rs", "")
+ .publish();
+ Package::new("with_updates", "1.0.2")
+ .file("src/lib.rs", "")
+ .publish();
+ Package::new("with_updates", "3.0.1")
+ .file("src/lib.rs", "")
+ .publish();
+ Package::new("big_update", "2.0.0")
+ .file("src/lib.rs", "")
+ .publish();
+
+ // This is a hack to force cargo to update the index. Cargo can't do this
+ // automatically because doing a network update on every build would be a
+ // bad idea. Under normal circumstances, we'll hope the user has done
+ // something else along the way to trigger an update (building some other
+ // project or something). This could use some more consideration of how to
+ // handle this better (maybe only trigger an update if it hasn't updated
+ // in a long while?).
+ p.cargo("update -p without_updates").run();
+
+ let update_message = "\
+- Some affected dependencies have newer versions available.
+You may want to consider updating them to a newer version to see if the issue has been fixed.
+
+big_update v1.0.0 has the following newer versions available: 2.0.0
+with_updates v1.0.0 has the following newer versions available: 1.0.1, 1.0.2, 3.0.1
+";
+
+ p.cargo("check --future-incompat-report")
+ .masquerade_as_nightly_cargo(&["future-incompat-test"])
+ .env("RUSTFLAGS", "-Zfuture-incompat-test")
+ .with_stderr_contains(update_message)
+ .run();
+
+ p.cargo("report future-incompatibilities")
+ .with_stdout_contains(update_message)
+ .run()
+}
diff --git a/src/tools/cargo/tests/testsuite/generate_lockfile.rs b/src/tools/cargo/tests/testsuite/generate_lockfile.rs
new file mode 100644
index 000000000..d2b633605
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/generate_lockfile.rs
@@ -0,0 +1,230 @@
+//! Tests for the `cargo generate-lockfile` command.
+
+use cargo_test_support::registry::{Package, RegistryBuilder};
+use cargo_test_support::{basic_manifest, paths, project, ProjectBuilder};
+use std::fs;
+
+#[cargo_test]
+fn adding_and_removing_packages() {
+ let p = project()
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("generate-lockfile").run();
+
+ let lock1 = p.read_lockfile();
+
+ // add a dep
+ p.change_file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.1"
+
+ [dependencies.bar]
+ path = "bar"
+ "#,
+ );
+ p.cargo("generate-lockfile").run();
+ let lock2 = p.read_lockfile();
+ assert_ne!(lock1, lock2);
+
+ // change the dep
+ p.change_file("bar/Cargo.toml", &basic_manifest("bar", "0.0.2"));
+ p.cargo("generate-lockfile").run();
+ let lock3 = p.read_lockfile();
+ assert_ne!(lock1, lock3);
+ assert_ne!(lock2, lock3);
+
+ // remove the dep
+ println!("lock4");
+ p.change_file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.1"
+ "#,
+ );
+ p.cargo("generate-lockfile").run();
+ let lock4 = p.read_lockfile();
+ assert_eq!(lock1, lock4);
+}
+
+#[cargo_test]
+fn no_index_update_sparse() {
+ let _registry = RegistryBuilder::new().http_index().build();
+ no_index_update();
+}
+
+#[cargo_test]
+fn no_index_update_git() {
+ no_index_update();
+}
+
+fn no_index_update() {
+ Package::new("serde", "1.0.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.1"
+
+ [dependencies]
+ serde = "1.0"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("generate-lockfile")
+ .with_stderr("[UPDATING] `[..]` index")
+ .run();
+
+ p.cargo("generate-lockfile -Zno-index-update")
+ .masquerade_as_nightly_cargo(&["no-index-update"])
+ .with_stdout("")
+ .with_stderr("")
+ .run();
+}
+
+#[cargo_test]
+fn preserve_metadata() {
+ let p = project()
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("generate-lockfile").run();
+
+ let metadata = r#"
+[metadata]
+bar = "baz"
+foo = "bar"
+"#;
+ let lock = p.read_lockfile();
+ let data = lock + metadata;
+ p.change_file("Cargo.lock", &data);
+
+ // Build and make sure the metadata is still there
+ p.cargo("build").run();
+ let lock = p.read_lockfile();
+ assert!(lock.contains(metadata.trim()), "{}", lock);
+
+ // Update and make sure the metadata is still there
+ p.cargo("update").run();
+ let lock = p.read_lockfile();
+ assert!(lock.contains(metadata.trim()), "{}", lock);
+}
+
+#[cargo_test]
+fn preserve_line_endings_issue_2076() {
+ let p = project()
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ let lockfile = p.root().join("Cargo.lock");
+ p.cargo("generate-lockfile").run();
+ assert!(lockfile.is_file());
+ p.cargo("generate-lockfile").run();
+
+ let lock0 = p.read_lockfile();
+
+ assert!(lock0.starts_with("# This file is automatically @generated by Cargo.\n# It is not intended for manual editing.\n"));
+
+ let lock1 = lock0.replace("\n", "\r\n");
+ p.change_file("Cargo.lock", &lock1);
+
+ p.cargo("generate-lockfile").run();
+
+ let lock2 = p.read_lockfile();
+
+ assert!(lock2.starts_with("# This file is automatically @generated by Cargo.\r\n# It is not intended for manual editing.\r\n"));
+ assert_eq!(lock1, lock2);
+}
+
+#[cargo_test]
+fn cargo_update_generate_lockfile() {
+ let p = project().file("src/main.rs", "fn main() {}").build();
+
+ let lockfile = p.root().join("Cargo.lock");
+ assert!(!lockfile.is_file());
+ p.cargo("update").with_stdout("").run();
+ assert!(lockfile.is_file());
+
+ fs::remove_file(p.root().join("Cargo.lock")).unwrap();
+
+ assert!(!lockfile.is_file());
+ p.cargo("update").with_stdout("").run();
+ assert!(lockfile.is_file());
+}
+
+#[cargo_test]
+fn duplicate_entries_in_lockfile() {
+ let _a = ProjectBuilder::new(paths::root().join("a"))
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ authors = []
+ version = "0.0.1"
+
+ [dependencies]
+ common = {path="common"}
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ let common_toml = &basic_manifest("common", "0.0.1");
+
+ let _common_in_a = ProjectBuilder::new(paths::root().join("a/common"))
+ .file("Cargo.toml", common_toml)
+ .file("src/lib.rs", "")
+ .build();
+
+ let b = ProjectBuilder::new(paths::root().join("b"))
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ authors = []
+ version = "0.0.1"
+
+ [dependencies]
+ common = {path="common"}
+ a = {path="../a"}
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ let _common_in_b = ProjectBuilder::new(paths::root().join("b/common"))
+ .file("Cargo.toml", common_toml)
+ .file("src/lib.rs", "")
+ .build();
+
+ // should fail due to a duplicate package `common` in the lock file
+ b.cargo("build")
+ .with_status(101)
+ .with_stderr_contains(
+ "[..]package collision in the lockfile: packages common [..] and \
+ common [..] are different, but only one can be written to \
+ lockfile unambiguously",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/git.rs b/src/tools/cargo/tests/testsuite/git.rs
new file mode 100644
index 000000000..b170c204f
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/git.rs
@@ -0,0 +1,3702 @@
+//! Tests for git support.
+
+use std::fs;
+use std::io::prelude::*;
+use std::net::{TcpListener, TcpStream};
+use std::path::Path;
+use std::str;
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::Arc;
+use std::thread;
+
+use cargo_test_support::git::cargo_uses_gitoxide;
+use cargo_test_support::paths::{self, CargoPathExt};
+use cargo_test_support::registry::Package;
+use cargo_test_support::{basic_lib_manifest, basic_manifest, git, main_file, path2url, project};
+use cargo_test_support::{sleep_ms, t, Project};
+
+#[cargo_test]
+fn cargo_compile_simple_git_dep() {
+ let project = project();
+ let git_project = git::new("dep1", |project| {
+ project
+ .file("Cargo.toml", &basic_lib_manifest("dep1"))
+ .file(
+ "src/dep1.rs",
+ r#"
+ pub fn hello() -> &'static str {
+ "hello world"
+ }
+ "#,
+ )
+ });
+
+ let project = project
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.dep1]
+
+ git = '{}'
+ "#,
+ git_project.url()
+ ),
+ )
+ .file(
+ "src/main.rs",
+ &main_file(r#""{}", dep1::hello()"#, &["dep1"]),
+ )
+ .build();
+
+ let git_root = git_project.root();
+
+ project
+ .cargo("build")
+ .with_stderr(&format!(
+ "[UPDATING] git repository `{}`\n\
+ [COMPILING] dep1 v0.5.0 ({}#[..])\n\
+ [COMPILING] foo v0.5.0 ([CWD])\n\
+ [FINISHED] dev [unoptimized + debuginfo] target(s) in [..]\n",
+ path2url(&git_root),
+ path2url(&git_root),
+ ))
+ .run();
+
+ assert!(project.bin("foo").is_file());
+
+ project
+ .process(&project.bin("foo"))
+ .with_stdout("hello world\n")
+ .run();
+}
+
+#[cargo_test]
+fn cargo_compile_git_dep_branch() {
+ let project = project();
+ let git_project = git::new("dep1", |project| {
+ project
+ .file("Cargo.toml", &basic_lib_manifest("dep1"))
+ .file(
+ "src/dep1.rs",
+ r#"
+ pub fn hello() -> &'static str {
+ "hello world"
+ }
+ "#,
+ )
+ });
+
+ // Make a new branch based on the current HEAD commit
+ let repo = git2::Repository::open(&git_project.root()).unwrap();
+ let head = repo.head().unwrap().target().unwrap();
+ let head = repo.find_commit(head).unwrap();
+ repo.branch("branchy", &head, true).unwrap();
+
+ let project = project
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.dep1]
+
+ git = '{}'
+ branch = "branchy"
+
+ "#,
+ git_project.url()
+ ),
+ )
+ .file(
+ "src/main.rs",
+ &main_file(r#""{}", dep1::hello()"#, &["dep1"]),
+ )
+ .build();
+
+ let git_root = git_project.root();
+
+ project
+ .cargo("build")
+ .with_stderr(&format!(
+ "[UPDATING] git repository `{}`\n\
+ [COMPILING] dep1 v0.5.0 ({}?branch=branchy#[..])\n\
+ [COMPILING] foo v0.5.0 ([CWD])\n\
+ [FINISHED] dev [unoptimized + debuginfo] target(s) in [..]\n",
+ path2url(&git_root),
+ path2url(&git_root),
+ ))
+ .run();
+
+ assert!(project.bin("foo").is_file());
+
+ project
+ .process(&project.bin("foo"))
+ .with_stdout("hello world\n")
+ .run();
+}
+
+#[cargo_test]
+fn cargo_compile_git_dep_tag() {
+ let project = project();
+ let git_project = git::new("dep1", |project| {
+ project
+ .file("Cargo.toml", &basic_lib_manifest("dep1"))
+ .file(
+ "src/dep1.rs",
+ r#"
+ pub fn hello() -> &'static str {
+ "hello world"
+ }
+ "#,
+ )
+ });
+
+ // Make a tag corresponding to the current HEAD
+ let repo = git2::Repository::open(&git_project.root()).unwrap();
+ let head = repo.head().unwrap().target().unwrap();
+ repo.tag(
+ "v0.1.0",
+ &repo.find_object(head, None).unwrap(),
+ &repo.signature().unwrap(),
+ "make a new tag",
+ false,
+ )
+ .unwrap();
+
+ let project = project
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.dep1]
+
+ git = '{}'
+ tag = "v0.1.0"
+ "#,
+ git_project.url()
+ ),
+ )
+ .file(
+ "src/main.rs",
+ &main_file(r#""{}", dep1::hello()"#, &["dep1"]),
+ )
+ .build();
+
+ let git_root = git_project.root();
+
+ project
+ .cargo("build")
+ .with_stderr(&format!(
+ "[UPDATING] git repository `{}`\n\
+ [COMPILING] dep1 v0.5.0 ({}?tag=v0.1.0#[..])\n\
+ [COMPILING] foo v0.5.0 ([CWD])\n\
+ [FINISHED] dev [unoptimized + debuginfo] target(s) in [..]\n",
+ path2url(&git_root),
+ path2url(&git_root),
+ ))
+ .run();
+
+ assert!(project.bin("foo").is_file());
+
+ project
+ .process(&project.bin("foo"))
+ .with_stdout("hello world\n")
+ .run();
+
+ project.cargo("build").run();
+}
+
+#[cargo_test]
+fn cargo_compile_git_dep_pull_request() {
+ let project = project();
+ let git_project = git::new("dep1", |project| {
+ project
+ .file("Cargo.toml", &basic_lib_manifest("dep1"))
+ .file(
+ "src/dep1.rs",
+ r#"
+ pub fn hello() -> &'static str {
+ "hello world"
+ }
+ "#,
+ )
+ });
+
+ // Make a reference in GitHub's pull request ref naming convention.
+ let repo = git2::Repository::open(&git_project.root()).unwrap();
+ let oid = repo.refname_to_id("HEAD").unwrap();
+ let force = false;
+ let log_message = "open pull request";
+ repo.reference("refs/pull/330/head", oid, force, log_message)
+ .unwrap();
+
+ let project = project
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+
+ [dependencies]
+ dep1 = {{ git = "{}", rev = "refs/pull/330/head" }}
+ "#,
+ git_project.url()
+ ),
+ )
+ .file(
+ "src/main.rs",
+ &main_file(r#""{}", dep1::hello()"#, &["dep1"]),
+ )
+ .build();
+
+ let git_root = git_project.root();
+
+ project
+ .cargo("build")
+ .with_stderr(&format!(
+ "[UPDATING] git repository `{}`\n\
+ [COMPILING] dep1 v0.5.0 ({}?rev=refs/pull/330/head#[..])\n\
+ [COMPILING] foo v0.0.0 ([CWD])\n\
+ [FINISHED] dev [unoptimized + debuginfo] target(s) in [..]\n",
+ path2url(&git_root),
+ path2url(&git_root),
+ ))
+ .run();
+
+ assert!(project.bin("foo").is_file());
+}
+
+#[cargo_test]
+fn cargo_compile_with_nested_paths() {
+ let git_project = git::new("dep1", |project| {
+ project
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "dep1"
+ version = "0.5.0"
+ authors = ["carlhuda@example.com"]
+
+ [dependencies.dep2]
+
+ version = "0.5.0"
+ path = "vendor/dep2"
+
+ [lib]
+
+ name = "dep1"
+ "#,
+ )
+ .file(
+ "src/dep1.rs",
+ r#"
+ extern crate dep2;
+
+ pub fn hello() -> &'static str {
+ dep2::hello()
+ }
+ "#,
+ )
+ .file("vendor/dep2/Cargo.toml", &basic_lib_manifest("dep2"))
+ .file(
+ "vendor/dep2/src/dep2.rs",
+ r#"
+ pub fn hello() -> &'static str {
+ "hello world"
+ }
+ "#,
+ )
+ });
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.dep1]
+
+ version = "0.5.0"
+ git = '{}'
+
+ [[bin]]
+
+ name = "foo"
+ "#,
+ git_project.url()
+ ),
+ )
+ .file(
+ "src/foo.rs",
+ &main_file(r#""{}", dep1::hello()"#, &["dep1"]),
+ )
+ .build();
+
+ p.cargo("build").run();
+
+ assert!(p.bin("foo").is_file());
+
+ p.process(&p.bin("foo")).with_stdout("hello world\n").run();
+}
+
+#[cargo_test]
+fn cargo_compile_with_malformed_nested_paths() {
+ let git_project = git::new("dep1", |project| {
+ project
+ .file("Cargo.toml", &basic_lib_manifest("dep1"))
+ .file(
+ "src/dep1.rs",
+ r#"
+ pub fn hello() -> &'static str {
+ "hello world"
+ }
+ "#,
+ )
+ .file("vendor/dep2/Cargo.toml", "!INVALID!")
+ .file(
+ "vendor/dep3/Cargo.toml",
+ r#"
+ [package]
+ name = "dep3"
+ version = "0.5.0"
+ [dependencies]
+ subdep1 = { path = "../require-extra-build-step" }
+ "#,
+ )
+ .file("vendor/dep3/src/lib.rs", "")
+ });
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.dep1]
+
+ version = "0.5.0"
+ git = '{}'
+
+ [[bin]]
+
+ name = "foo"
+ "#,
+ git_project.url()
+ ),
+ )
+ .file(
+ "src/foo.rs",
+ &main_file(r#""{}", dep1::hello()"#, &["dep1"]),
+ )
+ .build();
+
+ p.cargo("build").run();
+
+ assert!(p.bin("foo").is_file());
+
+ p.process(&p.bin("foo")).with_stdout("hello world\n").run();
+}
+
+#[cargo_test]
+fn cargo_compile_with_meta_package() {
+ let git_project = git::new("meta-dep", |project| {
+ project
+ .file("dep1/Cargo.toml", &basic_lib_manifest("dep1"))
+ .file(
+ "dep1/src/dep1.rs",
+ r#"
+ pub fn hello() -> &'static str {
+ "this is dep1"
+ }
+ "#,
+ )
+ .file("dep2/Cargo.toml", &basic_lib_manifest("dep2"))
+ .file(
+ "dep2/src/dep2.rs",
+ r#"
+ pub fn hello() -> &'static str {
+ "this is dep2"
+ }
+ "#,
+ )
+ });
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.dep1]
+
+ version = "0.5.0"
+ git = '{}'
+
+ [dependencies.dep2]
+
+ version = "0.5.0"
+ git = '{}'
+
+ [[bin]]
+
+ name = "foo"
+ "#,
+ git_project.url(),
+ git_project.url()
+ ),
+ )
+ .file(
+ "src/foo.rs",
+ &main_file(
+ r#""{} {}", dep1::hello(), dep2::hello()"#,
+ &["dep1", "dep2"],
+ ),
+ )
+ .build();
+
+ p.cargo("build").run();
+
+ assert!(p.bin("foo").is_file());
+
+ p.process(&p.bin("foo"))
+ .with_stdout("this is dep1 this is dep2\n")
+ .run();
+}
+
+#[cargo_test]
+fn cargo_compile_with_short_ssh_git() {
+ let url = "git@github.com:a/dep";
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.dep]
+
+ git = "{}"
+
+ [[bin]]
+
+ name = "foo"
+ "#,
+ url
+ ),
+ )
+ .file(
+ "src/foo.rs",
+ &main_file(r#""{}", dep1::hello()"#, &["dep1"]),
+ )
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stdout("")
+ .with_stderr(&format!(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ invalid url `{}`: relative URL without a base
+",
+ url
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn two_revs_same_deps() {
+ let bar = git::new("meta-dep", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("bar", "0.0.0"))
+ .file("src/lib.rs", "pub fn bar() -> i32 { 1 }")
+ });
+
+ let repo = git2::Repository::open(&bar.root()).unwrap();
+ let rev1 = repo.revparse_single("HEAD").unwrap().id();
+
+ // Commit the changes and make sure we trigger a recompile
+ bar.change_file("src/lib.rs", "pub fn bar() -> i32 { 2 }");
+ git::add(&repo);
+ let rev2 = git::commit(&repo);
+
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+
+ [dependencies.bar]
+ git = '{}'
+ rev = "{}"
+
+ [dependencies.baz]
+ path = "../baz"
+ "#,
+ bar.url(),
+ rev1
+ ),
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ extern crate bar;
+ extern crate baz;
+
+ fn main() {
+ assert_eq!(bar::bar(), 1);
+ assert_eq!(baz::baz(), 2);
+ }
+ "#,
+ )
+ .build();
+
+ let _baz = project()
+ .at("baz")
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "baz"
+ version = "0.0.0"
+ authors = []
+
+ [dependencies.bar]
+ git = '{}'
+ rev = "{}"
+ "#,
+ bar.url(),
+ rev2
+ ),
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ extern crate bar;
+ pub fn baz() -> i32 { bar::bar() }
+ "#,
+ )
+ .build();
+
+ foo.cargo("build -v").run();
+ assert!(foo.bin("foo").is_file());
+ foo.process(&foo.bin("foo")).run();
+}
+
+#[cargo_test]
+fn recompilation() {
+ let git_project = git::new("bar", |project| {
+ project
+ .file("Cargo.toml", &basic_lib_manifest("bar"))
+ .file("src/bar.rs", "pub fn bar() {}")
+ });
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.bar]
+
+ version = "0.5.0"
+ git = '{}'
+ "#,
+ git_project.url()
+ ),
+ )
+ .file("src/main.rs", &main_file(r#""{:?}", bar::bar()"#, &["bar"]))
+ .build();
+
+ // First time around we should compile both foo and bar
+ p.cargo("check")
+ .with_stderr(&format!(
+ "[UPDATING] git repository `{}`\n\
+ [CHECKING] bar v0.5.0 ({}#[..])\n\
+ [CHECKING] foo v0.5.0 ([CWD])\n\
+ [FINISHED] dev [unoptimized + debuginfo] target(s) \
+ in [..]\n",
+ git_project.url(),
+ git_project.url(),
+ ))
+ .run();
+
+ // Don't recompile the second time
+ p.cargo("check").with_stdout("").run();
+
+ // Modify a file manually, shouldn't trigger a recompile
+ git_project.change_file("src/bar.rs", r#"pub fn bar() { println!("hello!"); }"#);
+
+ p.cargo("check").with_stdout("").run();
+
+ p.cargo("update")
+ .with_stderr(&format!(
+ "[UPDATING] git repository `{}`",
+ git_project.url()
+ ))
+ .run();
+
+ p.cargo("check").with_stdout("").run();
+
+ // Commit the changes and make sure we don't trigger a recompile because the
+ // lock file says not to change
+ let repo = git2::Repository::open(&git_project.root()).unwrap();
+ git::add(&repo);
+ git::commit(&repo);
+
+ println!("compile after commit");
+ p.cargo("check").with_stdout("").run();
+ p.root().move_into_the_past();
+
+ // Update the dependency and carry on!
+ p.cargo("update")
+ .with_stderr(&format!(
+ "[UPDATING] git repository `{}`\n\
+ [UPDATING] bar v0.5.0 ([..]) -> #[..]\n\
+ ",
+ git_project.url()
+ ))
+ .run();
+ println!("going for the last compile");
+ p.cargo("check")
+ .with_stderr(&format!(
+ "[CHECKING] bar v0.5.0 ({}#[..])\n\
+ [CHECKING] foo v0.5.0 ([CWD])\n\
+ [FINISHED] dev [unoptimized + debuginfo] target(s) \
+ in [..]\n",
+ git_project.url(),
+ ))
+ .run();
+
+ // Make sure clean only cleans one dep
+ p.cargo("clean -p foo").with_stdout("").run();
+ p.cargo("check")
+ .with_stderr(
+ "[CHECKING] foo v0.5.0 ([CWD])\n\
+ [FINISHED] dev [unoptimized + debuginfo] target(s) \
+ in [..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn update_with_shared_deps() {
+ let git_project = git::new("bar", |project| {
+ project
+ .file("Cargo.toml", &basic_lib_manifest("bar"))
+ .file("src/bar.rs", "pub fn bar() {}")
+ });
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.dep1]
+ path = "dep1"
+ [dependencies.dep2]
+ path = "dep2"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ #[allow(unused_extern_crates)]
+ extern crate dep1;
+ #[allow(unused_extern_crates)]
+ extern crate dep2;
+ fn main() {}
+ "#,
+ )
+ .file(
+ "dep1/Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "dep1"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.bar]
+ version = "0.5.0"
+ git = '{}'
+ "#,
+ git_project.url()
+ ),
+ )
+ .file("dep1/src/lib.rs", "")
+ .file(
+ "dep2/Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "dep2"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.bar]
+ version = "0.5.0"
+ git = '{}'
+ "#,
+ git_project.url()
+ ),
+ )
+ .file("dep2/src/lib.rs", "")
+ .build();
+
+ // First time around we should compile both foo and bar
+ p.cargo("check")
+ .with_stderr(&format!(
+ "\
+[UPDATING] git repository `{git}`
+[CHECKING] bar v0.5.0 ({git}#[..])
+[CHECKING] [..] v0.5.0 ([..])
+[CHECKING] [..] v0.5.0 ([..])
+[CHECKING] foo v0.5.0 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]\n",
+ git = git_project.url(),
+ ))
+ .run();
+
+ // Modify a file manually, and commit it
+ git_project.change_file("src/bar.rs", r#"pub fn bar() { println!("hello!"); }"#);
+ let repo = git2::Repository::open(&git_project.root()).unwrap();
+ let old_head = repo.head().unwrap().target().unwrap();
+ git::add(&repo);
+ git::commit(&repo);
+
+ sleep_ms(1000);
+
+ // By default, not transitive updates
+ println!("dep1 update");
+ p.cargo("update -p dep1").with_stdout("").run();
+
+ // Don't do anything bad on a weird --precise argument
+ println!("bar bad precise update");
+ p.cargo("update -p bar --precise 0.1.2")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] Unable to update [..]
+
+Caused by:
+ precise value for git is not a git revision: 0.1.2
+
+Caused by:
+ unable to parse OID - contains invalid characters; class=Invalid (3)
+",
+ )
+ .run();
+
+ // Specifying a precise rev to the old rev shouldn't actually update
+ // anything because we already have the rev in the db.
+ println!("bar precise update");
+ p.cargo("update -p bar --precise")
+ .arg(&old_head.to_string())
+ .with_stdout("")
+ .run();
+
+ // Updating aggressively should, however, update the repo.
+ println!("dep1 aggressive update");
+ p.cargo("update -p dep1 --aggressive")
+ .with_stderr(&format!(
+ "[UPDATING] git repository `{}`\n\
+ [UPDATING] bar v0.5.0 ([..]) -> #[..]\n\
+ ",
+ git_project.url()
+ ))
+ .run();
+
+ // Make sure we still only compile one version of the git repo
+ println!("build");
+ p.cargo("check")
+ .with_stderr(&format!(
+ "\
+[CHECKING] bar v0.5.0 ({git}#[..])
+[CHECKING] [..] v0.5.0 ([CWD][..]dep[..])
+[CHECKING] [..] v0.5.0 ([CWD][..]dep[..])
+[CHECKING] foo v0.5.0 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]\n",
+ git = git_project.url(),
+ ))
+ .run();
+
+ // We should be able to update transitive deps
+ p.cargo("update -p bar")
+ .with_stderr(&format!(
+ "[UPDATING] git repository `{}`",
+ git_project.url()
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn dep_with_submodule() {
+ let project = project();
+ let git_project = git::new("dep1", |project| {
+ project.file("Cargo.toml", &basic_manifest("dep1", "0.5.0"))
+ });
+ let git_project2 = git::new("dep2", |project| project.file("lib.rs", "pub fn dep() {}"));
+
+ let repo = git2::Repository::open(&git_project.root()).unwrap();
+ let url = path2url(git_project2.root()).to_string();
+ git::add_submodule(&repo, &url, Path::new("src"));
+ git::commit(&repo);
+
+ let project = project
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.dep1]
+
+ git = '{}'
+ "#,
+ git_project.url()
+ ),
+ )
+ .file(
+ "src/lib.rs",
+ "extern crate dep1; pub fn foo() { dep1::dep() }",
+ )
+ .build();
+
+ project
+ .cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] git repository [..]
+[UPDATING] git submodule `file://[..]/dep2`
+[CHECKING] dep1 [..]
+[CHECKING] foo [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]\n",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn dep_with_relative_submodule() {
+ let foo = project();
+ let base = git::new("base", |project| {
+ project
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "base"
+ version = "0.5.0"
+
+ [dependencies]
+ deployment.path = "deployment"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn dep() {
+ deployment::deployment_func();
+ }
+ "#,
+ )
+ });
+ let _deployment = git::new("deployment", |project| {
+ project
+ .file("src/lib.rs", "pub fn deployment_func() {}")
+ .file("Cargo.toml", &basic_lib_manifest("deployment"))
+ });
+
+ let base_repo = git2::Repository::open(&base.root()).unwrap();
+ git::add_submodule(&base_repo, "../deployment", Path::new("deployment"));
+ git::commit(&base_repo);
+
+ let project = foo
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+
+ [dependencies.base]
+ git = '{}'
+ "#,
+ base.url()
+ ),
+ )
+ .file("src/lib.rs", "pub fn foo() { }")
+ .build();
+
+ project
+ .cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] git repository [..]
+[UPDATING] git submodule `file://[..]/deployment`
+[CHECKING] deployment [..]
+[CHECKING] base [..]
+[CHECKING] foo [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]\n",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn dep_with_bad_submodule() {
+ let project = project();
+ let git_project = git::new("dep1", |project| {
+ project.file("Cargo.toml", &basic_manifest("dep1", "0.5.0"))
+ });
+ let git_project2 = git::new("dep2", |project| project.file("lib.rs", "pub fn dep() {}"));
+
+ let repo = git2::Repository::open(&git_project.root()).unwrap();
+ let url = path2url(git_project2.root()).to_string();
+ git::add_submodule(&repo, &url, Path::new("src"));
+ git::commit(&repo);
+
+ // now amend the first commit on git_project2 to make submodule ref point to not-found
+ // commit
+ let repo = git2::Repository::open(&git_project2.root()).unwrap();
+ let original_submodule_ref = repo.refname_to_id("refs/heads/master").unwrap();
+ let commit = repo.find_commit(original_submodule_ref).unwrap();
+ commit
+ .amend(
+ Some("refs/heads/master"),
+ None,
+ None,
+ None,
+ Some("something something"),
+ None,
+ )
+ .unwrap();
+
+ let p = project
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.dep1]
+
+ git = '{}'
+ "#,
+ git_project.url()
+ ),
+ )
+ .file(
+ "src/lib.rs",
+ "extern crate dep1; pub fn foo() { dep1::dep() }",
+ )
+ .build();
+
+ let expected = format!(
+ "\
+[UPDATING] git repository [..]
+[UPDATING] git submodule `file://[..]/dep2`
+[ERROR] failed to get `dep1` as a dependency of package `foo v0.5.0 [..]`
+
+Caused by:
+ failed to load source for dependency `dep1`
+
+Caused by:
+ Unable to update {}
+
+Caused by:
+ failed to update submodule `src`
+
+Caused by:
+ object not found - no match for id [..]
+",
+ path2url(git_project.root())
+ );
+
+ p.cargo("check")
+ .with_stderr(expected)
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn dep_with_skipped_submodule() {
+ // Ensure we skip dependency submodules if their update strategy is `none`.
+ let qux = git::new("qux", |project| {
+ project.no_manifest().file("README", "skip me")
+ });
+
+ let bar = git::new("bar", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("bar", "0.0.0"))
+ .file("src/lib.rs", "")
+ });
+
+ // `qux` is a submodule of `bar`, but we don't want to update it.
+ let repo = git2::Repository::open(&bar.root()).unwrap();
+ git::add_submodule(&repo, qux.url().as_str(), Path::new("qux"));
+
+ let mut conf = git2::Config::open(&bar.root().join(".gitmodules")).unwrap();
+ conf.set_str("submodule.qux.update", "none").unwrap();
+
+ git::add(&repo);
+ git::commit(&repo);
+
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+
+ [dependencies.bar]
+ git = "{}"
+ "#,
+ bar.url()
+ ),
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ foo.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] git repository `file://[..]/bar`
+[SKIPPING] git submodule `file://[..]/qux` [..]
+[CHECKING] bar [..]
+[CHECKING] foo [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]\n",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn ambiguous_published_deps() {
+ let project = project();
+ let git_project = git::new("dep", |project| {
+ project
+ .file(
+ "aaa/Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "bar"
+ version = "0.5.0"
+ publish = true
+ "#
+ ),
+ )
+ .file("aaa/src/lib.rs", "")
+ .file(
+ "bbb/Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "bar"
+ version = "0.5.0"
+ publish = true
+ "#
+ ),
+ )
+ .file("bbb/src/lib.rs", "")
+ });
+
+ let p = project
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.bar]
+ git = '{}'
+ "#,
+ git_project.url()
+ ),
+ )
+ .file("src/main.rs", "fn main() { }")
+ .build();
+
+ p.cargo("build").run();
+ p.cargo("run")
+ .with_stderr(
+ "\
+[WARNING] skipping duplicate package `bar` found at `[..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `target/debug/foo[EXE]`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn two_deps_only_update_one() {
+ let project = project();
+ let git1 = git::new("dep1", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("dep1", "0.5.0"))
+ .file("src/lib.rs", "")
+ });
+ let git2 = git::new("dep2", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("dep2", "0.5.0"))
+ .file("src/lib.rs", "")
+ });
+
+ let p = project
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.dep1]
+ git = '{}'
+ [dependencies.dep2]
+ git = '{}'
+ "#,
+ git1.url(),
+ git2.url()
+ ),
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ fn oid_to_short_sha(oid: git2::Oid) -> String {
+ oid.to_string()[..8].to_string()
+ }
+ fn git_repo_head_sha(p: &Project) -> String {
+ let repo = git2::Repository::open(p.root()).unwrap();
+ let head = repo.head().unwrap().target().unwrap();
+ oid_to_short_sha(head)
+ }
+
+ println!("dep1 head sha: {}", git_repo_head_sha(&git1));
+ println!("dep2 head sha: {}", git_repo_head_sha(&git2));
+
+ p.cargo("check")
+ .with_stderr(
+ "[UPDATING] git repository `[..]`\n\
+ [UPDATING] git repository `[..]`\n\
+ [CHECKING] [..] v0.5.0 ([..])\n\
+ [CHECKING] [..] v0.5.0 ([..])\n\
+ [CHECKING] foo v0.5.0 ([CWD])\n\
+ [FINISHED] dev [unoptimized + debuginfo] target(s) in [..]\n",
+ )
+ .run();
+
+ git1.change_file("src/lib.rs", "pub fn foo() {}");
+ let repo = git2::Repository::open(&git1.root()).unwrap();
+ git::add(&repo);
+ let oid = git::commit(&repo);
+ println!("dep1 head sha: {}", oid_to_short_sha(oid));
+
+ p.cargo("update -p dep1")
+ .with_stderr(&format!(
+ "[UPDATING] git repository `{}`\n\
+ [UPDATING] dep1 v0.5.0 ([..]) -> #[..]\n\
+ ",
+ git1.url()
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn stale_cached_version() {
+ let bar = git::new("meta-dep", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("bar", "0.0.0"))
+ .file("src/lib.rs", "pub fn bar() -> i32 { 1 }")
+ });
+
+ // Update the git database in the cache with the current state of the git
+ // repo
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+
+ [dependencies.bar]
+ git = '{}'
+ "#,
+ bar.url()
+ ),
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ extern crate bar;
+
+ fn main() { assert_eq!(bar::bar(), 1) }
+ "#,
+ )
+ .build();
+
+ foo.cargo("build").run();
+ foo.process(&foo.bin("foo")).run();
+
+ // Update the repo, and simulate someone else updating the lock file and then
+ // us pulling it down.
+ bar.change_file("src/lib.rs", "pub fn bar() -> i32 { 1 + 0 }");
+ let repo = git2::Repository::open(&bar.root()).unwrap();
+ git::add(&repo);
+ git::commit(&repo);
+
+ sleep_ms(1000);
+
+ let rev = repo.revparse_single("HEAD").unwrap().id();
+
+ foo.change_file(
+ "Cargo.lock",
+ &format!(
+ r#"
+ [[package]]
+ name = "foo"
+ version = "0.0.0"
+ dependencies = [
+ 'bar 0.0.0 (git+{url}#{hash})'
+ ]
+
+ [[package]]
+ name = "bar"
+ version = "0.0.0"
+ source = 'git+{url}#{hash}'
+ "#,
+ url = bar.url(),
+ hash = rev
+ ),
+ );
+
+ // Now build!
+ foo.cargo("build")
+ .with_stderr(&format!(
+ "\
+[UPDATING] git repository `{bar}`
+[COMPILING] bar v0.0.0 ({bar}#[..])
+[COMPILING] foo v0.0.0 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ bar = bar.url(),
+ ))
+ .run();
+ foo.process(&foo.bin("foo")).run();
+}
+
+#[cargo_test]
+fn dep_with_changed_submodule() {
+ let project = project();
+ let git_project = git::new("dep1", |project| {
+ project.file("Cargo.toml", &basic_manifest("dep1", "0.5.0"))
+ });
+
+ let git_project2 = git::new("dep2", |project| {
+ project.file("lib.rs", "pub fn dep() -> &'static str { \"project2\" }")
+ });
+
+ let git_project3 = git::new("dep3", |project| {
+ project.file("lib.rs", "pub fn dep() -> &'static str { \"project3\" }")
+ });
+
+ let repo = git2::Repository::open(&git_project.root()).unwrap();
+ let mut sub = git::add_submodule(&repo, &git_project2.url().to_string(), Path::new("src"));
+ git::commit(&repo);
+
+ let p = project
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+ [dependencies.dep1]
+ git = '{}'
+ "#,
+ git_project.url()
+ ),
+ )
+ .file(
+ "src/main.rs",
+ "
+ extern crate dep1;
+ pub fn main() { println!(\"{}\", dep1::dep()) }
+ ",
+ )
+ .build();
+
+ println!("first run");
+ p.cargo("run")
+ .with_stderr(
+ "[UPDATING] git repository `[..]`\n\
+ [UPDATING] git submodule `file://[..]/dep2`\n\
+ [COMPILING] dep1 v0.5.0 ([..])\n\
+ [COMPILING] foo v0.5.0 ([..])\n\
+ [FINISHED] dev [unoptimized + debuginfo] target(s) in \
+ [..]\n\
+ [RUNNING] `target/debug/foo[EXE]`\n",
+ )
+ .with_stdout("project2\n")
+ .run();
+
+ git_project.change_file(
+ ".gitmodules",
+ &format!(
+ "[submodule \"src\"]\n\tpath = src\n\turl={}",
+ git_project3.url()
+ ),
+ );
+
+ // Sync the submodule and reset it to the new remote.
+ sub.sync().unwrap();
+ {
+ let subrepo = sub.open().unwrap();
+ subrepo
+ .remote_add_fetch("origin", "refs/heads/*:refs/heads/*")
+ .unwrap();
+ subrepo
+ .remote_set_url("origin", &git_project3.url().to_string())
+ .unwrap();
+ let mut origin = subrepo.find_remote("origin").unwrap();
+ origin.fetch(&Vec::<String>::new(), None, None).unwrap();
+ let id = subrepo.refname_to_id("refs/remotes/origin/master").unwrap();
+ let obj = subrepo.find_object(id, None).unwrap();
+ subrepo.reset(&obj, git2::ResetType::Hard, None).unwrap();
+ }
+ sub.add_to_index(true).unwrap();
+ git::add(&repo);
+ git::commit(&repo);
+
+ sleep_ms(1000);
+ // Update the dependency and carry on!
+ println!("update");
+ p.cargo("update -v")
+ .with_stderr("")
+ .with_stderr(&format!(
+ "[UPDATING] git repository `{}`\n\
+ [UPDATING] git submodule `file://[..]/dep3`\n\
+ [UPDATING] dep1 v0.5.0 ([..]) -> #[..]\n\
+ ",
+ git_project.url()
+ ))
+ .run();
+
+ println!("last run");
+ p.cargo("run")
+ .with_stderr(
+ "[COMPILING] dep1 v0.5.0 ([..])\n\
+ [COMPILING] foo v0.5.0 ([..])\n\
+ [FINISHED] dev [unoptimized + debuginfo] target(s) in \
+ [..]\n\
+ [RUNNING] `target/debug/foo[EXE]`\n",
+ )
+ .with_stdout("project3\n")
+ .run();
+}
+
+#[cargo_test]
+fn dev_deps_with_testing() {
+ let p2 = git::new("bar", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("bar", "0.5.0"))
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn gimme() -> &'static str { "zoidberg" }
+ "#,
+ )
+ });
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dev-dependencies.bar]
+ version = "0.5.0"
+ git = '{}'
+ "#,
+ p2.url()
+ ),
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {}
+
+ #[cfg(test)]
+ mod tests {
+ extern crate bar;
+ #[test] fn foo() { bar::gimme(); }
+ }
+ "#,
+ )
+ .build();
+
+ // Generate a lock file which did not use `bar` to compile, but had to update
+ // `bar` to generate the lock file
+ p.cargo("check")
+ .with_stderr(&format!(
+ "\
+[UPDATING] git repository `{bar}`
+[CHECKING] foo v0.5.0 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ bar = p2.url()
+ ))
+ .run();
+
+ // Make sure we use the previous resolution of `bar` instead of updating it
+ // a second time.
+ p.cargo("test")
+ .with_stderr(
+ "\
+[COMPILING] [..] v0.5.0 ([..])
+[COMPILING] [..] v0.5.0 ([..]
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo-[..][EXE])",
+ )
+ .with_stdout_contains("test tests::foo ... ok")
+ .run();
+}
+
+#[cargo_test]
+fn git_build_cmd_freshness() {
+ let foo = git::new("foo", |project| {
+ project
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file("build.rs", "fn main() {}")
+ .file("src/lib.rs", "pub fn bar() -> i32 { 1 }")
+ .file(".gitignore", "src/bar.rs")
+ });
+ foo.root().move_into_the_past();
+
+ sleep_ms(1000);
+
+ foo.cargo("check")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.0 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ // Smoke test to make sure it doesn't compile again
+ println!("first pass");
+ foo.cargo("check").with_stdout("").run();
+
+ // Modify an ignored file and make sure we don't rebuild
+ println!("second pass");
+ foo.change_file("src/bar.rs", "");
+ foo.cargo("check").with_stdout("").run();
+}
+
+#[cargo_test]
+fn git_name_not_always_needed() {
+ let p2 = git::new("bar", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("bar", "0.5.0"))
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn gimme() -> &'static str { "zoidberg" }
+ "#,
+ )
+ });
+
+ let repo = git2::Repository::open(&p2.root()).unwrap();
+ let mut cfg = repo.config().unwrap();
+ let _ = cfg.remove("user.name");
+ let _ = cfg.remove("user.email");
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+
+ [dev-dependencies.bar]
+ git = '{}'
+ "#,
+ p2.url()
+ ),
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ // Generate a lock file which did not use `bar` to compile, but had to update
+ // `bar` to generate the lock file
+ p.cargo("check")
+ .with_stderr(&format!(
+ "\
+[UPDATING] git repository `{bar}`
+[CHECKING] foo v0.5.0 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ bar = p2.url()
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn git_repo_changing_no_rebuild() {
+ let bar = git::new("bar", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("bar", "0.5.0"))
+ .file("src/lib.rs", "pub fn bar() -> i32 { 1 }")
+ });
+
+ // Lock p1 to the first rev in the git repo
+ let p1 = project()
+ .at("p1")
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "p1"
+ version = "0.5.0"
+ authors = []
+ build = 'build.rs'
+ [dependencies.bar]
+ git = '{}'
+ "#,
+ bar.url()
+ ),
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("build.rs", "fn main() {}")
+ .build();
+ p1.root().move_into_the_past();
+ p1.cargo("check")
+ .with_stderr(&format!(
+ "\
+[UPDATING] git repository `{bar}`
+[COMPILING] [..]
+[CHECKING] [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ bar = bar.url()
+ ))
+ .run();
+
+ // Make a commit to lock p2 to a different rev
+ bar.change_file("src/lib.rs", "pub fn bar() -> i32 { 2 }");
+ let repo = git2::Repository::open(&bar.root()).unwrap();
+ git::add(&repo);
+ git::commit(&repo);
+
+ // Lock p2 to the second rev
+ let p2 = project()
+ .at("p2")
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "p2"
+ version = "0.5.0"
+ authors = []
+ [dependencies.bar]
+ git = '{}'
+ "#,
+ bar.url()
+ ),
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+ p2.cargo("check")
+ .with_stderr(&format!(
+ "\
+[UPDATING] git repository `{bar}`
+[CHECKING] [..]
+[CHECKING] [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ bar = bar.url()
+ ))
+ .run();
+
+ // And now for the real test! Make sure that p1 doesn't get rebuilt
+ // even though the git repo has changed.
+ p1.cargo("check").with_stdout("").run();
+}
+
+#[cargo_test]
+fn git_dep_build_cmd() {
+ let p = git::new("foo", |project| {
+ project
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.bar]
+
+ version = "0.5.0"
+ path = "bar"
+
+ [[bin]]
+
+ name = "foo"
+ "#,
+ )
+ .file("src/foo.rs", &main_file(r#""{}", bar::gimme()"#, &["bar"]))
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+
+ name = "bar"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+ build = "build.rs"
+
+ [lib]
+ name = "bar"
+ path = "src/bar.rs"
+ "#,
+ )
+ .file(
+ "bar/src/bar.rs.in",
+ r#"
+ pub fn gimme() -> i32 { 0 }
+ "#,
+ )
+ .file(
+ "bar/build.rs",
+ r#"
+ use std::fs;
+ fn main() {
+ fs::copy("src/bar.rs.in", "src/bar.rs").unwrap();
+ }
+ "#,
+ )
+ });
+
+ p.root().join("bar").move_into_the_past();
+
+ p.cargo("build").run();
+
+ p.process(&p.bin("foo")).with_stdout("0\n").run();
+
+ // Touching bar.rs.in should cause the `build` command to run again.
+ p.change_file("bar/src/bar.rs.in", "pub fn gimme() -> i32 { 1 }");
+
+ p.cargo("build").run();
+
+ p.process(&p.bin("foo")).with_stdout("1\n").run();
+}
+
+#[cargo_test]
+fn fetch_downloads() {
+ let bar = git::new("bar", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("bar", "0.5.0"))
+ .file("src/lib.rs", "pub fn bar() -> i32 { 1 }")
+ });
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ [dependencies.bar]
+ git = '{}'
+ "#,
+ bar.url()
+ ),
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+ p.cargo("fetch")
+ .with_stderr(&format!(
+ "[UPDATING] git repository `{url}`",
+ url = bar.url()
+ ))
+ .run();
+
+ p.cargo("fetch").with_stdout("").run();
+}
+
+#[cargo_test]
+fn fetch_downloads_with_git2_first_then_with_gitoxide_and_vice_versa() {
+ let bar = git::new("bar", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("bar", "0.5.0"))
+ .file("src/lib.rs", "pub fn bar() -> i32 { 1 }")
+ });
+ let feature_configuration = if cargo_uses_gitoxide() {
+ // When we are always using `gitoxide` by default, create the registry with git2 as well as the download…
+ "-Zgitoxide=internal-use-git2"
+ } else {
+ // …otherwise create the registry and the git download with `gitoxide`.
+ "-Zgitoxide=fetch"
+ };
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ [dependencies.bar]
+ git = '{url}'
+ "#,
+ url = bar.url()
+ ),
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+ p.cargo("fetch")
+ .arg(feature_configuration)
+ .masquerade_as_nightly_cargo(&["unstable features must be available for -Z gitoxide"])
+ .with_stderr(&format!(
+ "[UPDATING] git repository `{url}`",
+ url = bar.url()
+ ))
+ .run();
+
+ Package::new("bar", "1.0.0").publish(); // trigger a crates-index change.
+ p.cargo("fetch").with_stdout("").run();
+}
+
+#[cargo_test]
+fn warnings_in_git_dep() {
+ let bar = git::new("bar", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("bar", "0.5.0"))
+ .file("src/lib.rs", "fn unused() {}")
+ });
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ [dependencies.bar]
+ git = '{}'
+ "#,
+ bar.url()
+ ),
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(&format!(
+ "[UPDATING] git repository `{}`\n\
+ [CHECKING] bar v0.5.0 ({}#[..])\n\
+ [CHECKING] foo v0.5.0 ([CWD])\n\
+ [FINISHED] dev [unoptimized + debuginfo] target(s) in [..]\n",
+ bar.url(),
+ bar.url(),
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn update_ambiguous() {
+ let bar1 = git::new("bar1", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("bar", "0.5.0"))
+ .file("src/lib.rs", "")
+ });
+ let bar2 = git::new("bar2", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("bar", "0.6.0"))
+ .file("src/lib.rs", "")
+ });
+ let baz = git::new("baz", |project| {
+ project
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "baz"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.bar]
+ git = '{}'
+ "#,
+ bar2.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ });
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ [dependencies.bar]
+ git = '{}'
+ [dependencies.baz]
+ git = '{}'
+ "#,
+ bar1.url(),
+ baz.url()
+ ),
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("generate-lockfile").run();
+ p.cargo("update -p bar")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] There are multiple `bar` packages in your project, and the specification `bar` \
+is ambiguous.
+Please re-run this command with `-p <spec>` where `<spec>` is one of the \
+following:
+ bar@0.[..].0
+ bar@0.[..].0
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn update_one_dep_in_repo_with_many_deps() {
+ let bar = git::new("bar", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("bar", "0.5.0"))
+ .file("src/lib.rs", "")
+ .file("a/Cargo.toml", &basic_manifest("a", "0.5.0"))
+ .file("a/src/lib.rs", "")
+ });
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ [dependencies.bar]
+ git = '{}'
+ [dependencies.a]
+ git = '{}'
+ "#,
+ bar.url(),
+ bar.url()
+ ),
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("generate-lockfile").run();
+ p.cargo("update -p bar")
+ .with_stderr(&format!("[UPDATING] git repository `{}`", bar.url()))
+ .run();
+}
+
+#[cargo_test]
+fn switch_deps_does_not_update_transitive() {
+ let transitive = git::new("transitive", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("transitive", "0.5.0"))
+ .file("src/lib.rs", "")
+ });
+ let dep1 = git::new("dep1", |project| {
+ project
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "dep"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.transitive]
+ git = '{}'
+ "#,
+ transitive.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ });
+ let dep2 = git::new("dep2", |project| {
+ project
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "dep"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.transitive]
+ git = '{}'
+ "#,
+ transitive.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ });
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ [dependencies.dep]
+ git = '{}'
+ "#,
+ dep1.url()
+ ),
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(&format!(
+ "\
+[UPDATING] git repository `{}`
+[UPDATING] git repository `{}`
+[CHECKING] transitive [..]
+[CHECKING] dep [..]
+[CHECKING] foo [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ dep1.url(),
+ transitive.url()
+ ))
+ .run();
+
+ // Update the dependency to point to the second repository, but this
+ // shouldn't update the transitive dependency which is the same.
+ p.change_file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ [dependencies.dep]
+ git = '{}'
+ "#,
+ dep2.url()
+ ),
+ );
+
+ p.cargo("check")
+ .with_stderr(&format!(
+ "\
+[UPDATING] git repository `{}`
+[CHECKING] dep [..]
+[CHECKING] foo [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ dep2.url()
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn update_one_source_updates_all_packages_in_that_git_source() {
+ let dep = git::new("dep", |project| {
+ project
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "dep"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies.a]
+ path = "a"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("a/Cargo.toml", &basic_manifest("a", "0.5.0"))
+ .file("a/src/lib.rs", "")
+ });
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ [dependencies.dep]
+ git = '{}'
+ "#,
+ dep.url()
+ ),
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check").run();
+
+ let repo = git2::Repository::open(&dep.root()).unwrap();
+ let rev1 = repo.revparse_single("HEAD").unwrap().id();
+
+ // Just be sure to change a file
+ dep.change_file("src/lib.rs", "pub fn bar() -> i32 { 2 }");
+ git::add(&repo);
+ git::commit(&repo);
+
+ p.cargo("update -p dep").run();
+ let lockfile = p.read_lockfile();
+ assert!(
+ !lockfile.contains(&rev1.to_string()),
+ "{} in {}",
+ rev1,
+ lockfile
+ );
+}
+
+#[cargo_test]
+fn switch_sources() {
+ let a1 = git::new("a1", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("a", "0.5.0"))
+ .file("src/lib.rs", "")
+ });
+ let a2 = git::new("a2", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("a", "0.5.1"))
+ .file("src/lib.rs", "")
+ });
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ [dependencies.b]
+ path = "b"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "b/Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "b"
+ version = "0.5.0"
+ authors = []
+ [dependencies.a]
+ git = '{}'
+ "#,
+ a1.url()
+ ),
+ )
+ .file("b/src/lib.rs", "pub fn main() {}")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] git repository `file://[..]a1`
+[CHECKING] a v0.5.0 ([..]a1#[..]
+[CHECKING] b v0.5.0 ([..])
+[CHECKING] foo v0.5.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.change_file(
+ "b/Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "b"
+ version = "0.5.0"
+ authors = []
+ [dependencies.a]
+ git = '{}'
+ "#,
+ a2.url()
+ ),
+ );
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] git repository `file://[..]a2`
+[CHECKING] a v0.5.1 ([..]a2#[..]
+[CHECKING] b v0.5.0 ([..])
+[CHECKING] foo v0.5.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn dont_require_submodules_are_checked_out() {
+ let p = project().build();
+ let git1 = git::new("dep1", |p| {
+ p.file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file("build.rs", "fn main() {}")
+ .file("src/lib.rs", "")
+ .file("a/foo", "")
+ });
+ let git2 = git::new("dep2", |p| p);
+
+ let repo = git2::Repository::open(&git1.root()).unwrap();
+ let url = path2url(git2.root()).to_string();
+ git::add_submodule(&repo, &url, Path::new("a/submodule"));
+ git::commit(&repo);
+
+ git2::Repository::init(&p.root()).unwrap();
+ let url = path2url(git1.root()).to_string();
+ let dst = paths::home().join("foo");
+ git2::Repository::clone(&url, &dst).unwrap();
+
+ git1.cargo("check -v").cwd(&dst).run();
+}
+
+#[cargo_test]
+fn doctest_same_name() {
+ let a2 = git::new("a2", |p| {
+ p.file("Cargo.toml", &basic_manifest("a", "0.5.0"))
+ .file("src/lib.rs", "pub fn a2() {}")
+ });
+
+ let a1 = git::new("a1", |p| {
+ p.file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "a"
+ version = "0.5.0"
+ authors = []
+ [dependencies]
+ a = {{ git = '{}' }}
+ "#,
+ a2.url()
+ ),
+ )
+ .file("src/lib.rs", "extern crate a; pub fn a1() {}")
+ });
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ a = {{ git = '{}' }}
+ "#,
+ a1.url()
+ ),
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ #[macro_use]
+ extern crate a;
+ "#,
+ )
+ .build();
+
+ p.cargo("test -v").run();
+}
+
+#[cargo_test]
+fn lints_are_suppressed() {
+ let a = git::new("a", |p| {
+ p.file("Cargo.toml", &basic_manifest("a", "0.5.0")).file(
+ "src/lib.rs",
+ "
+ use std::option;
+ ",
+ )
+ });
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ a = {{ git = '{}' }}
+ "#,
+ a.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] git repository `[..]`
+[CHECKING] a v0.5.0 ([..])
+[CHECKING] foo v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn denied_lints_are_allowed() {
+ let a = git::new("a", |p| {
+ p.file("Cargo.toml", &basic_manifest("a", "0.5.0")).file(
+ "src/lib.rs",
+ "
+ #![deny(warnings)]
+ use std::option;
+ ",
+ )
+ });
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ a = {{ git = '{}' }}
+ "#,
+ a.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] git repository `[..]`
+[CHECKING] a v0.5.0 ([..])
+[CHECKING] foo v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn add_a_git_dep() {
+ let git = git::new("git", |p| {
+ p.file("Cargo.toml", &basic_manifest("git", "0.5.0"))
+ .file("src/lib.rs", "")
+ });
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ a = {{ path = 'a' }}
+ git = {{ git = '{}' }}
+ "#,
+ git.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .file("a/Cargo.toml", &basic_manifest("a", "0.0.1"))
+ .file("a/src/lib.rs", "")
+ .build();
+
+ p.cargo("check").run();
+
+ assert!(paths::home().join(".cargo/git/CACHEDIR.TAG").is_file());
+
+ p.change_file(
+ "a/Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ git = {{ git = '{}' }}
+ "#,
+ git.url()
+ ),
+ );
+
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn two_at_rev_instead_of_tag() {
+ let git = git::new("git", |p| {
+ p.file("Cargo.toml", &basic_manifest("git1", "0.5.0"))
+ .file("src/lib.rs", "")
+ .file("a/Cargo.toml", &basic_manifest("git2", "0.5.0"))
+ .file("a/src/lib.rs", "")
+ });
+
+ // Make a tag corresponding to the current HEAD
+ let repo = git2::Repository::open(&git.root()).unwrap();
+ let head = repo.head().unwrap().target().unwrap();
+ repo.tag(
+ "v0.1.0",
+ &repo.find_object(head, None).unwrap(),
+ &repo.signature().unwrap(),
+ "make a new tag",
+ false,
+ )
+ .unwrap();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ git1 = {{ git = '{0}', rev = 'v0.1.0' }}
+ git2 = {{ git = '{0}', rev = 'v0.1.0' }}
+ "#,
+ git.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("generate-lockfile").run();
+ p.cargo("check -v").run();
+}
+
+#[cargo_test]
+fn include_overrides_gitignore() {
+ // Make sure that `package.include` takes precedence over .gitignore.
+ let p = git::new("foo", |repo| {
+ repo.file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ include = ["src/lib.rs", "ignored.txt", "Cargo.toml"]
+ "#,
+ )
+ .file(
+ ".gitignore",
+ r#"
+ /target
+ Cargo.lock
+ ignored.txt
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("ignored.txt", "")
+ .file("build.rs", "fn main() {}")
+ });
+
+ p.cargo("check").run();
+ p.change_file("ignored.txt", "Trigger rebuild.");
+ p.cargo("check -v")
+ .with_stderr(
+ "\
+[DIRTY] foo v0.5.0 ([..]): the precalculated components changed
+[COMPILING] foo v0.5.0 ([..])
+[RUNNING] `[..]build-script-build[..]`
+[RUNNING] `rustc --crate-name foo src/lib.rs [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ p.cargo("package --list --allow-dirty")
+ .with_stdout(
+ "\
+Cargo.toml
+Cargo.toml.orig
+ignored.txt
+src/lib.rs
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn invalid_git_dependency_manifest() {
+ let project = project();
+ let git_project = git::new("dep1", |project| {
+ project
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "dep1"
+ version = "0.5.0"
+ authors = ["carlhuda@example.com"]
+ categories = ["algorithms"]
+ categories = ["algorithms"]
+
+ [lib]
+
+ name = "dep1"
+ "#,
+ )
+ .file(
+ "src/dep1.rs",
+ r#"
+ pub fn hello() -> &'static str {
+ "hello world"
+ }
+ "#,
+ )
+ });
+
+ let project = project
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.dep1]
+
+ git = '{}'
+ "#,
+ git_project.url()
+ ),
+ )
+ .file(
+ "src/main.rs",
+ &main_file(r#""{}", dep1::hello()"#, &["dep1"]),
+ )
+ .build();
+
+ let git_root = git_project.root();
+
+ project
+ .cargo("check")
+ .with_status(101)
+ .with_stderr(&format!(
+ "\
+[UPDATING] git repository `{}`
+[ERROR] failed to get `dep1` as a dependency of package `foo v0.5.0 ([..])`
+
+Caused by:
+ failed to load source for dependency `dep1`
+
+Caused by:
+ Unable to update {}
+
+Caused by:
+ failed to parse manifest at `[..]`
+
+Caused by:
+ could not parse input as TOML
+
+Caused by:
+ TOML parse error at line 8, column 21
+ |
+ 8 | categories = [\"algorithms\"]
+ | ^
+ duplicate key `categories` in table `package`
+",
+ path2url(&git_root),
+ path2url(&git_root),
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn failed_submodule_checkout() {
+ let project = project();
+ let git_project = git::new("dep1", |project| {
+ project.file("Cargo.toml", &basic_manifest("dep1", "0.5.0"))
+ });
+
+ let git_project2 = git::new("dep2", |project| project.file("lib.rs", ""));
+
+ let listener = TcpListener::bind("127.0.0.1:0").unwrap();
+ let addr = listener.local_addr().unwrap();
+ let done = Arc::new(AtomicBool::new(false));
+ let done2 = done.clone();
+
+ let t = thread::spawn(move || {
+ while !done2.load(Ordering::SeqCst) {
+ if let Ok((mut socket, _)) = listener.accept() {
+ drop(socket.write_all(b"foo\r\n"));
+ }
+ }
+ });
+
+ let repo = git2::Repository::open(&git_project2.root()).unwrap();
+ let url = format!("https://{}:{}/", addr.ip(), addr.port());
+ {
+ let mut s = repo.submodule(&url, Path::new("bar"), false).unwrap();
+ let subrepo = s.open().unwrap();
+ let mut cfg = subrepo.config().unwrap();
+ cfg.set_str("user.email", "foo@bar.com").unwrap();
+ cfg.set_str("user.name", "Foo Bar").unwrap();
+ git::commit(&subrepo);
+ s.add_finalize().unwrap();
+ }
+ git::commit(&repo);
+ drop((repo, url));
+
+ let repo = git2::Repository::open(&git_project.root()).unwrap();
+ let url = path2url(git_project2.root()).to_string();
+ git::add_submodule(&repo, &url, Path::new("src"));
+ git::commit(&repo);
+ drop(repo);
+
+ let project = project
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ dep1 = {{ git = '{}' }}
+ "#,
+ git_project.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ project
+ .cargo("check")
+ .with_status(101)
+ .with_stderr_contains(" failed to update submodule `src`")
+ .with_stderr_contains(" failed to update submodule `bar`")
+ .run();
+ project
+ .cargo("check")
+ .with_status(101)
+ .with_stderr_contains(" failed to update submodule `src`")
+ .with_stderr_contains(" failed to update submodule `bar`")
+ .run();
+
+ done.store(true, Ordering::SeqCst);
+ drop(TcpStream::connect(&addr));
+ t.join().unwrap();
+}
+
+#[cargo_test(requires_git)]
+fn use_the_cli() {
+ let project = project();
+ let git_project = git::new("dep1", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("dep1", "0.5.0"))
+ .file("src/lib.rs", "")
+ });
+
+ let project = project
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ dep1 = {{ git = '{}' }}
+ "#,
+ git_project.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ "
+ [net]
+ git-fetch-with-cli = true
+ ",
+ )
+ .build();
+
+ let stderr = "\
+[UPDATING] git repository `[..]`
+[RUNNING] `git fetch [..]`
+From [..]
+ * [new ref] -> origin/HEAD
+[CHECKING] dep1 [..]
+[RUNNING] `rustc [..]`
+[CHECKING] foo [..]
+[RUNNING] `rustc [..]`
+[FINISHED] [..]
+";
+
+ project.cargo("check -v").with_stderr(stderr).run();
+ assert!(paths::home().join(".cargo/git/CACHEDIR.TAG").is_file());
+}
+
+#[cargo_test]
+fn templatedir_doesnt_cause_problems() {
+ let git_project2 = git::new("dep2", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("dep2", "0.5.0"))
+ .file("src/lib.rs", "")
+ });
+ let git_project = git::new("dep1", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("dep1", "0.5.0"))
+ .file("src/lib.rs", "")
+ });
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "fo"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ dep1 = {{ git = '{}' }}
+ "#,
+ git_project.url()
+ ),
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ fs::write(
+ paths::home().join(".gitconfig"),
+ format!(
+ r#"
+ [init]
+ templatedir = {}
+ "#,
+ git_project2
+ .url()
+ .to_file_path()
+ .unwrap()
+ .to_str()
+ .unwrap()
+ .replace("\\", "/")
+ ),
+ )
+ .unwrap();
+
+ p.cargo("check").run();
+}
+
+#[cargo_test(requires_git)]
+fn git_with_cli_force() {
+ // Supports a force-pushed repo.
+ let git_project = git::new("dep1", |project| {
+ project
+ .file("Cargo.toml", &basic_lib_manifest("dep1"))
+ .file("src/lib.rs", r#"pub fn f() { println!("one"); }"#)
+ });
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ edition = "2018"
+
+ [dependencies]
+ dep1 = {{ git = "{}" }}
+ "#,
+ git_project.url()
+ ),
+ )
+ .file("src/main.rs", "fn main() { dep1::f(); }")
+ .file(
+ ".cargo/config",
+ "
+ [net]
+ git-fetch-with-cli = true
+ ",
+ )
+ .build();
+ p.cargo("build").run();
+ p.rename_run("foo", "foo1").with_stdout("one").run();
+
+ // commit --amend a change that will require a force fetch.
+ let repo = git2::Repository::open(&git_project.root()).unwrap();
+ git_project.change_file("src/lib.rs", r#"pub fn f() { println!("two"); }"#);
+ git::add(&repo);
+ let id = repo.refname_to_id("HEAD").unwrap();
+ let commit = repo.find_commit(id).unwrap();
+ let tree_id = t!(t!(repo.index()).write_tree());
+ t!(commit.amend(
+ Some("HEAD"),
+ None,
+ None,
+ None,
+ None,
+ Some(&t!(repo.find_tree(tree_id)))
+ ));
+ // Perform the fetch.
+ p.cargo("update").run();
+ p.cargo("build").run();
+ p.rename_run("foo", "foo2").with_stdout("two").run();
+}
+
+#[cargo_test(requires_git)]
+fn git_fetch_cli_env_clean() {
+ // This tests that git-fetch-with-cli works when GIT_DIR environment
+ // variable is set (for whatever reason).
+ let git_dep = git::new("dep1", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("dep1", "0.5.0"))
+ .file("src/lib.rs", "")
+ });
+
+ let git_proj = git::new("foo", |project| {
+ project
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ [dependencies]
+ dep1 = {{ git = '{}' }}
+ "#,
+ git_dep.url()
+ ),
+ )
+ .file("src/lib.rs", "pub extern crate dep1;")
+ .file(
+ ".cargo/config",
+ "
+ [net]
+ git-fetch-with-cli = true
+ ",
+ )
+ });
+
+ // The directory set here isn't too important. Pointing to our own git
+ // directory causes git to be confused and fail. Can also point to an
+ // empty directory, or a nonexistent one.
+ git_proj
+ .cargo("fetch")
+ .env("GIT_DIR", git_proj.root().join(".git"))
+ .run();
+}
+
+#[cargo_test]
+fn dirty_submodule() {
+ // `cargo package` warns for dirty file in submodule.
+ let (git_project, repo) = git::new_repo("foo", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("foo", "0.5.0"))
+ // This is necessary because `git::add` is too eager.
+ .file(".gitignore", "/target")
+ });
+ let git_project2 = git::new("src", |project| {
+ project.no_manifest().file("lib.rs", "pub fn f() {}")
+ });
+
+ let url = path2url(git_project2.root()).to_string();
+ git::add_submodule(&repo, &url, Path::new("src"));
+
+ // Submodule added, but not committed.
+ git_project
+ .cargo("package --no-verify")
+ .with_status(101)
+ .with_stderr(
+ "\
+[WARNING] manifest has no [..]
+See [..]
+[ERROR] 1 files in the working directory contain changes that were not yet committed into git:
+
+.gitmodules
+
+to proceed despite [..]
+",
+ )
+ .run();
+
+ git::commit(&repo);
+ git_project.cargo("package --no-verify").run();
+
+ // Modify file, check for warning.
+ git_project.change_file("src/lib.rs", "");
+ git_project
+ .cargo("package --no-verify")
+ .with_status(101)
+ .with_stderr(
+ "\
+[WARNING] manifest has no [..]
+See [..]
+[ERROR] 1 files in the working directory contain changes that were not yet committed into git:
+
+src/lib.rs
+
+to proceed despite [..]
+",
+ )
+ .run();
+ // Commit the change.
+ let sub_repo = git2::Repository::open(git_project.root().join("src")).unwrap();
+ git::add(&sub_repo);
+ git::commit(&sub_repo);
+ git::add(&repo);
+ git::commit(&repo);
+ git_project.cargo("package --no-verify").run();
+
+ // Try with a nested submodule.
+ let git_project3 = git::new("bar", |project| project.no_manifest().file("mod.rs", ""));
+ let url = path2url(git_project3.root()).to_string();
+ git::add_submodule(&sub_repo, &url, Path::new("bar"));
+ git_project
+ .cargo("package --no-verify")
+ .with_status(101)
+ .with_stderr(
+ "\
+[WARNING] manifest has no [..]
+See [..]
+[ERROR] 1 files in the working directory contain changes that were not yet committed into git:
+
+src/.gitmodules
+
+to proceed despite [..]
+",
+ )
+ .run();
+
+ // Commit the submodule addition.
+ git::commit(&sub_repo);
+ git::add(&repo);
+ git::commit(&repo);
+ git_project.cargo("package --no-verify").run();
+ // Modify within nested submodule.
+ git_project.change_file("src/bar/new_file.rs", "//test");
+ git_project
+ .cargo("package --no-verify")
+ .with_status(101)
+ .with_stderr(
+ "\
+[WARNING] manifest has no [..]
+See [..]
+[ERROR] 1 files in the working directory contain changes that were not yet committed into git:
+
+src/bar/new_file.rs
+
+to proceed despite [..]
+",
+ )
+ .run();
+ // And commit the change.
+ let sub_sub_repo = git2::Repository::open(git_project.root().join("src/bar")).unwrap();
+ git::add(&sub_sub_repo);
+ git::commit(&sub_sub_repo);
+ git::add(&sub_repo);
+ git::commit(&sub_repo);
+ git::add(&repo);
+ git::commit(&repo);
+ git_project.cargo("package --no-verify").run();
+}
+
+#[cargo_test]
+fn default_not_master() {
+ let project = project();
+
+ // Create a repository with a `master` branch, but switch the head to a
+ // branch called `main` at the same time.
+ let (git_project, repo) = git::new_repo("dep1", |project| {
+ project
+ .file("Cargo.toml", &basic_lib_manifest("dep1"))
+ .file("src/lib.rs", "pub fn foo() {}")
+ });
+ let head_id = repo.head().unwrap().target().unwrap();
+ let head = repo.find_commit(head_id).unwrap();
+ repo.branch("main", &head, false).unwrap();
+ repo.set_head("refs/heads/main").unwrap();
+
+ // Then create a commit on the new `main` branch so `master` and `main`
+ // differ.
+ git_project.change_file("src/lib.rs", "pub fn bar() {}");
+ git::add(&repo);
+ git::commit(&repo);
+
+ let project = project
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ [dependencies]
+ dep1 = {{ git = '{}' }}
+ "#,
+ git_project.url()
+ ),
+ )
+ .file("src/lib.rs", "pub fn foo() { dep1::bar() }")
+ .build();
+
+ project
+ .cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] git repository `[..]`
+[CHECKING] dep1 v0.5.0 ([..])
+[CHECKING] foo v0.5.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn historical_lockfile_works() {
+ let project = project();
+
+ let (git_project, repo) = git::new_repo("dep1", |project| {
+ project
+ .file("Cargo.toml", &basic_lib_manifest("dep1"))
+ .file("src/lib.rs", "")
+ });
+ let head_id = repo.head().unwrap().target().unwrap();
+
+ let project = project
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+
+ [dependencies]
+ dep1 = {{ git = '{}', branch = 'master' }}
+ "#,
+ git_project.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ project.cargo("check").run();
+ project.change_file(
+ "Cargo.lock",
+ &format!(
+ r#"# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "dep1"
+version = "0.5.0"
+source = "git+{}#{}"
+
+[[package]]
+name = "foo"
+version = "0.5.0"
+dependencies = [
+ "dep1",
+]
+"#,
+ git_project.url(),
+ head_id
+ ),
+ );
+ project
+ .cargo("check")
+ .with_stderr("[FINISHED] [..]\n")
+ .run();
+}
+
+#[cargo_test]
+fn historical_lockfile_works_with_vendor() {
+ let project = project();
+
+ let (git_project, repo) = git::new_repo("dep1", |project| {
+ project
+ .file("Cargo.toml", &basic_lib_manifest("dep1"))
+ .file("src/lib.rs", "")
+ });
+ let head_id = repo.head().unwrap().target().unwrap();
+
+ let project = project
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+
+ [dependencies]
+ dep1 = {{ git = '{}', branch = 'master' }}
+ "#,
+ git_project.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ let output = project.cargo("vendor").exec_with_output().unwrap();
+ project.change_file(".cargo/config", str::from_utf8(&output.stdout).unwrap());
+ project.change_file(
+ "Cargo.lock",
+ &format!(
+ r#"# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "dep1"
+version = "0.5.0"
+source = "git+{}#{}"
+
+[[package]]
+name = "foo"
+version = "0.5.0"
+dependencies = [
+ "dep1",
+]
+"#,
+ git_project.url(),
+ head_id
+ ),
+ );
+ project.cargo("check").run();
+}
+
+#[cargo_test]
+fn two_dep_forms() {
+ let project = project();
+
+ let (git_project, _repo) = git::new_repo("dep1", |project| {
+ project
+ .file("Cargo.toml", &basic_lib_manifest("dep1"))
+ .file("src/lib.rs", "")
+ });
+
+ let project = project
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ [dependencies]
+ dep1 = {{ git = '{}', branch = 'master' }}
+ a = {{ path = 'a' }}
+ "#,
+ git_project.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "a/Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "a"
+ version = "0.5.0"
+ [dependencies]
+ dep1 = {{ git = '{}' }}
+ "#,
+ git_project.url()
+ ),
+ )
+ .file("a/src/lib.rs", "")
+ .build();
+
+ // This'll download the git repository twice, one with HEAD and once with
+ // the master branch. Then it'll compile 4 crates, the 2 git deps, then
+ // the two local deps.
+ project
+ .cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[UPDATING] [..]
+[CHECKING] [..]
+[CHECKING] [..]
+[CHECKING] [..]
+[CHECKING] [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn metadata_master_consistency() {
+ // SourceId consistency in the `cargo metadata` output when `master` is
+ // explicit or implicit, using new or old Cargo.lock.
+ let (git_project, git_repo) = git::new_repo("bar", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("bar", "1.0.0"))
+ .file("src/lib.rs", "")
+ });
+ let bar_hash = git_repo.head().unwrap().target().unwrap().to_string();
+
+ // Explicit branch="master" with a lock file created before 1.47 (does not contain ?branch=master).
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = {{ git = "{}", branch = "master" }}
+ "#,
+ git_project.url()
+ ),
+ )
+ .file(
+ "Cargo.lock",
+ &format!(
+ r#"
+ [[package]]
+ name = "bar"
+ version = "1.0.0"
+ source = "git+{}#{}"
+
+ [[package]]
+ name = "foo"
+ version = "0.1.0"
+ dependencies = [
+ "bar",
+ ]
+ "#,
+ git_project.url(),
+ bar_hash,
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ let metadata = |bar_source| -> String {
+ r#"
+ {
+ "packages": [
+ {
+ "name": "bar",
+ "version": "1.0.0",
+ "id": "bar 1.0.0 (__BAR_SOURCE__#__BAR_HASH__)",
+ "license": null,
+ "license_file": null,
+ "description": null,
+ "source": "__BAR_SOURCE__#__BAR_HASH__",
+ "dependencies": [],
+ "targets": "{...}",
+ "features": {},
+ "manifest_path": "[..]",
+ "metadata": null,
+ "publish": null,
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "keywords": [],
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "edition": "2015",
+ "links": null
+ },
+ {
+ "name": "foo",
+ "version": "0.1.0",
+ "id": "foo 0.1.0 [..]",
+ "license": null,
+ "license_file": null,
+ "description": null,
+ "source": null,
+ "dependencies": [
+ {
+ "name": "bar",
+ "source": "__BAR_SOURCE__",
+ "req": "*",
+ "kind": null,
+ "rename": null,
+ "optional": false,
+ "uses_default_features": true,
+ "features": [],
+ "target": null,
+ "registry": null
+ }
+ ],
+ "targets": "{...}",
+ "features": {},
+ "manifest_path": "[..]",
+ "metadata": null,
+ "publish": null,
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "keywords": [],
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "edition": "2015",
+ "links": null
+ }
+ ],
+ "workspace_members": [
+ "foo 0.1.0 [..]"
+ ],
+ "resolve": {
+ "nodes": [
+ {
+ "id": "bar 1.0.0 (__BAR_SOURCE__#__BAR_HASH__)",
+ "dependencies": [],
+ "deps": [],
+ "features": []
+ },
+ {
+ "id": "foo 0.1.0 [..]",
+ "dependencies": [
+ "bar 1.0.0 (__BAR_SOURCE__#__BAR_HASH__)"
+ ],
+ "deps": [
+ {
+ "name": "bar",
+ "pkg": "bar 1.0.0 (__BAR_SOURCE__#__BAR_HASH__)",
+ "dep_kinds": [
+ {
+ "kind": null,
+ "target": null
+ }
+ ]
+ }
+ ],
+ "features": []
+ }
+ ],
+ "root": "foo 0.1.0 [..]"
+ },
+ "target_directory": "[..]",
+ "version": 1,
+ "workspace_root": "[..]",
+ "metadata": null
+ }
+ "#
+ .replace("__BAR_SOURCE__", bar_source)
+ .replace("__BAR_HASH__", &bar_hash)
+ };
+
+ let bar_source = format!("git+{}?branch=master", git_project.url());
+ p.cargo("metadata").with_json(&metadata(&bar_source)).run();
+
+ // Conversely, remove branch="master" from Cargo.toml, but use a new Cargo.lock that has ?branch=master.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = {{ git = "{}" }}
+ "#,
+ git_project.url()
+ ),
+ )
+ .file(
+ "Cargo.lock",
+ &format!(
+ r#"
+ [[package]]
+ name = "bar"
+ version = "1.0.0"
+ source = "git+{}?branch=master#{}"
+
+ [[package]]
+ name = "foo"
+ version = "0.1.0"
+ dependencies = [
+ "bar",
+ ]
+ "#,
+ git_project.url(),
+ bar_hash
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ // No ?branch=master!
+ let bar_source = format!("git+{}", git_project.url());
+ p.cargo("metadata").with_json(&metadata(&bar_source)).run();
+}
+
+#[cargo_test]
+fn git_with_force_push() {
+ // Checks that cargo can handle force-pushes to git repos.
+ // This works by having a git dependency that is updated with an amend
+ // commit, and tries with various forms (default branch, branch, rev,
+ // tag).
+ let main = |text| format!(r#"pub fn f() {{ println!("{}"); }}"#, text);
+ let (git_project, repo) = git::new_repo("dep1", |project| {
+ project
+ .file("Cargo.toml", &basic_lib_manifest("dep1"))
+ .file("src/lib.rs", &main("one"))
+ });
+ let manifest = |extra| {
+ format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ edition = "2018"
+
+ [dependencies]
+ dep1 = {{ git = "{}"{} }}
+ "#,
+ git_project.url(),
+ extra
+ )
+ };
+ let p = project()
+ .file("Cargo.toml", &manifest(""))
+ .file("src/main.rs", "fn main() { dep1::f(); }")
+ .build();
+ // Download the original and make sure it is OK.
+ p.cargo("build").run();
+ p.rename_run("foo", "foo1").with_stdout("one").run();
+
+ let find_head = || t!(t!(repo.head()).peel_to_commit());
+
+ let amend_commit = |text| {
+ // commit --amend a change that will require a force fetch.
+ git_project.change_file("src/lib.rs", &main(text));
+ git::add(&repo);
+ let commit = find_head();
+ let tree_id = t!(t!(repo.index()).write_tree());
+ t!(commit.amend(
+ Some("HEAD"),
+ None,
+ None,
+ None,
+ None,
+ Some(&t!(repo.find_tree(tree_id)))
+ ));
+ };
+
+ let mut rename_annoyance = 1;
+
+ let mut verify = |text: &str| {
+ // Perform the fetch.
+ p.cargo("update").run();
+ p.cargo("build").run();
+ rename_annoyance += 1;
+ p.rename_run("foo", &format!("foo{}", rename_annoyance))
+ .with_stdout(text)
+ .run();
+ };
+
+ amend_commit("two");
+ verify("two");
+
+ // Try with a rev.
+ let head1 = find_head().id().to_string();
+ let extra = format!(", rev = \"{}\"", head1);
+ p.change_file("Cargo.toml", &manifest(&extra));
+ verify("two");
+ amend_commit("three");
+ let head2 = find_head().id().to_string();
+ assert_ne!(&head1, &head2);
+ let extra = format!(", rev = \"{}\"", head2);
+ p.change_file("Cargo.toml", &manifest(&extra));
+ verify("three");
+
+ // Try with a tag.
+ git::tag(&repo, "my-tag");
+ p.change_file("Cargo.toml", &manifest(", tag = \"my-tag\""));
+ verify("three");
+ amend_commit("tag-three");
+ let head = t!(t!(repo.head()).peel(git2::ObjectType::Commit));
+ t!(repo.tag("my-tag", &head, &t!(repo.signature()), "move tag", true));
+ verify("tag-three");
+
+ // Try with a branch.
+ let br = t!(repo.branch("awesome-stuff", &find_head(), false));
+ t!(repo.checkout_tree(&t!(br.get().peel(git2::ObjectType::Tree)), None));
+ t!(repo.set_head("refs/heads/awesome-stuff"));
+ git_project.change_file("src/lib.rs", &main("awesome-three"));
+ git::add(&repo);
+ git::commit(&repo);
+ p.change_file("Cargo.toml", &manifest(", branch = \"awesome-stuff\""));
+ verify("awesome-three");
+ amend_commit("awesome-four");
+ verify("awesome-four");
+}
+
+#[cargo_test]
+fn corrupted_checkout() {
+ // Test what happens if the checkout is corrupted somehow.
+ _corrupted_checkout(false);
+}
+
+#[cargo_test]
+fn corrupted_checkout_with_cli() {
+ // Test what happens if the checkout is corrupted somehow with git cli.
+ _corrupted_checkout(true);
+}
+
+fn _corrupted_checkout(with_cli: bool) {
+ let git_project = git::new("dep1", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("dep1", "0.5.0"))
+ .file("src/lib.rs", "")
+ });
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ dep1 = {{ git = "{}" }}
+ "#,
+ git_project.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("fetch").run();
+
+ let mut paths = t!(glob::glob(
+ paths::home()
+ .join(".cargo/git/checkouts/dep1-*/*")
+ .to_str()
+ .unwrap()
+ ));
+ let path = paths.next().unwrap().unwrap();
+ let ok = path.join(".cargo-ok");
+
+ // Deleting this file simulates an interrupted checkout.
+ t!(fs::remove_file(&ok));
+
+ // This should refresh the checkout.
+ let mut e = p.cargo("fetch");
+ if with_cli {
+ e.env("CARGO_NET_GIT_FETCH_WITH_CLI", "true");
+ }
+ e.run();
+ assert!(ok.exists());
+}
+
+#[cargo_test]
+fn cleans_temp_pack_files() {
+ // Checks that cargo removes temp files left by libgit2 when it is
+ // interrupted (see clean_repo_temp_files).
+ Package::new("bar", "1.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("fetch").run();
+ // Simulate what happens when libgit2 is interrupted while indexing a pack file.
+ let tmp_path = super::git_gc::find_index().join(".git/objects/pack/pack_git2_91ab40da04fdc2e7");
+ fs::write(&tmp_path, "test").unwrap();
+ let mut perms = fs::metadata(&tmp_path).unwrap().permissions();
+ perms.set_readonly(true);
+ fs::set_permissions(&tmp_path, perms).unwrap();
+
+ // Trigger an index update.
+ p.cargo("generate-lockfile").run();
+ assert!(!tmp_path.exists());
+}
diff --git a/src/tools/cargo/tests/testsuite/git_auth.rs b/src/tools/cargo/tests/testsuite/git_auth.rs
new file mode 100644
index 000000000..b6e68fa3d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/git_auth.rs
@@ -0,0 +1,437 @@
+//! Tests for git authentication.
+
+use std::collections::HashSet;
+use std::io::prelude::*;
+use std::io::BufReader;
+use std::net::{SocketAddr, TcpListener};
+use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
+use std::sync::Arc;
+use std::thread::{self, JoinHandle};
+
+use cargo_test_support::git::cargo_uses_gitoxide;
+use cargo_test_support::paths;
+use cargo_test_support::{basic_manifest, project};
+
+fn setup_failed_auth_test() -> (SocketAddr, JoinHandle<()>, Arc<AtomicUsize>) {
+ let server = TcpListener::bind("127.0.0.1:0").unwrap();
+ let addr = server.local_addr().unwrap();
+
+ fn headers(rdr: &mut dyn BufRead) -> HashSet<String> {
+ let valid = ["GET", "Authorization", "Accept"];
+ rdr.lines()
+ .map(|s| s.unwrap())
+ .take_while(|s| s.len() > 2)
+ .map(|s| s.trim().to_string())
+ .filter(|s| valid.iter().any(|prefix| s.starts_with(*prefix)))
+ .collect()
+ }
+
+ let connections = Arc::new(AtomicUsize::new(0));
+ let connections2 = connections.clone();
+ let t = thread::spawn(move || {
+ let mut conn = BufReader::new(server.accept().unwrap().0);
+ let req = headers(&mut conn);
+ connections2.fetch_add(1, SeqCst);
+ conn.get_mut()
+ .write_all(
+ b"HTTP/1.1 401 Unauthorized\r\n\
+ WWW-Authenticate: Basic realm=\"wheee\"\r\n\
+ Content-Length: 0\r\n\
+ \r\n",
+ )
+ .unwrap();
+ assert_eq!(
+ req,
+ vec![
+ "GET /foo/bar/info/refs?service=git-upload-pack HTTP/1.1",
+ "Accept: */*",
+ ]
+ .into_iter()
+ .map(|s| s.to_string())
+ .collect()
+ );
+
+ let req = headers(&mut conn);
+ connections2.fetch_add(1, SeqCst);
+ conn.get_mut()
+ .write_all(
+ b"HTTP/1.1 401 Unauthorized\r\n\
+ WWW-Authenticate: Basic realm=\"wheee\"\r\n\
+ \r\n",
+ )
+ .unwrap();
+ assert_eq!(
+ req,
+ vec![
+ "GET /foo/bar/info/refs?service=git-upload-pack HTTP/1.1",
+ "Authorization: Basic Zm9vOmJhcg==",
+ "Accept: */*",
+ ]
+ .into_iter()
+ .map(|s| s.to_string())
+ .collect()
+ );
+ });
+
+ let script = project()
+ .at("script")
+ .file("Cargo.toml", &basic_manifest("script", "0.1.0"))
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ println!("username=foo");
+ println!("password=bar");
+ }
+ "#,
+ )
+ .build();
+
+ script.cargo("build -v").run();
+ let script = script.bin("script");
+
+ let config = paths::home().join(".gitconfig");
+ let mut config = git2::Config::open(&config).unwrap();
+ config
+ .set_str(
+ "credential.helper",
+ // This is a bash script so replace `\` with `/` for Windows
+ &script.display().to_string().replace("\\", "/"),
+ )
+ .unwrap();
+ (addr, t, connections)
+}
+
+// Tests that HTTP auth is offered from `credential.helper`.
+#[cargo_test]
+fn http_auth_offered() {
+ // TODO(Seb): remove this once possible.
+ if cargo_uses_gitoxide() {
+ // Without the fixes in https://github.com/Byron/gitoxide/releases/tag/gix-v0.41.0 this test is flaky.
+ return;
+ }
+ let (addr, t, connections) = setup_failed_auth_test();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ git = "http://127.0.0.1:{}/foo/bar"
+ "#,
+ addr.port()
+ ),
+ )
+ .file("src/main.rs", "")
+ .file(
+ ".cargo/config",
+ "[net]
+ retry = 0
+ ",
+ )
+ .build();
+
+ // This is a "contains" check because the last error differs by platform,
+ // may span multiple lines, and isn't relevant to this test.
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr_contains(&format!(
+ "\
+[UPDATING] git repository `http://{addr}/foo/bar`
+[ERROR] failed to get `bar` as a dependency of package `foo v0.0.1 [..]`
+
+Caused by:
+ failed to load source for dependency `bar`
+
+Caused by:
+ Unable to update http://{addr}/foo/bar
+
+Caused by:
+ failed to clone into: [..]
+
+Caused by:
+ failed to authenticate when downloading repository
+
+ * attempted to find username/password via `credential.helper`, but [..]
+
+ if the git CLI succeeds then `net.git-fetch-with-cli` may help here
+ https://[..]
+
+Caused by:
+"
+ ))
+ .run();
+
+ assert_eq!(connections.load(SeqCst), 2);
+ t.join().ok().unwrap();
+}
+
+// Boy, sure would be nice to have a TLS implementation in rust!
+#[cargo_test]
+fn https_something_happens() {
+ let server = TcpListener::bind("127.0.0.1:0").unwrap();
+ let addr = server.local_addr().unwrap();
+ let t = thread::spawn(move || {
+ let mut conn = server.accept().unwrap().0;
+ drop(conn.write(b"1234"));
+ drop(conn.shutdown(std::net::Shutdown::Write));
+ drop(conn.read(&mut [0; 16]));
+ });
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ git = "https://127.0.0.1:{}/foo/bar"
+ "#,
+ addr.port()
+ ),
+ )
+ .file("src/main.rs", "")
+ .file(
+ ".cargo/config",
+ "[net]
+ retry = 0
+ ",
+ )
+ .build();
+
+ p.cargo("check -v")
+ .with_status(101)
+ .with_stderr_contains(&format!(
+ "[UPDATING] git repository `https://{addr}/foo/bar`"
+ ))
+ .with_stderr_contains(&format!(
+ "\
+Caused by:
+ {errmsg}
+",
+ errmsg = if cargo_uses_gitoxide() {
+ "[..]SSL connect error [..]"
+ } else if cfg!(windows) {
+ "[..]failed to send request: [..]"
+ } else if cfg!(target_os = "macos") {
+ // macOS is difficult to tests as some builds may use Security.framework,
+ // while others may use OpenSSL. In that case, let's just not verify the error
+ // message here.
+ "[..]"
+ } else {
+ "[..]SSL error: [..]"
+ }
+ ))
+ .run();
+
+ t.join().ok().unwrap();
+}
+
+// It would sure be nice to have an SSH implementation in Rust!
+#[cargo_test]
+fn ssh_something_happens() {
+ let server = TcpListener::bind("127.0.0.1:0").unwrap();
+ let addr = server.local_addr().unwrap();
+ let t = thread::spawn(move || {
+ drop(server.accept().unwrap());
+ });
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ git = "ssh://127.0.0.1:{}/foo/bar"
+ "#,
+ addr.port()
+ ),
+ )
+ .file("src/main.rs", "")
+ .build();
+
+ let (expected_ssh_message, expected_update) = if cargo_uses_gitoxide() {
+ // Due to the usage of `ssh` and `ssh.exe` respectively, the messages change.
+ // This will be adjusted to use `ssh2` to get rid of this dependency and have uniform messaging.
+ let message = if cfg!(windows) {
+ // The order of multiple possible messages isn't deterministic within `ssh`, and `gitoxide` detects both
+ // but gets to report only the first. Thus this test can flip-flop from one version of the error to the other
+ // and we can't test for that.
+ // We'd want to test for:
+ // "[..]ssh: connect to host 127.0.0.1 [..]"
+ // ssh: connect to host example.org port 22: No route to host
+ // "[..]banner exchange: Connection to 127.0.0.1 [..]"
+ // banner exchange: Connection to 127.0.0.1 port 62250: Software caused connection abort
+ // But since there is no common meaningful sequence or word, we can only match a small telling sequence of characters.
+ "[..]onnect[..]"
+ } else {
+ "[..]Connection [..] by [..]"
+ };
+ (
+ message,
+ format!("[..]Unable to update ssh://{addr}/foo/bar"),
+ )
+ } else {
+ (
+ "\
+Caused by:
+ [..]failed to start SSH session: Failed getting banner[..]
+",
+ format!("[UPDATING] git repository `ssh://{addr}/foo/bar`"),
+ )
+ };
+ p.cargo("check -v")
+ .with_status(101)
+ .with_stderr_contains(&expected_update)
+ .with_stderr_contains(expected_ssh_message)
+ .run();
+ t.join().ok().unwrap();
+}
+
+#[cargo_test]
+fn net_err_suggests_fetch_with_cli() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+
+ [dependencies]
+ foo = { git = "ssh://needs-proxy.invalid/git" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check -v")
+ .with_status(101)
+ .with_stderr(format!(
+ "\
+[UPDATING] git repository `ssh://needs-proxy.invalid/git`
+warning: spurious network error[..]
+warning: spurious network error[..]
+warning: spurious network error[..]
+[ERROR] failed to get `foo` as a dependency of package `foo v0.0.0 [..]`
+
+Caused by:
+ failed to load source for dependency `foo`
+
+Caused by:
+ Unable to update ssh://needs-proxy.invalid/git
+
+Caused by:
+ failed to clone into: [..]
+
+Caused by:
+ network failure seems to have happened
+ if a proxy or similar is necessary `net.git-fetch-with-cli` may help here
+ https://[..]
+
+Caused by:
+ {trailer}
+",
+ trailer = if cargo_uses_gitoxide() {
+ "An IO error occurred when talking to the server\n\nCaused by:\n ssh: Could not resolve hostname needs-proxy.invalid[..]"
+ } else {
+ "failed to resolve address for needs-proxy.invalid[..]"
+ }
+ ))
+ .run();
+
+ p.change_file(
+ ".cargo/config",
+ "
+ [net]
+ git-fetch-with-cli = true
+ ",
+ );
+
+ p.cargo("check -v")
+ .with_status(101)
+ .with_stderr_contains("[..]Unable to update[..]")
+ .with_stderr_does_not_contain("[..]try enabling `git-fetch-with-cli`[..]")
+ .run();
+}
+
+#[cargo_test]
+fn instead_of_url_printed() {
+ // TODO(Seb): remove this once possible.
+ if cargo_uses_gitoxide() {
+ // Without the fixes in https://github.com/Byron/gitoxide/releases/tag/gix-v0.41.0 this test is flaky.
+ return;
+ }
+ let (addr, t, _connections) = setup_failed_auth_test();
+ let config = paths::home().join(".gitconfig");
+ let mut config = git2::Config::open(&config).unwrap();
+ config
+ .set_str(
+ &format!("url.http://{}/.insteadOf", addr),
+ "https://foo.bar/",
+ )
+ .unwrap();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ git = "https://foo.bar/foo/bar"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(&format!(
+ "\
+[UPDATING] git repository `https://foo.bar/foo/bar`
+[ERROR] failed to get `bar` as a dependency of package `foo [..]`
+
+Caused by:
+ failed to load source for dependency `bar`
+
+Caused by:
+ Unable to update https://foo.bar/foo/bar
+
+Caused by:
+ failed to clone into: [..]
+
+Caused by:
+ failed to authenticate when downloading repository: http://{addr}/foo/bar
+
+ * attempted to find username/password via `credential.helper`, but maybe the found credentials were incorrect
+
+ if the git CLI succeeds then `net.git-fetch-with-cli` may help here
+ https://[..]
+
+Caused by:
+ [..]
+{trailer}",
+ trailer = if cargo_uses_gitoxide() { "\nCaused by:\n [..]" } else { "" }
+ ))
+ .run();
+
+ t.join().ok().unwrap();
+}
diff --git a/src/tools/cargo/tests/testsuite/git_gc.rs b/src/tools/cargo/tests/testsuite/git_gc.rs
new file mode 100644
index 000000000..fd4fe30a9
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/git_gc.rs
@@ -0,0 +1,117 @@
+//! Tests for git garbage collection.
+
+use std::env;
+use std::ffi::OsStr;
+use std::path::PathBuf;
+
+use cargo_test_support::git;
+use cargo_test_support::git::cargo_uses_gitoxide;
+use cargo_test_support::paths;
+use cargo_test_support::project;
+use cargo_test_support::registry::Package;
+
+use url::Url;
+
+pub fn find_index() -> PathBuf {
+ let dir = paths::home().join(".cargo/registry/index");
+ dir.read_dir().unwrap().next().unwrap().unwrap().path()
+}
+
+fn run_test(path_env: Option<&OsStr>) {
+ const N: usize = 50;
+
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "*"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ Package::new("bar", "0.1.0").publish();
+
+ foo.cargo("check").run();
+
+ let index = find_index();
+ let path = paths::home().join("tmp");
+ let url = Url::from_file_path(&path).unwrap().to_string();
+ let repo = git2::Repository::init(&path).unwrap();
+ let index = git2::Repository::open(&index).unwrap();
+ let mut cfg = repo.config().unwrap();
+ cfg.set_str("user.email", "foo@bar.com").unwrap();
+ cfg.set_str("user.name", "Foo Bar").unwrap();
+ let mut cfg = index.config().unwrap();
+ cfg.set_str("user.email", "foo@bar.com").unwrap();
+ cfg.set_str("user.name", "Foo Bar").unwrap();
+
+ for _ in 0..N {
+ git::commit(&repo);
+ index
+ .remote_anonymous(&url)
+ .unwrap()
+ .fetch(&["refs/heads/master:refs/remotes/foo/master"], None, None)
+ .unwrap();
+ }
+ drop((repo, index));
+ Package::new("bar", "0.1.1").publish();
+
+ let before = find_index()
+ .join(".git/objects/pack")
+ .read_dir()
+ .unwrap()
+ .count();
+ assert!(before > N);
+
+ let mut cmd = foo.cargo("update");
+ cmd.env("__CARGO_PACKFILE_LIMIT", "10");
+ if let Some(path) = path_env {
+ cmd.env("PATH", path);
+ }
+ cmd.env("CARGO_LOG", "trace");
+ cmd.run();
+ let after = find_index()
+ .join(".git/objects/pack")
+ .read_dir()
+ .unwrap()
+ .count();
+ assert!(
+ after < before,
+ "packfiles before: {}\n\
+ packfiles after: {}",
+ before,
+ after
+ );
+}
+
+#[cargo_test(requires_git)]
+fn use_git_gc() {
+ run_test(None);
+}
+
+#[cargo_test]
+fn avoid_using_git() {
+ if cargo_uses_gitoxide() {
+ // file protocol without git binary is currently not possible - needs built-in upload-pack.
+ // See https://github.com/Byron/gitoxide/issues/734 (support for the file protocol) progress updates.
+ return;
+ }
+ let path = env::var_os("PATH").unwrap_or_default();
+ let mut paths = env::split_paths(&path).collect::<Vec<_>>();
+ let idx = paths
+ .iter()
+ .position(|p| p.join("git").exists() || p.join("git.exe").exists());
+ match idx {
+ Some(i) => {
+ paths.remove(i);
+ }
+ None => return,
+ }
+ run_test(Some(&env::join_paths(&paths).unwrap()));
+}
diff --git a/src/tools/cargo/tests/testsuite/glob_targets.rs b/src/tools/cargo/tests/testsuite/glob_targets.rs
new file mode 100644
index 000000000..8021dffa9
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/glob_targets.rs
@@ -0,0 +1,539 @@
+//! Tests for target filter flags with glob patterns.
+
+use cargo_test_support::{project, Project};
+
+#[cargo_test]
+fn build_example() {
+ full_project()
+ .cargo("build -v --example 'ex*1'")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name example1 [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_bin() {
+ full_project()
+ .cargo("build -v --bin 'bi*1'")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name bin1 [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_bench() {
+ full_project()
+ .cargo("build -v --bench 'be*1'")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name bench1 [..]`")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name bin2 [..]`")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name bin1 [..]`")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name foo [..]`")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name [..]`
+[RUNNING] `rustc --crate-name [..]`
+[RUNNING] `rustc --crate-name [..]`
+[RUNNING] `rustc --crate-name [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_test() {
+ full_project()
+ .cargo("build -v --test 'te*1'")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name test1 [..]`")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name bin2 [..]`")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name bin1 [..]`")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name foo [..]`")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name [..]`
+[RUNNING] `rustc --crate-name [..]`
+[RUNNING] `rustc --crate-name [..]`
+[RUNNING] `rustc --crate-name [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn check_example() {
+ full_project()
+ .cargo("check -v --example 'ex*1'")
+ .with_stderr(
+ "\
+[CHECKING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name example1 [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn check_bin() {
+ full_project()
+ .cargo("check -v --bin 'bi*1'")
+ .with_stderr(
+ "\
+[CHECKING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name bin1 [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn check_bench() {
+ full_project()
+ .cargo("check -v --bench 'be*1'")
+ .with_stderr(
+ "\
+[CHECKING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name bench1 [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn check_test() {
+ full_project()
+ .cargo("check -v --test 'te*1'")
+ .with_stderr(
+ "\
+[CHECKING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name test1 [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn doc_bin() {
+ full_project()
+ .cargo("doc -v --bin 'bi*1'")
+ .with_stderr(
+ "\
+[DOCUMENTING] foo v0.0.1 ([CWD])
+[RUNNING] `rustdoc --crate-type bin --crate-name bin1 [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn fix_example() {
+ full_project()
+ .cargo("fix -v --example 'ex*1' --allow-no-vcs")
+ .with_stderr(
+ "\
+[CHECKING] foo v0.0.1 ([CWD])
+[RUNNING] `[..] rustc --crate-name example1 [..]`
+[FIXING] examples/example1.rs
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn fix_bin() {
+ full_project()
+ .cargo("fix -v --bin 'bi*1' --allow-no-vcs")
+ .with_stderr(
+ "\
+[CHECKING] foo v0.0.1 ([CWD])
+[RUNNING] `[..] rustc --crate-name bin1 [..]`
+[FIXING] src/bin/bin1.rs
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn fix_bench() {
+ full_project()
+ .cargo("fix -v --bench 'be*1' --allow-no-vcs")
+ .with_stderr(
+ "\
+[CHECKING] foo v0.0.1 ([CWD])
+[RUNNING] `[..] rustc --crate-name bench1 [..]`
+[FIXING] benches/bench1.rs
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn fix_test() {
+ full_project()
+ .cargo("fix -v --test 'te*1' --allow-no-vcs")
+ .with_stderr(
+ "\
+[CHECKING] foo v0.0.1 ([CWD])
+[RUNNING] `[..] rustc --crate-name test1 [..]`
+[FIXING] tests/test1.rs
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn run_example_and_bin() {
+ let p = full_project();
+ p.cargo("run -v --bin 'bi*1'")
+ .with_status(101)
+ .with_stderr("[ERROR] `cargo run` does not support glob patterns on target selection")
+ .run();
+
+ p.cargo("run -v --example 'ex*1'")
+ .with_status(101)
+ .with_stderr("[ERROR] `cargo run` does not support glob patterns on target selection")
+ .run();
+}
+
+#[cargo_test]
+fn test_example() {
+ full_project()
+ .cargo("test -v --example 'ex*1'")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name example1 [..]`
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..]example1[..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn test_bin() {
+ full_project()
+ .cargo("test -v --bin 'bi*1'")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name bin1 [..]`
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..]bin1[..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn test_bench() {
+ full_project()
+ .cargo("test -v --bench 'be*1'")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name bench1 [..]`")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name bin2 [..]`")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name bin1 [..]`")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name foo [..]`")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name [..]`
+[RUNNING] `rustc --crate-name [..]`
+[RUNNING] `rustc --crate-name [..]`
+[RUNNING] `rustc --crate-name [..]`
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..]bench1[..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn test_test() {
+ full_project()
+ .cargo("test -v --test 'te*1'")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name test1 [..]`")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name bin2 [..]`")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name bin1 [..]`")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name foo [..]`")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name [..]`
+[RUNNING] `rustc --crate-name [..]`
+[RUNNING] `rustc --crate-name [..]`
+[RUNNING] `rustc --crate-name [..]`
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..]test1[..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bench_example() {
+ full_project()
+ .cargo("bench -v --example 'ex*1'")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name example1 [..]`
+[FINISHED] bench [optimized] target(s) in [..]
+[RUNNING] `[..]example1[..] --bench`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bench_bin() {
+ full_project()
+ .cargo("bench -v --bin 'bi*1'")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name bin1 [..]`
+[FINISHED] bench [optimized] target(s) in [..]
+[RUNNING] `[..]bin1[..] --bench`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bench_bench() {
+ full_project()
+ .cargo("bench -v --bench 'be*1'")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name bench1 [..]`")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name bin2 [..]`")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name bin1 [..]`")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name foo [..]`")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name [..]`
+[RUNNING] `rustc --crate-name [..]`
+[RUNNING] `rustc --crate-name [..]`
+[RUNNING] `rustc --crate-name [..]`
+[FINISHED] bench [optimized] target(s) in [..]
+[RUNNING] `[..]bench1[..] --bench`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bench_test() {
+ full_project()
+ .cargo("bench -v --test 'te*1'")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name test1 [..]`")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name bin2 [..]`")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name bin1 [..]`")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name foo [..]`")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name [..]`
+[RUNNING] `rustc --crate-name [..]`
+[RUNNING] `rustc --crate-name [..]`
+[RUNNING] `rustc --crate-name [..]`
+[FINISHED] bench [optimized] target(s) in [..]
+[RUNNING] `[..]test1[..] --bench`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn install_example() {
+ full_project()
+ .cargo("install --path . --example 'ex*1'")
+ .with_stderr(
+ "\
+[INSTALLING] foo v0.0.1 ([CWD])
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] release [optimized] target(s) in [..]
+[INSTALLING] [..]/home/.cargo/bin/example1[EXE]
+[INSTALLED] package `foo v0.0.1 ([CWD])` (executable `example1[EXE]`)
+[WARNING] be sure to add [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn install_bin() {
+ full_project()
+ .cargo("install --path . --bin 'bi*1'")
+ .with_stderr(
+ "\
+[INSTALLING] foo v0.0.1 ([CWD])
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] release [optimized] target(s) in [..]
+[INSTALLING] [..]/home/.cargo/bin/bin1[EXE]
+[INSTALLED] package `foo v0.0.1 ([CWD])` (executable `bin1[EXE]`)
+[WARNING] be sure to add [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rustdoc_example() {
+ full_project()
+ .cargo("rustdoc -v --example 'ex*1'")
+ .with_stderr(
+ "\
+[DOCUMENTING] foo v0.0.1 ([CWD])
+[RUNNING] `rustdoc --crate-type bin --crate-name example1 [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rustdoc_bin() {
+ full_project()
+ .cargo("rustdoc -v --bin 'bi*1'")
+ .with_stderr(
+ "\
+[DOCUMENTING] foo v0.0.1 ([CWD])
+[RUNNING] `rustdoc --crate-type bin --crate-name bin1 [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rustdoc_bench() {
+ full_project()
+ .cargo("rustdoc -v --bench 'be*1'")
+ .with_stderr(
+ "\
+[DOCUMENTING] foo v0.0.1 ([CWD])
+[RUNNING] `rustdoc --crate-type bin --crate-name bench1 [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rustdoc_test() {
+ full_project()
+ .cargo("rustdoc -v --test 'te*1'")
+ .with_stderr(
+ "\
+[DOCUMENTING] foo v0.0.1 ([CWD])
+[RUNNING] `rustdoc --crate-type bin --crate-name test1 [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rustc_example() {
+ full_project()
+ .cargo("rustc -v --example 'ex*1'")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name example1 [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rustc_bin() {
+ full_project()
+ .cargo("rustc -v --bin 'bi*1'")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name bin1 [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rustc_bench() {
+ full_project()
+ .cargo("rustc -v --bench 'be*1'")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name bench1 [..]`")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name bin2 [..]`")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name bin1 [..]`")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name foo [..]`")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name [..]`
+[RUNNING] `rustc --crate-name [..]`
+[RUNNING] `rustc --crate-name [..]`
+[RUNNING] `rustc --crate-name [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rustc_test() {
+ full_project()
+ .cargo("rustc -v --test 'te*1'")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name test1 [..]`")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name bin2 [..]`")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name bin1 [..]`")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name foo [..]`")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name [..]`
+[RUNNING] `rustc --crate-name [..]`
+[RUNNING] `rustc --crate-name [..]`
+[RUNNING] `rustc --crate-name [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+fn full_project() -> Project {
+ project()
+ .file("examples/example1.rs", "fn main() { }")
+ .file("examples/example2.rs", "fn main() { }")
+ .file("benches/bench1.rs", "")
+ .file("benches/bench2.rs", "")
+ .file("tests/test1.rs", "")
+ .file("tests/test2.rs", "")
+ .file("src/main.rs", "fn main() { }")
+ .file("src/bin/bin1.rs", "fn main() { }")
+ .file("src/bin/bin2.rs", "fn main() { }")
+ .build()
+}
diff --git a/src/tools/cargo/tests/testsuite/help.rs b/src/tools/cargo/tests/testsuite/help.rs
new file mode 100644
index 000000000..fdb527e76
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/help.rs
@@ -0,0 +1,219 @@
+//! Tests for cargo's help output.
+
+use cargo_test_support::registry::Package;
+use cargo_test_support::{basic_manifest, cargo_exe, cargo_process, paths, process, project};
+use std::fs;
+use std::path::Path;
+use std::str::from_utf8;
+
+#[cargo_test]
+fn help() {
+ cargo_process("").run();
+ cargo_process("help").run();
+ cargo_process("-h").run();
+ cargo_process("help build").run();
+ cargo_process("build -h").run();
+ cargo_process("help help").run();
+ // Ensure that help output goes to stdout, not stderr.
+ cargo_process("search --help").with_stderr("").run();
+ cargo_process("search --help")
+ .with_stdout_contains("[..] --frozen [..]")
+ .run();
+}
+
+#[cargo_test]
+fn help_external_subcommand() {
+ // Check that `help external-subcommand` forwards the --help flag to the
+ // given subcommand.
+ Package::new("cargo-fake-help", "1.0.0")
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ if ::std::env::args().nth(2) == Some(String::from("--help")) {
+ println!("fancy help output");
+ }
+ }
+ "#,
+ )
+ .publish();
+ cargo_process("install cargo-fake-help").run();
+ cargo_process("help fake-help")
+ .with_stdout("fancy help output\n")
+ .run();
+}
+
+#[cargo_test]
+fn z_flags_help() {
+ // Test that the output of `cargo -Z help` shows a different help screen with
+ // all the `-Z` flags.
+ cargo_process("-Z help")
+ .with_stdout_contains(
+ " -Z allow-features[..]-- Allow *only* the listed unstable features",
+ )
+ .run();
+}
+
+fn help_with_man(display_command: &str) {
+ // Build a "man" process that just echoes the contents.
+ let p = project()
+ .at(display_command)
+ .file("Cargo.toml", &basic_manifest(display_command, "1.0.0"))
+ .file(
+ "src/main.rs",
+ &r#"
+ fn main() {
+ eprintln!("custom __COMMAND__");
+ let path = std::env::args().skip(1).next().unwrap();
+ let mut f = std::fs::File::open(path).unwrap();
+ std::io::copy(&mut f, &mut std::io::stdout()).unwrap();
+ }
+ "#
+ .replace("__COMMAND__", display_command),
+ )
+ .build();
+ p.cargo("build").run();
+
+ help_with_man_and_path(display_command, "build", "build", &p.target_debug_dir());
+}
+
+fn help_with_man_and_path(
+ display_command: &str,
+ subcommand: &str,
+ actual_subcommand: &str,
+ path: &Path,
+) {
+ let contents = if display_command == "man" {
+ fs::read_to_string(format!("src/etc/man/cargo-{}.1", actual_subcommand)).unwrap()
+ } else {
+ fs::read_to_string(format!(
+ "src/doc/man/generated_txt/cargo-{}.txt",
+ actual_subcommand
+ ))
+ .unwrap()
+ };
+
+ let output = process(&cargo_exe())
+ .arg("help")
+ .arg(subcommand)
+ .env("PATH", path)
+ .exec_with_output()
+ .unwrap();
+ assert!(output.status.success());
+ let stderr = from_utf8(&output.stderr).unwrap();
+ if display_command.is_empty() {
+ assert_eq!(stderr, "");
+ } else {
+ assert_eq!(stderr, format!("custom {}\n", display_command));
+ }
+ let stdout = from_utf8(&output.stdout).unwrap();
+ assert_eq!(stdout, contents);
+}
+
+fn help_with_stdout_and_path(subcommand: &str, path: &Path) -> String {
+ let output = process(&cargo_exe())
+ .arg("help")
+ .arg(subcommand)
+ .env("PATH", path)
+ .exec_with_output()
+ .unwrap();
+ assert!(output.status.success());
+ let stderr = from_utf8(&output.stderr).unwrap();
+ assert_eq!(stderr, "");
+ let stdout = from_utf8(&output.stdout).unwrap();
+ stdout.to_string()
+}
+
+#[cargo_test]
+fn help_man() {
+ // Checks that `help command` displays the man page using the given command.
+ help_with_man("man");
+ help_with_man("less");
+ help_with_man("more");
+
+ // Check with no commands in PATH.
+ help_with_man_and_path("", "build", "build", Path::new(""));
+}
+
+#[cargo_test]
+fn help_alias() {
+ // Check that `help some_alias` will resolve.
+ help_with_man_and_path("", "b", "build", Path::new(""));
+
+ let config = paths::root().join(".cargo/config");
+ fs::create_dir_all(config.parent().unwrap()).unwrap();
+ fs::write(
+ config,
+ r#"
+ [alias]
+ empty-alias = ""
+ simple-alias = "build"
+ complex-alias = ["build", "--release"]
+ "#,
+ )
+ .unwrap();
+
+ // The `empty-alias` returns an error.
+ cargo_process("help empty-alias")
+ .env("PATH", Path::new(""))
+ .with_stderr_contains("[..]The subcommand 'empty-alias' wasn't recognized[..]")
+ .run_expect_error();
+
+ // Because `simple-alias` aliases a subcommand with no arguments, help shows the manpage.
+ help_with_man_and_path("", "simple-alias", "build", Path::new(""));
+
+ // Help for `complex-alias` displays the full alias command.
+ let out = help_with_stdout_and_path("complex-alias", Path::new(""));
+ assert_eq!(out, "`complex-alias` is aliased to `build --release`\n");
+}
+
+#[cargo_test]
+fn alias_z_flag_help() {
+ cargo_process("build -Z help")
+ .with_stdout_contains(
+ " -Z allow-features[..]-- Allow *only* the listed unstable features",
+ )
+ .run();
+
+ cargo_process("run -Z help")
+ .with_stdout_contains(
+ " -Z allow-features[..]-- Allow *only* the listed unstable features",
+ )
+ .run();
+
+ cargo_process("check -Z help")
+ .with_stdout_contains(
+ " -Z allow-features[..]-- Allow *only* the listed unstable features",
+ )
+ .run();
+
+ cargo_process("test -Z help")
+ .with_stdout_contains(
+ " -Z allow-features[..]-- Allow *only* the listed unstable features",
+ )
+ .run();
+
+ cargo_process("b -Z help")
+ .with_stdout_contains(
+ " -Z allow-features[..]-- Allow *only* the listed unstable features",
+ )
+ .run();
+
+ cargo_process("r -Z help")
+ .with_stdout_contains(
+ " -Z allow-features[..]-- Allow *only* the listed unstable features",
+ )
+ .run();
+
+ cargo_process("c -Z help")
+ .with_stdout_contains(
+ " -Z allow-features[..]-- Allow *only* the listed unstable features",
+ )
+ .run();
+
+ cargo_process("t -Z help")
+ .with_stdout_contains(
+ " -Z allow-features[..]-- Allow *only* the listed unstable features",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/https.rs b/src/tools/cargo/tests/testsuite/https.rs
new file mode 100644
index 000000000..501eeae05
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/https.rs
@@ -0,0 +1,152 @@
+//! Network tests for https transport.
+//!
+//! Note that these tests will generally require setting CARGO_CONTAINER_TESTS
+//! or CARGO_PUBLIC_NETWORK_TESTS.
+
+use cargo_test_support::containers::Container;
+use cargo_test_support::project;
+
+#[cargo_test(container_test)]
+fn self_signed_should_fail() {
+ // Cargo should not allow a connection to a self-signed certificate.
+ let apache = Container::new("apache").launch();
+ let port = apache.port_mappings[&443];
+ let url = format!("https://127.0.0.1:{port}/repos/bar.git");
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = {{ git = "{url}" }}
+ "#
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+ // I think the text here depends on the curl backend.
+ let err_msg = if cfg!(target_os = "macos") {
+ "unexpected return value from ssl handshake -9806; class=Ssl (16)"
+ } else if cfg!(unix) {
+ "the SSL certificate is invalid; class=Ssl (16); code=Certificate (-17)"
+ } else if cfg!(windows) {
+ "user cancelled certificate check; class=Http (34); code=Certificate (-17)"
+ } else {
+ panic!("target not supported");
+ };
+ p.cargo("fetch")
+ .with_status(101)
+ .with_stderr(&format!(
+ "\
+[UPDATING] git repository `https://127.0.0.1:[..]/repos/bar.git`
+error: failed to get `bar` as a dependency of package `foo v0.1.0 ([ROOT]/foo)`
+
+Caused by:
+ failed to load source for dependency `bar`
+
+Caused by:
+ Unable to update https://127.0.0.1:[..]/repos/bar.git
+
+Caused by:
+ failed to clone into: [ROOT]/home/.cargo/git/db/bar-[..]
+
+Caused by:
+ network failure seems to have happened
+ if a proxy or similar is necessary `net.git-fetch-with-cli` may help here
+ https://doc.rust-lang.org/cargo/reference/config.html#netgit-fetch-with-cli
+
+Caused by:
+ {err_msg}
+"
+ ))
+ .run();
+}
+
+#[cargo_test(container_test)]
+fn self_signed_with_cacert() {
+ // When using cainfo, that should allow a connection to a self-signed cert.
+
+ if cfg!(target_os = "macos") {
+ // This test only seems to work with the
+ // curl-sys/force-system-lib-on-osx feature enabled. For some reason
+ // SecureTransport doesn't seem to like the self-signed certificate.
+ // It works if the certificate is manually approved via Keychain
+ // Access. The system libcurl is built with a LibreSSL fallback which
+ // is used when CAINFO is set, which seems to work correctly. This
+ // could use some more investigation. The official Rust binaries use
+ // curl-sys/force-system-lib-on-osx so it is mostly an issue for local
+ // testing.
+ //
+ // The error is:
+ // [60] SSL peer certificate or SSH remote key was not OK (SSL:
+ // certificate verification failed (result: 5)); class=Net (12)
+ let curl_v = curl::Version::get();
+ if curl_v.vendored() {
+ eprintln!(
+ "vendored curl not supported on macOS, \
+ set curl-sys/force-system-lib-on-osx to enable"
+ );
+ return;
+ }
+ }
+
+ let apache = Container::new("apache").launch();
+ let port = apache.port_mappings[&443];
+ let url = format!("https://127.0.0.1:{port}/repos/bar.git");
+ let server_crt = apache.read_file("/usr/local/apache2/conf/server.crt");
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = {{ git = "{url}" }}
+ "#
+ ),
+ )
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config.toml",
+ &format!(
+ r#"
+ [http]
+ cainfo = "server.crt"
+ "#
+ ),
+ )
+ .file("server.crt", &server_crt)
+ .build();
+ p.cargo("fetch")
+ .with_stderr("[UPDATING] git repository `https://127.0.0.1:[..]/repos/bar.git`")
+ .run();
+}
+
+#[cargo_test(public_network_test)]
+fn github_works() {
+ // Check that an https connection to github.com works.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bitflags = { git = "https://github.com/rust-lang/bitflags.git", tag="1.3.2" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("fetch")
+ .with_stderr("[UPDATING] git repository `https://github.com/rust-lang/bitflags.git`")
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/inheritable_workspace_fields.rs b/src/tools/cargo/tests/testsuite/inheritable_workspace_fields.rs
new file mode 100644
index 000000000..92c96b985
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/inheritable_workspace_fields.rs
@@ -0,0 +1,1717 @@
+//! Tests for inheriting Cargo.toml fields with field.workspace = true
+use cargo_test_support::registry::{Dependency, Package, RegistryBuilder};
+use cargo_test_support::{
+ basic_lib_manifest, basic_manifest, git, path2url, paths, project, publish, registry,
+};
+
+#[cargo_test]
+fn permit_additional_workspace_fields() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar"]
+ [workspace.package]
+ version = "1.2.3"
+ authors = ["Rustaceans"]
+ description = "This is a crate"
+ documentation = "https://www.rust-lang.org/learn"
+ readme = "README.md"
+ homepage = "https://www.rust-lang.org"
+ repository = "https://github.com/example/example"
+ license = "MIT"
+ license-file = "LICENSE"
+ keywords = ["cli"]
+ categories = ["development-tools"]
+ publish = false
+ edition = "2018"
+ rust-version = "1.60"
+ exclude = ["foo.txt"]
+ include = ["bar.txt", "**/*.rs", "Cargo.toml", "LICENSE", "README.md"]
+
+ [workspace.package.badges]
+ gitlab = { repository = "https://gitlab.com/rust-lang/rust", branch = "master" }
+
+ [workspace.dependencies]
+ dep = "0.1"
+ "#,
+ )
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+ workspace = ".."
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check")
+ // Should not warn about unused fields.
+ .with_stderr(
+ "\
+[CHECKING] bar v0.1.0 ([CWD]/bar)
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("check").run();
+ let lockfile = p.read_lockfile();
+ assert!(!lockfile.contains("dep"));
+}
+
+#[cargo_test]
+fn deny_optional_dependencies() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar"]
+
+ [workspace.dependencies]
+ dep1 = { version = "0.1", optional = true }
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+ workspace = ".."
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]foo/Cargo.toml`
+
+Caused by:
+ dep1 is optional, but workspace dependencies cannot be optional
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn inherit_own_workspace_fields() {
+ let registry = RegistryBuilder::new().http_api().http_index().build();
+
+ let p = project().build();
+
+ let _ = git::repo(&paths::root().join("foo"))
+ .file(
+ "Cargo.toml",
+ r#"
+ badges.workspace = true
+
+ [package]
+ name = "foo"
+ version.workspace = true
+ authors.workspace = true
+ description.workspace = true
+ documentation.workspace = true
+ homepage.workspace = true
+ repository.workspace = true
+ license.workspace = true
+ keywords.workspace = true
+ categories.workspace = true
+ publish.workspace = true
+ edition.workspace = true
+ rust-version.workspace = true
+ exclude.workspace = true
+ include.workspace = true
+
+ [workspace]
+ members = []
+ [workspace.package]
+ version = "1.2.3"
+ authors = ["Rustaceans"]
+ description = "This is a crate"
+ documentation = "https://www.rust-lang.org/learn"
+ homepage = "https://www.rust-lang.org"
+ repository = "https://github.com/example/example"
+ license = "MIT"
+ keywords = ["cli"]
+ categories = ["development-tools"]
+ publish = true
+ edition = "2018"
+ rust-version = "1.60"
+ exclude = ["foo.txt"]
+ include = ["bar.txt", "**/*.rs", "Cargo.toml"]
+ [workspace.package.badges]
+ gitlab = { repository = "https://gitlab.com/rust-lang/rust", branch = "master" }
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("foo.txt", "") // should be ignored when packaging
+ .file("bar.txt", "") // should be included when packaging
+ .build();
+
+ p.cargo("publish")
+ .replace_crates_io(registry.index_url())
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[WARNING] [..]
+[..]
+[VERIFYING] foo v1.2.3 [..]
+[COMPILING] foo v1.2.3 [..]
+[FINISHED] [..]
+[PACKAGED] [..]
+[UPLOADING] foo v1.2.3 [..]
+[UPLOADED] foo v1.2.3 to registry `crates-io`
+note: Waiting for `foo v1.2.3` to be available at registry `crates-io`.
+You may press ctrl-c to skip waiting; the crate should be available shortly.
+[PUBLISHED] foo v1.2.3 at registry `crates-io`
+",
+ )
+ .run();
+
+ publish::validate_upload_with_contents(
+ r#"
+ {
+ "authors": ["Rustaceans"],
+ "badges": {
+ "gitlab": { "branch": "master", "repository": "https://gitlab.com/rust-lang/rust" }
+ },
+ "categories": ["development-tools"],
+ "deps": [],
+ "description": "This is a crate",
+ "documentation": "https://www.rust-lang.org/learn",
+ "features": {},
+ "homepage": "https://www.rust-lang.org",
+ "keywords": ["cli"],
+ "license": "MIT",
+ "license_file": null,
+ "links": null,
+ "name": "foo",
+ "readme": null,
+ "readme_file": null,
+ "repository": "https://github.com/example/example",
+ "vers": "1.2.3"
+ }
+ "#,
+ "foo-1.2.3.crate",
+ &[
+ "Cargo.lock",
+ "Cargo.toml",
+ "Cargo.toml.orig",
+ "src/main.rs",
+ ".cargo_vcs_info.json",
+ "bar.txt",
+ ],
+ &[(
+ "Cargo.toml",
+ &format!(
+ r#"{}
+[package]
+edition = "2018"
+rust-version = "1.60"
+name = "foo"
+version = "1.2.3"
+authors = ["Rustaceans"]
+exclude = ["foo.txt"]
+include = [
+ "bar.txt",
+ "**/*.rs",
+ "Cargo.toml",
+]
+publish = true
+description = "This is a crate"
+homepage = "https://www.rust-lang.org"
+documentation = "https://www.rust-lang.org/learn"
+keywords = ["cli"]
+categories = ["development-tools"]
+license = "MIT"
+repository = "https://github.com/example/example"
+
+[badges.gitlab]
+branch = "master"
+repository = "https://gitlab.com/rust-lang/rust"
+"#,
+ cargo::core::package::MANIFEST_PREAMBLE
+ ),
+ )],
+ );
+}
+
+#[cargo_test]
+fn inherit_own_dependencies() {
+ let registry = RegistryBuilder::new().http_api().http_index().build();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.2.0"
+ authors = []
+
+ [dependencies]
+ dep.workspace = true
+
+ [build-dependencies]
+ dep-build.workspace = true
+
+ [dev-dependencies]
+ dep-dev.workspace = true
+
+ [workspace]
+ members = []
+
+ [workspace.dependencies]
+ dep = "0.1"
+ dep-build = "0.8"
+ dep-dev = "0.5.2"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("dep", "0.1.2").publish();
+ Package::new("dep-build", "0.8.2").publish();
+ Package::new("dep-dev", "0.5.2").publish();
+
+ p.cargo("check")
+ // Unordered because the download order is nondeterministic.
+ .with_stderr_unordered(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] dep v0.1.2 ([..])
+[DOWNLOADED] dep-build v0.8.2 ([..])
+[CHECKING] dep v0.1.2
+[CHECKING] bar v0.2.0 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("check").run();
+ let lockfile = p.read_lockfile();
+ assert!(lockfile.contains("dep"));
+ assert!(lockfile.contains("dep-dev"));
+ assert!(lockfile.contains("dep-build"));
+
+ p.cargo("publish")
+ .replace_crates_io(registry.index_url())
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[WARNING] [..]
+[..]
+[PACKAGING] bar v0.2.0 [..]
+[UPDATING] [..]
+[VERIFYING] bar v0.2.0 [..]
+[COMPILING] dep v0.1.2
+[COMPILING] bar v0.2.0 [..]
+[FINISHED] [..]
+[PACKAGED] [..]
+[UPLOADING] bar v0.2.0 [..]
+[UPLOADED] bar v0.2.0 to registry `crates-io`
+note: Waiting for `bar v0.2.0` to be available at registry `crates-io`.
+You may press ctrl-c to skip waiting; the crate should be available shortly.
+[PUBLISHED] bar v0.2.0 at registry `crates-io`
+",
+ )
+ .run();
+
+ publish::validate_upload_with_contents(
+ r#"
+ {
+ "authors": [],
+ "badges": {},
+ "categories": [],
+ "deps": [
+ {
+ "default_features": true,
+ "features": [],
+ "kind": "normal",
+ "name": "dep",
+ "optional": false,
+ "target": null,
+ "version_req": "^0.1"
+ },
+ {
+ "default_features": true,
+ "features": [],
+ "kind": "dev",
+ "name": "dep-dev",
+ "optional": false,
+ "target": null,
+ "version_req": "^0.5.2"
+ },
+ {
+ "default_features": true,
+ "features": [],
+ "kind": "build",
+ "name": "dep-build",
+ "optional": false,
+ "target": null,
+ "version_req": "^0.8"
+ }
+ ],
+ "description": null,
+ "documentation": null,
+ "features": {},
+ "homepage": null,
+ "keywords": [],
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "name": "bar",
+ "readme": null,
+ "readme_file": null,
+ "repository": null,
+ "vers": "0.2.0"
+ }
+ "#,
+ "bar-0.2.0.crate",
+ &["Cargo.toml", "Cargo.toml.orig", "Cargo.lock", "src/main.rs"],
+ &[(
+ "Cargo.toml",
+ &format!(
+ r#"{}
+[package]
+name = "bar"
+version = "0.2.0"
+authors = []
+
+[dependencies.dep]
+version = "0.1"
+
+[dev-dependencies.dep-dev]
+version = "0.5.2"
+
+[build-dependencies.dep-build]
+version = "0.8"
+"#,
+ cargo::core::package::MANIFEST_PREAMBLE
+ ),
+ )],
+ );
+}
+
+#[cargo_test]
+fn inherit_own_detailed_dependencies() {
+ let registry = RegistryBuilder::new().http_api().http_index().build();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.2.0"
+ authors = []
+
+ [dependencies]
+ dep.workspace = true
+
+ [workspace]
+ members = []
+
+ [workspace.dependencies]
+ dep = { version = "0.1.2", features = ["testing"] }
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("dep", "0.1.2")
+ .feature("testing", &vec![])
+ .publish();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] dep v0.1.2 ([..])
+[CHECKING] dep v0.1.2
+[CHECKING] bar v0.2.0 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("check").run();
+ let lockfile = p.read_lockfile();
+ assert!(lockfile.contains("dep"));
+
+ p.cargo("publish")
+ .replace_crates_io(registry.index_url())
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[WARNING] [..]
+[..]
+[PACKAGING] bar v0.2.0 [..]
+[UPDATING] [..]
+[VERIFYING] bar v0.2.0 [..]
+[COMPILING] dep v0.1.2
+[COMPILING] bar v0.2.0 [..]
+[FINISHED] [..]
+[PACKAGED] [..]
+[UPLOADING] bar v0.2.0 [..]
+[UPLOADED] bar v0.2.0 to registry `crates-io`
+note: Waiting for `bar v0.2.0` to be available at registry `crates-io`.
+You may press ctrl-c to skip waiting; the crate should be available shortly.
+[PUBLISHED] bar v0.2.0 at registry `crates-io`
+",
+ )
+ .run();
+
+ publish::validate_upload_with_contents(
+ r#"
+ {
+ "authors": [],
+ "badges": {},
+ "categories": [],
+ "deps": [
+ {
+ "default_features": true,
+ "features": ["testing"],
+ "kind": "normal",
+ "name": "dep",
+ "optional": false,
+ "target": null,
+ "version_req": "^0.1.2"
+ }
+ ],
+ "description": null,
+ "documentation": null,
+ "features": {},
+ "homepage": null,
+ "keywords": [],
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "name": "bar",
+ "readme": null,
+ "readme_file": null,
+ "repository": null,
+ "vers": "0.2.0"
+ }
+ "#,
+ "bar-0.2.0.crate",
+ &["Cargo.toml", "Cargo.toml.orig", "Cargo.lock", "src/main.rs"],
+ &[(
+ "Cargo.toml",
+ &format!(
+ r#"{}
+[package]
+name = "bar"
+version = "0.2.0"
+authors = []
+
+[dependencies.dep]
+version = "0.1.2"
+features = ["testing"]
+"#,
+ cargo::core::package::MANIFEST_PREAMBLE
+ ),
+ )],
+ );
+}
+
+#[cargo_test]
+fn inherit_from_own_undefined_field() {
+ registry::init();
+
+ let p = project().build();
+
+ let _ = git::repo(&paths::root().join("foo"))
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "1.2.5"
+ authors = ["rustaceans"]
+ description.workspace = true
+
+ [workspace]
+ members = []
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[CWD]/Cargo.toml`
+
+Caused by:
+ error inheriting `description` from workspace root manifest's `workspace.package.description`
+
+Caused by:
+ `workspace.package.description` was not defined
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn inherited_dependencies_union_features() {
+ Package::new("dep", "0.1.0")
+ .feature("fancy", &["fancy_dep"])
+ .feature("dancy", &["dancy_dep"])
+ .add_dep(Dependency::new("fancy_dep", "0.2").optional(true))
+ .add_dep(Dependency::new("dancy_dep", "0.6").optional(true))
+ .file("src/lib.rs", "")
+ .publish();
+
+ Package::new("fancy_dep", "0.2.4").publish();
+ Package::new("dancy_dep", "0.6.8").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.2.0"
+ authors = []
+ [dependencies]
+ dep = { workspace = true, features = ["dancy"] }
+
+ [workspace]
+ members = []
+ [workspace.dependencies]
+ dep = { version = "0.1", features = ["fancy"] }
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] fancy_dep v0.2.4 ([..])
+[DOWNLOADED] dep v0.1.0 ([..])
+[DOWNLOADED] dancy_dep v0.6.8 ([..])
+[CHECKING] [..]
+[CHECKING] [..]
+[CHECKING] dep v0.1.0
+[CHECKING] bar v0.2.0 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ let lockfile = p.read_lockfile();
+ assert!(lockfile.contains("dep"));
+ assert!(lockfile.contains("fancy_dep"));
+ assert!(lockfile.contains("dancy_dep"));
+}
+
+#[cargo_test]
+fn inherit_workspace_fields() {
+ let registry = RegistryBuilder::new().http_api().http_index().build();
+
+ let p = project().build();
+
+ let _ = git::repo(&paths::root().join("foo"))
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar"]
+ [workspace.package]
+ version = "1.2.3"
+ authors = ["Rustaceans"]
+ description = "This is a crate"
+ documentation = "https://www.rust-lang.org/learn"
+ readme = "README.md"
+ homepage = "https://www.rust-lang.org"
+ repository = "https://github.com/example/example"
+ license = "MIT"
+ license-file = "LICENSE"
+ keywords = ["cli"]
+ categories = ["development-tools"]
+ publish = true
+ edition = "2018"
+ rust-version = "1.60"
+ exclude = ["foo.txt"]
+ include = ["bar.txt", "**/*.rs", "Cargo.toml", "LICENSE", "README.md"]
+ [workspace.package.badges]
+ gitlab = { repository = "https://gitlab.com/rust-lang/rust", branch = "master" }
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ badges.workspace = true
+ [package]
+ name = "bar"
+ workspace = ".."
+ version.workspace = true
+ authors.workspace = true
+ description.workspace = true
+ documentation.workspace = true
+ readme.workspace = true
+ homepage.workspace = true
+ repository.workspace = true
+ license.workspace = true
+ license-file.workspace = true
+ keywords.workspace = true
+ categories.workspace = true
+ publish.workspace = true
+ edition.workspace = true
+ rust-version.workspace = true
+ exclude.workspace = true
+ include.workspace = true
+ "#,
+ )
+ .file("LICENSE", "license")
+ .file("README.md", "README.md")
+ .file("bar/src/main.rs", "fn main() {}")
+ .file("bar/foo.txt", "") // should be ignored when packaging
+ .file("bar/bar.txt", "") // should be included when packaging
+ .build();
+
+ p.cargo("publish")
+ .replace_crates_io(registry.index_url())
+ .cwd("bar")
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[WARNING] [..]
+[..]
+[VERIFYING] bar v1.2.3 [..]
+[WARNING] [..]
+[..]
+[..]
+[..]
+[COMPILING] bar v1.2.3 [..]
+[FINISHED] [..]
+[PACKAGED] [..]
+[UPLOADING] bar v1.2.3 [..]
+[UPLOADED] bar v1.2.3 to registry `crates-io`
+note: Waiting for `bar v1.2.3` to be available at registry `crates-io`.
+You may press ctrl-c to skip waiting; the crate should be available shortly.
+[PUBLISHED] bar v1.2.3 at registry `crates-io`
+",
+ )
+ .run();
+
+ publish::validate_upload_with_contents(
+ r#"
+ {
+ "authors": ["Rustaceans"],
+ "badges": {
+ "gitlab": { "branch": "master", "repository": "https://gitlab.com/rust-lang/rust" }
+ },
+ "categories": ["development-tools"],
+ "deps": [],
+ "description": "This is a crate",
+ "documentation": "https://www.rust-lang.org/learn",
+ "features": {},
+ "homepage": "https://www.rust-lang.org",
+ "keywords": ["cli"],
+ "license": "MIT",
+ "license_file": "../LICENSE",
+ "links": null,
+ "name": "bar",
+ "readme": "README.md",
+ "readme_file": "../README.md",
+ "repository": "https://github.com/example/example",
+ "vers": "1.2.3"
+ }
+ "#,
+ "bar-1.2.3.crate",
+ &[
+ "Cargo.lock",
+ "Cargo.toml",
+ "Cargo.toml.orig",
+ "src/main.rs",
+ "README.md",
+ "LICENSE",
+ ".cargo_vcs_info.json",
+ "bar.txt",
+ ],
+ &[(
+ "Cargo.toml",
+ &format!(
+ r#"{}
+[package]
+edition = "2018"
+rust-version = "1.60"
+name = "bar"
+version = "1.2.3"
+authors = ["Rustaceans"]
+exclude = ["foo.txt"]
+include = [
+ "bar.txt",
+ "**/*.rs",
+ "Cargo.toml",
+ "LICENSE",
+ "README.md",
+]
+publish = true
+description = "This is a crate"
+homepage = "https://www.rust-lang.org"
+documentation = "https://www.rust-lang.org/learn"
+readme = "README.md"
+keywords = ["cli"]
+categories = ["development-tools"]
+license = "MIT"
+license-file = "LICENSE"
+repository = "https://github.com/example/example"
+
+[badges.gitlab]
+branch = "master"
+repository = "https://gitlab.com/rust-lang/rust"
+"#,
+ cargo::core::package::MANIFEST_PREAMBLE
+ ),
+ )],
+ );
+}
+
+#[cargo_test]
+fn inherit_dependencies() {
+ let registry = RegistryBuilder::new().http_api().http_index().build();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar"]
+ [workspace.dependencies]
+ dep = "0.1"
+ dep-build = "0.8"
+ dep-dev = "0.5.2"
+ "#,
+ )
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ workspace = ".."
+ name = "bar"
+ version = "0.2.0"
+ authors = []
+ [dependencies]
+ dep.workspace = true
+ [build-dependencies]
+ dep-build.workspace = true
+ [dev-dependencies]
+ dep-dev.workspace = true
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("dep", "0.1.2").publish();
+ Package::new("dep-build", "0.8.2").publish();
+ Package::new("dep-dev", "0.5.2").publish();
+
+ p.cargo("check")
+ // Unordered because the download order is nondeterministic.
+ .with_stderr_unordered(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] dep v0.1.2 ([..])
+[DOWNLOADED] dep-build v0.8.2 ([..])
+[CHECKING] dep v0.1.2
+[CHECKING] bar v0.2.0 ([CWD]/bar)
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("check").run();
+ let lockfile = p.read_lockfile();
+ assert!(lockfile.contains("dep"));
+ assert!(lockfile.contains("dep-dev"));
+ assert!(lockfile.contains("dep-build"));
+
+ p.cargo("publish")
+ .replace_crates_io(registry.index_url())
+ .cwd("bar")
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[WARNING] [..]
+[..]
+[PACKAGING] bar v0.2.0 [..]
+[UPDATING] [..]
+[VERIFYING] bar v0.2.0 [..]
+[COMPILING] dep v0.1.2
+[COMPILING] bar v0.2.0 [..]
+[FINISHED] [..]
+[PACKAGED] [..]
+[UPLOADING] bar v0.2.0 [..]
+[UPLOADED] bar v0.2.0 to registry `crates-io`
+note: Waiting for `bar v0.2.0` to be available at registry `crates-io`.
+You may press ctrl-c to skip waiting; the crate should be available shortly.
+[PUBLISHED] bar v0.2.0 at registry `crates-io`
+",
+ )
+ .run();
+
+ publish::validate_upload_with_contents(
+ r#"
+ {
+ "authors": [],
+ "badges": {},
+ "categories": [],
+ "deps": [
+ {
+ "default_features": true,
+ "features": [],
+ "kind": "normal",
+ "name": "dep",
+ "optional": false,
+ "target": null,
+ "version_req": "^0.1"
+ },
+ {
+ "default_features": true,
+ "features": [],
+ "kind": "dev",
+ "name": "dep-dev",
+ "optional": false,
+ "target": null,
+ "version_req": "^0.5.2"
+ },
+ {
+ "default_features": true,
+ "features": [],
+ "kind": "build",
+ "name": "dep-build",
+ "optional": false,
+ "target": null,
+ "version_req": "^0.8"
+ }
+ ],
+ "description": null,
+ "documentation": null,
+ "features": {},
+ "homepage": null,
+ "keywords": [],
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "name": "bar",
+ "readme": null,
+ "readme_file": null,
+ "repository": null,
+ "vers": "0.2.0"
+ }
+ "#,
+ "bar-0.2.0.crate",
+ &["Cargo.toml", "Cargo.toml.orig", "Cargo.lock", "src/main.rs"],
+ &[(
+ "Cargo.toml",
+ &format!(
+ r#"{}
+[package]
+name = "bar"
+version = "0.2.0"
+authors = []
+
+[dependencies.dep]
+version = "0.1"
+
+[dev-dependencies.dep-dev]
+version = "0.5.2"
+
+[build-dependencies.dep-build]
+version = "0.8"
+"#,
+ cargo::core::package::MANIFEST_PREAMBLE
+ ),
+ )],
+ );
+}
+
+#[cargo_test]
+fn inherit_target_dependencies() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar"]
+ [workspace.dependencies]
+ dep = "0.1"
+ "#,
+ )
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ workspace = ".."
+ name = "bar"
+ version = "0.2.0"
+ authors = []
+ [target.'cfg(unix)'.dependencies]
+ dep.workspace = true
+ [target.'cfg(windows)'.dependencies]
+ dep.workspace = true
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("dep", "0.1.2").publish();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] dep v0.1.2 ([..])
+[CHECKING] dep v0.1.2
+[CHECKING] bar v0.2.0 ([CWD]/bar)
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ let lockfile = p.read_lockfile();
+ assert!(lockfile.contains("dep"));
+}
+
+#[cargo_test]
+fn inherit_dependency_override_optional() {
+ Package::new("dep", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar"]
+ [workspace.dependencies]
+ dep = "0.1.0"
+ "#,
+ )
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ workspace = ".."
+ name = "bar"
+ version = "0.2.0"
+ authors = []
+ [dependencies]
+ dep = { workspace = true, optional = true }
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[CHECKING] bar v0.2.0 ([CWD]/bar)
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn inherit_dependency_features() {
+ Package::new("dep", "0.1.0")
+ .feature("fancy", &["fancy_dep"])
+ .add_dep(Dependency::new("fancy_dep", "0.2").optional(true))
+ .file("src/lib.rs", "")
+ .publish();
+
+ Package::new("fancy_dep", "0.2.4").publish();
+ Package::new("dancy_dep", "0.6.8").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.2.0"
+ authors = []
+ [dependencies]
+ dep = { workspace = true, features = ["fancy"] }
+
+ [workspace]
+ members = []
+ [workspace.dependencies]
+ dep = "0.1"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] fancy_dep v0.2.4 ([..])
+[DOWNLOADED] dep v0.1.0 ([..])
+[CHECKING] fancy_dep v0.2.4
+[CHECKING] dep v0.1.0
+[CHECKING] bar v0.2.0 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ let lockfile = p.read_lockfile();
+ assert!(lockfile.contains("dep"));
+ assert!(lockfile.contains("fancy_dep"));
+}
+
+#[cargo_test]
+fn inherit_detailed_dependencies() {
+ let git_project = git::new("detailed", |project| {
+ project
+ .file("Cargo.toml", &basic_lib_manifest("detailed"))
+ .file(
+ "src/detailed.rs",
+ r#"
+ pub fn hello() -> &'static str {
+ "hello world"
+ }
+ "#,
+ )
+ });
+
+ // Make a new branch based on the current HEAD commit
+ let repo = git2::Repository::open(&git_project.root()).unwrap();
+ let head = repo.head().unwrap().target().unwrap();
+ let head = repo.find_commit(head).unwrap();
+ repo.branch("branchy", &head, true).unwrap();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [workspace]
+ members = ["bar"]
+ [workspace.dependencies]
+ detailed = {{ git = '{}', branch = "branchy" }}
+ "#,
+ git_project.url()
+ ),
+ )
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ workspace = ".."
+ name = "bar"
+ version = "0.2.0"
+ authors = []
+ [dependencies]
+ detailed.workspace = true
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}")
+ .build();
+
+ let git_root = git_project.root();
+
+ p.cargo("check")
+ .with_stderr(&format!(
+ "\
+[UPDATING] git repository `{}`\n\
+[CHECKING] detailed v0.5.0 ({}?branch=branchy#[..])\n\
+[CHECKING] bar v0.2.0 ([CWD]/bar)\n\
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]\n",
+ path2url(&git_root),
+ path2url(&git_root),
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn inherit_path_dependencies() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar"]
+ [workspace.dependencies]
+ dep = { path = "dep" }
+ "#,
+ )
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ workspace = ".."
+ name = "bar"
+ version = "0.2.0"
+ authors = []
+ [dependencies]
+ dep.workspace = true
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}")
+ .file("dep/Cargo.toml", &basic_manifest("dep", "0.9.0"))
+ .file("dep/src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[CHECKING] dep v0.9.0 ([CWD]/dep)
+[CHECKING] bar v0.2.0 ([CWD]/bar)
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ let lockfile = p.read_lockfile();
+ assert!(lockfile.contains("dep"));
+}
+
+#[cargo_test]
+fn error_workspace_false() {
+ registry::init();
+
+ let p = project().build();
+
+ let _ = git::repo(&paths::root().join("foo"))
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ workspace = ".."
+ version = "1.2.3"
+ authors = ["rustaceans"]
+ description = { workspace = false }
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check")
+ .cwd("bar")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[CWD]/Cargo.toml`
+
+Caused by:
+ `workspace` cannot be false
+ in `package.description.workspace`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn error_workspace_dependency_looked_for_workspace_itself() {
+ registry::init();
+
+ let p = project().build();
+
+ let _ = git::repo(&paths::root().join("foo"))
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "1.2.3"
+
+ [dependencies]
+ dep.workspace = true
+
+ [workspace]
+ members = []
+
+ [workspace.dependencies]
+ dep.workspace = true
+
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[WARNING] [CWD]/Cargo.toml: unused manifest key: workspace.dependencies.dep.workspace
+[WARNING] [CWD]/Cargo.toml: dependency (dep) specified without providing a local path, Git repository, or version to use. This will be considered an error in future versions
+[UPDATING] `dummy-registry` index
+[ERROR] no matching package named `dep` found
+location searched: registry `crates-io`
+required by package `bar v1.2.3 ([CWD])`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn error_malformed_workspace_root() {
+ registry::init();
+
+ let p = project().build();
+
+ let _ = git::repo(&paths::root().join("foo"))
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = [invalid toml
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ workspace = ".."
+ version = "1.2.3"
+ authors = ["rustaceans"]
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check")
+ .cwd("bar")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]/foo/Cargo.toml`
+
+Caused by:
+ [..]
+
+Caused by:
+ [..]
+ |
+ 3 | members = [invalid toml
+ | ^
+ invalid array
+ expected `]`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn error_no_root_workspace() {
+ registry::init();
+
+ let p = project().build();
+
+ let _ = git::repo(&paths::root().join("foo"))
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ workspace = ".."
+ version = "1.2.3"
+ authors = ["rustaceans"]
+ description.workspace = true
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check")
+ .cwd("bar")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]/Cargo.toml`
+
+Caused by:
+ error inheriting `description` from workspace root manifest's `workspace.package.description`
+
+Caused by:
+ root of a workspace inferred but wasn't a root: [..]/Cargo.toml
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn error_inherit_unspecified_dependency() {
+ let p = project().build();
+
+ let _ = git::repo(&paths::root().join("foo"))
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ workspace = ".."
+ version = "1.2.3"
+ authors = ["rustaceans"]
+ [dependencies]
+ foo.workspace = true
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check")
+ .cwd("bar")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[CWD]/Cargo.toml`
+
+Caused by:
+ error inheriting `foo` from workspace root manifest's `workspace.dependencies.foo`
+
+Caused by:
+ `workspace.dependencies` was not defined
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn warn_inherit_def_feat_true_member_def_feat_false() {
+ Package::new("dep", "0.1.0")
+ .feature("default", &["fancy_dep"])
+ .add_dep(Dependency::new("fancy_dep", "0.2").optional(true))
+ .file("src/lib.rs", "")
+ .publish();
+
+ Package::new("fancy_dep", "0.2.4").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.2.0"
+ authors = []
+ [dependencies]
+ dep = { workspace = true, default-features = false }
+
+ [workspace]
+ members = []
+ [workspace.dependencies]
+ dep = { version = "0.1.0", default-features = true }
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[WARNING] [CWD]/Cargo.toml: `default-features` is ignored for dep, since `default-features` was \
+true for `workspace.dependencies.dep`, this could become a hard error in the future
+[UPDATING] `dummy-registry` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] fancy_dep v0.2.4 ([..])
+[DOWNLOADED] dep v0.1.0 ([..])
+[CHECKING] fancy_dep v0.2.4
+[CHECKING] dep v0.1.0
+[CHECKING] bar v0.2.0 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn warn_inherit_simple_member_def_feat_false() {
+ Package::new("dep", "0.1.0")
+ .feature("default", &["fancy_dep"])
+ .add_dep(Dependency::new("fancy_dep", "0.2").optional(true))
+ .file("src/lib.rs", "")
+ .publish();
+
+ Package::new("fancy_dep", "0.2.4").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.2.0"
+ authors = []
+ [dependencies]
+ dep = { workspace = true, default-features = false }
+
+ [workspace]
+ members = []
+ [workspace.dependencies]
+ dep = "0.1.0"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[WARNING] [CWD]/Cargo.toml: `default-features` is ignored for dep, since `default-features` was \
+not specified for `workspace.dependencies.dep`, this could become a hard error in the future
+[UPDATING] `dummy-registry` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] fancy_dep v0.2.4 ([..])
+[DOWNLOADED] dep v0.1.0 ([..])
+[CHECKING] fancy_dep v0.2.4
+[CHECKING] dep v0.1.0
+[CHECKING] bar v0.2.0 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn inherit_def_feat_false_member_def_feat_true() {
+ Package::new("dep", "0.1.0")
+ .feature("default", &["fancy_dep"])
+ .add_dep(Dependency::new("fancy_dep", "0.2").optional(true))
+ .file("src/lib.rs", "")
+ .publish();
+
+ Package::new("fancy_dep", "0.2.4").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.2.0"
+ authors = []
+ [dependencies]
+ dep = { workspace = true, default-features = true }
+
+ [workspace]
+ members = []
+ [workspace.dependencies]
+ dep = { version = "0.1.0", default-features = false }
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] fancy_dep v0.2.4 ([..])
+[DOWNLOADED] dep v0.1.0 ([..])
+[CHECKING] fancy_dep v0.2.4
+[CHECKING] dep v0.1.0
+[CHECKING] bar v0.2.0 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cannot_inherit_in_patch() {
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = []
+
+ [workspace.dependencies]
+ bar = { path = "bar" }
+
+ [package]
+ name = "foo"
+ version = "0.2.0"
+
+ [patch.crates-io]
+ bar.workspace = true
+
+ [dependencies]
+ bar = "0.1.0"
+
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[WARNING] [CWD]/Cargo.toml: unused manifest key: patch.crates-io.bar.workspace
+[WARNING] [CWD]/Cargo.toml: dependency (bar) specified without providing a local path, Git repository, or version to use. This will be considered an error in future versions
+[UPDATING] `dummy-registry` index
+[ERROR] failed to resolve patches for `https://github.com/rust-lang/crates.io-index`
+
+Caused by:
+ patch for `bar` in `https://github.com/rust-lang/crates.io-index` points to the same source, but patches must point to different sources
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn warn_inherit_unused_manifest_key_dep() {
+ Package::new("dep", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = []
+ [workspace.dependencies]
+ dep = { version = "0.1", wxz = "wxz" }
+
+ [package]
+ name = "bar"
+ version = "0.2.0"
+ authors = []
+
+ [dependencies]
+ dep = { workspace = true, wxz = "wxz" }
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[WARNING] [CWD]/Cargo.toml: unused manifest key: workspace.dependencies.dep.wxz
+[WARNING] [CWD]/Cargo.toml: unused manifest key: dependencies.dep.wxz
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] dep v0.1.0 ([..])
+[CHECKING] [..]
+[CHECKING] bar v0.2.0 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn warn_inherit_unused_manifest_key_package() {
+ Package::new("dep", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ badges = { workspace = true, xyz = "abc"}
+
+ [workspace]
+ members = []
+ [workspace.package]
+ version = "1.2.3"
+ authors = ["Rustaceans"]
+ description = "This is a crate"
+ documentation = "https://www.rust-lang.org/learn"
+ homepage = "https://www.rust-lang.org"
+ repository = "https://github.com/example/example"
+ license = "MIT"
+ keywords = ["cli"]
+ categories = ["development-tools"]
+ publish = true
+ edition = "2018"
+ rust-version = "1.60"
+ exclude = ["foo.txt"]
+ include = ["bar.txt", "**/*.rs", "Cargo.toml"]
+ [workspace.package.badges]
+ gitlab = { repository = "https://gitlab.com/rust-lang/rust", branch = "master" }
+
+ [package]
+ name = "bar"
+ version = { workspace = true, xyz = "abc"}
+ authors = { workspace = true, xyz = "abc"}
+ description = { workspace = true, xyz = "abc"}
+ documentation = { workspace = true, xyz = "abc"}
+ homepage = { workspace = true, xyz = "abc"}
+ repository = { workspace = true, xyz = "abc"}
+ license = { workspace = true, xyz = "abc"}
+ keywords = { workspace = true, xyz = "abc"}
+ categories = { workspace = true, xyz = "abc"}
+ publish = { workspace = true, xyz = "abc"}
+ edition = { workspace = true, xyz = "abc"}
+ rust-version = { workspace = true, xyz = "abc"}
+ exclude = { workspace = true, xyz = "abc"}
+ include = { workspace = true, xyz = "abc"}
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[WARNING] [CWD]/Cargo.toml: unused manifest key: package.authors.xyz
+[WARNING] [CWD]/Cargo.toml: unused manifest key: package.categories.xyz
+[WARNING] [CWD]/Cargo.toml: unused manifest key: package.description.xyz
+[WARNING] [CWD]/Cargo.toml: unused manifest key: package.documentation.xyz
+[WARNING] [CWD]/Cargo.toml: unused manifest key: package.edition.xyz
+[WARNING] [CWD]/Cargo.toml: unused manifest key: package.exclude.xyz
+[WARNING] [CWD]/Cargo.toml: unused manifest key: package.homepage.xyz
+[WARNING] [CWD]/Cargo.toml: unused manifest key: package.include.xyz
+[WARNING] [CWD]/Cargo.toml: unused manifest key: package.keywords.xyz
+[WARNING] [CWD]/Cargo.toml: unused manifest key: package.license.xyz
+[WARNING] [CWD]/Cargo.toml: unused manifest key: package.publish.xyz
+[WARNING] [CWD]/Cargo.toml: unused manifest key: package.repository.xyz
+[WARNING] [CWD]/Cargo.toml: unused manifest key: package.rust-version.xyz
+[WARNING] [CWD]/Cargo.toml: unused manifest key: package.version.xyz
+[CHECKING] bar v1.2.3 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/init/auto_git/in b/src/tools/cargo/tests/testsuite/init/auto_git/in
new file mode 120000
index 000000000..1202506b6
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/auto_git/in
@@ -0,0 +1 @@
+../empty_dir \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/init/auto_git/mod.rs b/src/tools/cargo/tests/testsuite/init/auto_git/mod.rs
new file mode 100644
index 000000000..68c217520
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/auto_git/mod.rs
@@ -0,0 +1,22 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = &project.root();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init --lib")
+ .current_dir(project_root)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
+ assert!(project_root.join(".git").is_dir());
+}
diff --git a/src/tools/cargo/tests/testsuite/init/auto_git/out/Cargo.toml b/src/tools/cargo/tests/testsuite/init/auto_git/out/Cargo.toml
new file mode 100644
index 000000000..dcdb8da2c
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/auto_git/out/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "case"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
diff --git a/src/tools/cargo/tests/testsuite/init/auto_git/out/src/lib.rs b/src/tools/cargo/tests/testsuite/init/auto_git/out/src/lib.rs
new file mode 100644
index 000000000..7d12d9af8
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/auto_git/out/src/lib.rs
@@ -0,0 +1,14 @@
+pub fn add(left: usize, right: usize) -> usize {
+ left + right
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn it_works() {
+ let result = add(2, 2);
+ assert_eq!(result, 4);
+ }
+}
diff --git a/src/tools/cargo/tests/testsuite/init/auto_git/stderr.log b/src/tools/cargo/tests/testsuite/init/auto_git/stderr.log
new file mode 100644
index 000000000..f459bf226
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/auto_git/stderr.log
@@ -0,0 +1 @@
+ Created library package
diff --git a/src/tools/cargo/tests/testsuite/init/auto_git/stdout.log b/src/tools/cargo/tests/testsuite/init/auto_git/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/auto_git/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit/in/src/main.rs b/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit/in/src/main.rs
new file mode 100644
index 000000000..65fdcf8da
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit/in/src/main.rs
@@ -0,0 +1,4 @@
+fn main() {
+ println!("Check that our file is not overwritten")
+}
+
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit/mod.rs b/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit/mod.rs
new file mode 100644
index 000000000..326bd218a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit/mod.rs
@@ -0,0 +1,21 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = &project.root();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init --bin --vcs none")
+ .current_dir(project_root)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit/out/Cargo.toml b/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit/out/Cargo.toml
new file mode 100644
index 000000000..dcdb8da2c
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit/out/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "case"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit/out/src/main.rs b/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit/out/src/main.rs
new file mode 100644
index 000000000..65fdcf8da
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit/out/src/main.rs
@@ -0,0 +1,4 @@
+fn main() {
+ println!("Check that our file is not overwritten")
+}
+
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit/stderr.log b/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit/stderr.log
new file mode 100644
index 000000000..3847e4e4a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit/stderr.log
@@ -0,0 +1 @@
+ Created binary (application) package
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit/stdout.log b/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit_nosrc/in/main.rs b/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit_nosrc/in/main.rs
new file mode 100644
index 000000000..65fdcf8da
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit_nosrc/in/main.rs
@@ -0,0 +1,4 @@
+fn main() {
+ println!("Check that our file is not overwritten")
+}
+
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit_nosrc/mod.rs b/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit_nosrc/mod.rs
new file mode 100644
index 000000000..1f16fb659
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit_nosrc/mod.rs
@@ -0,0 +1,22 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = &project.root();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init --bin --vcs none")
+ .current_dir(project_root)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), project_root);
+ assert!(!project_root.join("src").is_dir());
+}
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit_nosrc/out/Cargo.toml b/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit_nosrc/out/Cargo.toml
new file mode 100644
index 000000000..5c6c9158c
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit_nosrc/out/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "case"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+
+[[bin]]
+name = "case"
+path = "main.rs"
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit_nosrc/out/main.rs b/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit_nosrc/out/main.rs
new file mode 100644
index 000000000..65fdcf8da
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit_nosrc/out/main.rs
@@ -0,0 +1,4 @@
+fn main() {
+ println!("Check that our file is not overwritten")
+}
+
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit_nosrc/stderr.log b/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit_nosrc/stderr.log
new file mode 100644
index 000000000..3847e4e4a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit_nosrc/stderr.log
@@ -0,0 +1 @@
+ Created binary (application) package
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit_nosrc/stdout.log b/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit_nosrc/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_explicit_nosrc/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit/in/src/main.rs b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit/in/src/main.rs
new file mode 100644
index 000000000..65fdcf8da
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit/in/src/main.rs
@@ -0,0 +1,4 @@
+fn main() {
+ println!("Check that our file is not overwritten")
+}
+
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit/mod.rs b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit/mod.rs
new file mode 100644
index 000000000..12349a09b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit/mod.rs
@@ -0,0 +1,21 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = &project.root();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init --vcs none")
+ .current_dir(project_root)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit/out/Cargo.toml b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit/out/Cargo.toml
new file mode 100644
index 000000000..dcdb8da2c
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit/out/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "case"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit/out/src/main.rs b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit/out/src/main.rs
new file mode 100644
index 000000000..65fdcf8da
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit/out/src/main.rs
@@ -0,0 +1,4 @@
+fn main() {
+ println!("Check that our file is not overwritten")
+}
+
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit/stderr.log b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit/stderr.log
new file mode 100644
index 000000000..3847e4e4a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit/stderr.log
@@ -0,0 +1 @@
+ Created binary (application) package
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit/stdout.log b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namenosrc/in/case.rs b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namenosrc/in/case.rs
new file mode 100644
index 000000000..65fdcf8da
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namenosrc/in/case.rs
@@ -0,0 +1,4 @@
+fn main() {
+ println!("Check that our file is not overwritten")
+}
+
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namenosrc/mod.rs b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namenosrc/mod.rs
new file mode 100644
index 000000000..fe65940db
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namenosrc/mod.rs
@@ -0,0 +1,22 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = &project.root();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init --vcs none")
+ .current_dir(project_root)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), project_root);
+ assert!(!project_root.join("src").is_dir());
+}
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namenosrc/out/Cargo.toml b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namenosrc/out/Cargo.toml
new file mode 100644
index 000000000..8da5fe778
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namenosrc/out/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "case"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+
+[[bin]]
+name = "case"
+path = "case.rs"
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namenosrc/out/case.rs b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namenosrc/out/case.rs
new file mode 100644
index 000000000..65fdcf8da
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namenosrc/out/case.rs
@@ -0,0 +1,4 @@
+fn main() {
+ println!("Check that our file is not overwritten")
+}
+
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namenosrc/stderr.log b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namenosrc/stderr.log
new file mode 100644
index 000000000..3847e4e4a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namenosrc/stderr.log
@@ -0,0 +1 @@
+ Created binary (application) package
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namenosrc/stdout.log b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namenosrc/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namenosrc/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namesrc/in/src/case.rs b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namesrc/in/src/case.rs
new file mode 100644
index 000000000..65fdcf8da
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namesrc/in/src/case.rs
@@ -0,0 +1,4 @@
+fn main() {
+ println!("Check that our file is not overwritten")
+}
+
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namesrc/mod.rs b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namesrc/mod.rs
new file mode 100644
index 000000000..d3e8e66df
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namesrc/mod.rs
@@ -0,0 +1,22 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = &project.root();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init --vcs none")
+ .current_dir(project_root)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), project_root);
+ assert!(!project_root.join("src/main.rs").is_file());
+}
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namesrc/out/Cargo.toml b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namesrc/out/Cargo.toml
new file mode 100644
index 000000000..dec0aaea9
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namesrc/out/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "case"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+
+[[bin]]
+name = "case"
+path = "src/case.rs"
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namesrc/out/src/case.rs b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namesrc/out/src/case.rs
new file mode 100644
index 000000000..65fdcf8da
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namesrc/out/src/case.rs
@@ -0,0 +1,4 @@
+fn main() {
+ println!("Check that our file is not overwritten")
+}
+
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namesrc/stderr.log b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namesrc/stderr.log
new file mode 100644
index 000000000..3847e4e4a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namesrc/stderr.log
@@ -0,0 +1 @@
+ Created binary (application) package
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namesrc/stdout.log b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namesrc/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_namesrc/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_nosrc/in/main.rs b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_nosrc/in/main.rs
new file mode 100644
index 000000000..65fdcf8da
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_nosrc/in/main.rs
@@ -0,0 +1,4 @@
+fn main() {
+ println!("Check that our file is not overwritten")
+}
+
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_nosrc/mod.rs b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_nosrc/mod.rs
new file mode 100644
index 000000000..fe65940db
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_nosrc/mod.rs
@@ -0,0 +1,22 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = &project.root();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init --vcs none")
+ .current_dir(project_root)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), project_root);
+ assert!(!project_root.join("src").is_dir());
+}
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_nosrc/out/Cargo.toml b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_nosrc/out/Cargo.toml
new file mode 100644
index 000000000..5c6c9158c
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_nosrc/out/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "case"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+
+[[bin]]
+name = "case"
+path = "main.rs"
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_nosrc/out/main.rs b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_nosrc/out/main.rs
new file mode 100644
index 000000000..65fdcf8da
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_nosrc/out/main.rs
@@ -0,0 +1,4 @@
+fn main() {
+ println!("Check that our file is not overwritten")
+}
+
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_nosrc/stderr.log b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_nosrc/stderr.log
new file mode 100644
index 000000000..3847e4e4a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_nosrc/stderr.log
@@ -0,0 +1 @@
+ Created binary (application) package
diff --git a/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_nosrc/stdout.log b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_nosrc/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/bin_already_exists_implicit_nosrc/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/both_lib_and_bin/mod.rs b/src/tools/cargo/tests/testsuite/init/both_lib_and_bin/mod.rs
new file mode 100644
index 000000000..c9232320a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/both_lib_and_bin/mod.rs
@@ -0,0 +1,19 @@
+use cargo_test_support::paths;
+use cargo_test_support::prelude::*;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let cwd = paths::root();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init --lib --bin")
+ .current_dir(&cwd)
+ .assert()
+ .code(101)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert!(!cwd.join("Cargo.toml").is_file());
+}
diff --git a/src/tools/cargo/tests/testsuite/init/both_lib_and_bin/stderr.log b/src/tools/cargo/tests/testsuite/init/both_lib_and_bin/stderr.log
new file mode 100644
index 000000000..9d635a427
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/both_lib_and_bin/stderr.log
@@ -0,0 +1 @@
+error: can't specify both lib and binary outputs
diff --git a/src/tools/cargo/tests/testsuite/init/both_lib_and_bin/stdout.log b/src/tools/cargo/tests/testsuite/init/both_lib_and_bin/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/both_lib_and_bin/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/cant_create_library_when_both_binlib_present/in/case.rs b/src/tools/cargo/tests/testsuite/init/cant_create_library_when_both_binlib_present/in/case.rs
new file mode 100644
index 000000000..f328e4d9d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/cant_create_library_when_both_binlib_present/in/case.rs
@@ -0,0 +1 @@
+fn main() {}
diff --git a/src/tools/cargo/tests/testsuite/init/cant_create_library_when_both_binlib_present/in/lib.rs b/src/tools/cargo/tests/testsuite/init/cant_create_library_when_both_binlib_present/in/lib.rs
new file mode 100644
index 000000000..59760b549
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/cant_create_library_when_both_binlib_present/in/lib.rs
@@ -0,0 +1 @@
+fn f() {}
diff --git a/src/tools/cargo/tests/testsuite/init/cant_create_library_when_both_binlib_present/mod.rs b/src/tools/cargo/tests/testsuite/init/cant_create_library_when_both_binlib_present/mod.rs
new file mode 100644
index 000000000..5e9e1b94c
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/cant_create_library_when_both_binlib_present/mod.rs
@@ -0,0 +1,18 @@
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = &project.root();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init --lib")
+ .current_dir(project_root)
+ .assert()
+ .code(101)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+}
diff --git a/src/tools/cargo/tests/testsuite/init/cant_create_library_when_both_binlib_present/stderr.log b/src/tools/cargo/tests/testsuite/init/cant_create_library_when_both_binlib_present/stderr.log
new file mode 100644
index 000000000..c08dce96b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/cant_create_library_when_both_binlib_present/stderr.log
@@ -0,0 +1 @@
+error: cannot have a package with multiple libraries, found both `case.rs` and `lib.rs`
diff --git a/src/tools/cargo/tests/testsuite/init/cant_create_library_when_both_binlib_present/stdout.log b/src/tools/cargo/tests/testsuite/init/cant_create_library_when_both_binlib_present/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/cant_create_library_when_both_binlib_present/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/confused_by_multiple_lib_files/in/lib.rs b/src/tools/cargo/tests/testsuite/init/confused_by_multiple_lib_files/in/lib.rs
new file mode 100644
index 000000000..321163744
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/confused_by_multiple_lib_files/in/lib.rs
@@ -0,0 +1 @@
+fn f() { println!("lib.rs"); }
diff --git a/src/tools/cargo/tests/testsuite/init/confused_by_multiple_lib_files/in/src/lib.rs b/src/tools/cargo/tests/testsuite/init/confused_by_multiple_lib_files/in/src/lib.rs
new file mode 100644
index 000000000..f71455a1a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/confused_by_multiple_lib_files/in/src/lib.rs
@@ -0,0 +1 @@
+fn f() { println!("src/lib.rs"); }
diff --git a/src/tools/cargo/tests/testsuite/init/confused_by_multiple_lib_files/mod.rs b/src/tools/cargo/tests/testsuite/init/confused_by_multiple_lib_files/mod.rs
new file mode 100644
index 000000000..d1cba2ff7
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/confused_by_multiple_lib_files/mod.rs
@@ -0,0 +1,22 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = &project.root();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init --vcs none")
+ .current_dir(project_root)
+ .assert()
+ .code(101)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), project_root);
+ assert!(!project_root.join("Cargo.toml").is_file());
+}
diff --git a/src/tools/cargo/tests/testsuite/init/confused_by_multiple_lib_files/out/lib.rs b/src/tools/cargo/tests/testsuite/init/confused_by_multiple_lib_files/out/lib.rs
new file mode 100644
index 000000000..321163744
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/confused_by_multiple_lib_files/out/lib.rs
@@ -0,0 +1 @@
+fn f() { println!("lib.rs"); }
diff --git a/src/tools/cargo/tests/testsuite/init/confused_by_multiple_lib_files/out/src/lib.rs b/src/tools/cargo/tests/testsuite/init/confused_by_multiple_lib_files/out/src/lib.rs
new file mode 100644
index 000000000..f71455a1a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/confused_by_multiple_lib_files/out/src/lib.rs
@@ -0,0 +1 @@
+fn f() { println!("src/lib.rs"); }
diff --git a/src/tools/cargo/tests/testsuite/init/confused_by_multiple_lib_files/stderr.log b/src/tools/cargo/tests/testsuite/init/confused_by_multiple_lib_files/stderr.log
new file mode 100644
index 000000000..8dbd2aaf0
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/confused_by_multiple_lib_files/stderr.log
@@ -0,0 +1 @@
+error: cannot have a package with multiple libraries, found both `src/lib.rs` and `lib.rs`
diff --git a/src/tools/cargo/tests/testsuite/init/confused_by_multiple_lib_files/stdout.log b/src/tools/cargo/tests/testsuite/init/confused_by_multiple_lib_files/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/confused_by_multiple_lib_files/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/creates_binary_when_both_binlib_present/in/case.rs b/src/tools/cargo/tests/testsuite/init/creates_binary_when_both_binlib_present/in/case.rs
new file mode 100644
index 000000000..f328e4d9d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/creates_binary_when_both_binlib_present/in/case.rs
@@ -0,0 +1 @@
+fn main() {}
diff --git a/src/tools/cargo/tests/testsuite/init/creates_binary_when_both_binlib_present/in/lib.rs b/src/tools/cargo/tests/testsuite/init/creates_binary_when_both_binlib_present/in/lib.rs
new file mode 100644
index 000000000..59760b549
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/creates_binary_when_both_binlib_present/in/lib.rs
@@ -0,0 +1 @@
+fn f() {}
diff --git a/src/tools/cargo/tests/testsuite/init/creates_binary_when_both_binlib_present/mod.rs b/src/tools/cargo/tests/testsuite/init/creates_binary_when_both_binlib_present/mod.rs
new file mode 100644
index 000000000..326bd218a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/creates_binary_when_both_binlib_present/mod.rs
@@ -0,0 +1,21 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = &project.root();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init --bin --vcs none")
+ .current_dir(project_root)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/init/creates_binary_when_both_binlib_present/out/Cargo.toml b/src/tools/cargo/tests/testsuite/init/creates_binary_when_both_binlib_present/out/Cargo.toml
new file mode 100644
index 000000000..675c888a5
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/creates_binary_when_both_binlib_present/out/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "case"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+
+[[bin]]
+name = "case"
+path = "case.rs"
+
+[lib]
+name = "case"
+path = "lib.rs"
diff --git a/src/tools/cargo/tests/testsuite/init/creates_binary_when_both_binlib_present/out/case.rs b/src/tools/cargo/tests/testsuite/init/creates_binary_when_both_binlib_present/out/case.rs
new file mode 100644
index 000000000..f328e4d9d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/creates_binary_when_both_binlib_present/out/case.rs
@@ -0,0 +1 @@
+fn main() {}
diff --git a/src/tools/cargo/tests/testsuite/init/creates_binary_when_both_binlib_present/out/lib.rs b/src/tools/cargo/tests/testsuite/init/creates_binary_when_both_binlib_present/out/lib.rs
new file mode 100644
index 000000000..59760b549
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/creates_binary_when_both_binlib_present/out/lib.rs
@@ -0,0 +1 @@
+fn f() {}
diff --git a/src/tools/cargo/tests/testsuite/init/creates_binary_when_both_binlib_present/stderr.log b/src/tools/cargo/tests/testsuite/init/creates_binary_when_both_binlib_present/stderr.log
new file mode 100644
index 000000000..3847e4e4a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/creates_binary_when_both_binlib_present/stderr.log
@@ -0,0 +1 @@
+ Created binary (application) package
diff --git a/src/tools/cargo/tests/testsuite/init/creates_binary_when_both_binlib_present/stdout.log b/src/tools/cargo/tests/testsuite/init/creates_binary_when_both_binlib_present/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/creates_binary_when_both_binlib_present/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/creates_binary_when_instructed_and_has_lib_file/in/case.rs b/src/tools/cargo/tests/testsuite/init/creates_binary_when_instructed_and_has_lib_file/in/case.rs
new file mode 100644
index 000000000..59760b549
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/creates_binary_when_instructed_and_has_lib_file/in/case.rs
@@ -0,0 +1 @@
+fn f() {}
diff --git a/src/tools/cargo/tests/testsuite/init/creates_binary_when_instructed_and_has_lib_file/mod.rs b/src/tools/cargo/tests/testsuite/init/creates_binary_when_instructed_and_has_lib_file/mod.rs
new file mode 100644
index 000000000..326bd218a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/creates_binary_when_instructed_and_has_lib_file/mod.rs
@@ -0,0 +1,21 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = &project.root();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init --bin --vcs none")
+ .current_dir(project_root)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/init/creates_binary_when_instructed_and_has_lib_file/out/Cargo.toml b/src/tools/cargo/tests/testsuite/init/creates_binary_when_instructed_and_has_lib_file/out/Cargo.toml
new file mode 100644
index 000000000..8da5fe778
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/creates_binary_when_instructed_and_has_lib_file/out/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "case"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+
+[[bin]]
+name = "case"
+path = "case.rs"
diff --git a/src/tools/cargo/tests/testsuite/init/creates_binary_when_instructed_and_has_lib_file/out/case.rs b/src/tools/cargo/tests/testsuite/init/creates_binary_when_instructed_and_has_lib_file/out/case.rs
new file mode 100644
index 000000000..59760b549
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/creates_binary_when_instructed_and_has_lib_file/out/case.rs
@@ -0,0 +1 @@
+fn f() {}
diff --git a/src/tools/cargo/tests/testsuite/init/creates_binary_when_instructed_and_has_lib_file/stderr.log b/src/tools/cargo/tests/testsuite/init/creates_binary_when_instructed_and_has_lib_file/stderr.log
new file mode 100644
index 000000000..ec428f31c
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/creates_binary_when_instructed_and_has_lib_file/stderr.log
@@ -0,0 +1,2 @@
+warning: file `case.rs` seems to be a library file
+ Created binary (application) package
diff --git a/src/tools/cargo/tests/testsuite/init/creates_binary_when_instructed_and_has_lib_file/stdout.log b/src/tools/cargo/tests/testsuite/init/creates_binary_when_instructed_and_has_lib_file/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/creates_binary_when_instructed_and_has_lib_file/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/creates_library_when_instructed_and_has_bin_file/in/case.rs b/src/tools/cargo/tests/testsuite/init/creates_library_when_instructed_and_has_bin_file/in/case.rs
new file mode 100644
index 000000000..f328e4d9d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/creates_library_when_instructed_and_has_bin_file/in/case.rs
@@ -0,0 +1 @@
+fn main() {}
diff --git a/src/tools/cargo/tests/testsuite/init/creates_library_when_instructed_and_has_bin_file/mod.rs b/src/tools/cargo/tests/testsuite/init/creates_library_when_instructed_and_has_bin_file/mod.rs
new file mode 100644
index 000000000..59c192cb9
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/creates_library_when_instructed_and_has_bin_file/mod.rs
@@ -0,0 +1,21 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = &project.root();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init --lib --vcs none")
+ .current_dir(project_root)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/init/creates_library_when_instructed_and_has_bin_file/out/Cargo.toml b/src/tools/cargo/tests/testsuite/init/creates_library_when_instructed_and_has_bin_file/out/Cargo.toml
new file mode 100644
index 000000000..2c0464468
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/creates_library_when_instructed_and_has_bin_file/out/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "case"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+
+[lib]
+name = "case"
+path = "case.rs"
diff --git a/src/tools/cargo/tests/testsuite/init/creates_library_when_instructed_and_has_bin_file/out/case.rs b/src/tools/cargo/tests/testsuite/init/creates_library_when_instructed_and_has_bin_file/out/case.rs
new file mode 100644
index 000000000..f328e4d9d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/creates_library_when_instructed_and_has_bin_file/out/case.rs
@@ -0,0 +1 @@
+fn main() {}
diff --git a/src/tools/cargo/tests/testsuite/init/creates_library_when_instructed_and_has_bin_file/stderr.log b/src/tools/cargo/tests/testsuite/init/creates_library_when_instructed_and_has_bin_file/stderr.log
new file mode 100644
index 000000000..bf070e2da
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/creates_library_when_instructed_and_has_bin_file/stderr.log
@@ -0,0 +1,2 @@
+warning: file `case.rs` seems to be a binary (application) file
+ Created library package
diff --git a/src/tools/cargo/tests/testsuite/init/creates_library_when_instructed_and_has_bin_file/stdout.log b/src/tools/cargo/tests/testsuite/init/creates_library_when_instructed_and_has_bin_file/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/creates_library_when_instructed_and_has_bin_file/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/empty_dir/.keep b/src/tools/cargo/tests/testsuite/init/empty_dir/.keep
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/empty_dir/.keep
diff --git a/src/tools/cargo/tests/testsuite/init/empty_dir/mod.rs b/src/tools/cargo/tests/testsuite/init/empty_dir/mod.rs
new file mode 100644
index 000000000..074954f01
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/empty_dir/mod.rs
@@ -0,0 +1,7 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::{command_is_available, paths, Project};
+use std::fs;
+use std::process::Command;
+
+use crate::test_root;
diff --git a/src/tools/cargo/tests/testsuite/init/explicit_bin_with_git/in b/src/tools/cargo/tests/testsuite/init/explicit_bin_with_git/in
new file mode 120000
index 000000000..1202506b6
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/explicit_bin_with_git/in
@@ -0,0 +1 @@
+../empty_dir \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/init/explicit_bin_with_git/mod.rs b/src/tools/cargo/tests/testsuite/init/explicit_bin_with_git/mod.rs
new file mode 100644
index 000000000..7314e955c
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/explicit_bin_with_git/mod.rs
@@ -0,0 +1,21 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = &project.root();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init --vcs git --bin")
+ .current_dir(project_root)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/init/explicit_bin_with_git/out/Cargo.toml b/src/tools/cargo/tests/testsuite/init/explicit_bin_with_git/out/Cargo.toml
new file mode 100644
index 000000000..dcdb8da2c
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/explicit_bin_with_git/out/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "case"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
diff --git a/src/tools/cargo/tests/testsuite/init/explicit_bin_with_git/out/src/main.rs b/src/tools/cargo/tests/testsuite/init/explicit_bin_with_git/out/src/main.rs
new file mode 100644
index 000000000..e7a11a969
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/explicit_bin_with_git/out/src/main.rs
@@ -0,0 +1,3 @@
+fn main() {
+ println!("Hello, world!");
+}
diff --git a/src/tools/cargo/tests/testsuite/init/explicit_bin_with_git/stderr.log b/src/tools/cargo/tests/testsuite/init/explicit_bin_with_git/stderr.log
new file mode 100644
index 000000000..3847e4e4a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/explicit_bin_with_git/stderr.log
@@ -0,0 +1 @@
+ Created binary (application) package
diff --git a/src/tools/cargo/tests/testsuite/init/explicit_bin_with_git/stdout.log b/src/tools/cargo/tests/testsuite/init/explicit_bin_with_git/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/explicit_bin_with_git/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/formats_source/in/rustfmt.toml b/src/tools/cargo/tests/testsuite/init/formats_source/in/rustfmt.toml
new file mode 100644
index 000000000..b196eaa2d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/formats_source/in/rustfmt.toml
@@ -0,0 +1 @@
+tab_spaces = 2
diff --git a/src/tools/cargo/tests/testsuite/init/formats_source/mod.rs b/src/tools/cargo/tests/testsuite/init/formats_source/mod.rs
new file mode 100644
index 000000000..ac1fb6271
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/formats_source/mod.rs
@@ -0,0 +1,29 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::{process, Project};
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ // This cannot use `requires_rustfmt` because rustfmt is not available in
+ // the rust-lang/rust environment. Additionally, if running cargo without
+ // rustup (but with rustup installed), this test also fails due to HOME
+ // preventing the proxy from choosing a toolchain.
+ if let Err(e) = process("rustfmt").arg("-V").exec_with_output() {
+ eprintln!("skipping test, rustfmt not available:\n{e:?}");
+ return;
+ }
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = &project.root();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init --lib --vcs none")
+ .current_dir(project_root)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/init/formats_source/out/Cargo.toml b/src/tools/cargo/tests/testsuite/init/formats_source/out/Cargo.toml
new file mode 100644
index 000000000..dcdb8da2c
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/formats_source/out/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "case"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
diff --git a/src/tools/cargo/tests/testsuite/init/formats_source/out/rustfmt.toml b/src/tools/cargo/tests/testsuite/init/formats_source/out/rustfmt.toml
new file mode 100644
index 000000000..b196eaa2d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/formats_source/out/rustfmt.toml
@@ -0,0 +1 @@
+tab_spaces = 2
diff --git a/src/tools/cargo/tests/testsuite/init/formats_source/out/src/lib.rs b/src/tools/cargo/tests/testsuite/init/formats_source/out/src/lib.rs
new file mode 100644
index 000000000..3b9acffd5
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/formats_source/out/src/lib.rs
@@ -0,0 +1,14 @@
+pub fn add(left: usize, right: usize) -> usize {
+ left + right
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn it_works() {
+ let result = add(2, 2);
+ assert_eq!(result, 4);
+ }
+}
diff --git a/src/tools/cargo/tests/testsuite/init/formats_source/stderr.log b/src/tools/cargo/tests/testsuite/init/formats_source/stderr.log
new file mode 100644
index 000000000..f459bf226
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/formats_source/stderr.log
@@ -0,0 +1 @@
+ Created library package
diff --git a/src/tools/cargo/tests/testsuite/init/formats_source/stdout.log b/src/tools/cargo/tests/testsuite/init/formats_source/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/formats_source/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/fossil_autodetect/in/.fossil/.keep b/src/tools/cargo/tests/testsuite/init/fossil_autodetect/in/.fossil/.keep
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/fossil_autodetect/in/.fossil/.keep
diff --git a/src/tools/cargo/tests/testsuite/init/fossil_autodetect/mod.rs b/src/tools/cargo/tests/testsuite/init/fossil_autodetect/mod.rs
new file mode 100644
index 000000000..d45ba868a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/fossil_autodetect/mod.rs
@@ -0,0 +1,22 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = &project.root();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init --lib")
+ .current_dir(project_root)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), project_root);
+ assert!(!project_root.join(".git").is_dir());
+}
diff --git a/src/tools/cargo/tests/testsuite/init/fossil_autodetect/out/.fossil-settings/clean-glob b/src/tools/cargo/tests/testsuite/init/fossil_autodetect/out/.fossil-settings/clean-glob
new file mode 100644
index 000000000..a9d37c560
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/fossil_autodetect/out/.fossil-settings/clean-glob
@@ -0,0 +1,2 @@
+target
+Cargo.lock
diff --git a/src/tools/cargo/tests/testsuite/init/fossil_autodetect/out/.fossil-settings/ignore-glob b/src/tools/cargo/tests/testsuite/init/fossil_autodetect/out/.fossil-settings/ignore-glob
new file mode 100644
index 000000000..a9d37c560
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/fossil_autodetect/out/.fossil-settings/ignore-glob
@@ -0,0 +1,2 @@
+target
+Cargo.lock
diff --git a/src/tools/cargo/tests/testsuite/init/fossil_autodetect/out/Cargo.toml b/src/tools/cargo/tests/testsuite/init/fossil_autodetect/out/Cargo.toml
new file mode 100644
index 000000000..dcdb8da2c
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/fossil_autodetect/out/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "case"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
diff --git a/src/tools/cargo/tests/testsuite/init/fossil_autodetect/out/src/lib.rs b/src/tools/cargo/tests/testsuite/init/fossil_autodetect/out/src/lib.rs
new file mode 100644
index 000000000..7d12d9af8
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/fossil_autodetect/out/src/lib.rs
@@ -0,0 +1,14 @@
+pub fn add(left: usize, right: usize) -> usize {
+ left + right
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn it_works() {
+ let result = add(2, 2);
+ assert_eq!(result, 4);
+ }
+}
diff --git a/src/tools/cargo/tests/testsuite/init/fossil_autodetect/stderr.log b/src/tools/cargo/tests/testsuite/init/fossil_autodetect/stderr.log
new file mode 100644
index 000000000..f459bf226
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/fossil_autodetect/stderr.log
@@ -0,0 +1 @@
+ Created library package
diff --git a/src/tools/cargo/tests/testsuite/init/fossil_autodetect/stdout.log b/src/tools/cargo/tests/testsuite/init/fossil_autodetect/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/fossil_autodetect/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/git_autodetect/mod.rs b/src/tools/cargo/tests/testsuite/init/git_autodetect/mod.rs
new file mode 100644
index 000000000..aef47bc7d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/git_autodetect/mod.rs
@@ -0,0 +1,24 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::paths;
+use cargo_test_support::prelude::*;
+use std::fs;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project_root = &paths::root().join("foo");
+ // Need to create `.git` dir manually because it cannot be tracked under a git repo
+ fs::create_dir_all(project_root.join(".git")).unwrap();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init --lib")
+ .current_dir(project_root)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), project_root);
+ assert!(project_root.join(".git").is_dir());
+}
diff --git a/src/tools/cargo/tests/testsuite/init/git_autodetect/out/Cargo.toml b/src/tools/cargo/tests/testsuite/init/git_autodetect/out/Cargo.toml
new file mode 100644
index 000000000..1d9cfe317
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/git_autodetect/out/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "foo"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
diff --git a/src/tools/cargo/tests/testsuite/init/git_autodetect/out/src/lib.rs b/src/tools/cargo/tests/testsuite/init/git_autodetect/out/src/lib.rs
new file mode 100644
index 000000000..7d12d9af8
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/git_autodetect/out/src/lib.rs
@@ -0,0 +1,14 @@
+pub fn add(left: usize, right: usize) -> usize {
+ left + right
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn it_works() {
+ let result = add(2, 2);
+ assert_eq!(result, 4);
+ }
+}
diff --git a/src/tools/cargo/tests/testsuite/init/git_autodetect/stderr.log b/src/tools/cargo/tests/testsuite/init/git_autodetect/stderr.log
new file mode 100644
index 000000000..f459bf226
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/git_autodetect/stderr.log
@@ -0,0 +1 @@
+ Created library package
diff --git a/src/tools/cargo/tests/testsuite/init/git_autodetect/stdout.log b/src/tools/cargo/tests/testsuite/init/git_autodetect/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/git_autodetect/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/git_ignore_exists_no_conflicting_entries/mod.rs b/src/tools/cargo/tests/testsuite/init/git_ignore_exists_no_conflicting_entries/mod.rs
new file mode 100644
index 000000000..cd4437c65
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/git_ignore_exists_no_conflicting_entries/mod.rs
@@ -0,0 +1,22 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = &project.root();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init --lib --edition 2015")
+ .current_dir(project_root)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), project_root);
+ assert!(project_root.join(".git").is_dir());
+}
diff --git a/src/tools/cargo/tests/testsuite/init/git_ignore_exists_no_conflicting_entries/out/Cargo.toml b/src/tools/cargo/tests/testsuite/init/git_ignore_exists_no_conflicting_entries/out/Cargo.toml
new file mode 100644
index 000000000..a6269fdcd
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/git_ignore_exists_no_conflicting_entries/out/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "case"
+version = "0.1.0"
+edition = "2015"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
diff --git a/src/tools/cargo/tests/testsuite/init/git_ignore_exists_no_conflicting_entries/out/src/lib.rs b/src/tools/cargo/tests/testsuite/init/git_ignore_exists_no_conflicting_entries/out/src/lib.rs
new file mode 100644
index 000000000..7d12d9af8
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/git_ignore_exists_no_conflicting_entries/out/src/lib.rs
@@ -0,0 +1,14 @@
+pub fn add(left: usize, right: usize) -> usize {
+ left + right
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn it_works() {
+ let result = add(2, 2);
+ assert_eq!(result, 4);
+ }
+}
diff --git a/src/tools/cargo/tests/testsuite/init/git_ignore_exists_no_conflicting_entries/stderr.log b/src/tools/cargo/tests/testsuite/init/git_ignore_exists_no_conflicting_entries/stderr.log
new file mode 100644
index 000000000..f459bf226
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/git_ignore_exists_no_conflicting_entries/stderr.log
@@ -0,0 +1 @@
+ Created library package
diff --git a/src/tools/cargo/tests/testsuite/init/git_ignore_exists_no_conflicting_entries/stdout.log b/src/tools/cargo/tests/testsuite/init/git_ignore_exists_no_conflicting_entries/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/git_ignore_exists_no_conflicting_entries/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/ignores_failure_to_format_source/in/rustfmt.toml b/src/tools/cargo/tests/testsuite/init/ignores_failure_to_format_source/in/rustfmt.toml
new file mode 100644
index 000000000..b196eaa2d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/ignores_failure_to_format_source/in/rustfmt.toml
@@ -0,0 +1 @@
+tab_spaces = 2
diff --git a/src/tools/cargo/tests/testsuite/init/ignores_failure_to_format_source/mod.rs b/src/tools/cargo/tests/testsuite/init/ignores_failure_to_format_source/mod.rs
new file mode 100644
index 000000000..fd9394049
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/ignores_failure_to_format_source/mod.rs
@@ -0,0 +1,22 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = &project.root();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init --lib --vcs none")
+ .env("PATH", "") // pretend that `rustfmt` is missing
+ .current_dir(project_root)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/init/ignores_failure_to_format_source/out/Cargo.toml b/src/tools/cargo/tests/testsuite/init/ignores_failure_to_format_source/out/Cargo.toml
new file mode 100644
index 000000000..dcdb8da2c
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/ignores_failure_to_format_source/out/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "case"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
diff --git a/src/tools/cargo/tests/testsuite/init/ignores_failure_to_format_source/out/rustfmt.toml b/src/tools/cargo/tests/testsuite/init/ignores_failure_to_format_source/out/rustfmt.toml
new file mode 100644
index 000000000..b196eaa2d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/ignores_failure_to_format_source/out/rustfmt.toml
@@ -0,0 +1 @@
+tab_spaces = 2
diff --git a/src/tools/cargo/tests/testsuite/init/ignores_failure_to_format_source/out/src/lib.rs b/src/tools/cargo/tests/testsuite/init/ignores_failure_to_format_source/out/src/lib.rs
new file mode 100644
index 000000000..7d12d9af8
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/ignores_failure_to_format_source/out/src/lib.rs
@@ -0,0 +1,14 @@
+pub fn add(left: usize, right: usize) -> usize {
+ left + right
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn it_works() {
+ let result = add(2, 2);
+ assert_eq!(result, 4);
+ }
+}
diff --git a/src/tools/cargo/tests/testsuite/init/ignores_failure_to_format_source/stderr.log b/src/tools/cargo/tests/testsuite/init/ignores_failure_to_format_source/stderr.log
new file mode 100644
index 000000000..f459bf226
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/ignores_failure_to_format_source/stderr.log
@@ -0,0 +1 @@
+ Created library package
diff --git a/src/tools/cargo/tests/testsuite/init/ignores_failure_to_format_source/stdout.log b/src/tools/cargo/tests/testsuite/init/ignores_failure_to_format_source/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/ignores_failure_to_format_source/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/inferred_bin_with_git/in/main.rs b/src/tools/cargo/tests/testsuite/init/inferred_bin_with_git/in/main.rs
new file mode 100644
index 000000000..f328e4d9d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/inferred_bin_with_git/in/main.rs
@@ -0,0 +1 @@
+fn main() {}
diff --git a/src/tools/cargo/tests/testsuite/init/inferred_bin_with_git/mod.rs b/src/tools/cargo/tests/testsuite/init/inferred_bin_with_git/mod.rs
new file mode 100644
index 000000000..80bec8893
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/inferred_bin_with_git/mod.rs
@@ -0,0 +1,21 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = &project.root();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init --vcs git")
+ .current_dir(project_root)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/init/inferred_bin_with_git/out/Cargo.toml b/src/tools/cargo/tests/testsuite/init/inferred_bin_with_git/out/Cargo.toml
new file mode 100644
index 000000000..5c6c9158c
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/inferred_bin_with_git/out/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "case"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+
+[[bin]]
+name = "case"
+path = "main.rs"
diff --git a/src/tools/cargo/tests/testsuite/init/inferred_bin_with_git/out/main.rs b/src/tools/cargo/tests/testsuite/init/inferred_bin_with_git/out/main.rs
new file mode 100644
index 000000000..f328e4d9d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/inferred_bin_with_git/out/main.rs
@@ -0,0 +1 @@
+fn main() {}
diff --git a/src/tools/cargo/tests/testsuite/init/inferred_bin_with_git/stderr.log b/src/tools/cargo/tests/testsuite/init/inferred_bin_with_git/stderr.log
new file mode 100644
index 000000000..3847e4e4a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/inferred_bin_with_git/stderr.log
@@ -0,0 +1 @@
+ Created binary (application) package
diff --git a/src/tools/cargo/tests/testsuite/init/inferred_bin_with_git/stdout.log b/src/tools/cargo/tests/testsuite/init/inferred_bin_with_git/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/inferred_bin_with_git/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/inferred_lib_with_git/in/lib.rs b/src/tools/cargo/tests/testsuite/init/inferred_lib_with_git/in/lib.rs
new file mode 100644
index 000000000..59760b549
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/inferred_lib_with_git/in/lib.rs
@@ -0,0 +1 @@
+fn f() {}
diff --git a/src/tools/cargo/tests/testsuite/init/inferred_lib_with_git/mod.rs b/src/tools/cargo/tests/testsuite/init/inferred_lib_with_git/mod.rs
new file mode 100644
index 000000000..80bec8893
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/inferred_lib_with_git/mod.rs
@@ -0,0 +1,21 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = &project.root();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init --vcs git")
+ .current_dir(project_root)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/init/inferred_lib_with_git/out/Cargo.toml b/src/tools/cargo/tests/testsuite/init/inferred_lib_with_git/out/Cargo.toml
new file mode 100644
index 000000000..39e95fe94
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/inferred_lib_with_git/out/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "case"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+
+[lib]
+name = "case"
+path = "lib.rs"
diff --git a/src/tools/cargo/tests/testsuite/init/inferred_lib_with_git/out/lib.rs b/src/tools/cargo/tests/testsuite/init/inferred_lib_with_git/out/lib.rs
new file mode 100644
index 000000000..59760b549
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/inferred_lib_with_git/out/lib.rs
@@ -0,0 +1 @@
+fn f() {}
diff --git a/src/tools/cargo/tests/testsuite/init/inferred_lib_with_git/stderr.log b/src/tools/cargo/tests/testsuite/init/inferred_lib_with_git/stderr.log
new file mode 100644
index 000000000..f459bf226
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/inferred_lib_with_git/stderr.log
@@ -0,0 +1 @@
+ Created library package
diff --git a/src/tools/cargo/tests/testsuite/init/inferred_lib_with_git/stdout.log b/src/tools/cargo/tests/testsuite/init/inferred_lib_with_git/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/inferred_lib_with_git/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/invalid_dir_name/mod.rs b/src/tools/cargo/tests/testsuite/init/invalid_dir_name/mod.rs
new file mode 100644
index 000000000..2b1be9022
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/invalid_dir_name/mod.rs
@@ -0,0 +1,21 @@
+use cargo_test_support::paths;
+use cargo_test_support::prelude::*;
+use std::fs;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let foo = &paths::root().join("foo.bar");
+ fs::create_dir_all(foo).unwrap();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init")
+ .current_dir(foo)
+ .assert()
+ .code(101)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert!(!foo.join("Cargo.toml").is_file());
+}
diff --git a/src/tools/cargo/tests/testsuite/init/invalid_dir_name/stderr.log b/src/tools/cargo/tests/testsuite/init/invalid_dir_name/stderr.log
new file mode 100644
index 000000000..86d2c665f
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/invalid_dir_name/stderr.log
@@ -0,0 +1,8 @@
+error: invalid character `.` in package name: `foo.bar`, characters must be Unicode XID characters (numbers, `-`, `_`, or most letters)
+If you need a package name to not match the directory name, consider using --name flag.
+If you need a binary with the name "foo.bar", use a valid package name, and set the binary name to be different from the package. This can be done by setting the binary filename to `src/bin/foo.bar.rs` or change the name in Cargo.toml with:
+
+ [[bin]]
+ name = "foo.bar"
+ path = "src/main.rs"
+
diff --git a/src/tools/cargo/tests/testsuite/init/invalid_dir_name/stdout.log b/src/tools/cargo/tests/testsuite/init/invalid_dir_name/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/invalid_dir_name/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/lib_already_exists_nosrc/in/lib.rs b/src/tools/cargo/tests/testsuite/init/lib_already_exists_nosrc/in/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/lib_already_exists_nosrc/in/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/init/lib_already_exists_nosrc/mod.rs b/src/tools/cargo/tests/testsuite/init/lib_already_exists_nosrc/mod.rs
new file mode 100644
index 000000000..d3e8e66df
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/lib_already_exists_nosrc/mod.rs
@@ -0,0 +1,22 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = &project.root();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init --vcs none")
+ .current_dir(project_root)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), project_root);
+ assert!(!project_root.join("src/main.rs").is_file());
+}
diff --git a/src/tools/cargo/tests/testsuite/init/lib_already_exists_nosrc/out/Cargo.toml b/src/tools/cargo/tests/testsuite/init/lib_already_exists_nosrc/out/Cargo.toml
new file mode 100644
index 000000000..39e95fe94
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/lib_already_exists_nosrc/out/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "case"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+
+[lib]
+name = "case"
+path = "lib.rs"
diff --git a/src/tools/cargo/tests/testsuite/init/lib_already_exists_nosrc/out/lib.rs b/src/tools/cargo/tests/testsuite/init/lib_already_exists_nosrc/out/lib.rs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/lib_already_exists_nosrc/out/lib.rs
diff --git a/src/tools/cargo/tests/testsuite/init/lib_already_exists_nosrc/stderr.log b/src/tools/cargo/tests/testsuite/init/lib_already_exists_nosrc/stderr.log
new file mode 100644
index 000000000..f459bf226
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/lib_already_exists_nosrc/stderr.log
@@ -0,0 +1 @@
+ Created library package
diff --git a/src/tools/cargo/tests/testsuite/init/lib_already_exists_nosrc/stdout.log b/src/tools/cargo/tests/testsuite/init/lib_already_exists_nosrc/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/lib_already_exists_nosrc/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/lib_already_exists_src/in/src/lib.rs b/src/tools/cargo/tests/testsuite/init/lib_already_exists_src/in/src/lib.rs
new file mode 100644
index 000000000..59760b549
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/lib_already_exists_src/in/src/lib.rs
@@ -0,0 +1 @@
+fn f() {}
diff --git a/src/tools/cargo/tests/testsuite/init/lib_already_exists_src/mod.rs b/src/tools/cargo/tests/testsuite/init/lib_already_exists_src/mod.rs
new file mode 100644
index 000000000..d3e8e66df
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/lib_already_exists_src/mod.rs
@@ -0,0 +1,22 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = &project.root();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init --vcs none")
+ .current_dir(project_root)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), project_root);
+ assert!(!project_root.join("src/main.rs").is_file());
+}
diff --git a/src/tools/cargo/tests/testsuite/init/lib_already_exists_src/out/Cargo.toml b/src/tools/cargo/tests/testsuite/init/lib_already_exists_src/out/Cargo.toml
new file mode 100644
index 000000000..dcdb8da2c
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/lib_already_exists_src/out/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "case"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
diff --git a/src/tools/cargo/tests/testsuite/init/lib_already_exists_src/out/src/lib.rs b/src/tools/cargo/tests/testsuite/init/lib_already_exists_src/out/src/lib.rs
new file mode 100644
index 000000000..59760b549
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/lib_already_exists_src/out/src/lib.rs
@@ -0,0 +1 @@
+fn f() {}
diff --git a/src/tools/cargo/tests/testsuite/init/lib_already_exists_src/stderr.log b/src/tools/cargo/tests/testsuite/init/lib_already_exists_src/stderr.log
new file mode 100644
index 000000000..f459bf226
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/lib_already_exists_src/stderr.log
@@ -0,0 +1 @@
+ Created library package
diff --git a/src/tools/cargo/tests/testsuite/init/lib_already_exists_src/stdout.log b/src/tools/cargo/tests/testsuite/init/lib_already_exists_src/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/lib_already_exists_src/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/mercurial_autodetect/mod.rs b/src/tools/cargo/tests/testsuite/init/mercurial_autodetect/mod.rs
new file mode 100644
index 000000000..d45ba868a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/mercurial_autodetect/mod.rs
@@ -0,0 +1,22 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = &project.root();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init --lib")
+ .current_dir(project_root)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), project_root);
+ assert!(!project_root.join(".git").is_dir());
+}
diff --git a/src/tools/cargo/tests/testsuite/init/mercurial_autodetect/out/Cargo.toml b/src/tools/cargo/tests/testsuite/init/mercurial_autodetect/out/Cargo.toml
new file mode 100644
index 000000000..dcdb8da2c
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/mercurial_autodetect/out/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "case"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
diff --git a/src/tools/cargo/tests/testsuite/init/mercurial_autodetect/out/src/lib.rs b/src/tools/cargo/tests/testsuite/init/mercurial_autodetect/out/src/lib.rs
new file mode 100644
index 000000000..7d12d9af8
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/mercurial_autodetect/out/src/lib.rs
@@ -0,0 +1,14 @@
+pub fn add(left: usize, right: usize) -> usize {
+ left + right
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn it_works() {
+ let result = add(2, 2);
+ assert_eq!(result, 4);
+ }
+}
diff --git a/src/tools/cargo/tests/testsuite/init/mercurial_autodetect/stderr.log b/src/tools/cargo/tests/testsuite/init/mercurial_autodetect/stderr.log
new file mode 100644
index 000000000..f459bf226
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/mercurial_autodetect/stderr.log
@@ -0,0 +1 @@
+ Created library package
diff --git a/src/tools/cargo/tests/testsuite/init/mercurial_autodetect/stdout.log b/src/tools/cargo/tests/testsuite/init/mercurial_autodetect/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/mercurial_autodetect/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/mod.rs b/src/tools/cargo/tests/testsuite/init/mod.rs
new file mode 100644
index 000000000..99df9d39d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/mod.rs
@@ -0,0 +1,42 @@
+//! Tests for the `cargo init` command.
+
+mod auto_git;
+mod bin_already_exists_explicit;
+mod bin_already_exists_explicit_nosrc;
+mod bin_already_exists_implicit;
+mod bin_already_exists_implicit_namenosrc;
+mod bin_already_exists_implicit_namesrc;
+mod bin_already_exists_implicit_nosrc;
+mod both_lib_and_bin;
+mod cant_create_library_when_both_binlib_present;
+mod confused_by_multiple_lib_files;
+mod creates_binary_when_both_binlib_present;
+mod creates_binary_when_instructed_and_has_lib_file;
+mod creates_library_when_instructed_and_has_bin_file;
+mod explicit_bin_with_git;
+mod formats_source;
+mod fossil_autodetect;
+mod git_autodetect;
+mod git_ignore_exists_no_conflicting_entries;
+mod ignores_failure_to_format_source;
+mod inferred_bin_with_git;
+mod inferred_lib_with_git;
+mod invalid_dir_name;
+mod lib_already_exists_nosrc;
+mod lib_already_exists_src;
+mod mercurial_autodetect;
+mod multibin_project_name_clash;
+#[cfg(not(windows))]
+mod no_filename;
+#[cfg(unix)]
+mod path_contains_separator;
+mod pijul_autodetect;
+mod reserved_name;
+mod simple_bin;
+mod simple_git;
+mod simple_git_ignore_exists;
+mod simple_hg;
+mod simple_hg_ignore_exists;
+mod simple_lib;
+mod unknown_flags;
+mod with_argument;
diff --git a/src/tools/cargo/tests/testsuite/init/multibin_project_name_clash/in/case.rs b/src/tools/cargo/tests/testsuite/init/multibin_project_name_clash/in/case.rs
new file mode 100644
index 000000000..b31221118
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/multibin_project_name_clash/in/case.rs
@@ -0,0 +1 @@
+fn main() { println!("foo.rs"); }
diff --git a/src/tools/cargo/tests/testsuite/init/multibin_project_name_clash/in/main.rs b/src/tools/cargo/tests/testsuite/init/multibin_project_name_clash/in/main.rs
new file mode 100644
index 000000000..7937627b9
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/multibin_project_name_clash/in/main.rs
@@ -0,0 +1 @@
+fn main() { println!("main.rs"); }
diff --git a/src/tools/cargo/tests/testsuite/init/multibin_project_name_clash/mod.rs b/src/tools/cargo/tests/testsuite/init/multibin_project_name_clash/mod.rs
new file mode 100644
index 000000000..fdd4476d9
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/multibin_project_name_clash/mod.rs
@@ -0,0 +1,22 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = &project.root();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init --lib --vcs none")
+ .current_dir(project_root)
+ .assert()
+ .code(101)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), project_root);
+ assert!(!project_root.join("Cargo.toml").is_file());
+}
diff --git a/src/tools/cargo/tests/testsuite/init/multibin_project_name_clash/out/case.rs b/src/tools/cargo/tests/testsuite/init/multibin_project_name_clash/out/case.rs
new file mode 100644
index 000000000..b31221118
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/multibin_project_name_clash/out/case.rs
@@ -0,0 +1 @@
+fn main() { println!("foo.rs"); }
diff --git a/src/tools/cargo/tests/testsuite/init/multibin_project_name_clash/out/main.rs b/src/tools/cargo/tests/testsuite/init/multibin_project_name_clash/out/main.rs
new file mode 100644
index 000000000..7937627b9
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/multibin_project_name_clash/out/main.rs
@@ -0,0 +1 @@
+fn main() { println!("main.rs"); }
diff --git a/src/tools/cargo/tests/testsuite/init/multibin_project_name_clash/stderr.log b/src/tools/cargo/tests/testsuite/init/multibin_project_name_clash/stderr.log
new file mode 100644
index 000000000..21a1dabee
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/multibin_project_name_clash/stderr.log
@@ -0,0 +1,4 @@
+error: multiple possible binary sources found:
+ main.rs
+ case.rs
+cannot automatically generate Cargo.toml as the main target would be ambiguous
diff --git a/src/tools/cargo/tests/testsuite/init/multibin_project_name_clash/stdout.log b/src/tools/cargo/tests/testsuite/init/multibin_project_name_clash/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/multibin_project_name_clash/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/no_filename/mod.rs b/src/tools/cargo/tests/testsuite/init/no_filename/mod.rs
new file mode 100644
index 000000000..8edfd2823
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/no_filename/mod.rs
@@ -0,0 +1,16 @@
+use cargo_test_support::paths;
+use cargo_test_support::prelude::*;
+
+use cargo_test_support::curr_dir;
+
+#[cfg(not(windows))]
+#[cargo_test]
+fn case() {
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init /")
+ .current_dir(paths::root())
+ .assert()
+ .code(101)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+}
diff --git a/src/tools/cargo/tests/testsuite/init/no_filename/stderr.log b/src/tools/cargo/tests/testsuite/init/no_filename/stderr.log
new file mode 100644
index 000000000..bd087ec90
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/no_filename/stderr.log
@@ -0,0 +1 @@
+error: cannot auto-detect package name from path "/" ; use --name to override
diff --git a/src/tools/cargo/tests/testsuite/init/no_filename/stdout.log b/src/tools/cargo/tests/testsuite/init/no_filename/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/no_filename/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/path_contains_separator/in/.keep b/src/tools/cargo/tests/testsuite/init/path_contains_separator/in/.keep
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/path_contains_separator/in/.keep
diff --git a/src/tools/cargo/tests/testsuite/init/path_contains_separator/mod.rs b/src/tools/cargo/tests/testsuite/init/path_contains_separator/mod.rs
new file mode 100644
index 000000000..0a12f4269
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/path_contains_separator/mod.rs
@@ -0,0 +1,26 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::{t, Project};
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = &project.root().join("test:ing");
+
+ if !project_root.exists() {
+ t!(std::fs::create_dir(&project_root));
+ }
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init --bin --vcs none --edition 2015 --name testing")
+ .current_dir(project_root)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), project_root);
+ assert!(!project_root.join(".gitignore").is_file());
+}
diff --git a/src/tools/cargo/tests/testsuite/init/path_contains_separator/out/Cargo.toml b/src/tools/cargo/tests/testsuite/init/path_contains_separator/out/Cargo.toml
new file mode 100644
index 000000000..11465f1fc
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/path_contains_separator/out/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "testing"
+version = "0.1.0"
+edition = "2015"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
diff --git a/src/tools/cargo/tests/testsuite/init/path_contains_separator/out/src/main.rs b/src/tools/cargo/tests/testsuite/init/path_contains_separator/out/src/main.rs
new file mode 100644
index 000000000..e7a11a969
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/path_contains_separator/out/src/main.rs
@@ -0,0 +1,3 @@
+fn main() {
+ println!("Hello, world!");
+}
diff --git a/src/tools/cargo/tests/testsuite/init/path_contains_separator/stderr.log b/src/tools/cargo/tests/testsuite/init/path_contains_separator/stderr.log
new file mode 100644
index 000000000..d7947aea2
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/path_contains_separator/stderr.log
@@ -0,0 +1,3 @@
+warning: the path `[ROOT]/case/test:ing/.` contains invalid PATH characters (usually `:`, `;`, or `"`)
+It is recommended to use a different name to avoid problems.
+ Created binary (application) package
diff --git a/src/tools/cargo/tests/testsuite/init/path_contains_separator/stdout.log b/src/tools/cargo/tests/testsuite/init/path_contains_separator/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/path_contains_separator/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/pijul_autodetect/in/.pijul/.keep b/src/tools/cargo/tests/testsuite/init/pijul_autodetect/in/.pijul/.keep
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/pijul_autodetect/in/.pijul/.keep
diff --git a/src/tools/cargo/tests/testsuite/init/pijul_autodetect/mod.rs b/src/tools/cargo/tests/testsuite/init/pijul_autodetect/mod.rs
new file mode 100644
index 000000000..d45ba868a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/pijul_autodetect/mod.rs
@@ -0,0 +1,22 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = &project.root();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init --lib")
+ .current_dir(project_root)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), project_root);
+ assert!(!project_root.join(".git").is_dir());
+}
diff --git a/src/tools/cargo/tests/testsuite/init/pijul_autodetect/out/.ignore b/src/tools/cargo/tests/testsuite/init/pijul_autodetect/out/.ignore
new file mode 100644
index 000000000..4fffb2f89
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/pijul_autodetect/out/.ignore
@@ -0,0 +1,2 @@
+/target
+/Cargo.lock
diff --git a/src/tools/cargo/tests/testsuite/init/pijul_autodetect/out/Cargo.toml b/src/tools/cargo/tests/testsuite/init/pijul_autodetect/out/Cargo.toml
new file mode 100644
index 000000000..dcdb8da2c
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/pijul_autodetect/out/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "case"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
diff --git a/src/tools/cargo/tests/testsuite/init/pijul_autodetect/out/src/lib.rs b/src/tools/cargo/tests/testsuite/init/pijul_autodetect/out/src/lib.rs
new file mode 100644
index 000000000..7d12d9af8
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/pijul_autodetect/out/src/lib.rs
@@ -0,0 +1,14 @@
+pub fn add(left: usize, right: usize) -> usize {
+ left + right
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn it_works() {
+ let result = add(2, 2);
+ assert_eq!(result, 4);
+ }
+}
diff --git a/src/tools/cargo/tests/testsuite/init/pijul_autodetect/stderr.log b/src/tools/cargo/tests/testsuite/init/pijul_autodetect/stderr.log
new file mode 100644
index 000000000..f459bf226
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/pijul_autodetect/stderr.log
@@ -0,0 +1 @@
+ Created library package
diff --git a/src/tools/cargo/tests/testsuite/init/pijul_autodetect/stdout.log b/src/tools/cargo/tests/testsuite/init/pijul_autodetect/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/pijul_autodetect/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/reserved_name/mod.rs b/src/tools/cargo/tests/testsuite/init/reserved_name/mod.rs
new file mode 100644
index 000000000..cc65fd0a1
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/reserved_name/mod.rs
@@ -0,0 +1,21 @@
+use cargo_test_support::paths;
+use cargo_test_support::prelude::*;
+use std::fs;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project_root = &paths::root().join("test");
+ fs::create_dir_all(project_root).unwrap();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init")
+ .current_dir(project_root)
+ .assert()
+ .code(101)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert!(!project_root.join("Cargo.toml").is_file());
+}
diff --git a/src/tools/cargo/tests/testsuite/init/reserved_name/stderr.log b/src/tools/cargo/tests/testsuite/init/reserved_name/stderr.log
new file mode 100644
index 000000000..748971bdf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/reserved_name/stderr.log
@@ -0,0 +1,8 @@
+error: the name `test` cannot be used as a package name, it conflicts with Rust's built-in test library
+If you need a package name to not match the directory name, consider using --name flag.
+If you need a binary with the name "test", use a valid package name, and set the binary name to be different from the package. This can be done by setting the binary filename to `src/bin/test.rs` or change the name in Cargo.toml with:
+
+ [[bin]]
+ name = "test"
+ path = "src/main.rs"
+
diff --git a/src/tools/cargo/tests/testsuite/init/reserved_name/stdout.log b/src/tools/cargo/tests/testsuite/init/reserved_name/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/reserved_name/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/simple_bin/in b/src/tools/cargo/tests/testsuite/init/simple_bin/in
new file mode 120000
index 000000000..1202506b6
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/simple_bin/in
@@ -0,0 +1 @@
+../empty_dir \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/init/simple_bin/mod.rs b/src/tools/cargo/tests/testsuite/init/simple_bin/mod.rs
new file mode 100644
index 000000000..eaf0955f9
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/simple_bin/mod.rs
@@ -0,0 +1,29 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = &project.root();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init --bin --vcs none --edition 2015")
+ .current_dir(project_root)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), project_root);
+ assert!(!project_root.join(".gitignore").is_file());
+
+ snapbox::cmd::Command::cargo_ui()
+ .current_dir(project_root)
+ .arg("build")
+ .assert()
+ .success();
+ assert!(project.bin("case").is_file());
+}
diff --git a/src/tools/cargo/tests/testsuite/init/simple_bin/out/Cargo.toml b/src/tools/cargo/tests/testsuite/init/simple_bin/out/Cargo.toml
new file mode 100644
index 000000000..a6269fdcd
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/simple_bin/out/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "case"
+version = "0.1.0"
+edition = "2015"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
diff --git a/src/tools/cargo/tests/testsuite/init/simple_bin/out/src/main.rs b/src/tools/cargo/tests/testsuite/init/simple_bin/out/src/main.rs
new file mode 100644
index 000000000..e7a11a969
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/simple_bin/out/src/main.rs
@@ -0,0 +1,3 @@
+fn main() {
+ println!("Hello, world!");
+}
diff --git a/src/tools/cargo/tests/testsuite/init/simple_bin/stderr.log b/src/tools/cargo/tests/testsuite/init/simple_bin/stderr.log
new file mode 100644
index 000000000..3847e4e4a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/simple_bin/stderr.log
@@ -0,0 +1 @@
+ Created binary (application) package
diff --git a/src/tools/cargo/tests/testsuite/init/simple_bin/stdout.log b/src/tools/cargo/tests/testsuite/init/simple_bin/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/simple_bin/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/simple_git/in b/src/tools/cargo/tests/testsuite/init/simple_git/in
new file mode 120000
index 000000000..1202506b6
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/simple_git/in
@@ -0,0 +1 @@
+../empty_dir \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/init/simple_git/mod.rs b/src/tools/cargo/tests/testsuite/init/simple_git/mod.rs
new file mode 100644
index 000000000..c373fe2a2
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/simple_git/mod.rs
@@ -0,0 +1,22 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = &project.root();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init --lib --vcs git")
+ .current_dir(project_root)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), project_root);
+ assert!(project_root.join(".git").is_dir());
+}
diff --git a/src/tools/cargo/tests/testsuite/init/simple_git/out/Cargo.toml b/src/tools/cargo/tests/testsuite/init/simple_git/out/Cargo.toml
new file mode 100644
index 000000000..dcdb8da2c
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/simple_git/out/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "case"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
diff --git a/src/tools/cargo/tests/testsuite/init/simple_git/out/src/lib.rs b/src/tools/cargo/tests/testsuite/init/simple_git/out/src/lib.rs
new file mode 100644
index 000000000..7d12d9af8
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/simple_git/out/src/lib.rs
@@ -0,0 +1,14 @@
+pub fn add(left: usize, right: usize) -> usize {
+ left + right
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn it_works() {
+ let result = add(2, 2);
+ assert_eq!(result, 4);
+ }
+}
diff --git a/src/tools/cargo/tests/testsuite/init/simple_git/stderr.log b/src/tools/cargo/tests/testsuite/init/simple_git/stderr.log
new file mode 100644
index 000000000..f459bf226
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/simple_git/stderr.log
@@ -0,0 +1 @@
+ Created library package
diff --git a/src/tools/cargo/tests/testsuite/init/simple_git/stdout.log b/src/tools/cargo/tests/testsuite/init/simple_git/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/simple_git/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/simple_git_ignore_exists/mod.rs b/src/tools/cargo/tests/testsuite/init/simple_git_ignore_exists/mod.rs
new file mode 100644
index 000000000..142e86efd
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/simple_git_ignore_exists/mod.rs
@@ -0,0 +1,28 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = &project.root();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init --lib --edition 2015")
+ .current_dir(project_root)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), project_root);
+ assert!(project_root.join(".git").is_dir());
+
+ snapbox::cmd::Command::cargo_ui()
+ .current_dir(project_root)
+ .arg("build")
+ .assert()
+ .success();
+}
diff --git a/src/tools/cargo/tests/testsuite/init/simple_git_ignore_exists/out/Cargo.toml b/src/tools/cargo/tests/testsuite/init/simple_git_ignore_exists/out/Cargo.toml
new file mode 100644
index 000000000..a6269fdcd
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/simple_git_ignore_exists/out/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "case"
+version = "0.1.0"
+edition = "2015"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
diff --git a/src/tools/cargo/tests/testsuite/init/simple_git_ignore_exists/out/src/lib.rs b/src/tools/cargo/tests/testsuite/init/simple_git_ignore_exists/out/src/lib.rs
new file mode 100644
index 000000000..7d12d9af8
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/simple_git_ignore_exists/out/src/lib.rs
@@ -0,0 +1,14 @@
+pub fn add(left: usize, right: usize) -> usize {
+ left + right
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn it_works() {
+ let result = add(2, 2);
+ assert_eq!(result, 4);
+ }
+}
diff --git a/src/tools/cargo/tests/testsuite/init/simple_git_ignore_exists/stderr.log b/src/tools/cargo/tests/testsuite/init/simple_git_ignore_exists/stderr.log
new file mode 100644
index 000000000..f459bf226
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/simple_git_ignore_exists/stderr.log
@@ -0,0 +1 @@
+ Created library package
diff --git a/src/tools/cargo/tests/testsuite/init/simple_git_ignore_exists/stdout.log b/src/tools/cargo/tests/testsuite/init/simple_git_ignore_exists/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/simple_git_ignore_exists/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/simple_hg/in b/src/tools/cargo/tests/testsuite/init/simple_hg/in
new file mode 120000
index 000000000..1202506b6
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/simple_hg/in
@@ -0,0 +1 @@
+../empty_dir \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/init/simple_hg/mod.rs b/src/tools/cargo/tests/testsuite/init/simple_hg/mod.rs
new file mode 100644
index 000000000..1d6765453
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/simple_hg/mod.rs
@@ -0,0 +1,22 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test(requires_hg)]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = &project.root();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init --lib --vcs hg")
+ .current_dir(project_root)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), project_root);
+ assert!(!project_root.join(".git").is_dir());
+}
diff --git a/src/tools/cargo/tests/testsuite/init/simple_hg/out/Cargo.toml b/src/tools/cargo/tests/testsuite/init/simple_hg/out/Cargo.toml
new file mode 100644
index 000000000..dcdb8da2c
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/simple_hg/out/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "case"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
diff --git a/src/tools/cargo/tests/testsuite/init/simple_hg/out/src/lib.rs b/src/tools/cargo/tests/testsuite/init/simple_hg/out/src/lib.rs
new file mode 100644
index 000000000..7d12d9af8
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/simple_hg/out/src/lib.rs
@@ -0,0 +1,14 @@
+pub fn add(left: usize, right: usize) -> usize {
+ left + right
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn it_works() {
+ let result = add(2, 2);
+ assert_eq!(result, 4);
+ }
+}
diff --git a/src/tools/cargo/tests/testsuite/init/simple_hg/stderr.log b/src/tools/cargo/tests/testsuite/init/simple_hg/stderr.log
new file mode 100644
index 000000000..f459bf226
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/simple_hg/stderr.log
@@ -0,0 +1 @@
+ Created library package
diff --git a/src/tools/cargo/tests/testsuite/init/simple_hg/stdout.log b/src/tools/cargo/tests/testsuite/init/simple_hg/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/simple_hg/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/simple_hg_ignore_exists/mod.rs b/src/tools/cargo/tests/testsuite/init/simple_hg_ignore_exists/mod.rs
new file mode 100644
index 000000000..d45ba868a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/simple_hg_ignore_exists/mod.rs
@@ -0,0 +1,22 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = &project.root();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init --lib")
+ .current_dir(project_root)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), project_root);
+ assert!(!project_root.join(".git").is_dir());
+}
diff --git a/src/tools/cargo/tests/testsuite/init/simple_hg_ignore_exists/out/Cargo.toml b/src/tools/cargo/tests/testsuite/init/simple_hg_ignore_exists/out/Cargo.toml
new file mode 100644
index 000000000..dcdb8da2c
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/simple_hg_ignore_exists/out/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "case"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
diff --git a/src/tools/cargo/tests/testsuite/init/simple_hg_ignore_exists/out/src/lib.rs b/src/tools/cargo/tests/testsuite/init/simple_hg_ignore_exists/out/src/lib.rs
new file mode 100644
index 000000000..7d12d9af8
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/simple_hg_ignore_exists/out/src/lib.rs
@@ -0,0 +1,14 @@
+pub fn add(left: usize, right: usize) -> usize {
+ left + right
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn it_works() {
+ let result = add(2, 2);
+ assert_eq!(result, 4);
+ }
+}
diff --git a/src/tools/cargo/tests/testsuite/init/simple_hg_ignore_exists/stderr.log b/src/tools/cargo/tests/testsuite/init/simple_hg_ignore_exists/stderr.log
new file mode 100644
index 000000000..f459bf226
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/simple_hg_ignore_exists/stderr.log
@@ -0,0 +1 @@
+ Created library package
diff --git a/src/tools/cargo/tests/testsuite/init/simple_hg_ignore_exists/stdout.log b/src/tools/cargo/tests/testsuite/init/simple_hg_ignore_exists/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/simple_hg_ignore_exists/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/simple_lib/in b/src/tools/cargo/tests/testsuite/init/simple_lib/in
new file mode 120000
index 000000000..1202506b6
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/simple_lib/in
@@ -0,0 +1 @@
+../empty_dir \ No newline at end of file
diff --git a/src/tools/cargo/tests/testsuite/init/simple_lib/mod.rs b/src/tools/cargo/tests/testsuite/init/simple_lib/mod.rs
new file mode 100644
index 000000000..d6bae5167
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/simple_lib/mod.rs
@@ -0,0 +1,29 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = &project.root();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init --lib --vcs none --edition 2015")
+ .current_dir(project_root)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), project_root);
+ assert!(!project_root.join(".gitignore").is_file());
+
+ snapbox::cmd::Command::cargo_ui()
+ .current_dir(project_root)
+ .arg("build")
+ .assert()
+ .success();
+ assert!(!project.bin("foo").is_file());
+}
diff --git a/src/tools/cargo/tests/testsuite/init/simple_lib/out/Cargo.toml b/src/tools/cargo/tests/testsuite/init/simple_lib/out/Cargo.toml
new file mode 100644
index 000000000..a6269fdcd
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/simple_lib/out/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "case"
+version = "0.1.0"
+edition = "2015"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
diff --git a/src/tools/cargo/tests/testsuite/init/simple_lib/out/src/lib.rs b/src/tools/cargo/tests/testsuite/init/simple_lib/out/src/lib.rs
new file mode 100644
index 000000000..7d12d9af8
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/simple_lib/out/src/lib.rs
@@ -0,0 +1,14 @@
+pub fn add(left: usize, right: usize) -> usize {
+ left + right
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn it_works() {
+ let result = add(2, 2);
+ assert_eq!(result, 4);
+ }
+}
diff --git a/src/tools/cargo/tests/testsuite/init/simple_lib/stderr.log b/src/tools/cargo/tests/testsuite/init/simple_lib/stderr.log
new file mode 100644
index 000000000..f459bf226
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/simple_lib/stderr.log
@@ -0,0 +1 @@
+ Created library package
diff --git a/src/tools/cargo/tests/testsuite/init/simple_lib/stdout.log b/src/tools/cargo/tests/testsuite/init/simple_lib/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/simple_lib/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/unknown_flags/mod.rs b/src/tools/cargo/tests/testsuite/init/unknown_flags/mod.rs
new file mode 100644
index 000000000..4289b4b9e
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/unknown_flags/mod.rs
@@ -0,0 +1,15 @@
+use cargo_test_support::paths;
+use cargo_test_support::prelude::*;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init foo --flag")
+ .current_dir(paths::root())
+ .assert()
+ .code(1)
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+}
diff --git a/src/tools/cargo/tests/testsuite/init/unknown_flags/stderr.log b/src/tools/cargo/tests/testsuite/init/unknown_flags/stderr.log
new file mode 100644
index 000000000..980e8acd8
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/unknown_flags/stderr.log
@@ -0,0 +1,7 @@
+error: unexpected argument '--flag' found
+
+ tip: to pass '--flag' as a value, use '-- --flag'
+
+Usage: cargo[EXE] init <path>
+
+For more information, try '--help'.
diff --git a/src/tools/cargo/tests/testsuite/init/unknown_flags/stdout.log b/src/tools/cargo/tests/testsuite/init/unknown_flags/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/unknown_flags/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/init/with_argument/in/foo/.keep b/src/tools/cargo/tests/testsuite/init/with_argument/in/foo/.keep
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/with_argument/in/foo/.keep
diff --git a/src/tools/cargo/tests/testsuite/init/with_argument/mod.rs b/src/tools/cargo/tests/testsuite/init/with_argument/mod.rs
new file mode 100644
index 000000000..0b5e342a1
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/with_argument/mod.rs
@@ -0,0 +1,21 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::prelude::*;
+use cargo_test_support::Project;
+
+use cargo_test_support::curr_dir;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(curr_dir!().join("in"));
+ let project_root = &project.root();
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg_line("init foo --vcs none")
+ .current_dir(project_root)
+ .assert()
+ .success()
+ .stdout_matches_path(curr_dir!().join("stdout.log"))
+ .stderr_matches_path(curr_dir!().join("stderr.log"));
+
+ assert_ui().subset_matches(curr_dir!().join("out"), project_root);
+}
diff --git a/src/tools/cargo/tests/testsuite/init/with_argument/out/foo/Cargo.toml b/src/tools/cargo/tests/testsuite/init/with_argument/out/foo/Cargo.toml
new file mode 100644
index 000000000..1d9cfe317
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/with_argument/out/foo/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "foo"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
diff --git a/src/tools/cargo/tests/testsuite/init/with_argument/out/foo/src/main.rs b/src/tools/cargo/tests/testsuite/init/with_argument/out/foo/src/main.rs
new file mode 100644
index 000000000..e7a11a969
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/with_argument/out/foo/src/main.rs
@@ -0,0 +1,3 @@
+fn main() {
+ println!("Hello, world!");
+}
diff --git a/src/tools/cargo/tests/testsuite/init/with_argument/stderr.log b/src/tools/cargo/tests/testsuite/init/with_argument/stderr.log
new file mode 100644
index 000000000..3847e4e4a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/with_argument/stderr.log
@@ -0,0 +1 @@
+ Created binary (application) package
diff --git a/src/tools/cargo/tests/testsuite/init/with_argument/stdout.log b/src/tools/cargo/tests/testsuite/init/with_argument/stdout.log
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/init/with_argument/stdout.log
diff --git a/src/tools/cargo/tests/testsuite/install.rs b/src/tools/cargo/tests/testsuite/install.rs
new file mode 100644
index 000000000..dd9844f17
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/install.rs
@@ -0,0 +1,2289 @@
+//! Tests for the `cargo install` command.
+
+use std::fs::{self, OpenOptions};
+use std::io::prelude::*;
+use std::path::Path;
+
+use cargo_test_support::compare;
+use cargo_test_support::cross_compile;
+use cargo_test_support::git;
+use cargo_test_support::registry::{self, registry_path, Package};
+use cargo_test_support::{
+ basic_manifest, cargo_process, no_such_file_err_msg, project, project_in, symlink_supported, t,
+};
+use cargo_util::ProcessError;
+
+use cargo_test_support::install::{
+ assert_has_installed_exe, assert_has_not_installed_exe, cargo_home,
+};
+use cargo_test_support::paths::{self, CargoPathExt};
+use std::env;
+use std::path::PathBuf;
+
+fn pkg(name: &str, vers: &str) {
+ Package::new(name, vers)
+ .file("src/lib.rs", "")
+ .file(
+ "src/main.rs",
+ &format!("extern crate {}; fn main() {{}}", name),
+ )
+ .publish();
+}
+
+#[cargo_test]
+fn simple() {
+ pkg("foo", "0.0.1");
+
+ cargo_process("install foo")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] foo v0.0.1 (registry [..])
+[INSTALLING] foo v0.0.1
+[COMPILING] foo v0.0.1
+[FINISHED] release [optimized] target(s) in [..]
+[INSTALLING] [CWD]/home/.cargo/bin/foo[EXE]
+[INSTALLED] package `foo v0.0.1` (executable `foo[EXE]`)
+[WARNING] be sure to add `[..]` to your PATH to be able to run the installed binaries
+",
+ )
+ .run();
+ assert_has_installed_exe(cargo_home(), "foo");
+
+ cargo_process("uninstall foo")
+ .with_stderr("[REMOVING] [CWD]/home/.cargo/bin/foo[EXE]")
+ .run();
+ assert_has_not_installed_exe(cargo_home(), "foo");
+}
+
+#[cargo_test]
+fn simple_with_message_format() {
+ pkg("foo", "0.0.1");
+
+ cargo_process("install foo --message-format=json")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] foo v0.0.1 (registry [..])
+[INSTALLING] foo v0.0.1
+[COMPILING] foo v0.0.1
+[FINISHED] release [optimized] target(s) in [..]
+[INSTALLING] [CWD]/home/.cargo/bin/foo[EXE]
+[INSTALLED] package `foo v0.0.1` (executable `foo[EXE]`)
+[WARNING] be sure to add `[..]` to your PATH to be able to run the installed binaries
+",
+ )
+ .with_json(
+ r#"
+ {
+ "reason": "compiler-artifact",
+ "package_id": "foo 0.0.1 ([..])",
+ "manifest_path": "[..]",
+ "target": {
+ "kind": [
+ "lib"
+ ],
+ "crate_types": [
+ "lib"
+ ],
+ "name": "foo",
+ "src_path": "[..]/foo-0.0.1/src/lib.rs",
+ "edition": "2015",
+ "doc": true,
+ "doctest": true,
+ "test": true
+ },
+ "profile": "{...}",
+ "features": [],
+ "filenames": "{...}",
+ "executable": null,
+ "fresh": false
+ }
+
+ {
+ "reason": "compiler-artifact",
+ "package_id": "foo 0.0.1 ([..])",
+ "manifest_path": "[..]",
+ "target": {
+ "kind": [
+ "bin"
+ ],
+ "crate_types": [
+ "bin"
+ ],
+ "name": "foo",
+ "src_path": "[..]/foo-0.0.1/src/main.rs",
+ "edition": "2015",
+ "doc": true,
+ "doctest": false,
+ "test": true
+ },
+ "profile": "{...}",
+ "features": [],
+ "filenames": "{...}",
+ "executable": "[..]",
+ "fresh": false
+ }
+
+ {"reason":"build-finished","success":true}
+ "#,
+ )
+ .run();
+ assert_has_installed_exe(cargo_home(), "foo");
+}
+
+#[cargo_test]
+fn with_index() {
+ let registry = registry::init();
+ pkg("foo", "0.0.1");
+
+ cargo_process("install foo --index")
+ .arg(registry.index_url().as_str())
+ .with_stderr(&format!(
+ "\
+[UPDATING] `{reg}` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] foo v0.0.1 (registry `{reg}`)
+[INSTALLING] foo v0.0.1 (registry `{reg}`)
+[COMPILING] foo v0.0.1 (registry `{reg}`)
+[FINISHED] release [optimized] target(s) in [..]
+[INSTALLING] [CWD]/home/.cargo/bin/foo[EXE]
+[INSTALLED] package `foo v0.0.1 (registry `{reg}`)` (executable `foo[EXE]`)
+[WARNING] be sure to add `[..]` to your PATH to be able to run the installed binaries
+",
+ reg = registry_path().to_str().unwrap()
+ ))
+ .run();
+ assert_has_installed_exe(cargo_home(), "foo");
+
+ cargo_process("uninstall foo")
+ .with_stderr("[REMOVING] [CWD]/home/.cargo/bin/foo[EXE]")
+ .run();
+ assert_has_not_installed_exe(cargo_home(), "foo");
+}
+
+#[cargo_test]
+fn multiple_pkgs() {
+ pkg("foo", "0.0.1");
+ pkg("bar", "0.0.2");
+
+ cargo_process("install foo bar baz")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] foo v0.0.1 (registry `dummy-registry`)
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.0.2 (registry `dummy-registry`)
+[ERROR] could not find `baz` in registry `[..]` with version `*`
+[INSTALLING] foo v0.0.1
+[COMPILING] foo v0.0.1
+[FINISHED] release [optimized] target(s) in [..]
+[INSTALLING] [CWD]/home/.cargo/bin/foo[EXE]
+[INSTALLED] package `foo v0.0.1` (executable `foo[EXE]`)
+[INSTALLING] bar v0.0.2
+[COMPILING] bar v0.0.2
+[FINISHED] release [optimized] target(s) in [..]
+[INSTALLING] [CWD]/home/.cargo/bin/bar[EXE]
+[INSTALLED] package `bar v0.0.2` (executable `bar[EXE]`)
+[SUMMARY] Successfully installed foo, bar! Failed to install baz (see error(s) above).
+[WARNING] be sure to add `[..]` to your PATH to be able to run the installed binaries
+[ERROR] some crates failed to install
+",
+ )
+ .run();
+ assert_has_installed_exe(cargo_home(), "foo");
+ assert_has_installed_exe(cargo_home(), "bar");
+
+ cargo_process("uninstall foo bar")
+ .with_stderr(
+ "\
+[REMOVING] [CWD]/home/.cargo/bin/foo[EXE]
+[REMOVING] [CWD]/home/.cargo/bin/bar[EXE]
+[SUMMARY] Successfully uninstalled foo, bar!
+",
+ )
+ .run();
+
+ assert_has_not_installed_exe(cargo_home(), "foo");
+ assert_has_not_installed_exe(cargo_home(), "bar");
+}
+
+fn path() -> Vec<PathBuf> {
+ env::split_paths(&env::var_os("PATH").unwrap_or_default()).collect()
+}
+
+#[cargo_test]
+fn multiple_pkgs_path_set() {
+ // confirm partial failure results in 101 status code and does not have the
+ // '[WARNING] be sure to add `[..]` to your PATH to be able to run the installed binaries'
+ // even if CARGO_HOME/bin is in the PATH
+ pkg("foo", "0.0.1");
+ pkg("bar", "0.0.2");
+
+ // add CARGO_HOME/bin to path
+ let mut path = path();
+ path.push(cargo_home().join("bin"));
+ let new_path = env::join_paths(path).unwrap();
+ cargo_process("install foo bar baz")
+ .env("PATH", new_path)
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] foo v0.0.1 (registry `dummy-registry`)
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.0.2 (registry `dummy-registry`)
+[ERROR] could not find `baz` in registry `[..]` with version `*`
+[INSTALLING] foo v0.0.1
+[COMPILING] foo v0.0.1
+[FINISHED] release [optimized] target(s) in [..]
+[INSTALLING] [CWD]/home/.cargo/bin/foo[EXE]
+[INSTALLED] package `foo v0.0.1` (executable `foo[EXE]`)
+[INSTALLING] bar v0.0.2
+[COMPILING] bar v0.0.2
+[FINISHED] release [optimized] target(s) in [..]
+[INSTALLING] [CWD]/home/.cargo/bin/bar[EXE]
+[INSTALLED] package `bar v0.0.2` (executable `bar[EXE]`)
+[SUMMARY] Successfully installed foo, bar! Failed to install baz (see error(s) above).
+[ERROR] some crates failed to install
+",
+ )
+ .run();
+ assert_has_installed_exe(cargo_home(), "foo");
+ assert_has_installed_exe(cargo_home(), "bar");
+
+ cargo_process("uninstall foo bar")
+ .with_stderr(
+ "\
+[REMOVING] [CWD]/home/.cargo/bin/foo[EXE]
+[REMOVING] [CWD]/home/.cargo/bin/bar[EXE]
+[SUMMARY] Successfully uninstalled foo, bar!
+",
+ )
+ .run();
+
+ assert_has_not_installed_exe(cargo_home(), "foo");
+ assert_has_not_installed_exe(cargo_home(), "bar");
+}
+
+#[cargo_test]
+fn pick_max_version() {
+ pkg("foo", "0.1.0");
+ pkg("foo", "0.2.0");
+ pkg("foo", "0.2.1");
+ pkg("foo", "0.2.1-pre.1");
+ pkg("foo", "0.3.0-pre.2");
+
+ cargo_process("install foo")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] foo v0.2.1 (registry [..])
+[INSTALLING] foo v0.2.1
+[COMPILING] foo v0.2.1
+[FINISHED] release [optimized] target(s) in [..]
+[INSTALLING] [CWD]/home/.cargo/bin/foo[EXE]
+[INSTALLED] package `foo v0.2.1` (executable `foo[EXE]`)
+[WARNING] be sure to add `[..]` to your PATH to be able to run the installed binaries
+",
+ )
+ .run();
+ assert_has_installed_exe(cargo_home(), "foo");
+}
+
+#[cargo_test]
+fn installs_beta_version_by_explicit_name_from_git() {
+ let p = git::repo(&paths::root().join("foo"))
+ .file("Cargo.toml", &basic_manifest("foo", "0.3.0-beta.1"))
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ cargo_process("install --git")
+ .arg(p.url().to_string())
+ .arg("foo")
+ .run();
+ assert_has_installed_exe(cargo_home(), "foo");
+}
+
+#[cargo_test]
+fn missing() {
+ pkg("foo", "0.0.1");
+ cargo_process("install bar")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] [..] index
+[ERROR] could not find `bar` in registry `[..]` with version `*`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn missing_current_working_directory() {
+ cargo_process("install .")
+ .with_status(101)
+ .with_stderr(
+ "error: To install the binaries for the package in current working \
+ directory use `cargo install --path .`. \n\
+ Use `cargo build` if you want to simply build the package.",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bad_version() {
+ pkg("foo", "0.0.1");
+ cargo_process("install foo --version=0.2.0")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] [..] index
+[ERROR] could not find `foo` in registry `[..]` with version `=0.2.0`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bad_paths() {
+ cargo_process("install")
+ .with_status(101)
+ .with_stderr("[ERROR] `[CWD]` is not a crate root; specify a crate to install [..]")
+ .run();
+
+ cargo_process("install --path .")
+ .with_status(101)
+ .with_stderr("[ERROR] `[CWD]` does not contain a Cargo.toml file[..]")
+ .run();
+
+ let toml = paths::root().join("Cargo.toml");
+ fs::write(toml, "").unwrap();
+ cargo_process("install --path Cargo.toml")
+ .with_status(101)
+ .with_stderr("[ERROR] `[CWD]/Cargo.toml` is not a directory[..]")
+ .run();
+
+ cargo_process("install --path .")
+ .with_status(101)
+ .with_stderr_contains("[ERROR] failed to parse manifest at `[CWD]/Cargo.toml`")
+ .run();
+}
+
+#[cargo_test]
+fn install_location_precedence() {
+ pkg("foo", "0.0.1");
+
+ let root = paths::root();
+ let t1 = root.join("t1");
+ let t2 = root.join("t2");
+ let t3 = root.join("t3");
+ let t4 = cargo_home();
+
+ fs::create_dir(root.join(".cargo")).unwrap();
+ fs::write(
+ root.join(".cargo/config"),
+ &format!(
+ "[install]
+ root = '{}'
+ ",
+ t3.display()
+ ),
+ )
+ .unwrap();
+
+ println!("install --root");
+
+ cargo_process("install foo --root")
+ .arg(&t1)
+ .env("CARGO_INSTALL_ROOT", &t2)
+ .run();
+ assert_has_installed_exe(&t1, "foo");
+ assert_has_not_installed_exe(&t2, "foo");
+
+ println!("install CARGO_INSTALL_ROOT");
+
+ cargo_process("install foo")
+ .env("CARGO_INSTALL_ROOT", &t2)
+ .run();
+ assert_has_installed_exe(&t2, "foo");
+ assert_has_not_installed_exe(&t3, "foo");
+
+ println!("install install.root");
+
+ cargo_process("install foo").run();
+ assert_has_installed_exe(&t3, "foo");
+ assert_has_not_installed_exe(&t4, "foo");
+
+ fs::remove_file(root.join(".cargo/config")).unwrap();
+
+ println!("install cargo home");
+
+ cargo_process("install foo").run();
+ assert_has_installed_exe(&t4, "foo");
+}
+
+#[cargo_test]
+fn install_path() {
+ let p = project().file("src/main.rs", "fn main() {}").build();
+
+ cargo_process("install --path").arg(p.root()).run();
+ assert_has_installed_exe(cargo_home(), "foo");
+ // path-style installs force a reinstall
+ p.cargo("install --path .")
+ .with_stderr(
+ "\
+[INSTALLING] foo v0.0.1 [..]
+[FINISHED] release [..]
+[REPLACING] [..]/.cargo/bin/foo[EXE]
+[REPLACED] package `foo v0.0.1 [..]` with `foo v0.0.1 [..]` (executable `foo[EXE]`)
+[WARNING] be sure to add [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn install_target_dir() {
+ let p = project().file("src/main.rs", "fn main() {}").build();
+
+ p.cargo("install --target-dir td_test")
+ .with_stderr(
+ "\
+[WARNING] Using `cargo install` [..]
+[INSTALLING] foo v0.0.1 [..]
+[COMPILING] foo v0.0.1 [..]
+[FINISHED] release [..]
+[INSTALLING] [..]foo[EXE]
+[INSTALLED] package `foo v0.0.1 [..]foo[..]` (executable `foo[EXE]`)
+[WARNING] be sure to add [..]
+",
+ )
+ .run();
+
+ let mut path = p.root();
+ path.push("td_test");
+ assert!(path.exists());
+
+ #[cfg(not(windows))]
+ path.push("release/foo");
+ #[cfg(windows)]
+ path.push("release/foo.exe");
+ assert!(path.exists());
+}
+
+#[cargo_test]
+#[cfg(target_os = "linux")]
+fn install_path_with_lowercase_cargo_toml() {
+ let toml = paths::root().join("cargo.toml");
+ fs::write(toml, "").unwrap();
+
+ cargo_process("install --path .")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] `[CWD]` does not contain a Cargo.toml file, \
+but found cargo.toml please try to rename it to Cargo.toml. --path must point to a directory containing a Cargo.toml file.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn install_relative_path_outside_current_ws() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+
+ [workspace]
+ members = ["baz"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "baz/Cargo.toml",
+ r#"
+ [package]
+ name = "baz"
+ version = "0.1.0"
+ authors = []
+ edition = "2021"
+
+ [dependencies]
+ foo = "1"
+ "#,
+ )
+ .file("baz/src/lib.rs", "")
+ .build();
+
+ let _bin_project = project_in("bar")
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("install --path ../bar/foo")
+ .with_stderr(&format!(
+ "\
+[INSTALLING] foo v0.0.1 ([..]/bar/foo)
+[COMPILING] foo v0.0.1 ([..]/bar/foo)
+[FINISHED] release [..]
+[INSTALLING] {home}/bin/foo[EXE]
+[INSTALLED] package `foo v0.0.1 ([..]/bar/foo)` (executable `foo[EXE]`)
+[WARNING] be sure to add [..]
+",
+ home = cargo_home().display(),
+ ))
+ .run();
+
+ // Validate the workspace error message to display available targets.
+ p.cargo("install --path ../bar/foo --bin")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] \"--bin\" takes one argument.
+Available binaries:
+ foo
+
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn multiple_packages_containing_binaries() {
+ let p = git::repo(&paths::root().join("foo"))
+ .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("src/main.rs", "fn main() {}")
+ .file("a/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("a/src/main.rs", "fn main() {}")
+ .build();
+
+ let git_url = p.url().to_string();
+ cargo_process("install --git")
+ .arg(p.url().to_string())
+ .with_status(101)
+ .with_stderr(format!(
+ "\
+[UPDATING] git repository [..]
+[ERROR] multiple packages with binaries found: bar, foo. \
+When installing a git repository, cargo will always search the entire repo for any Cargo.toml.
+Please specify a package, e.g. `cargo install --git {git_url} bar`.
+"
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn multiple_packages_matching_example() {
+ let p = git::repo(&paths::root().join("foo"))
+ .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("src/lib.rs", "")
+ .file("examples/ex1.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "")
+ .file("bar/examples/ex1.rs", "fn main() {}")
+ .build();
+
+ let git_url = p.url().to_string();
+ cargo_process("install --example ex1 --git")
+ .arg(p.url().to_string())
+ .with_status(101)
+ .with_stderr(format!(
+ "\
+[UPDATING] git repository [..]
+[ERROR] multiple packages with examples found: bar, foo. \
+When installing a git repository, cargo will always search the entire repo for any Cargo.toml.
+Please specify a package, e.g. `cargo install --git {git_url} bar`."
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn multiple_binaries_deep_select_uses_package_name() {
+ let p = git::repo(&paths::root().join("foo"))
+ .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("bar/baz/src/main.rs", "fn main() {}")
+ .build();
+
+ cargo_process("install --git")
+ .arg(p.url().to_string())
+ .arg("baz")
+ .run();
+}
+
+#[cargo_test]
+fn multiple_binaries_in_selected_package_installs_all() {
+ let p = git::repo(&paths::root().join("foo"))
+ .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/bin/bin1.rs", "fn main() {}")
+ .file("bar/src/bin/bin2.rs", "fn main() {}")
+ .build();
+
+ cargo_process("install --git")
+ .arg(p.url().to_string())
+ .arg("bar")
+ .run();
+
+ let cargo_home = cargo_home();
+ assert_has_installed_exe(&cargo_home, "bin1");
+ assert_has_installed_exe(&cargo_home, "bin2");
+}
+
+#[cargo_test]
+fn multiple_binaries_in_selected_package_with_bin_option_installs_only_one() {
+ let p = git::repo(&paths::root().join("foo"))
+ .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/bin/bin1.rs", "fn main() {}")
+ .file("bar/src/bin/bin2.rs", "fn main() {}")
+ .build();
+
+ cargo_process("install --bin bin1 --git")
+ .arg(p.url().to_string())
+ .arg("bar")
+ .run();
+
+ let cargo_home = cargo_home();
+ assert_has_installed_exe(&cargo_home, "bin1");
+ assert_has_not_installed_exe(&cargo_home, "bin2");
+}
+
+#[cargo_test]
+fn multiple_crates_select() {
+ let p = git::repo(&paths::root().join("foo"))
+ .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("src/main.rs", "fn main() {}")
+ .file("a/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("a/src/main.rs", "fn main() {}")
+ .build();
+
+ cargo_process("install --git")
+ .arg(p.url().to_string())
+ .arg("foo")
+ .run();
+ assert_has_installed_exe(cargo_home(), "foo");
+ assert_has_not_installed_exe(cargo_home(), "bar");
+
+ cargo_process("install --git")
+ .arg(p.url().to_string())
+ .arg("bar")
+ .run();
+ assert_has_installed_exe(cargo_home(), "bar");
+}
+
+#[cargo_test]
+fn multiple_crates_git_all() {
+ let p = git::repo(&paths::root().join("foo"))
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bin1", "bin2"]
+ "#,
+ )
+ .file("bin1/Cargo.toml", &basic_manifest("bin1", "0.1.0"))
+ .file("bin2/Cargo.toml", &basic_manifest("bin2", "0.1.0"))
+ .file(
+ "bin1/src/main.rs",
+ r#"fn main() { println!("Hello, world!"); }"#,
+ )
+ .file(
+ "bin2/src/main.rs",
+ r#"fn main() { println!("Hello, world!"); }"#,
+ )
+ .build();
+
+ cargo_process(&format!("install --git {} bin1 bin2", p.url().to_string())).run();
+}
+
+#[cargo_test]
+fn multiple_crates_auto_binaries() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ bar = { path = "a" }
+ "#,
+ )
+ .file("src/main.rs", "extern crate bar; fn main() {}")
+ .file("a/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("a/src/lib.rs", "")
+ .build();
+
+ cargo_process("install --path").arg(p.root()).run();
+ assert_has_installed_exe(cargo_home(), "foo");
+}
+
+#[cargo_test]
+fn multiple_crates_auto_examples() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ bar = { path = "a" }
+ "#,
+ )
+ .file("src/lib.rs", "extern crate bar;")
+ .file(
+ "examples/foo.rs",
+ "
+ extern crate bar;
+ extern crate foo;
+ fn main() {}
+ ",
+ )
+ .file("a/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("a/src/lib.rs", "")
+ .build();
+
+ cargo_process("install --path")
+ .arg(p.root())
+ .arg("--example=foo")
+ .run();
+ assert_has_installed_exe(cargo_home(), "foo");
+}
+
+#[cargo_test]
+fn no_binaries_or_examples() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ bar = { path = "a" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("a/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("a/src/lib.rs", "")
+ .build();
+
+ cargo_process("install --path")
+ .arg(p.root())
+ .with_status(101)
+ .with_stderr("[ERROR] no packages found with binaries or examples")
+ .run();
+}
+
+#[cargo_test]
+fn no_binaries() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file("examples/foo.rs", "fn main() {}")
+ .build();
+
+ cargo_process("install --path")
+ .arg(p.root())
+ .arg("foo")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] there is nothing to install in `foo v0.0.1 ([..])`, because it has no binaries[..]
+[..]
+To use a library crate, add it as a dependency to a Cargo project with `cargo add`.",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn examples() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file("examples/foo.rs", "extern crate foo; fn main() {}")
+ .build();
+
+ cargo_process("install --path")
+ .arg(p.root())
+ .arg("--example=foo")
+ .run();
+ assert_has_installed_exe(cargo_home(), "foo");
+}
+
+#[cargo_test]
+fn install_force() {
+ let p = project().file("src/main.rs", "fn main() {}").build();
+
+ cargo_process("install --path").arg(p.root()).run();
+
+ let p = project()
+ .at("foo2")
+ .file("Cargo.toml", &basic_manifest("foo", "0.2.0"))
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ cargo_process("install --force --path")
+ .arg(p.root())
+ .with_stderr(
+ "\
+[INSTALLING] foo v0.2.0 ([..])
+[COMPILING] foo v0.2.0 ([..])
+[FINISHED] release [optimized] target(s) in [..]
+[REPLACING] [CWD]/home/.cargo/bin/foo[EXE]
+[REPLACED] package `foo v0.0.1 ([..]/foo)` with `foo v0.2.0 ([..]/foo2)` (executable `foo[EXE]`)
+[WARNING] be sure to add `[..]` to your PATH to be able to run the installed binaries
+",
+ )
+ .run();
+
+ cargo_process("install --list")
+ .with_stdout(
+ "\
+foo v0.2.0 ([..]):
+ foo[..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn install_force_partial_overlap() {
+ let p = project()
+ .file("src/bin/foo-bin1.rs", "fn main() {}")
+ .file("src/bin/foo-bin2.rs", "fn main() {}")
+ .build();
+
+ cargo_process("install --path").arg(p.root()).run();
+
+ let p = project()
+ .at("foo2")
+ .file("Cargo.toml", &basic_manifest("foo", "0.2.0"))
+ .file("src/bin/foo-bin2.rs", "fn main() {}")
+ .file("src/bin/foo-bin3.rs", "fn main() {}")
+ .build();
+
+ cargo_process("install --force --path")
+ .arg(p.root())
+ .with_stderr(
+ "\
+[INSTALLING] foo v0.2.0 ([..])
+[COMPILING] foo v0.2.0 ([..])
+[FINISHED] release [optimized] target(s) in [..]
+[INSTALLING] [CWD]/home/.cargo/bin/foo-bin3[EXE]
+[REPLACING] [CWD]/home/.cargo/bin/foo-bin2[EXE]
+[REMOVING] executable `[..]/bin/foo-bin1[EXE]` from previous version foo v0.0.1 [..]
+[INSTALLED] package `foo v0.2.0 ([..]/foo2)` (executable `foo-bin3[EXE]`)
+[REPLACED] package `foo v0.0.1 ([..]/foo)` with `foo v0.2.0 ([..]/foo2)` (executable `foo-bin2[EXE]`)
+[WARNING] be sure to add `[..]` to your PATH to be able to run the installed binaries
+",
+ )
+ .run();
+
+ cargo_process("install --list")
+ .with_stdout(
+ "\
+foo v0.2.0 ([..]):
+ foo-bin2[..]
+ foo-bin3[..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn install_force_bin() {
+ let p = project()
+ .file("src/bin/foo-bin1.rs", "fn main() {}")
+ .file("src/bin/foo-bin2.rs", "fn main() {}")
+ .build();
+
+ cargo_process("install --path").arg(p.root()).run();
+
+ let p = project()
+ .at("foo2")
+ .file("Cargo.toml", &basic_manifest("foo", "0.2.0"))
+ .file("src/bin/foo-bin1.rs", "fn main() {}")
+ .file("src/bin/foo-bin2.rs", "fn main() {}")
+ .build();
+
+ cargo_process("install --force --bin foo-bin2 --path")
+ .arg(p.root())
+ .with_stderr(
+ "\
+[INSTALLING] foo v0.2.0 ([..])
+[COMPILING] foo v0.2.0 ([..])
+[FINISHED] release [optimized] target(s) in [..]
+[REPLACING] [CWD]/home/.cargo/bin/foo-bin2[EXE]
+[REPLACED] package `foo v0.0.1 ([..]/foo)` with `foo v0.2.0 ([..]/foo2)` (executable `foo-bin2[EXE]`)
+[WARNING] be sure to add `[..]` to your PATH to be able to run the installed binaries
+",
+ )
+ .run();
+
+ cargo_process("install --list")
+ .with_stdout(
+ "\
+foo v0.0.1 ([..]):
+ foo-bin1[..]
+foo v0.2.0 ([..]):
+ foo-bin2[..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn compile_failure() {
+ let p = project().file("src/main.rs", "").build();
+
+ cargo_process("install --path")
+ .arg(p.root())
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+[ERROR] could not compile `foo` (bin \"foo\") due to previous error
+[ERROR] failed to compile `foo v0.0.1 ([..])`, intermediate artifacts can be \
+ found at `[..]target`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn git_repo() {
+ let p = git::repo(&paths::root().join("foo"))
+ .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ // Use `--locked` to test that we don't even try to write a lock file.
+ cargo_process("install --locked --git")
+ .arg(p.url().to_string())
+ .with_stderr(
+ "\
+[UPDATING] git repository `[..]`
+[WARNING] no Cargo.lock file published in foo v0.1.0 ([..])
+[INSTALLING] foo v0.1.0 ([..])
+[COMPILING] foo v0.1.0 ([..])
+[FINISHED] release [optimized] target(s) in [..]
+[INSTALLING] [CWD]/home/.cargo/bin/foo[EXE]
+[INSTALLED] package `foo v0.1.0 ([..]/foo#[..])` (executable `foo[EXE]`)
+[WARNING] be sure to add `[..]` to your PATH to be able to run the installed binaries
+",
+ )
+ .run();
+ assert_has_installed_exe(cargo_home(), "foo");
+ assert_has_installed_exe(cargo_home(), "foo");
+}
+
+#[cargo_test]
+#[cfg(target_os = "linux")]
+fn git_repo_with_lowercase_cargo_toml() {
+ let p = git::repo(&paths::root().join("foo"))
+ .file("cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ cargo_process("install --git")
+ .arg(p.url().to_string())
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] git repository [..]
+[ERROR] Could not find Cargo.toml in `[..]`, but found cargo.toml please try to rename it to Cargo.toml
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn list() {
+ pkg("foo", "0.0.1");
+ pkg("bar", "0.2.1");
+ pkg("bar", "0.2.2");
+
+ cargo_process("install --list").with_stdout("").run();
+
+ cargo_process("install bar --version =0.2.1").run();
+ cargo_process("install foo").run();
+ cargo_process("install --list")
+ .with_stdout(
+ "\
+bar v0.2.1:
+ bar[..]
+foo v0.0.1:
+ foo[..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn list_error() {
+ pkg("foo", "0.0.1");
+ cargo_process("install foo").run();
+ cargo_process("install --list")
+ .with_stdout(
+ "\
+foo v0.0.1:
+ foo[..]
+",
+ )
+ .run();
+ let mut worldfile_path = cargo_home();
+ worldfile_path.push(".crates.toml");
+ let mut worldfile = OpenOptions::new()
+ .write(true)
+ .open(worldfile_path)
+ .expect(".crates.toml should be there");
+ worldfile.write_all(b"\x00").unwrap();
+ drop(worldfile);
+ cargo_process("install --list --verbose")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse crate metadata at `[..]`
+
+Caused by:
+ invalid TOML found for metadata
+
+Caused by:
+ TOML parse error at line 1, column 1
+ |
+ 1 | [..]
+ | ^
+ invalid key
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn uninstall_pkg_does_not_exist() {
+ cargo_process("uninstall foo")
+ .with_status(101)
+ .with_stderr("[ERROR] package ID specification `foo` did not match any packages")
+ .run();
+}
+
+#[cargo_test]
+fn uninstall_bin_does_not_exist() {
+ pkg("foo", "0.0.1");
+
+ cargo_process("install foo").run();
+ cargo_process("uninstall foo --bin=bar")
+ .with_status(101)
+ .with_stderr("[ERROR] binary `bar[..]` not installed as part of `foo v0.0.1`")
+ .run();
+}
+
+#[cargo_test]
+fn uninstall_piecemeal() {
+ let p = project()
+ .file("src/bin/foo.rs", "fn main() {}")
+ .file("src/bin/bar.rs", "fn main() {}")
+ .build();
+
+ cargo_process("install --path").arg(p.root()).run();
+ assert_has_installed_exe(cargo_home(), "foo");
+ assert_has_installed_exe(cargo_home(), "bar");
+
+ cargo_process("uninstall foo --bin=bar")
+ .with_stderr("[REMOVING] [..]bar[..]")
+ .run();
+
+ assert_has_installed_exe(cargo_home(), "foo");
+ assert_has_not_installed_exe(cargo_home(), "bar");
+
+ cargo_process("uninstall foo --bin=foo")
+ .with_stderr("[REMOVING] [..]foo[..]")
+ .run();
+ assert_has_not_installed_exe(cargo_home(), "foo");
+
+ cargo_process("uninstall foo")
+ .with_status(101)
+ .with_stderr("[ERROR] package ID specification `foo` did not match any packages")
+ .run();
+}
+
+#[cargo_test]
+fn subcommand_works_out_of_the_box() {
+ Package::new("cargo-foo", "1.0.0")
+ .file("src/main.rs", r#"fn main() { println!("bar"); }"#)
+ .publish();
+ cargo_process("install cargo-foo").run();
+ cargo_process("foo").with_stdout("bar\n").run();
+ cargo_process("--list")
+ .with_stdout_contains(" foo\n")
+ .run();
+}
+
+#[cargo_test]
+fn installs_from_cwd_by_default() {
+ let p = project().file("src/main.rs", "fn main() {}").build();
+
+ p.cargo("install")
+ .with_stderr_contains(
+ "warning: Using `cargo install` to install the binaries for the \
+ package in current working directory is deprecated, \
+ use `cargo install --path .` instead. \
+ Use `cargo build` if you want to simply build the package.",
+ )
+ .run();
+ assert_has_installed_exe(cargo_home(), "foo");
+}
+
+#[cargo_test]
+fn installs_from_cwd_with_2018_warnings() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ edition = "2018"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("install")
+ .with_status(101)
+ .with_stderr_contains(
+ "error: Using `cargo install` to install the binaries for the \
+ package in current working directory is no longer supported, \
+ use `cargo install --path .` instead. \
+ Use `cargo build` if you want to simply build the package.",
+ )
+ .run();
+ assert_has_not_installed_exe(cargo_home(), "foo");
+}
+
+#[cargo_test]
+fn uninstall_cwd() {
+ let p = project().file("src/main.rs", "fn main() {}").build();
+ p.cargo("install --path .")
+ .with_stderr(&format!(
+ "\
+[INSTALLING] foo v0.0.1 ([CWD])
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] release [optimized] target(s) in [..]
+[INSTALLING] {home}/bin/foo[EXE]
+[INSTALLED] package `foo v0.0.1 ([..]/foo)` (executable `foo[EXE]`)
+[WARNING] be sure to add `{home}/bin` to your PATH to be able to run the installed binaries",
+ home = cargo_home().display(),
+ ))
+ .run();
+ assert_has_installed_exe(cargo_home(), "foo");
+
+ p.cargo("uninstall")
+ .with_stdout("")
+ .with_stderr(&format!(
+ "[REMOVING] {home}/bin/foo[EXE]",
+ home = cargo_home().display()
+ ))
+ .run();
+ assert_has_not_installed_exe(cargo_home(), "foo");
+}
+
+#[cargo_test]
+fn uninstall_cwd_not_installed() {
+ let p = project().file("src/main.rs", "fn main() {}").build();
+ p.cargo("uninstall")
+ .with_status(101)
+ .with_stdout("")
+ .with_stderr("error: package `foo v0.0.1 ([CWD])` is not installed")
+ .run();
+}
+
+#[cargo_test]
+fn uninstall_cwd_no_project() {
+ cargo_process("uninstall")
+ .with_status(101)
+ .with_stdout("")
+ .with_stderr(format!(
+ "\
+[ERROR] failed to read `[CWD]/Cargo.toml`
+
+Caused by:
+ {err_msg}",
+ err_msg = no_such_file_err_msg(),
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn do_not_rebuilds_on_local_install() {
+ let p = project().file("src/main.rs", "fn main() {}").build();
+
+ p.cargo("build --release").run();
+ cargo_process("install --path")
+ .arg(p.root())
+ .with_stderr(
+ "\
+[INSTALLING] [..]
+[FINISHED] release [optimized] target(s) in [..]
+[INSTALLING] [..]
+[INSTALLED] package `foo v0.0.1 ([..]/foo)` (executable `foo[EXE]`)
+[WARNING] be sure to add `[..]` to your PATH to be able to run the installed binaries
+",
+ )
+ .run();
+
+ assert!(p.build_dir().exists());
+ assert!(p.release_bin("foo").exists());
+ assert_has_installed_exe(cargo_home(), "foo");
+}
+
+#[cargo_test]
+fn reports_unsuccessful_subcommand_result() {
+ Package::new("cargo-fail", "1.0.0")
+ .file("src/main.rs", "fn main() { panic!(); }")
+ .publish();
+ cargo_process("install cargo-fail").run();
+ cargo_process("--list")
+ .with_stdout_contains(" fail\n")
+ .run();
+ cargo_process("fail")
+ .with_status(101)
+ .with_stderr_contains("thread '[..]' panicked at 'explicit panic', [..]")
+ .run();
+}
+
+#[cargo_test]
+fn git_with_lockfile() {
+ let p = git::repo(&paths::root().join("foo"))
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ bar = { path = "bar" }
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "fn main() {}")
+ .file(
+ "Cargo.lock",
+ r#"
+ [[package]]
+ name = "foo"
+ version = "0.1.0"
+ dependencies = [ "bar 0.1.0" ]
+
+ [[package]]
+ name = "bar"
+ version = "0.1.0"
+ "#,
+ )
+ .build();
+
+ cargo_process("install --git")
+ .arg(p.url().to_string())
+ .run();
+}
+
+#[cargo_test]
+fn q_silences_warnings() {
+ let p = project().file("src/main.rs", "fn main() {}").build();
+
+ cargo_process("install -q --path")
+ .arg(p.root())
+ .with_stderr("")
+ .run();
+}
+
+#[cargo_test]
+fn readonly_dir() {
+ pkg("foo", "0.0.1");
+
+ let root = paths::root();
+ let dir = &root.join("readonly");
+ fs::create_dir(root.join("readonly")).unwrap();
+ let mut perms = fs::metadata(dir).unwrap().permissions();
+ perms.set_readonly(true);
+ fs::set_permissions(dir, perms).unwrap();
+
+ cargo_process("install foo").cwd(dir).run();
+ assert_has_installed_exe(cargo_home(), "foo");
+}
+
+#[cargo_test]
+fn use_path_workspace() {
+ Package::new("foo", "1.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+
+ [workspace]
+ members = ["baz"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "baz/Cargo.toml",
+ r#"
+ [package]
+ name = "baz"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ foo = "1"
+ "#,
+ )
+ .file("baz/src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+ let lock = p.read_lockfile();
+ p.cargo("install").run();
+ let lock2 = p.read_lockfile();
+ assert_eq!(lock, lock2, "different lockfiles");
+}
+
+#[cargo_test]
+fn path_install_workspace_root_despite_default_members() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "ws-root"
+ version = "0.1.0"
+ authors = []
+
+ [workspace]
+ members = ["ws-member"]
+ default-members = ["ws-member"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "ws-member/Cargo.toml",
+ r#"
+ [package]
+ name = "ws-member"
+ version = "0.1.0"
+ authors = []
+ "#,
+ )
+ .file("ws-member/src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("install --path")
+ .arg(p.root())
+ .arg("ws-root")
+ .with_stderr_contains(
+ "[INSTALLED] package `ws-root v0.1.0 ([..])` (executable `ws-root[EXE]`)",
+ )
+ // Particularly avoid "Installed package `ws-root v0.1.0 ([..]])` (executable `ws-member`)":
+ .with_stderr_does_not_contain("ws-member")
+ .run();
+}
+
+#[cargo_test]
+fn dev_dependencies_no_check() {
+ Package::new("foo", "1.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+
+ [dev-dependencies]
+ baz = "1.0.0"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr_contains("[..] no matching package named `baz` found")
+ .run();
+ p.cargo("install").run();
+}
+
+#[cargo_test]
+fn dev_dependencies_lock_file_untouched() {
+ Package::new("foo", "1.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [dev-dependencies]
+ bar = { path = "a" }
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("a/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("a/src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+ let lock = p.read_lockfile();
+ p.cargo("install").run();
+ let lock2 = p.read_lockfile();
+ assert!(lock == lock2, "different lockfiles");
+}
+
+#[cargo_test]
+fn install_target_native() {
+ pkg("foo", "0.1.0");
+
+ cargo_process("install foo --target")
+ .arg(cargo_test_support::rustc_host())
+ .run();
+ assert_has_installed_exe(cargo_home(), "foo");
+}
+
+#[cargo_test]
+fn install_target_foreign() {
+ if cross_compile::disabled() {
+ return;
+ }
+
+ pkg("foo", "0.1.0");
+
+ cargo_process("install foo --target")
+ .arg(cross_compile::alternate())
+ .run();
+ assert_has_installed_exe(cargo_home(), "foo");
+}
+
+#[cargo_test]
+fn vers_precise() {
+ pkg("foo", "0.1.1");
+ pkg("foo", "0.1.2");
+
+ cargo_process("install foo --vers 0.1.1")
+ .with_stderr_contains("[DOWNLOADED] foo v0.1.1 (registry [..])")
+ .run();
+}
+
+#[cargo_test]
+fn version_precise() {
+ pkg("foo", "0.1.1");
+ pkg("foo", "0.1.2");
+
+ cargo_process("install foo --version 0.1.1")
+ .with_stderr_contains("[DOWNLOADED] foo v0.1.1 (registry [..])")
+ .run();
+}
+
+#[cargo_test]
+fn inline_version_precise() {
+ pkg("foo", "0.1.1");
+ pkg("foo", "0.1.2");
+
+ cargo_process("install foo@0.1.1")
+ .with_stderr_contains("[DOWNLOADED] foo v0.1.1 (registry [..])")
+ .run();
+}
+
+#[cargo_test]
+fn inline_version_multiple() {
+ pkg("foo", "0.1.0");
+ pkg("foo", "0.1.1");
+ pkg("foo", "0.1.2");
+ pkg("bar", "0.2.0");
+ pkg("bar", "0.2.1");
+ pkg("bar", "0.2.2");
+
+ cargo_process("install foo@0.1.1 bar@0.2.1")
+ .with_stderr_contains("[DOWNLOADED] foo v0.1.1 (registry [..])")
+ .with_stderr_contains("[DOWNLOADED] bar v0.2.1 (registry [..])")
+ .run();
+}
+
+#[cargo_test]
+fn inline_version_without_name() {
+ pkg("foo", "0.1.1");
+ pkg("foo", "0.1.2");
+
+ cargo_process("install @0.1.1")
+ .with_status(101)
+ .with_stderr("error: missing crate name for `@0.1.1`")
+ .run();
+}
+
+#[cargo_test]
+fn inline_and_explicit_version() {
+ pkg("foo", "0.1.1");
+ pkg("foo", "0.1.2");
+
+ cargo_process("install foo@0.1.1 --version 0.1.1")
+ .with_status(101)
+ .with_stderr("error: cannot specify both `@0.1.1` and `--version`")
+ .run();
+}
+
+#[cargo_test]
+fn not_both_vers_and_version() {
+ pkg("foo", "0.1.1");
+ pkg("foo", "0.1.2");
+
+ cargo_process("install foo --version 0.1.1 --vers 0.1.2")
+ .with_status(1)
+ .with_stderr_contains(
+ "\
+[ERROR] the argument '--version <VERSION>' cannot be used multiple times
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn test_install_git_cannot_be_a_base_url() {
+ cargo_process("install --git github.com:rust-lang/rustfmt.git")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] invalid url `github.com:rust-lang/rustfmt.git`: cannot-be-a-base-URLs are not supported",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn uninstall_multiple_and_specifying_bin() {
+ cargo_process("uninstall foo bar --bin baz")
+ .with_status(101)
+ .with_stderr("\
+[ERROR] A binary can only be associated with a single installed package, specifying multiple specs with --bin is redundant.")
+ .run();
+}
+
+#[cargo_test]
+fn uninstall_with_empty_package_option() {
+ cargo_process("uninstall -p")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] \"--package <SPEC>\" requires a SPEC format value.
+Run `cargo help pkgid` for more information about SPEC format.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn uninstall_multiple_and_some_pkg_does_not_exist() {
+ pkg("foo", "0.0.1");
+
+ cargo_process("install foo").run();
+
+ cargo_process("uninstall foo bar")
+ .with_status(101)
+ .with_stderr(
+ "\
+[REMOVING] [CWD]/home/.cargo/bin/foo[EXE]
+error: package ID specification `bar` did not match any packages
+[SUMMARY] Successfully uninstalled foo! Failed to uninstall bar (see error(s) above).
+error: some packages failed to uninstall
+",
+ )
+ .run();
+
+ assert_has_not_installed_exe(cargo_home(), "foo");
+ assert_has_not_installed_exe(cargo_home(), "bar");
+}
+
+#[cargo_test]
+fn custom_target_dir_for_git_source() {
+ let p = git::repo(&paths::root().join("foo"))
+ .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ cargo_process("install --git")
+ .arg(p.url().to_string())
+ .run();
+ assert!(!paths::root().join("target/release").is_dir());
+
+ cargo_process("install --force --git")
+ .arg(p.url().to_string())
+ .env("CARGO_TARGET_DIR", "target")
+ .run();
+ assert!(paths::root().join("target/release").is_dir());
+}
+
+#[cargo_test]
+fn install_respects_lock_file() {
+ // `cargo install` now requires --locked to use a Cargo.lock.
+ Package::new("bar", "0.1.0").publish();
+ Package::new("bar", "0.1.1")
+ .file("src/lib.rs", "not rust")
+ .publish();
+ Package::new("foo", "0.1.0")
+ .dep("bar", "0.1")
+ .file("src/lib.rs", "")
+ .file(
+ "src/main.rs",
+ "extern crate foo; extern crate bar; fn main() {}",
+ )
+ .file(
+ "Cargo.lock",
+ r#"
+[[package]]
+name = "bar"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "foo"
+version = "0.1.0"
+dependencies = [
+ "bar 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+"#,
+ )
+ .publish();
+
+ cargo_process("install foo")
+ .with_stderr_contains("[..]not rust[..]")
+ .with_status(101)
+ .run();
+ cargo_process("install --locked foo").run();
+}
+
+#[cargo_test]
+fn install_path_respects_lock_file() {
+ // --path version of install_path_respects_lock_file, --locked is required
+ // to use Cargo.lock.
+ Package::new("bar", "0.1.0").publish();
+ Package::new("bar", "0.1.1")
+ .file("src/lib.rs", "not rust")
+ .publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "0.1"
+ "#,
+ )
+ .file("src/main.rs", "extern crate bar; fn main() {}")
+ .file(
+ "Cargo.lock",
+ r#"
+[[package]]
+name = "bar"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "foo"
+version = "0.1.0"
+dependencies = [
+ "bar 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+"#,
+ )
+ .build();
+
+ p.cargo("install --path .")
+ .with_stderr_contains("[..]not rust[..]")
+ .with_status(101)
+ .run();
+ p.cargo("install --path . --locked").run();
+}
+
+#[cargo_test]
+fn lock_file_path_deps_ok() {
+ Package::new("bar", "0.1.0").publish();
+
+ Package::new("foo", "0.1.0")
+ .dep("bar", "0.1")
+ .file("src/lib.rs", "")
+ .file(
+ "src/main.rs",
+ "extern crate foo; extern crate bar; fn main() {}",
+ )
+ .file(
+ "Cargo.lock",
+ r#"
+ [[package]]
+ name = "bar"
+ version = "0.1.0"
+
+ [[package]]
+ name = "foo"
+ version = "0.1.0"
+ dependencies = [
+ "bar 0.1.0",
+ ]
+ "#,
+ )
+ .publish();
+
+ cargo_process("install foo").run();
+}
+
+#[cargo_test]
+fn install_empty_argument() {
+ // Bug 5229
+ cargo_process("install")
+ .arg("")
+ .with_status(1)
+ .with_stderr_contains("[ERROR] a value is required for '[crate]...' but none was supplied")
+ .run();
+}
+
+#[cargo_test]
+fn git_repo_replace() {
+ let p = git::repo(&paths::root().join("foo"))
+ .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("src/main.rs", "fn main() {}")
+ .build();
+ let repo = git2::Repository::open(&p.root()).unwrap();
+ let old_rev = repo.revparse_single("HEAD").unwrap().id();
+ cargo_process("install --git")
+ .arg(p.url().to_string())
+ .run();
+ git::commit(&repo);
+ let new_rev = repo.revparse_single("HEAD").unwrap().id();
+ let mut path = paths::home();
+ path.push(".cargo/.crates.toml");
+
+ assert_ne!(old_rev, new_rev);
+ assert!(fs::read_to_string(path.clone())
+ .unwrap()
+ .contains(&format!("{}", old_rev)));
+ cargo_process("install --force --git")
+ .arg(p.url().to_string())
+ .run();
+ assert!(fs::read_to_string(path)
+ .unwrap()
+ .contains(&format!("{}", new_rev)));
+}
+
+#[cargo_test]
+fn workspace_uses_workspace_target_dir() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [workspace]
+
+ [dependencies]
+ bar = { path = 'bar' }
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build --release").cwd("bar").run();
+ cargo_process("install --path")
+ .arg(p.root().join("bar"))
+ .with_stderr(
+ "[INSTALLING] [..]
+[FINISHED] release [optimized] target(s) in [..]
+[INSTALLING] [..]
+[INSTALLED] package `bar v0.1.0 ([..]/bar)` (executable `bar[EXE]`)
+[WARNING] be sure to add `[..]` to your PATH to be able to run the installed binaries
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn install_ignores_local_cargo_config() {
+ pkg("bar", "0.0.1");
+
+ let p = project()
+ .file(
+ ".cargo/config",
+ r#"
+ [build]
+ target = "non-existing-target"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("install bar").run();
+ assert_has_installed_exe(cargo_home(), "bar");
+}
+
+#[cargo_test]
+fn install_ignores_unstable_table_in_local_cargo_config() {
+ pkg("bar", "0.0.1");
+
+ let p = project()
+ .file(
+ ".cargo/config",
+ r#"
+ [unstable]
+ build-std = ["core"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("install bar")
+ .masquerade_as_nightly_cargo(&["build-std"])
+ .run();
+ assert_has_installed_exe(cargo_home(), "bar");
+}
+
+#[cargo_test]
+fn install_global_cargo_config() {
+ pkg("bar", "0.0.1");
+
+ let config = cargo_home().join("config");
+ let mut toml = fs::read_to_string(&config).unwrap_or_default();
+
+ toml.push_str(
+ r#"
+ [build]
+ target = 'nonexistent'
+ "#,
+ );
+ fs::write(&config, toml).unwrap();
+
+ cargo_process("install bar")
+ .with_status(101)
+ .with_stderr_contains("[..]--target nonexistent[..]")
+ .run();
+}
+
+#[cargo_test]
+fn install_path_config() {
+ project()
+ .file(
+ ".cargo/config",
+ r#"
+ [build]
+ target = 'nonexistent'
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+ cargo_process("install --path foo")
+ .with_status(101)
+ .with_stderr_contains("[..]--target nonexistent[..]")
+ .run();
+}
+
+#[cargo_test]
+fn install_version_req() {
+ // Try using a few versionreq styles.
+ pkg("foo", "0.0.3");
+ pkg("foo", "1.0.4");
+ pkg("foo", "1.0.5");
+ cargo_process("install foo --version=*")
+ .with_stderr_does_not_contain("[WARNING][..]is not a valid semver[..]")
+ .with_stderr_contains("[INSTALLING] foo v1.0.5")
+ .run();
+ cargo_process("uninstall foo").run();
+ cargo_process("install foo --version=^1.0")
+ .with_stderr_does_not_contain("[WARNING][..]is not a valid semver[..]")
+ .with_stderr_contains("[INSTALLING] foo v1.0.5")
+ .run();
+ cargo_process("uninstall foo").run();
+ cargo_process("install foo --version=0.0.*")
+ .with_stderr_does_not_contain("[WARNING][..]is not a valid semver[..]")
+ .with_stderr_contains("[INSTALLING] foo v0.0.3")
+ .run();
+}
+
+#[cargo_test]
+fn git_install_reads_workspace_manifest() {
+ let p = git::repo(&paths::root().join("foo"))
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bin1"]
+
+ [profile.release]
+ incremental = 3
+ "#,
+ )
+ .file("bin1/Cargo.toml", &basic_manifest("bin1", "0.1.0"))
+ .file(
+ "bin1/src/main.rs",
+ r#"fn main() { println!("Hello, world!"); }"#,
+ )
+ .build();
+
+ cargo_process(&format!("install --git {}", p.url().to_string()))
+ .with_status(101)
+ .with_stderr_contains(" invalid type: integer `3`[..]")
+ .run();
+}
+
+#[cargo_test]
+fn install_git_with_symlink_home() {
+ // Ensure that `cargo install` with a git repo is OK when CARGO_HOME is a
+ // symlink, and uses an build script.
+ if !symlink_supported() {
+ return;
+ }
+ let p = git::new("foo", |p| {
+ p.file("Cargo.toml", &basic_manifest("foo", "1.0.0"))
+ .file("src/main.rs", "fn main() {}")
+ // This triggers discover_git_and_list_files for detecting changed files.
+ .file("build.rs", "fn main() {}")
+ });
+ #[cfg(unix)]
+ use std::os::unix::fs::symlink;
+ #[cfg(windows)]
+ use std::os::windows::fs::symlink_dir as symlink;
+
+ let actual = paths::root().join("actual-home");
+ t!(std::fs::create_dir(&actual));
+ t!(symlink(&actual, paths::home().join(".cargo")));
+ cargo_process("install --git")
+ .arg(p.url().to_string())
+ .with_stderr(
+ "\
+[UPDATING] git repository [..]
+[INSTALLING] foo v1.0.0 [..]
+[COMPILING] foo v1.0.0 [..]
+[FINISHED] [..]
+[INSTALLING] [..]home/.cargo/bin/foo[..]
+[INSTALLED] package `foo [..]
+[WARNING] be sure to add [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn install_yanked_cargo_package() {
+ Package::new("baz", "0.0.1").yanked(true).publish();
+ cargo_process("install baz --version 0.0.1")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+[ERROR] cannot install package `baz`, it has been yanked from registry `crates-io`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn install_cargo_package_in_a_patched_workspace() {
+ pkg("foo", "0.1.0");
+ pkg("fizz", "1.0.0");
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+
+ [workspace]
+ members = ["baz"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "baz/Cargo.toml",
+ r#"
+ [package]
+ name = "baz"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ fizz = "1"
+
+ [patch.crates-io]
+ fizz = { version = "=1.0.0" }
+ "#,
+ )
+ .file("baz/src/lib.rs", "")
+ .build();
+
+ let stderr = "\
+[WARNING] patch for the non root package will be ignored, specify patch at the workspace root:
+package: [..]/foo/baz/Cargo.toml
+workspace: [..]/foo/Cargo.toml
+";
+ p.cargo("check").with_stderr_contains(&stderr).run();
+
+ // A crate installation must not emit any message from a workspace under
+ // current working directory.
+ // See https://github.com/rust-lang/cargo/issues/8619
+ p.cargo("install foo")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] foo v0.1.0 (registry [..])
+[INSTALLING] foo v0.1.0
+[COMPILING] foo v0.1.0
+[FINISHED] release [optimized] target(s) in [..]
+[INSTALLING] [..]foo[EXE]
+[INSTALLED] package `foo v0.1.0` (executable `foo[EXE]`)
+[WARNING] be sure to add `[..]` to your PATH to be able to run the installed binaries
+",
+ )
+ .run();
+ assert_has_installed_exe(cargo_home(), "foo");
+}
+
+#[cargo_test]
+fn locked_install_without_published_lockfile() {
+ Package::new("foo", "0.1.0")
+ .file("src/main.rs", "//! Some docs\nfn main() {}")
+ .publish();
+
+ cargo_process("install foo --locked")
+ .with_stderr_contains("[WARNING] no Cargo.lock file published in foo v0.1.0")
+ .run();
+}
+
+#[cargo_test]
+fn install_semver_metadata() {
+ // Check trying to install a package that uses semver metadata.
+ // This uses alt registry because the bug this is exercising doesn't
+ // trigger with a replaced source.
+ registry::alt_init();
+ Package::new("foo", "1.0.0+abc")
+ .alternative(true)
+ .file("src/main.rs", "fn main() {}")
+ .publish();
+
+ cargo_process("install foo --registry alternative --version 1.0.0+abc").run();
+ cargo_process("install foo --registry alternative")
+ .with_stderr("\
+[UPDATING] `alternative` index
+[IGNORED] package `foo v1.0.0+abc (registry `alternative`)` is already installed, use --force to override
+[WARNING] be sure to add [..]
+")
+ .run();
+ // "Updating" is not displayed here due to the --version fast-path.
+ cargo_process("install foo --registry alternative --version 1.0.0+abc")
+ .with_stderr("\
+[IGNORED] package `foo v1.0.0+abc (registry `alternative`)` is already installed, use --force to override
+[WARNING] be sure to add [..]
+")
+ .run();
+ cargo_process("install foo --registry alternative --version 1.0.0 --force")
+ .with_stderr(
+ "\
+[UPDATING] `alternative` index
+[INSTALLING] foo v1.0.0+abc (registry `alternative`)
+[COMPILING] foo v1.0.0+abc (registry `alternative`)
+[FINISHED] [..]
+[REPLACING] [ROOT]/home/.cargo/bin/foo[EXE]
+[REPLACED] package [..]
+[WARNING] be sure to add [..]
+",
+ )
+ .run();
+ // Check that from a fresh cache will work without metadata, too.
+ paths::home().join(".cargo/registry").rm_rf();
+ paths::home().join(".cargo/bin").rm_rf();
+ cargo_process("install foo --registry alternative --version 1.0.0")
+ .with_stderr(
+ "\
+[UPDATING] `alternative` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] foo v1.0.0+abc (registry `alternative`)
+[INSTALLING] foo v1.0.0+abc (registry `alternative`)
+[COMPILING] foo v1.0.0+abc (registry `alternative`)
+[FINISHED] [..]
+[INSTALLING] [ROOT]/home/.cargo/bin/foo[EXE]
+[INSTALLED] package `foo v1.0.0+abc (registry `alternative`)` (executable `foo[EXE]`)
+[WARNING] be sure to add [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn no_auto_fix_note() {
+ Package::new("auto_fix", "0.0.1")
+ .file("src/lib.rs", "use std::io;")
+ .file(
+ "src/main.rs",
+ &format!("extern crate {}; use std::io; fn main() {{}}", "auto_fix"),
+ )
+ .publish();
+
+ // This should not contain a suggestion to run `cargo fix`
+ //
+ // This is checked by matching the full output as `with_stderr_does_not_contain`
+ // can be brittle
+ cargo_process("install auto_fix")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] auto_fix v0.0.1 (registry [..])
+[INSTALLING] auto_fix v0.0.1
+[COMPILING] auto_fix v0.0.1
+[FINISHED] release [optimized] target(s) in [..]
+[INSTALLING] [CWD]/home/.cargo/bin/auto_fix[EXE]
+[INSTALLED] package `auto_fix v0.0.1` (executable `auto_fix[EXE]`)
+[WARNING] be sure to add `[..]` to your PATH to be able to run the installed binaries
+",
+ )
+ .run();
+ assert_has_installed_exe(cargo_home(), "auto_fix");
+
+ cargo_process("uninstall auto_fix")
+ .with_stderr("[REMOVING] [CWD]/home/.cargo/bin/auto_fix[EXE]")
+ .run();
+ assert_has_not_installed_exe(cargo_home(), "auto_fix");
+}
+
+#[cargo_test]
+fn failed_install_retains_temp_directory() {
+ // Verifies that the temporary directory persists after a build failure.
+ Package::new("foo", "0.0.1")
+ .file("src/main.rs", "x")
+ .publish();
+ let err = cargo_process("install foo").exec_with_output().unwrap_err();
+ let err = err.downcast::<ProcessError>().unwrap();
+ let stderr = String::from_utf8(err.stderr.unwrap()).unwrap();
+ compare::match_contains(
+ "\
+[UPDATING] `dummy-registry` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] foo v0.0.1 (registry `dummy-registry`)
+[INSTALLING] foo v0.0.1
+[COMPILING] foo v0.0.1
+",
+ &stderr,
+ None,
+ )
+ .unwrap();
+ compare::match_contains(
+ "error: failed to compile `foo v0.0.1`, intermediate artifacts can be found at `[..]`",
+ &stderr,
+ None,
+ )
+ .unwrap();
+
+ // Find the path in the output.
+ let start = stderr.find("found at `").unwrap() + 10;
+ let end = stderr[start..].find('\n').unwrap() - 1;
+ let path = Path::new(&stderr[start..(end + start)]);
+ assert!(path.exists());
+ assert!(path.join("release/deps").exists());
+}
+
+#[cargo_test]
+fn sparse_install() {
+ // Checks for an issue where uninstalling something corrupted
+ // the SourceIds of sparse registries.
+ // See https://github.com/rust-lang/cargo/issues/11751
+ let _registry = registry::RegistryBuilder::new().http_index().build();
+
+ pkg("foo", "0.0.1");
+ pkg("bar", "0.0.1");
+
+ cargo_process("install foo --registry dummy-registry")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] foo v0.0.1 (registry `dummy-registry`)
+[INSTALLING] foo v0.0.1 (registry `dummy-registry`)
+[UPDATING] `dummy-registry` index
+[COMPILING] foo v0.0.1 (registry `dummy-registry`)
+[FINISHED] release [optimized] target(s) in [..]
+[INSTALLING] [ROOT]/home/.cargo/bin/foo[EXE]
+[INSTALLED] package `foo v0.0.1 (registry `dummy-registry`)` (executable `foo[EXE]`)
+[WARNING] be sure to add `[..]` to your PATH to be able to run the installed binaries
+",
+ )
+ .run();
+ assert_has_installed_exe(cargo_home(), "foo");
+ let assert_v1 = |expected| {
+ let v1 = fs::read_to_string(paths::home().join(".cargo/.crates.toml")).unwrap();
+ compare::assert_match_exact(expected, &v1);
+ };
+ assert_v1(
+ r#"[v1]
+"foo 0.0.1 (sparse+http://127.0.0.1:[..]/index/)" = ["foo[EXE]"]
+"#,
+ );
+ cargo_process("install bar").run();
+ assert_has_installed_exe(cargo_home(), "bar");
+ assert_v1(
+ r#"[v1]
+"bar 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = ["bar[EXE]"]
+"foo 0.0.1 (sparse+http://127.0.0.1:[..]/index/)" = ["foo[EXE]"]
+"#,
+ );
+
+ cargo_process("uninstall bar")
+ .with_stderr("[REMOVING] [CWD]/home/.cargo/bin/bar[EXE]")
+ .run();
+ assert_has_not_installed_exe(cargo_home(), "bar");
+ assert_v1(
+ r#"[v1]
+"foo 0.0.1 (sparse+http://127.0.0.1:[..]/index/)" = ["foo[EXE]"]
+"#,
+ );
+ cargo_process("uninstall foo")
+ .with_stderr("[REMOVING] [CWD]/home/.cargo/bin/foo[EXE]")
+ .run();
+ assert_has_not_installed_exe(cargo_home(), "foo");
+ assert_v1(
+ r#"[v1]
+"#,
+ );
+}
diff --git a/src/tools/cargo/tests/testsuite/install_upgrade.rs b/src/tools/cargo/tests/testsuite/install_upgrade.rs
new file mode 100644
index 000000000..ae641ba98
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/install_upgrade.rs
@@ -0,0 +1,862 @@
+//! Tests for `cargo install` where it upgrades a package if it is out-of-date.
+
+use cargo::core::PackageId;
+use std::collections::BTreeSet;
+use std::env;
+use std::fs;
+use std::path::PathBuf;
+use std::sync::atomic::{AtomicUsize, Ordering};
+
+use cargo_test_support::install::{cargo_home, exe};
+use cargo_test_support::paths::CargoPathExt;
+use cargo_test_support::registry::{self, Package};
+use cargo_test_support::{
+ basic_manifest, cargo_process, cross_compile, execs, git, process, project, Execs,
+};
+
+fn pkg_maybe_yanked(name: &str, vers: &str, yanked: bool) {
+ Package::new(name, vers)
+ .yanked(yanked)
+ .file(
+ "src/main.rs",
+ r#"fn main() { println!("{}", env!("CARGO_PKG_VERSION")) }"#,
+ )
+ .publish();
+}
+
+// Helper for publishing a package.
+fn pkg(name: &str, vers: &str) {
+ pkg_maybe_yanked(name, vers, false)
+}
+
+fn v1_path() -> PathBuf {
+ cargo_home().join(".crates.toml")
+}
+
+fn v2_path() -> PathBuf {
+ cargo_home().join(".crates2.json")
+}
+
+fn load_crates1() -> toml::Value {
+ toml::from_str(&fs::read_to_string(v1_path()).unwrap()).unwrap()
+}
+
+fn load_crates2() -> serde_json::Value {
+ serde_json::from_str(&fs::read_to_string(v2_path()).unwrap()).unwrap()
+}
+
+fn installed_exe(name: &str) -> PathBuf {
+ cargo_home().join("bin").join(exe(name))
+}
+
+/// Helper for executing binaries installed by cargo.
+fn installed_process(name: &str) -> Execs {
+ static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
+ thread_local!(static UNIQUE_ID: usize = NEXT_ID.fetch_add(1, Ordering::SeqCst));
+
+ // This copies the executable to a unique name so that it may be safely
+ // replaced on Windows. See Project::rename_run for details.
+ let src = installed_exe(name);
+ let dst = installed_exe(&UNIQUE_ID.with(|my_id| format!("{}-{}", name, my_id)));
+ // Note: Cannot use copy. On Linux, file descriptors may be left open to
+ // the executable as other tests in other threads are constantly spawning
+ // new processes (see https://github.com/rust-lang/cargo/pull/5557 for
+ // more).
+ fs::rename(&src, &dst)
+ .unwrap_or_else(|e| panic!("Failed to rename `{:?}` to `{:?}`: {}", src, dst, e));
+ // Leave behind a fake file so that reinstall duplicate check works.
+ fs::write(src, "").unwrap();
+ let p = process(dst);
+ execs().with_process_builder(p)
+}
+
+/// Check that the given package name/version has the following bins listed in
+/// the trackers. Also verifies that both trackers are in sync and valid.
+/// Pass in an empty `bins` list to assert that the package is *not* installed.
+fn validate_trackers(name: &str, version: &str, bins: &[&str]) {
+ let v1 = load_crates1();
+ let v1_table = v1.get("v1").unwrap().as_table().unwrap();
+ let v2 = load_crates2();
+ let v2_table = v2["installs"].as_object().unwrap();
+ assert_eq!(v1_table.len(), v2_table.len());
+ // Convert `bins` to a BTreeSet.
+ let bins: BTreeSet<String> = bins
+ .iter()
+ .map(|b| format!("{}{}", b, env::consts::EXE_SUFFIX))
+ .collect();
+ // Check every entry matches between v1 and v2.
+ for (pkg_id_str, v1_bins) in v1_table {
+ let pkg_id: PackageId = toml::Value::from(pkg_id_str.to_string())
+ .try_into()
+ .unwrap();
+ let v1_bins: BTreeSet<String> = v1_bins
+ .as_array()
+ .unwrap()
+ .iter()
+ .map(|b| b.as_str().unwrap().to_string())
+ .collect();
+ if pkg_id.name().as_str() == name && pkg_id.version().to_string() == version {
+ if bins.is_empty() {
+ panic!(
+ "Expected {} to not be installed, but found: {:?}",
+ name, v1_bins
+ );
+ } else {
+ assert_eq!(bins, v1_bins);
+ }
+ }
+ let pkg_id_value = serde_json::to_value(&pkg_id).unwrap();
+ let pkg_id_str = pkg_id_value.as_str().unwrap();
+ let v2_info = v2_table
+ .get(pkg_id_str)
+ .expect("v2 missing v1 pkg")
+ .as_object()
+ .unwrap();
+ let v2_bins = v2_info["bins"].as_array().unwrap();
+ let v2_bins: BTreeSet<String> = v2_bins
+ .iter()
+ .map(|b| b.as_str().unwrap().to_string())
+ .collect();
+ assert_eq!(v1_bins, v2_bins);
+ }
+}
+
+#[cargo_test]
+fn registry_upgrade() {
+ // Installing and upgrading from a registry.
+ pkg("foo", "1.0.0");
+ cargo_process("install foo")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] foo v1.0.0 (registry [..])
+[INSTALLING] foo v1.0.0
+[COMPILING] foo v1.0.0
+[FINISHED] release [optimized] target(s) in [..]
+[INSTALLING] [CWD]/home/.cargo/bin/foo[EXE]
+[INSTALLED] package `foo v1.0.0` (executable `foo[EXE]`)
+[WARNING] be sure to add [..]
+",
+ )
+ .run();
+ installed_process("foo").with_stdout("1.0.0").run();
+ validate_trackers("foo", "1.0.0", &["foo"]);
+
+ cargo_process("install foo")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[IGNORED] package `foo v1.0.0` is already installed[..]
+[WARNING] be sure to add [..]
+",
+ )
+ .run();
+
+ pkg("foo", "1.0.1");
+
+ cargo_process("install foo")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] foo v1.0.1 (registry [..])
+[INSTALLING] foo v1.0.1
+[COMPILING] foo v1.0.1
+[FINISHED] release [optimized] target(s) in [..]
+[REPLACING] [CWD]/home/.cargo/bin/foo[EXE]
+[REPLACED] package `foo v1.0.0` with `foo v1.0.1` (executable `foo[EXE]`)
+[WARNING] be sure to add [..]
+",
+ )
+ .run();
+
+ installed_process("foo").with_stdout("1.0.1").run();
+ validate_trackers("foo", "1.0.1", &["foo"]);
+
+ cargo_process("install foo --version=1.0.0")
+ .with_stderr_contains("[COMPILING] foo v1.0.0")
+ .run();
+ installed_process("foo").with_stdout("1.0.0").run();
+ validate_trackers("foo", "1.0.0", &["foo"]);
+
+ cargo_process("install foo --version=^1.0")
+ .with_stderr_contains("[COMPILING] foo v1.0.1")
+ .run();
+ installed_process("foo").with_stdout("1.0.1").run();
+ validate_trackers("foo", "1.0.1", &["foo"]);
+
+ cargo_process("install foo --version=^1.0")
+ .with_stderr_contains("[IGNORED] package `foo v1.0.1` is already installed[..]")
+ .run();
+}
+
+#[cargo_test]
+fn uninstall() {
+ // Basic uninstall test.
+ pkg("foo", "1.0.0");
+ cargo_process("install foo").run();
+ cargo_process("uninstall foo").run();
+ let data = load_crates2();
+ assert_eq!(data["installs"].as_object().unwrap().len(), 0);
+ let v1_table = load_crates1();
+ assert_eq!(v1_table.get("v1").unwrap().as_table().unwrap().len(), 0);
+}
+
+#[cargo_test]
+fn upgrade_force() {
+ pkg("foo", "1.0.0");
+ cargo_process("install foo").run();
+ cargo_process("install foo --force")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[INSTALLING] foo v1.0.0
+[COMPILING] foo v1.0.0
+[FINISHED] release [optimized] target(s) in [..]
+[REPLACING] [..]/.cargo/bin/foo[EXE]
+[REPLACED] package `foo v1.0.0` with `foo v1.0.0` (executable `foo[EXE]`)
+[WARNING] be sure to add `[..]/.cargo/bin` to your PATH [..]
+",
+ )
+ .run();
+ validate_trackers("foo", "1.0.0", &["foo"]);
+}
+
+#[cargo_test]
+fn ambiguous_version_no_longer_allowed() {
+ // Non-semver-requirement is not allowed for `--version`.
+ pkg("foo", "1.0.0");
+ cargo_process("install foo --version=1.0")
+ .with_stderr(
+ "\
+[ERROR] the `--version` provided, `1.0`, is not a valid semver version: cannot parse '1.0' as a semver
+
+if you want to specify semver range, add an explicit qualifier, like ^1.0
+",
+ )
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn path_is_always_dirty() {
+ // --path should always reinstall.
+ let p = project().file("src/main.rs", "fn main() {}").build();
+ p.cargo("install --path .").run();
+ p.cargo("install --path .")
+ .with_stderr_contains("[REPLACING] [..]/foo[EXE]")
+ .run();
+}
+
+#[cargo_test]
+fn fails_for_conflicts_unknown() {
+ // If an untracked file is in the way, it should fail.
+ pkg("foo", "1.0.0");
+ let exe = installed_exe("foo");
+ exe.parent().unwrap().mkdir_p();
+ fs::write(exe, "").unwrap();
+ cargo_process("install foo")
+ .with_stderr_contains("[ERROR] binary `foo[EXE]` already exists in destination")
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn fails_for_conflicts_known() {
+ // If the same binary exists in another package, it should fail.
+ pkg("foo", "1.0.0");
+ Package::new("bar", "1.0.0")
+ .file("src/bin/foo.rs", "fn main() {}")
+ .publish();
+ cargo_process("install foo").run();
+ cargo_process("install bar")
+ .with_stderr_contains(
+ "[ERROR] binary `foo[EXE]` already exists in destination as part of `foo v1.0.0`",
+ )
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn supports_multiple_binary_names() {
+ // Can individually install with --bin or --example
+ Package::new("foo", "1.0.0")
+ .file("src/main.rs", r#"fn main() { println!("foo"); }"#)
+ .file("src/bin/a.rs", r#"fn main() { println!("a"); }"#)
+ .file("examples/ex1.rs", r#"fn main() { println!("ex1"); }"#)
+ .publish();
+ cargo_process("install foo --bin foo").run();
+ installed_process("foo").with_stdout("foo").run();
+ assert!(!installed_exe("a").exists());
+ assert!(!installed_exe("ex1").exists());
+ validate_trackers("foo", "1.0.0", &["foo"]);
+ cargo_process("install foo --bin a").run();
+ installed_process("a").with_stdout("a").run();
+ assert!(!installed_exe("ex1").exists());
+ validate_trackers("foo", "1.0.0", &["a", "foo"]);
+ cargo_process("install foo --example ex1").run();
+ installed_process("ex1").with_stdout("ex1").run();
+ validate_trackers("foo", "1.0.0", &["a", "ex1", "foo"]);
+ cargo_process("uninstall foo --bin foo").run();
+ assert!(!installed_exe("foo").exists());
+ assert!(installed_exe("ex1").exists());
+ validate_trackers("foo", "1.0.0", &["a", "ex1"]);
+ cargo_process("uninstall foo").run();
+ assert!(!installed_exe("ex1").exists());
+ assert!(!installed_exe("a").exists());
+}
+
+#[cargo_test]
+fn v1_already_installed_fresh() {
+ // Install with v1, then try to install again with v2.
+ pkg("foo", "1.0.0");
+ cargo_process("install foo").run();
+ cargo_process("install foo")
+ .with_stderr_contains("[IGNORED] package `foo v1.0.0` is already installed[..]")
+ .run();
+}
+
+#[cargo_test]
+fn v1_already_installed_dirty() {
+ // Install with v1, then install a new version with v2.
+ pkg("foo", "1.0.0");
+ cargo_process("install foo").run();
+ pkg("foo", "1.0.1");
+ cargo_process("install foo")
+ .with_stderr_contains("[COMPILING] foo v1.0.1")
+ .with_stderr_contains("[REPLACING] [..]/foo[EXE]")
+ .run();
+ validate_trackers("foo", "1.0.1", &["foo"]);
+}
+
+#[cargo_test]
+fn change_features_rebuilds() {
+ Package::new("foo", "1.0.0")
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ if cfg!(feature = "f1") {
+ println!("f1");
+ }
+ if cfg!(feature = "f2") {
+ println!("f2");
+ }
+ }
+ "#,
+ )
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "1.0.0"
+
+ [features]
+ f1 = []
+ f2 = []
+ default = ["f1"]
+ "#,
+ )
+ .publish();
+ cargo_process("install foo").run();
+ installed_process("foo").with_stdout("f1").run();
+ cargo_process("install foo --no-default-features").run();
+ installed_process("foo").with_stdout("").run();
+ cargo_process("install foo --all-features").run();
+ installed_process("foo").with_stdout("f1\nf2").run();
+ cargo_process("install foo --no-default-features --features=f1").run();
+ installed_process("foo").with_stdout("f1").run();
+}
+
+#[cargo_test]
+fn change_profile_rebuilds() {
+ pkg("foo", "1.0.0");
+ cargo_process("install foo").run();
+ cargo_process("install foo --debug")
+ .with_stderr_contains("[COMPILING] foo v1.0.0")
+ .with_stderr_contains("[REPLACING] [..]foo[EXE]")
+ .run();
+ cargo_process("install foo --debug")
+ .with_stderr_contains("[IGNORED] package `foo v1.0.0` is already installed[..]")
+ .run();
+}
+
+#[cargo_test]
+fn change_target_rebuilds() {
+ if cross_compile::disabled() {
+ return;
+ }
+ pkg("foo", "1.0.0");
+ cargo_process("install foo").run();
+ let target = cross_compile::alternate();
+ cargo_process("install foo -v --target")
+ .arg(&target)
+ .with_stderr_contains("[COMPILING] foo v1.0.0")
+ .with_stderr_contains("[REPLACING] [..]foo[EXE]")
+ .with_stderr_contains(&format!("[..]--target {}[..]", target))
+ .run();
+}
+
+#[cargo_test]
+fn change_bin_sets_rebuilds() {
+ // Changing which bins in a multi-bin project should reinstall.
+ Package::new("foo", "1.0.0")
+ .file("src/main.rs", "fn main() { }")
+ .file("src/bin/x.rs", "fn main() { }")
+ .file("src/bin/y.rs", "fn main() { }")
+ .publish();
+ cargo_process("install foo --bin x").run();
+ assert!(installed_exe("x").exists());
+ assert!(!installed_exe("y").exists());
+ assert!(!installed_exe("foo").exists());
+ validate_trackers("foo", "1.0.0", &["x"]);
+ cargo_process("install foo --bin y")
+ .with_stderr_contains("[INSTALLED] package `foo v1.0.0` (executable `y[EXE]`)")
+ .run();
+ assert!(installed_exe("x").exists());
+ assert!(installed_exe("y").exists());
+ assert!(!installed_exe("foo").exists());
+ validate_trackers("foo", "1.0.0", &["x", "y"]);
+ cargo_process("install foo")
+ .with_stderr_contains("[INSTALLED] package `foo v1.0.0` (executable `foo[EXE]`)")
+ .with_stderr_contains(
+ "[REPLACED] package `foo v1.0.0` with `foo v1.0.0` (executables `x[EXE]`, `y[EXE]`)",
+ )
+ .run();
+ assert!(installed_exe("x").exists());
+ assert!(installed_exe("y").exists());
+ assert!(installed_exe("foo").exists());
+ validate_trackers("foo", "1.0.0", &["foo", "x", "y"]);
+}
+
+#[cargo_test]
+fn forwards_compatible() {
+ // Unknown fields should be preserved.
+ pkg("foo", "1.0.0");
+ pkg("bar", "1.0.0");
+ cargo_process("install foo").run();
+ let key = "foo 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)";
+ let v2 = cargo_home().join(".crates2.json");
+ let mut data = load_crates2();
+ data["newfield"] = serde_json::Value::Bool(true);
+ data["installs"][key]["moreinfo"] = serde_json::Value::String("shazam".to_string());
+ fs::write(&v2, serde_json::to_string(&data).unwrap()).unwrap();
+ cargo_process("install bar").run();
+ let data: serde_json::Value = serde_json::from_str(&fs::read_to_string(&v2).unwrap()).unwrap();
+ assert_eq!(data["newfield"].as_bool().unwrap(), true);
+ assert_eq!(
+ data["installs"][key]["moreinfo"].as_str().unwrap(),
+ "shazam"
+ );
+}
+
+#[cargo_test]
+fn v2_syncs() {
+ // V2 inherits the installs from V1.
+ pkg("one", "1.0.0");
+ pkg("two", "1.0.0");
+ pkg("three", "1.0.0");
+ let p = project()
+ .file("src/bin/x.rs", "fn main() {}")
+ .file("src/bin/y.rs", "fn main() {}")
+ .build();
+ cargo_process("install one").run();
+ validate_trackers("one", "1.0.0", &["one"]);
+ p.cargo("install --path .").run();
+ validate_trackers("foo", "1.0.0", &["x", "y"]);
+ // v1 add/remove
+ cargo_process("install two").run();
+ cargo_process("uninstall one").run();
+ // This should pick up that `two` was added, `one` was removed.
+ cargo_process("install three").run();
+ validate_trackers("three", "1.0.0", &["three"]);
+ cargo_process("install --list")
+ .with_stdout(
+ "\
+foo v0.0.1 ([..]/foo):
+ x[EXE]
+ y[EXE]
+three v1.0.0:
+ three[EXE]
+two v1.0.0:
+ two[EXE]
+",
+ )
+ .run();
+ cargo_process("install one").run();
+ installed_process("one").with_stdout("1.0.0").run();
+ validate_trackers("one", "1.0.0", &["one"]);
+ cargo_process("install two")
+ .with_stderr_contains("[IGNORED] package `two v1.0.0` is already installed[..]")
+ .run();
+ // v1 remove
+ p.cargo("uninstall --bin x").run();
+ pkg("x", "1.0.0");
+ pkg("y", "1.0.0");
+ // This should succeed because `x` was removed in V1.
+ cargo_process("install x").run();
+ validate_trackers("x", "1.0.0", &["x"]);
+ // This should fail because `y` still exists in a different package.
+ cargo_process("install y")
+ .with_stderr_contains(
+ "[ERROR] binary `y[EXE]` already exists in destination \
+ as part of `foo v0.0.1 ([..])`",
+ )
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn upgrade_git() {
+ let git_project = git::new("foo", |project| project.file("src/main.rs", "fn main() {}"));
+ // install
+ cargo_process("install --git")
+ .arg(git_project.url().to_string())
+ .run();
+ // Check install stays fresh.
+ cargo_process("install --git")
+ .arg(git_project.url().to_string())
+ .with_stderr_contains(
+ "[IGNORED] package `foo v0.0.1 (file://[..]/foo#[..])` is \
+ already installed,[..]",
+ )
+ .run();
+ // Modify a file.
+ let repo = git2::Repository::open(git_project.root()).unwrap();
+ git_project.change_file("src/main.rs", r#"fn main() {println!("onomatopoeia");}"#);
+ git::add(&repo);
+ git::commit(&repo);
+ // Install should reinstall.
+ cargo_process("install --git")
+ .arg(git_project.url().to_string())
+ .with_stderr_contains("[COMPILING] foo v0.0.1 ([..])")
+ .with_stderr_contains("[REPLACING] [..]/foo[EXE]")
+ .run();
+ installed_process("foo").with_stdout("onomatopoeia").run();
+ // Check install stays fresh.
+ cargo_process("install --git")
+ .arg(git_project.url().to_string())
+ .with_stderr_contains(
+ "[IGNORED] package `foo v0.0.1 (file://[..]/foo#[..])` is \
+ already installed,[..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn switch_sources() {
+ // Installing what appears to be the same thing, but from different
+ // sources should reinstall.
+ registry::alt_init();
+ pkg("foo", "1.0.0");
+ Package::new("foo", "1.0.0")
+ .file("src/main.rs", r#"fn main() { println!("alt"); }"#)
+ .alternative(true)
+ .publish();
+ let p = project()
+ .at("foo-local") // so it doesn't use the same directory as the git project
+ .file("Cargo.toml", &basic_manifest("foo", "1.0.0"))
+ .file("src/main.rs", r#"fn main() { println!("local"); }"#)
+ .build();
+ let git_project = git::new("foo", |project| {
+ project.file("src/main.rs", r#"fn main() { println!("git"); }"#)
+ });
+
+ cargo_process("install foo").run();
+ installed_process("foo").with_stdout("1.0.0").run();
+ cargo_process("install foo --registry alternative").run();
+ installed_process("foo").with_stdout("alt").run();
+ p.cargo("install --path .").run();
+ installed_process("foo").with_stdout("local").run();
+ cargo_process("install --git")
+ .arg(git_project.url().to_string())
+ .run();
+ installed_process("foo").with_stdout("git").run();
+}
+
+#[cargo_test]
+fn multiple_report() {
+ // Testing the full output that indicates installed/ignored/replaced/summary.
+ pkg("one", "1.0.0");
+ pkg("two", "1.0.0");
+ fn three(vers: &str) {
+ Package::new("three", vers)
+ .file("src/main.rs", "fn main() { }")
+ .file("src/bin/x.rs", "fn main() { }")
+ .file("src/bin/y.rs", "fn main() { }")
+ .publish();
+ }
+ three("1.0.0");
+ cargo_process("install one two three")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] one v1.0.0 (registry `[..]`)
+[DOWNLOADING] crates ...
+[DOWNLOADED] two v1.0.0 (registry `[..]`)
+[DOWNLOADING] crates ...
+[DOWNLOADED] three v1.0.0 (registry `[..]`)
+[INSTALLING] one v1.0.0
+[COMPILING] one v1.0.0
+[FINISHED] release [optimized] target(s) in [..]
+[INSTALLING] [..]/.cargo/bin/one[EXE]
+[INSTALLED] package `one v1.0.0` (executable `one[EXE]`)
+[INSTALLING] two v1.0.0
+[COMPILING] two v1.0.0
+[FINISHED] release [optimized] target(s) in [..]
+[INSTALLING] [..]/.cargo/bin/two[EXE]
+[INSTALLED] package `two v1.0.0` (executable `two[EXE]`)
+[INSTALLING] three v1.0.0
+[COMPILING] three v1.0.0
+[FINISHED] release [optimized] target(s) in [..]
+[INSTALLING] [..]/.cargo/bin/three[EXE]
+[INSTALLING] [..]/.cargo/bin/x[EXE]
+[INSTALLING] [..]/.cargo/bin/y[EXE]
+[INSTALLED] package `three v1.0.0` (executables `three[EXE]`, `x[EXE]`, `y[EXE]`)
+[SUMMARY] Successfully installed one, two, three!
+[WARNING] be sure to add `[..]/.cargo/bin` to your PATH [..]
+",
+ )
+ .run();
+ pkg("foo", "1.0.1");
+ pkg("bar", "1.0.1");
+ three("1.0.1");
+ cargo_process("install one two three")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[IGNORED] package `one v1.0.0` is already installed, use --force to override
+[IGNORED] package `two v1.0.0` is already installed, use --force to override
+[DOWNLOADING] crates ...
+[DOWNLOADED] three v1.0.1 (registry `[..]`)
+[INSTALLING] three v1.0.1
+[COMPILING] three v1.0.1
+[FINISHED] release [optimized] target(s) in [..]
+[REPLACING] [..]/.cargo/bin/three[EXE]
+[REPLACING] [..]/.cargo/bin/x[EXE]
+[REPLACING] [..]/.cargo/bin/y[EXE]
+[REPLACED] package `three v1.0.0` with `three v1.0.1` (executables `three[EXE]`, `x[EXE]`, `y[EXE]`)
+[SUMMARY] Successfully installed one, two, three!
+[WARNING] be sure to add `[..]/.cargo/bin` to your PATH [..]
+",
+ )
+ .run();
+ cargo_process("uninstall three")
+ .with_stderr(
+ "\
+[REMOVING] [..]/.cargo/bin/three[EXE]
+[REMOVING] [..]/.cargo/bin/x[EXE]
+[REMOVING] [..]/.cargo/bin/y[EXE]
+",
+ )
+ .run();
+ cargo_process("install three --bin x")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[INSTALLING] three v1.0.1
+[COMPILING] three v1.0.1
+[FINISHED] release [optimized] target(s) in [..]
+[INSTALLING] [..]/.cargo/bin/x[EXE]
+[INSTALLED] package `three v1.0.1` (executable `x[EXE]`)
+[WARNING] be sure to add `[..]/.cargo/bin` to your PATH [..]
+",
+ )
+ .run();
+ cargo_process("install three")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[INSTALLING] three v1.0.1
+[COMPILING] three v1.0.1
+[FINISHED] release [optimized] target(s) in [..]
+[INSTALLING] [..]/.cargo/bin/three[EXE]
+[INSTALLING] [..]/.cargo/bin/y[EXE]
+[REPLACING] [..]/.cargo/bin/x[EXE]
+[INSTALLED] package `three v1.0.1` (executables `three[EXE]`, `y[EXE]`)
+[REPLACED] package `three v1.0.1` with `three v1.0.1` (executable `x[EXE]`)
+[WARNING] be sure to add `[..]/.cargo/bin` to your PATH [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn no_track() {
+ pkg("foo", "1.0.0");
+ cargo_process("install --no-track foo").run();
+ assert!(!v1_path().exists());
+ assert!(!v2_path().exists());
+ cargo_process("install --no-track foo")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[ERROR] binary `foo[EXE]` already exists in destination `[..]/.cargo/bin/foo[EXE]`
+Add --force to overwrite
+",
+ )
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn deletes_orphaned() {
+ // When an executable is removed from a project, upgrading should remove it.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("src/bin/other.rs", "fn main() {}")
+ .file("examples/ex1.rs", "fn main() {}")
+ .build();
+ p.cargo("install --path . --bins --examples").run();
+ assert!(installed_exe("other").exists());
+
+ // Remove a binary, add a new one, and bump the version.
+ fs::remove_file(p.root().join("src/bin/other.rs")).unwrap();
+ p.change_file("examples/ex2.rs", "fn main() {}");
+ p.change_file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.2.0"
+ "#,
+ );
+ p.cargo("install --path . --bins --examples")
+ .with_stderr(
+ "\
+[INSTALLING] foo v0.2.0 [..]
+[COMPILING] foo v0.2.0 [..]
+[FINISHED] release [..]
+[INSTALLING] [..]/.cargo/bin/ex2[EXE]
+[REPLACING] [..]/.cargo/bin/ex1[EXE]
+[REPLACING] [..]/.cargo/bin/foo[EXE]
+[REMOVING] executable `[..]/.cargo/bin/other[EXE]` from previous version foo v0.1.0 [..]
+[INSTALLED] package `foo v0.2.0 [..]` (executable `ex2[EXE]`)
+[REPLACED] package `foo v0.1.0 [..]` with `foo v0.2.0 [..]` (executables `ex1[EXE]`, `foo[EXE]`)
+[WARNING] be sure to add [..]
+",
+ )
+ .run();
+ assert!(!installed_exe("other").exists());
+ validate_trackers("foo", "0.2.0", &["foo", "ex1", "ex2"]);
+ // 0.1.0 should not have any entries.
+ validate_trackers("foo", "0.1.0", &[]);
+}
+
+#[cargo_test]
+fn already_installed_exact_does_not_update() {
+ pkg("foo", "1.0.0");
+ cargo_process("install foo --version=1.0.0").run();
+ cargo_process("install foo --version=1.0.0")
+ .with_stderr(
+ "\
+[IGNORED] package `foo v1.0.0` is already installed[..]
+[WARNING] be sure to add [..]
+",
+ )
+ .run();
+
+ cargo_process("install foo --version=>=1.0.0")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[IGNORED] package `foo v1.0.0` is already installed[..]
+[WARNING] be sure to add [..]
+",
+ )
+ .run();
+ pkg("foo", "1.0.1");
+ cargo_process("install foo --version=>=1.0.0")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] foo v1.0.1 (registry [..])
+[INSTALLING] foo v1.0.1
+[COMPILING] foo v1.0.1
+[FINISHED] release [optimized] target(s) in [..]
+[REPLACING] [CWD]/home/.cargo/bin/foo[EXE]
+[REPLACED] package `foo v1.0.0` with `foo v1.0.1` (executable `foo[EXE]`)
+[WARNING] be sure to add [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn already_installed_updates_yank_status_on_upgrade() {
+ pkg("foo", "1.0.0");
+ pkg_maybe_yanked("foo", "1.0.1", true);
+ cargo_process("install foo --version=1.0.0").run();
+
+ cargo_process("install foo --version=1.0.1")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+[ERROR] cannot install package `foo`, it has been yanked from registry `crates-io`
+",
+ )
+ .run();
+
+ pkg_maybe_yanked("foo", "1.0.1", false);
+
+ pkg("foo", "1.0.1");
+ cargo_process("install foo --version=1.0.1")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] foo v1.0.1 (registry [..])
+[INSTALLING] foo v1.0.1
+[COMPILING] foo v1.0.1
+[FINISHED] release [optimized] target(s) in [..]
+[REPLACING] [CWD]/home/.cargo/bin/foo[EXE]
+[REPLACED] package `foo v1.0.0` with `foo v1.0.1` (executable `foo[EXE]`)
+[WARNING] be sure to add [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn partially_already_installed_does_one_update() {
+ pkg("foo", "1.0.0");
+ cargo_process("install foo --version=1.0.0").run();
+ pkg("bar", "1.0.0");
+ pkg("baz", "1.0.0");
+ cargo_process("install foo bar baz --version=1.0.0")
+ .with_stderr(
+ "\
+[IGNORED] package `foo v1.0.0` is already installed[..]
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v1.0.0 (registry [..])
+[DOWNLOADING] crates ...
+[DOWNLOADED] baz v1.0.0 (registry [..])
+[INSTALLING] bar v1.0.0
+[COMPILING] bar v1.0.0
+[FINISHED] release [optimized] target(s) in [..]
+[INSTALLING] [CWD]/home/.cargo/bin/bar[EXE]
+[INSTALLED] package `bar v1.0.0` (executable `bar[EXE]`)
+[INSTALLING] baz v1.0.0
+[COMPILING] baz v1.0.0
+[FINISHED] release [optimized] target(s) in [..]
+[INSTALLING] [CWD]/home/.cargo/bin/baz[EXE]
+[INSTALLED] package `baz v1.0.0` (executable `baz[EXE]`)
+[SUMMARY] Successfully installed foo, bar, baz!
+[WARNING] be sure to add [..]
+",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/jobserver.rs b/src/tools/cargo/tests/testsuite/jobserver.rs
new file mode 100644
index 000000000..9ccff141e
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/jobserver.rs
@@ -0,0 +1,250 @@
+//! Tests for the jobserver protocol.
+
+use cargo_util::is_ci;
+use std::net::TcpListener;
+use std::process::Command;
+use std::thread;
+
+use cargo_test_support::install::{assert_has_installed_exe, cargo_home};
+use cargo_test_support::{cargo_exe, project};
+
+const EXE_CONTENT: &str = r#"
+use std::env;
+
+fn main() {
+ let var = env::var("CARGO_MAKEFLAGS").unwrap();
+ let arg = var.split(' ')
+ .find(|p| p.starts_with("--jobserver"))
+ .unwrap();
+ let val = &arg[arg.find('=').unwrap() + 1..];
+ validate(val);
+}
+
+#[cfg(unix)]
+fn validate(s: &str) {
+ use std::fs::{self, File};
+ use std::io::*;
+ use std::os::unix::prelude::*;
+
+ if let Some((r, w)) = s.split_once(',') {
+ // `--jobserver-auth=R,W`
+ unsafe {
+ let mut read = File::from_raw_fd(r.parse().unwrap());
+ let mut write = File::from_raw_fd(w.parse().unwrap());
+
+ let mut buf = [0];
+ assert_eq!(read.read(&mut buf).unwrap(), 1);
+ assert_eq!(write.write(&buf).unwrap(), 1);
+ }
+ } else {
+ // `--jobserver-auth=fifo:PATH` is the default since GNU Make 4.4
+ let (_, path) = s.split_once(':').expect("fifo:PATH");
+ assert!(fs::metadata(path).unwrap().file_type().is_fifo());
+ }
+}
+
+#[cfg(windows)]
+fn validate(_: &str) {
+ // a little too complicated for a test...
+}
+"#;
+
+#[cargo_test]
+fn jobserver_exists() {
+ let p = project()
+ .file("build.rs", EXE_CONTENT)
+ .file("src/lib.rs", "")
+ .build();
+
+ // Explicitly use `-j2` to ensure that there's eventually going to be a
+ // token to read from `validate` above, since running the build script
+ // itself consumes a token.
+ p.cargo("check -j2").run();
+}
+
+#[cargo_test]
+fn external_subcommand_inherits_jobserver() {
+ let make = if cfg!(windows) {
+ "mingw32-make"
+ } else {
+ "make"
+ };
+ if Command::new(make).arg("--version").output().is_err() {
+ return;
+ }
+
+ let name = "cargo-jobserver-check";
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "{name}"
+ version = "0.0.1"
+ "#
+ ),
+ )
+ .file("src/main.rs", EXE_CONTENT)
+ .file(
+ "Makefile",
+ "\
+all:
+\t+$(CARGO) jobserver-check
+",
+ )
+ .build();
+
+ p.cargo("install --path .").run();
+ assert_has_installed_exe(cargo_home(), name);
+
+ p.process(make).env("CARGO", cargo_exe()).arg("-j2").run();
+}
+
+#[cargo_test]
+fn makes_jobserver_used() {
+ let make = if cfg!(windows) {
+ "mingw32-make"
+ } else {
+ "make"
+ };
+ if !is_ci() && Command::new(make).arg("--version").output().is_err() {
+ return;
+ }
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ d1 = { path = "d1" }
+ d2 = { path = "d2" }
+ d3 = { path = "d3" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "d1/Cargo.toml",
+ r#"
+ [package]
+ name = "d1"
+ version = "0.0.1"
+ authors = []
+ build = "../dbuild.rs"
+ "#,
+ )
+ .file("d1/src/lib.rs", "")
+ .file(
+ "d2/Cargo.toml",
+ r#"
+ [package]
+ name = "d2"
+ version = "0.0.1"
+ authors = []
+ build = "../dbuild.rs"
+ "#,
+ )
+ .file("d2/src/lib.rs", "")
+ .file(
+ "d3/Cargo.toml",
+ r#"
+ [package]
+ name = "d3"
+ version = "0.0.1"
+ authors = []
+ build = "../dbuild.rs"
+ "#,
+ )
+ .file("d3/src/lib.rs", "")
+ .file(
+ "dbuild.rs",
+ r#"
+ use std::net::TcpStream;
+ use std::env;
+ use std::io::Read;
+
+ fn main() {
+ let addr = env::var("ADDR").unwrap();
+ let mut stream = TcpStream::connect(addr).unwrap();
+ let mut v = Vec::new();
+ stream.read_to_end(&mut v).unwrap();
+ }
+ "#,
+ )
+ .file(
+ "Makefile",
+ "\
+all:
+\t+$(CARGO) build
+",
+ )
+ .build();
+
+ let l = TcpListener::bind("127.0.0.1:0").unwrap();
+ let addr = l.local_addr().unwrap();
+
+ let child = thread::spawn(move || {
+ let a1 = l.accept().unwrap();
+ let a2 = l.accept().unwrap();
+ l.set_nonblocking(true).unwrap();
+
+ for _ in 0..1000 {
+ assert!(l.accept().is_err());
+ thread::yield_now();
+ }
+
+ drop(a1);
+ l.set_nonblocking(false).unwrap();
+ let a3 = l.accept().unwrap();
+
+ drop((a2, a3));
+ });
+
+ p.process(make)
+ .env("CARGO", cargo_exe())
+ .env("ADDR", addr.to_string())
+ .arg("-j2")
+ .run();
+ child.join().unwrap();
+}
+
+#[cargo_test]
+fn jobserver_and_j() {
+ let make = if cfg!(windows) {
+ "mingw32-make"
+ } else {
+ "make"
+ };
+ if !is_ci() && Command::new(make).arg("--version").output().is_err() {
+ return;
+ }
+
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(
+ "Makefile",
+ "\
+all:
+\t+$(CARGO) build -j2
+",
+ )
+ .build();
+
+ p.process(make)
+ .env("CARGO", cargo_exe())
+ .arg("-j2")
+ .with_stderr(
+ "\
+warning: a `-j` argument was passed to Cargo but Cargo is also configured \
+with an external jobserver in its environment, ignoring the `-j` parameter
+[COMPILING] [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/list_availables.rs b/src/tools/cargo/tests/testsuite/list_availables.rs
new file mode 100644
index 000000000..6bbbeb160
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/list_availables.rs
@@ -0,0 +1,232 @@
+//! Tests for packages/target filter flags giving suggestions on which
+//! packages/targets are available.
+
+use cargo_test_support::project;
+
+const EXAMPLE: u8 = 1 << 0;
+const BIN: u8 = 1 << 1;
+const TEST: u8 = 1 << 2;
+const BENCH: u8 = 1 << 3;
+const PACKAGE: u8 = 1 << 4;
+
+fn list_availables_test(command: &str, targets: u8) {
+ let full_project = project()
+ .file("examples/a.rs", "fn main() { }")
+ .file("examples/b.rs", "fn main() { }")
+ .file("benches/bench1.rs", "")
+ .file("benches/bench2.rs", "")
+ .file("tests/test1.rs", "")
+ .file("tests/test2.rs", "")
+ .file("src/main.rs", "fn main() { }")
+ .file("Cargo.lock", "") // for `cargo pkgid`
+ .build();
+
+ if targets & EXAMPLE != 0 {
+ full_project
+ .cargo(&format!("{} --example", command))
+ .with_stderr(
+ "\
+error: \"--example\" takes one argument.
+Available examples:
+ a
+ b
+
+",
+ )
+ .with_status(101)
+ .run();
+ }
+
+ if targets & BIN != 0 {
+ full_project
+ .cargo(&format!("{} --bin", command))
+ .with_stderr(
+ "\
+error: \"--bin\" takes one argument.
+Available binaries:
+ foo
+
+",
+ )
+ .with_status(101)
+ .run();
+ }
+
+ if targets & BENCH != 0 {
+ full_project
+ .cargo(&format!("{} --bench", command))
+ .with_stderr(
+ "\
+error: \"--bench\" takes one argument.
+Available benches:
+ bench1
+ bench2
+
+",
+ )
+ .with_status(101)
+ .run();
+ }
+
+ if targets & TEST != 0 {
+ full_project
+ .cargo(&format!("{} --test", command))
+ .with_stderr(
+ "\
+error: \"--test\" takes one argument.
+Available tests:
+ test1
+ test2
+
+",
+ )
+ .with_status(101)
+ .run();
+ }
+
+ if targets & PACKAGE != 0 {
+ full_project
+ .cargo(&format!("{} -p", command))
+ .with_stderr(
+ "\
+[ERROR] \"--package <SPEC>\" requires a SPEC format value, \
+which can be any package ID specifier in the dependency graph.
+Run `cargo help pkgid` for more information about SPEC format.
+
+Possible packages/workspace members:
+ foo
+
+",
+ )
+ .with_status(101)
+ .run();
+ }
+
+ let empty_project = project().file("src/lib.rs", "").build();
+
+ if targets & EXAMPLE != 0 {
+ empty_project
+ .cargo(&format!("{} --example", command))
+ .with_stderr(
+ "\
+error: \"--example\" takes one argument.
+No examples available.
+
+",
+ )
+ .with_status(101)
+ .run();
+ }
+
+ if targets & BIN != 0 {
+ empty_project
+ .cargo(&format!("{} --bin", command))
+ .with_stderr(
+ "\
+error: \"--bin\" takes one argument.
+No binaries available.
+
+",
+ )
+ .with_status(101)
+ .run();
+ }
+
+ if targets & BENCH != 0 {
+ empty_project
+ .cargo(&format!("{} --bench", command))
+ .with_stderr(
+ "\
+error: \"--bench\" takes one argument.
+No benches available.
+
+",
+ )
+ .with_status(101)
+ .run();
+ }
+
+ if targets & TEST != 0 {
+ empty_project
+ .cargo(&format!("{} --test", command))
+ .with_stderr(
+ "\
+error: \"--test\" takes one argument.
+No tests available.
+
+",
+ )
+ .with_status(101)
+ .run();
+ }
+}
+
+#[cargo_test]
+fn build_list_availables() {
+ list_availables_test("build", EXAMPLE | BIN | TEST | BENCH | PACKAGE);
+}
+
+#[cargo_test]
+fn check_list_availables() {
+ list_availables_test("check", EXAMPLE | BIN | TEST | BENCH | PACKAGE);
+}
+
+#[cargo_test]
+fn doc_list_availables() {
+ list_availables_test("doc", BIN | PACKAGE);
+}
+
+#[cargo_test]
+fn fix_list_availables() {
+ list_availables_test("fix", EXAMPLE | BIN | TEST | BENCH | PACKAGE);
+}
+
+#[cargo_test]
+fn run_list_availables() {
+ list_availables_test("run", EXAMPLE | BIN | PACKAGE);
+}
+
+#[cargo_test]
+fn test_list_availables() {
+ list_availables_test("test", EXAMPLE | BIN | TEST | BENCH | PACKAGE);
+}
+
+#[cargo_test]
+fn bench_list_availables() {
+ list_availables_test("bench", EXAMPLE | BIN | TEST | BENCH | PACKAGE);
+}
+
+#[cargo_test]
+fn install_list_availables() {
+ list_availables_test("install", EXAMPLE | BIN);
+}
+
+#[cargo_test]
+fn rustdoc_list_availables() {
+ list_availables_test("rustdoc", EXAMPLE | BIN | TEST | BENCH | PACKAGE);
+}
+
+#[cargo_test]
+fn rustc_list_availables() {
+ list_availables_test("rustc", EXAMPLE | BIN | TEST | BENCH | PACKAGE);
+}
+
+#[cargo_test]
+fn pkgid_list_availables() {
+ list_availables_test("pkgid", PACKAGE);
+}
+
+#[cargo_test]
+fn tree_list_availables() {
+ list_availables_test("tree", PACKAGE);
+}
+
+#[cargo_test]
+fn clean_list_availables() {
+ list_availables_test("clean", PACKAGE);
+}
+
+#[cargo_test]
+fn update_list_availables() {
+ list_availables_test("update", PACKAGE);
+}
diff --git a/src/tools/cargo/tests/testsuite/local_registry.rs b/src/tools/cargo/tests/testsuite/local_registry.rs
new file mode 100644
index 000000000..374ea9370
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/local_registry.rs
@@ -0,0 +1,528 @@
+//! Tests for local-registry sources.
+
+use cargo_test_support::paths::{self, CargoPathExt};
+use cargo_test_support::registry::{registry_path, Package};
+use cargo_test_support::{basic_manifest, project, t};
+use std::fs;
+
+fn setup() {
+ let root = paths::root();
+ t!(fs::create_dir(&root.join(".cargo")));
+ t!(fs::write(
+ root.join(".cargo/config"),
+ r#"
+ [source.crates-io]
+ registry = 'https://wut'
+ replace-with = 'my-awesome-local-registry'
+
+ [source.my-awesome-local-registry]
+ local-registry = 'registry'
+ "#
+ ));
+}
+
+#[cargo_test]
+fn simple() {
+ setup();
+ Package::new("bar", "0.0.1")
+ .local(true)
+ .file("src/lib.rs", "pub fn bar() {}")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.0.1"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "extern crate bar; pub fn foo() { bar::bar(); }",
+ )
+ .build();
+
+ p.cargo("build")
+ .with_stderr(
+ "\
+[UNPACKING] bar v0.0.1 ([..])
+[COMPILING] bar v0.0.1
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] [..]
+",
+ )
+ .run();
+ p.cargo("build").with_stderr("[FINISHED] [..]").run();
+ p.cargo("test").run();
+}
+
+#[cargo_test]
+fn not_found() {
+ setup();
+ // Publish a package so that the directory hierarchy is created.
+ // Note, however, that we declare a dependency on baZ.
+ Package::new("bar", "0.0.1").local(true).publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ baz = "0.0.1"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "extern crate baz; pub fn foo() { baz::bar(); }",
+ )
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] no matching package named `baz` found
+location searched: registry `crates-io`
+required by package `foo v0.0.1 ([..]/foo)`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn depend_on_yanked() {
+ setup();
+ Package::new("bar", "0.0.1").local(true).publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.0.1"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ // Run cargo to create lock file.
+ p.cargo("check").run();
+
+ registry_path().join("index").join("3").rm_rf();
+ Package::new("bar", "0.0.1")
+ .local(true)
+ .yanked(true)
+ .publish();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn multiple_versions() {
+ setup();
+ Package::new("bar", "0.0.1").local(true).publish();
+ Package::new("bar", "0.1.0")
+ .local(true)
+ .file("src/lib.rs", "pub fn bar() {}")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "*"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "extern crate bar; pub fn foo() { bar::bar(); }",
+ )
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UNPACKING] bar v0.1.0 ([..])
+[CHECKING] bar v0.1.0
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ Package::new("bar", "0.2.0")
+ .local(true)
+ .file("src/lib.rs", "pub fn bar() {}")
+ .publish();
+
+ p.cargo("update -v")
+ .with_stderr("[UPDATING] bar v0.1.0 -> v0.2.0")
+ .run();
+}
+
+#[cargo_test]
+fn multiple_names() {
+ setup();
+ Package::new("bar", "0.0.1")
+ .local(true)
+ .file("src/lib.rs", "pub fn bar() {}")
+ .publish();
+ Package::new("baz", "0.1.0")
+ .local(true)
+ .file("src/lib.rs", "pub fn baz() {}")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "*"
+ baz = "*"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ extern crate bar;
+ extern crate baz;
+ pub fn foo() {
+ bar::bar();
+ baz::baz();
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UNPACKING] [..]
+[UNPACKING] [..]
+[CHECKING] [..]
+[CHECKING] [..]
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn interdependent() {
+ setup();
+ Package::new("bar", "0.0.1")
+ .local(true)
+ .file("src/lib.rs", "pub fn bar() {}")
+ .publish();
+ Package::new("baz", "0.1.0")
+ .local(true)
+ .dep("bar", "*")
+ .file("src/lib.rs", "extern crate bar; pub fn baz() {}")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "*"
+ baz = "*"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ extern crate bar;
+ extern crate baz;
+ pub fn foo() {
+ bar::bar();
+ baz::baz();
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UNPACKING] [..]
+[UNPACKING] [..]
+[CHECKING] bar v0.0.1
+[CHECKING] baz v0.1.0
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn path_dep_rewritten() {
+ setup();
+ Package::new("bar", "0.0.1")
+ .local(true)
+ .file("src/lib.rs", "pub fn bar() {}")
+ .publish();
+ Package::new("baz", "0.1.0")
+ .local(true)
+ .dep("bar", "*")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "baz"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ bar = { path = "bar", version = "*" }
+ "#,
+ )
+ .file("src/lib.rs", "extern crate bar; pub fn baz() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "*"
+ baz = "*"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ extern crate bar;
+ extern crate baz;
+ pub fn foo() {
+ bar::bar();
+ baz::baz();
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UNPACKING] [..]
+[UNPACKING] [..]
+[CHECKING] bar v0.0.1
+[CHECKING] baz v0.1.0
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn invalid_dir_bad() {
+ setup();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "*"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ r#"
+ [source.crates-io]
+ registry = 'https://wut'
+ replace-with = 'my-awesome-local-directory'
+
+ [source.my-awesome-local-directory]
+ local-registry = '/path/to/nowhere'
+ "#,
+ )
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to get `bar` as a dependency of package `foo v0.0.1 [..]`
+
+Caused by:
+ failed to load source for dependency `bar`
+
+Caused by:
+ Unable to update registry `crates-io`
+
+Caused by:
+ failed to update replaced source registry `crates-io`
+
+Caused by:
+ local registry path is not a directory: [..]path[..]to[..]nowhere
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn different_directory_replacing_the_registry_is_bad() {
+ setup();
+
+ // Move our test's .cargo/config to a temporary location and publish a
+ // registry package we're going to use first.
+ let config = paths::root().join(".cargo");
+ let config_tmp = paths::root().join(".cargo-old");
+ t!(fs::rename(&config, &config_tmp));
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "*"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ // Generate a lock file against the crates.io registry
+ Package::new("bar", "0.0.1").publish();
+ p.cargo("check").run();
+
+ // Switch back to our directory source, and now that we're replacing
+ // crates.io make sure that this fails because we're replacing with a
+ // different checksum
+ config.rm_rf();
+ t!(fs::rename(&config_tmp, &config));
+ Package::new("bar", "0.0.1")
+ .file("src/lib.rs", "invalid")
+ .local(true)
+ .publish();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] checksum for `bar v0.0.1` changed between lock files
+
+this could be indicative of a few possible errors:
+
+ * the lock file is corrupt
+ * a replacement source in use (e.g., a mirror) returned a different checksum
+ * the source itself may be corrupt in one way or another
+
+unable to verify that `bar v0.0.1` is the same as when the lockfile was generated
+
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn crates_io_registry_url_is_optional() {
+ let root = paths::root();
+ t!(fs::create_dir(&root.join(".cargo")));
+ t!(fs::write(
+ root.join(".cargo/config"),
+ r#"
+ [source.crates-io]
+ replace-with = 'my-awesome-local-registry'
+
+ [source.my-awesome-local-registry]
+ local-registry = 'registry'
+ "#
+ ));
+
+ Package::new("bar", "0.0.1")
+ .local(true)
+ .file("src/lib.rs", "pub fn bar() {}")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.0.1"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "extern crate bar; pub fn foo() { bar::bar(); }",
+ )
+ .build();
+
+ p.cargo("build")
+ .with_stderr(
+ "\
+[UNPACKING] bar v0.0.1 ([..])
+[COMPILING] bar v0.0.1
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] [..]
+",
+ )
+ .run();
+ p.cargo("build").with_stderr("[FINISHED] [..]").run();
+ p.cargo("test").run();
+}
diff --git a/src/tools/cargo/tests/testsuite/locate_project.rs b/src/tools/cargo/tests/testsuite/locate_project.rs
new file mode 100644
index 000000000..7e8ceb4c6
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/locate_project.rs
@@ -0,0 +1,76 @@
+//! Tests for the `cargo locate-project` command.
+
+use cargo_test_support::project;
+
+#[cargo_test]
+fn simple() {
+ let p = project().build();
+
+ p.cargo("locate-project")
+ .with_json(r#"{"root": "[ROOT]/foo/Cargo.toml"}"#)
+ .run();
+}
+
+#[cargo_test]
+fn message_format() {
+ let p = project().build();
+
+ p.cargo("locate-project --message-format plain")
+ .with_stdout("[ROOT]/foo/Cargo.toml")
+ .run();
+
+ p.cargo("locate-project --message-format json")
+ .with_json(r#"{"root": "[ROOT]/foo/Cargo.toml"}"#)
+ .run();
+
+ p.cargo("locate-project --message-format cryptic")
+ .with_stderr("error: invalid message format specifier: `cryptic`")
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn workspace() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "outer"
+ version = "0.0.0"
+
+ [workspace]
+ members = ["inner"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "inner/Cargo.toml",
+ r#"
+ [package]
+ name = "inner"
+ version = "0.0.0"
+ "#,
+ )
+ .file("inner/src/lib.rs", "")
+ .build();
+
+ let outer_manifest = r#"{"root": "[ROOT]/foo/Cargo.toml"}"#;
+ let inner_manifest = r#"{"root": "[ROOT]/foo/inner/Cargo.toml"}"#;
+
+ p.cargo("locate-project").with_json(outer_manifest).run();
+
+ p.cargo("locate-project")
+ .cwd("inner")
+ .with_json(inner_manifest)
+ .run();
+
+ p.cargo("locate-project --workspace")
+ .with_json(outer_manifest)
+ .run();
+
+ p.cargo("locate-project --workspace")
+ .cwd("inner")
+ .with_json(outer_manifest)
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/lockfile_compat.rs b/src/tools/cargo/tests/testsuite/lockfile_compat.rs
new file mode 100644
index 000000000..aad8723c3
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/lockfile_compat.rs
@@ -0,0 +1,890 @@
+//! Tests for supporting older versions of the Cargo.lock file format.
+
+use cargo_test_support::compare::assert_match_exact;
+use cargo_test_support::git;
+use cargo_test_support::registry::Package;
+use cargo_test_support::{basic_lib_manifest, basic_manifest, project};
+
+#[cargo_test]
+fn oldest_lockfile_still_works() {
+ let cargo_commands = vec!["build", "update"];
+ for cargo_command in cargo_commands {
+ oldest_lockfile_still_works_with_command(cargo_command);
+ }
+}
+
+fn oldest_lockfile_still_works_with_command(cargo_command: &str) {
+ Package::new("bar", "0.1.0").publish();
+
+ let expected_lockfile = r#"# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "bar"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "[..]"
+
+[[package]]
+name = "foo"
+version = "0.0.1"
+dependencies = [
+ "bar",
+]
+"#;
+
+ let old_lockfile = r#"
+[root]
+name = "foo"
+version = "0.0.1"
+dependencies = [
+ "bar 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "bar"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+"#;
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("Cargo.lock", old_lockfile)
+ .build();
+
+ p.cargo(cargo_command).run();
+
+ let lock = p.read_lockfile();
+ assert_match_exact(expected_lockfile, &lock);
+}
+
+#[cargo_test]
+fn frozen_flag_preserves_old_lockfile() {
+ let cksum = Package::new("bar", "0.1.0").publish();
+
+ let old_lockfile = format!(
+ r#"[root]
+name = "foo"
+version = "0.0.1"
+dependencies = [
+ "bar 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "bar"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[metadata]
+"checksum bar 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "{}"
+"#,
+ cksum,
+ );
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("Cargo.lock", &old_lockfile)
+ .build();
+
+ p.cargo("check --locked").run();
+
+ let lock = p.read_lockfile();
+ assert_match_exact(&old_lockfile, &lock);
+}
+
+#[cargo_test]
+fn totally_wild_checksums_works() {
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "Cargo.lock",
+ r#"
+[[package]]
+name = "foo"
+version = "0.0.1"
+dependencies = [
+ "bar 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "bar"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[metadata]
+"checksum baz 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "checksum"
+"checksum bar 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "checksum"
+"#,
+ );
+
+ let p = p.build();
+
+ p.cargo("check").run();
+
+ let lock = p.read_lockfile();
+ assert_match_exact(
+ r#"# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "bar"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "[..]"
+
+[[package]]
+name = "foo"
+version = "0.0.1"
+dependencies = [
+ "bar",
+]
+"#,
+ &lock,
+ );
+}
+
+#[cargo_test]
+fn wrong_checksum_is_an_error() {
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "Cargo.lock",
+ r#"
+[[package]]
+name = "foo"
+version = "0.0.1"
+dependencies = [
+ "bar 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "bar"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[metadata]
+"checksum bar 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "checksum"
+"#,
+ );
+
+ let p = p.build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+error: checksum for `bar v0.1.0` changed between lock files
+
+this could be indicative of a few possible errors:
+
+ * the lock file is corrupt
+ * a replacement source in use (e.g., a mirror) returned a different checksum
+ * the source itself may be corrupt in one way or another
+
+unable to verify that `bar v0.1.0` is the same as when the lockfile was generated
+
+",
+ )
+ .run();
+}
+
+// If the checksum is unlisted in the lock file (e.g., <none>) yet we can
+// calculate it (e.g., it's a registry dep), then we should in theory just fill
+// it in.
+#[cargo_test]
+fn unlisted_checksum_is_bad_if_we_calculate() {
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "Cargo.lock",
+ r#"
+[[package]]
+name = "foo"
+version = "0.0.1"
+dependencies = [
+ "bar 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "bar"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[metadata]
+"checksum bar 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "<none>"
+"#,
+ );
+ let p = p.build();
+
+ p.cargo("fetch")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+error: checksum for `bar v0.1.0` was not previously calculated, but a checksum \
+could now be calculated
+
+this could be indicative of a few possible situations:
+
+ * the source `[..]` did not previously support checksums,
+ but was replaced with one that does
+ * newer Cargo implementations know how to checksum this source, but this
+ older implementation does not
+ * the lock file is corrupt
+
+",
+ )
+ .run();
+}
+
+// If the checksum is listed in the lock file yet we cannot calculate it (e.g.,
+// Git dependencies as of today), then make sure we choke.
+#[cargo_test]
+fn listed_checksum_bad_if_we_cannot_compute() {
+ let git = git::new("bar", |p| {
+ p.file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("src/lib.rs", "")
+ });
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = {{ git = '{}' }}
+ "#,
+ git.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "Cargo.lock",
+ &format!(
+ r#"
+[[package]]
+name = "foo"
+version = "0.0.1"
+dependencies = [
+ "bar 0.1.0 (git+{0})"
+]
+
+[[package]]
+name = "bar"
+version = "0.1.0"
+source = "git+{0}"
+
+[metadata]
+"checksum bar 0.1.0 (git+{0})" = "checksum"
+"#,
+ git.url()
+ ),
+ );
+
+ let p = p.build();
+
+ p.cargo("fetch")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] git repository `[..]`
+error: checksum for `bar v0.1.0 ([..])` could not be calculated, but a \
+checksum is listed in the existing lock file[..]
+
+this could be indicative of a few possible situations:
+
+ * the source `[..]` supports checksums,
+ but was replaced with one that doesn't
+ * the lock file is corrupt
+
+unable to verify that `bar v0.1.0 ([..])` is the same as when the lockfile was generated
+
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn current_lockfile_format() {
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "");
+ let p = p.build();
+
+ p.cargo("check").run();
+
+ let actual = p.read_lockfile();
+
+ let expected = "\
+# This file is automatically @generated by Cargo.\n# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = \"bar\"
+version = \"0.1.0\"
+source = \"registry+https://github.com/rust-lang/crates.io-index\"
+checksum = \"[..]\"
+
+[[package]]
+name = \"foo\"
+version = \"0.0.1\"
+dependencies = [
+ \"bar\",
+]
+";
+ assert_match_exact(expected, &actual);
+}
+
+#[cargo_test]
+fn lockfile_without_root() {
+ Package::new("bar", "0.1.0").publish();
+
+ let lockfile = r#"
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "bar"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "foo"
+version = "0.0.1"
+dependencies = [
+ "bar",
+]
+"#;
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("Cargo.lock", lockfile);
+
+ let p = p.build();
+
+ p.cargo("check").run();
+
+ let lock = p.read_lockfile();
+ assert_match_exact(
+ r#"# [..]
+# [..]
+version = 3
+
+[[package]]
+name = "bar"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "[..]"
+
+[[package]]
+name = "foo"
+version = "0.0.1"
+dependencies = [
+ "bar",
+]
+"#,
+ &lock,
+ );
+}
+
+#[cargo_test]
+fn locked_correct_error() {
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "");
+ let p = p.build();
+
+ p.cargo("check --locked")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+error: the lock file [CWD]/Cargo.lock needs to be updated but --locked was passed to prevent this
+If you want to try to generate the lock file without accessing the network, \
+remove the --locked flag and use --offline instead.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn v2_format_preserved() {
+ let cksum = Package::new("bar", "0.1.0").publish();
+
+ let lockfile = format!(
+ r#"# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "bar"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "{}"
+
+[[package]]
+name = "foo"
+version = "0.0.1"
+dependencies = [
+ "bar",
+]
+"#,
+ cksum
+ );
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("Cargo.lock", &lockfile)
+ .build();
+
+ p.cargo("fetch").run();
+
+ let lock = p.read_lockfile();
+ assert_match_exact(&lockfile, &lock);
+}
+
+#[cargo_test]
+fn v2_path_and_crates_io() {
+ let cksum010 = Package::new("a", "0.1.0").publish();
+ let cksum020 = Package::new("a", "0.2.0").publish();
+
+ let lockfile = format!(
+ r#"# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "a"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "{}"
+
+[[package]]
+name = "a"
+version = "0.2.0"
+
+[[package]]
+name = "a"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "{}"
+
+[[package]]
+name = "foo"
+version = "0.0.1"
+dependencies = [
+ "a 0.1.0",
+ "a 0.2.0",
+ "a 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+"#,
+ cksum010, cksum020,
+ );
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ a = { path = 'a' }
+ b = { version = "0.1", package = 'a' }
+ c = { version = "0.2", package = 'a' }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.2.0"
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .file("Cargo.lock", &lockfile)
+ .build();
+
+ p.cargo("fetch").run();
+ p.cargo("fetch").run();
+
+ let lock = p.read_lockfile();
+ assert_match_exact(&lockfile, &lock);
+}
+
+#[cargo_test]
+fn v3_and_git() {
+ let (git_project, repo) = git::new_repo("dep1", |project| {
+ project
+ .file("Cargo.toml", &basic_lib_manifest("dep1"))
+ .file("src/lib.rs", "")
+ });
+ let head_id = repo.head().unwrap().target().unwrap();
+
+ let lockfile = format!(
+ r#"# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "dep1"
+version = "0.5.0"
+source = "git+{}?branch=master#{}"
+
+[[package]]
+name = "foo"
+version = "0.0.1"
+dependencies = [
+ "dep1",
+]
+"#,
+ git_project.url(),
+ head_id,
+ );
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ dep1 = {{ git = '{}', branch = 'master' }}
+ "#,
+ git_project.url(),
+ ),
+ )
+ .file("src/lib.rs", "")
+ .file("Cargo.lock", "version = 3")
+ .build();
+
+ p.cargo("fetch").run();
+
+ let lock = p.read_lockfile();
+ assert_match_exact(&lockfile, &lock);
+}
+
+#[cargo_test]
+fn lock_from_the_future() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("Cargo.lock", "version = 10000000")
+ .build();
+
+ p.cargo("fetch")
+ .with_stderr(
+ "\
+error: failed to parse lock file at: [..]
+
+Caused by:
+ lock file version `10000000` was found, but this version of Cargo does not \
+ understand this lock file, perhaps Cargo needs to be updated?
+",
+ )
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn preserve_old_format_if_no_update_needed() {
+ let cksum = Package::new("bar", "0.1.0").publish();
+ let lockfile = format!(
+ r#"# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "bar"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "foo"
+version = "0.0.1"
+dependencies = [
+ "bar 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[metadata]
+"checksum bar 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "{}"
+"#,
+ cksum
+ );
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("Cargo.lock", &lockfile)
+ .build();
+
+ p.cargo("check --locked").run();
+}
+
+#[cargo_test]
+fn same_name_version_different_sources() {
+ let cksum = Package::new("foo", "0.1.0").publish();
+ let (git_project, repo) = git::new_repo("dep1", |project| {
+ project
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ });
+ let head_id = repo.head().unwrap().target().unwrap();
+
+ // Lockfile was generated with Rust 1.51
+ let lockfile = format!(
+ r#"# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "foo"
+version = "0.1.0"
+dependencies = [
+ "foo 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "foo 0.1.0 (git+{url})",
+]
+
+[[package]]
+name = "foo"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "{cksum}"
+
+[[package]]
+name = "foo"
+version = "0.1.0"
+source = "git+{url}#{sha}"
+"#,
+ sha = head_id,
+ url = git_project.url(),
+ cksum = cksum
+ );
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ foo = "0.1.0"
+ foo2 = {{ git = '{}', package = 'foo' }}
+ "#,
+ git_project.url(),
+ ),
+ )
+ .file("src/lib.rs", "")
+ .file("Cargo.lock", &lockfile)
+ .build();
+
+ p.cargo("check").run();
+
+ assert_eq!(p.read_file("Cargo.lock"), lockfile);
+}
+
+#[cargo_test]
+fn bad_data_in_lockfile_error_meg() {
+ Package::new("bar", "0.0.1").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "test"
+ version = "0.0.0"
+
+ [dependencies]
+ bar = "*"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "Cargo.lock",
+ r#"# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "bar"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e1b9346248cf3391ead604c4407258d327c28e37209f6d56127598165165dda"
+
+[[package]]
+name = "test"
+version = "0.0.0"
+dependencies = [
+ "bar",
+]"#,
+ )
+ .build();
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[..]
+[ERROR] failed to select a version for the requirement `bar = \"*\"` (locked to 0.1.0)
+candidate versions found which didn't match: 0.0.1
+location searched: `dummy-registry` index (which is replacing registry `crates-io`)
+required by package `test v0.0.0 ([..])`
+perhaps a crate was updated and forgotten to be re-vendored?
+",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/login.rs b/src/tools/cargo/tests/testsuite/login.rs
new file mode 100644
index 000000000..85b299f28
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/login.rs
@@ -0,0 +1,404 @@
+//! Tests for the `cargo login` command.
+
+use cargo_test_support::cargo_process;
+use cargo_test_support::paths::{self, CargoPathExt};
+use cargo_test_support::registry::{self, RegistryBuilder};
+use cargo_test_support::t;
+use std::fs;
+use std::path::PathBuf;
+
+const TOKEN: &str = "test-token";
+const TOKEN2: &str = "test-token2";
+const ORIGINAL_TOKEN: &str = "api-token";
+
+fn credentials_toml() -> PathBuf {
+ paths::home().join(".cargo/credentials.toml")
+}
+
+fn setup_new_credentials() {
+ setup_new_credentials_at(credentials_toml());
+}
+
+fn setup_new_credentials_at(config: PathBuf) {
+ t!(fs::create_dir_all(config.parent().unwrap()));
+ t!(fs::write(
+ &config,
+ format!(r#"token = "{token}""#, token = ORIGINAL_TOKEN)
+ ));
+}
+
+/// Asserts whether or not the token is set to the given value for the given registry.
+pub fn check_token(expected_token: Option<&str>, registry: Option<&str>) {
+ let credentials = credentials_toml();
+ assert!(credentials.is_file());
+
+ let contents = fs::read_to_string(&credentials).unwrap();
+ let toml: toml::Table = contents.parse().unwrap();
+
+ let actual_token = match registry {
+ // A registry has been provided, so check that the token exists in a
+ // table for the registry.
+ Some(registry) => toml
+ .get("registries")
+ .and_then(|registries_table| registries_table.get(registry))
+ .and_then(|registry_table| match registry_table.get("token") {
+ Some(&toml::Value::String(ref token)) => Some(token.as_str().to_string()),
+ _ => None,
+ }),
+ // There is no registry provided, so check the global token instead.
+ None => toml
+ .get("registry")
+ .and_then(|registry_table| registry_table.get("token"))
+ .and_then(|v| match v {
+ toml::Value::String(ref token) => Some(token.as_str().to_string()),
+ _ => None,
+ }),
+ };
+
+ match (actual_token, expected_token) {
+ (None, None) => {}
+ (Some(actual), Some(expected)) => assert_eq!(actual, expected),
+ (None, Some(expected)) => {
+ panic!("expected `{registry:?}` to be `{expected}`, but was not set")
+ }
+ (Some(actual), None) => {
+ panic!("expected `{registry:?}` to be unset, but was set to `{actual}`")
+ }
+ }
+}
+
+#[cargo_test]
+fn registry_credentials() {
+ let _alternative = RegistryBuilder::new().alternative().build();
+ let _alternative2 = RegistryBuilder::new()
+ .alternative_named("alternative2")
+ .build();
+
+ setup_new_credentials();
+
+ let reg = "alternative";
+
+ cargo_process("login --registry").arg(reg).arg(TOKEN).run();
+
+ // Ensure that we have not updated the default token
+ check_token(Some(ORIGINAL_TOKEN), None);
+
+ // Also ensure that we get the new token for the registry
+ check_token(Some(TOKEN), Some(reg));
+
+ let reg2 = "alternative2";
+ cargo_process("login --registry")
+ .arg(reg2)
+ .arg(TOKEN2)
+ .run();
+
+ // Ensure not overwriting 1st alternate registry token with
+ // 2nd alternate registry token (see rust-lang/cargo#7701).
+ check_token(Some(ORIGINAL_TOKEN), None);
+ check_token(Some(TOKEN), Some(reg));
+ check_token(Some(TOKEN2), Some(reg2));
+}
+
+#[cargo_test]
+fn empty_login_token() {
+ let registry = RegistryBuilder::new()
+ .no_configure_registry()
+ .no_configure_token()
+ .build();
+ setup_new_credentials();
+
+ cargo_process("login")
+ .replace_crates_io(registry.index_url())
+ .with_stdout("please paste the token found on [..]/me below")
+ .with_stdin("\t\n")
+ .with_stderr(
+ "\
+[UPDATING] crates.io index
+[ERROR] please provide a non-empty token
+",
+ )
+ .with_status(101)
+ .run();
+
+ cargo_process("login")
+ .replace_crates_io(registry.index_url())
+ .arg("")
+ .with_stderr(
+ "\
+[ERROR] please provide a non-empty token
+",
+ )
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn invalid_login_token() {
+ let registry = RegistryBuilder::new()
+ .no_configure_registry()
+ .no_configure_token()
+ .build();
+ setup_new_credentials();
+
+ let check = |stdin: &str, stderr: &str, status: i32| {
+ cargo_process("login")
+ .replace_crates_io(registry.index_url())
+ .with_stdout("please paste the token found on [..]/me below")
+ .with_stdin(stdin)
+ .with_stderr(stderr)
+ .with_status(status)
+ .run();
+ };
+
+ let invalid = |stdin: &str| {
+ check(
+ stdin,
+ "[ERROR] token contains invalid characters.
+Only printable ISO-8859-1 characters are allowed as it is sent in a HTTPS header.",
+ 101,
+ )
+ };
+ let valid = |stdin: &str| check(stdin, "[LOGIN] token for `crates.io` saved", 0);
+
+ // Update config.json so that the rest of the tests don't need to care
+ // whether or not `Updating` is printed.
+ check(
+ "test",
+ "\
+[UPDATING] crates.io index
+[LOGIN] token for `crates.io` saved
+",
+ 0,
+ );
+
+ invalid("😄");
+ invalid("\u{0016}");
+ invalid("\u{0000}");
+ invalid("你好");
+ valid("foo\tbar");
+ valid("foo bar");
+ valid(
+ r##"!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~"##,
+ );
+}
+
+#[cargo_test]
+fn bad_asymmetric_token_args() {
+ // These cases are kept brief as the implementation is covered by clap, so this is only smoke testing that we have clap configured correctly.
+ cargo_process("login --key-subject=foo tok")
+ .with_stderr_contains(
+ "error: the argument '--key-subject <SUBJECT>' cannot be used with '[token]'",
+ )
+ .with_status(1)
+ .run();
+
+ cargo_process("login --generate-keypair tok")
+ .with_stderr_contains(
+ "error: the argument '--generate-keypair' cannot be used with '[token]'",
+ )
+ .with_status(1)
+ .run();
+
+ cargo_process("login --secret-key tok")
+ .with_stderr_contains("error: the argument '--secret-key' cannot be used with '[token]'")
+ .with_status(1)
+ .run();
+
+ cargo_process("login --generate-keypair --secret-key")
+ .with_stderr_contains(
+ "error: the argument '--generate-keypair' cannot be used with '--secret-key'",
+ )
+ .with_status(1)
+ .run();
+}
+
+#[cargo_test]
+fn asymmetric_requires_nightly() {
+ let registry = registry::init();
+ cargo_process("login --key-subject=foo")
+ .replace_crates_io(registry.index_url())
+ .with_status(101)
+ .with_stderr_contains("[ERROR] the `key-subject` flag is unstable, pass `-Z registry-auth` to enable it\n\
+ See https://github.com/rust-lang/cargo/issues/10519 for more information about the `key-subject` flag.")
+ .run();
+ cargo_process("login --generate-keypair")
+ .replace_crates_io(registry.index_url())
+ .with_status(101)
+ .with_stderr_contains("[ERROR] the `generate-keypair` flag is unstable, pass `-Z registry-auth` to enable it\n\
+ See https://github.com/rust-lang/cargo/issues/10519 for more information about the `generate-keypair` flag.")
+ .run();
+ cargo_process("login --secret-key")
+ .replace_crates_io(registry.index_url())
+ .with_status(101)
+ .with_stderr_contains("[ERROR] the `secret-key` flag is unstable, pass `-Z registry-auth` to enable it\n\
+ See https://github.com/rust-lang/cargo/issues/10519 for more information about the `secret-key` flag.")
+ .run();
+}
+
+#[cargo_test]
+fn login_with_no_cargo_dir() {
+ // Create a config in the root directory because `login` requires the
+ // index to be updated, and we don't want to hit crates.io.
+ let registry = registry::init();
+ fs::rename(paths::home().join(".cargo"), paths::root().join(".cargo")).unwrap();
+ paths::home().rm_rf();
+ cargo_process("login foo -v")
+ .replace_crates_io(registry.index_url())
+ .run();
+ let credentials = fs::read_to_string(credentials_toml()).unwrap();
+ assert_eq!(credentials, "[registry]\ntoken = \"foo\"\n");
+}
+
+#[cargo_test]
+fn login_with_differently_sized_token() {
+ // Verify that the configuration file gets properly truncated.
+ let registry = registry::init();
+ let credentials = credentials_toml();
+ fs::remove_file(&credentials).unwrap();
+ cargo_process("login lmaolmaolmao -v")
+ .replace_crates_io(registry.index_url())
+ .run();
+ cargo_process("login lmao -v")
+ .replace_crates_io(registry.index_url())
+ .run();
+ cargo_process("login lmaolmaolmao -v")
+ .replace_crates_io(registry.index_url())
+ .run();
+ let credentials = fs::read_to_string(&credentials).unwrap();
+ assert_eq!(credentials, "[registry]\ntoken = \"lmaolmaolmao\"\n");
+}
+
+#[cargo_test]
+fn login_with_token_on_stdin() {
+ let registry = registry::init();
+ let credentials = credentials_toml();
+ fs::remove_file(&credentials).unwrap();
+ cargo_process("login lmao -v")
+ .replace_crates_io(registry.index_url())
+ .run();
+ cargo_process("login")
+ .replace_crates_io(registry.index_url())
+ .with_stdout("please paste the token found on [..]/me below")
+ .with_stdin("some token")
+ .run();
+ let credentials = fs::read_to_string(&credentials).unwrap();
+ assert_eq!(credentials, "[registry]\ntoken = \"some token\"\n");
+}
+
+#[cargo_test]
+fn login_with_asymmetric_token_and_subject_on_stdin() {
+ let registry = registry::init();
+ let credentials = credentials_toml();
+ fs::remove_file(&credentials).unwrap();
+ cargo_process("login --key-subject=foo --secret-key -v -Z registry-auth")
+ .masquerade_as_nightly_cargo(&["registry-auth"])
+ .replace_crates_io(registry.index_url())
+ .with_stdout(
+ "\
+ please paste the API secret key below
+k3.public.AmDwjlyf8jAV3gm5Z7Kz9xAOcsKslt_Vwp5v-emjFzBHLCtcANzTaVEghTNEMj9PkQ",
+ )
+ .with_stdin("k3.secret.fNYVuMvBgOlljt9TDohnaYLblghqaHoQquVZwgR6X12cBFHZLFsaU3q7X3k1Zn36")
+ .run();
+ let credentials = fs::read_to_string(&credentials).unwrap();
+ assert!(credentials.starts_with("[registry]\n"));
+ assert!(credentials.contains("secret-key-subject = \"foo\"\n"));
+ assert!(credentials.contains("secret-key = \"k3.secret.fNYVuMvBgOlljt9TDohnaYLblghqaHoQquVZwgR6X12cBFHZLFsaU3q7X3k1Zn36\"\n"));
+}
+
+#[cargo_test]
+fn login_with_asymmetric_token_on_stdin() {
+ let registry = registry::init();
+ let credentials = credentials_toml();
+ fs::remove_file(&credentials).unwrap();
+ cargo_process("login --secret-key -v -Z registry-auth")
+ .masquerade_as_nightly_cargo(&["registry-auth"])
+ .replace_crates_io(registry.index_url())
+ .with_stdout(
+ "\
+ please paste the API secret key below
+k3.public.AmDwjlyf8jAV3gm5Z7Kz9xAOcsKslt_Vwp5v-emjFzBHLCtcANzTaVEghTNEMj9PkQ",
+ )
+ .with_stdin("k3.secret.fNYVuMvBgOlljt9TDohnaYLblghqaHoQquVZwgR6X12cBFHZLFsaU3q7X3k1Zn36")
+ .run();
+ let credentials = fs::read_to_string(&credentials).unwrap();
+ assert_eq!(credentials, "[registry]\nsecret-key = \"k3.secret.fNYVuMvBgOlljt9TDohnaYLblghqaHoQquVZwgR6X12cBFHZLFsaU3q7X3k1Zn36\"\n");
+}
+
+#[cargo_test]
+fn login_with_asymmetric_key_subject_without_key() {
+ let registry = registry::init();
+ let credentials = credentials_toml();
+ fs::remove_file(&credentials).unwrap();
+ cargo_process("login --key-subject=foo -Z registry-auth")
+ .masquerade_as_nightly_cargo(&["registry-auth"])
+ .replace_crates_io(registry.index_url())
+ .with_stderr_contains("error: need a secret_key to set a key_subject")
+ .with_status(101)
+ .run();
+
+ // ok so add a secret_key to the credentials
+ cargo_process("login --secret-key -v -Z registry-auth")
+ .masquerade_as_nightly_cargo(&["registry-auth"])
+ .replace_crates_io(registry.index_url())
+ .with_stdout(
+ "please paste the API secret key below
+k3.public.AmDwjlyf8jAV3gm5Z7Kz9xAOcsKslt_Vwp5v-emjFzBHLCtcANzTaVEghTNEMj9PkQ",
+ )
+ .with_stdin("k3.secret.fNYVuMvBgOlljt9TDohnaYLblghqaHoQquVZwgR6X12cBFHZLFsaU3q7X3k1Zn36")
+ .run();
+
+ // and then it should work
+ cargo_process("login --key-subject=foo -Z registry-auth")
+ .masquerade_as_nightly_cargo(&["registry-auth"])
+ .replace_crates_io(registry.index_url())
+ .run();
+
+ let credentials = fs::read_to_string(&credentials).unwrap();
+ assert!(credentials.starts_with("[registry]\n"));
+ assert!(credentials.contains("secret-key-subject = \"foo\"\n"));
+ assert!(credentials.contains("secret-key = \"k3.secret.fNYVuMvBgOlljt9TDohnaYLblghqaHoQquVZwgR6X12cBFHZLFsaU3q7X3k1Zn36\"\n"));
+}
+
+#[cargo_test]
+fn login_with_generate_asymmetric_token() {
+ let registry = registry::init();
+ let credentials = credentials_toml();
+ fs::remove_file(&credentials).unwrap();
+ cargo_process("login --generate-keypair -Z registry-auth")
+ .masquerade_as_nightly_cargo(&["registry-auth"])
+ .replace_crates_io(registry.index_url())
+ .with_stdout("k3.public.[..]")
+ .run();
+ let credentials = fs::read_to_string(&credentials).unwrap();
+ assert!(credentials.contains("secret-key = \"k3.secret."));
+}
+
+#[cargo_test]
+fn default_registry_configured() {
+ // When registry.default is set, login should use that one when
+ // --registry is not used.
+ let _alternative = RegistryBuilder::new().alternative().build();
+ let cargo_home = paths::home().join(".cargo");
+ cargo_util::paths::append(
+ &cargo_home.join("config"),
+ br#"
+ [registry]
+ default = "alternative"
+ "#,
+ )
+ .unwrap();
+
+ cargo_process("login")
+ .arg("a-new-token")
+ .with_stderr(
+ "\
+[UPDATING] `alternative` index
+[LOGIN] token for `alternative` saved
+",
+ )
+ .run();
+
+ check_token(None, None);
+ check_token(Some("a-new-token"), Some("alternative"));
+}
diff --git a/src/tools/cargo/tests/testsuite/logout.rs b/src/tools/cargo/tests/testsuite/logout.rs
new file mode 100644
index 000000000..7b5e10de2
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/logout.rs
@@ -0,0 +1,104 @@
+//! Tests for the `cargo logout` command.
+
+use super::login::check_token;
+use cargo_test_support::paths::{self, CargoPathExt};
+use cargo_test_support::registry::TestRegistry;
+use cargo_test_support::{cargo_process, registry};
+
+fn simple_logout_test(registry: &TestRegistry, reg: Option<&str>, flag: &str, note: &str) {
+ let msg = reg.unwrap_or("crates-io");
+ check_token(Some(registry.token()), reg);
+ let mut cargo = cargo_process(&format!("logout {}", flag));
+ if reg.is_none() {
+ cargo.replace_crates_io(registry.index_url());
+ }
+ cargo
+ .with_stderr(&format!(
+ "\
+[LOGOUT] token for `{msg}` has been removed from local storage
+[NOTE] This does not revoke the token on the registry server.\n \
+If you need to revoke the token, visit {note} and follow the instructions there.
+"
+ ))
+ .run();
+ check_token(None, reg);
+
+ let mut cargo = cargo_process(&format!("logout {}", flag));
+ if reg.is_none() {
+ cargo.replace_crates_io(registry.index_url());
+ }
+ cargo
+ .with_stderr(&format!("[LOGOUT] not currently logged in to `{msg}`"))
+ .run();
+ check_token(None, reg);
+}
+
+#[cargo_test]
+fn default_registry_unconfigured() {
+ let registry = registry::init();
+ simple_logout_test(&registry, None, "", "<https://crates.io/me>");
+}
+
+#[cargo_test]
+fn other_registry() {
+ let registry = registry::alt_init();
+ simple_logout_test(
+ &registry,
+ Some("alternative"),
+ "--registry alternative",
+ "the `alternative` website",
+ );
+ // It should not touch crates.io.
+ check_token(Some("sekrit"), None);
+}
+
+#[cargo_test]
+fn default_registry_configured() {
+ // When registry.default is set, logout should use that one when
+ // --registry is not used.
+ let cargo_home = paths::home().join(".cargo");
+ cargo_home.mkdir_p();
+ cargo_util::paths::write(
+ &cargo_home.join("config.toml"),
+ r#"
+ [registry]
+ default = "dummy-registry"
+
+ [registries.dummy-registry]
+ index = "https://127.0.0.1/index"
+ "#,
+ )
+ .unwrap();
+ cargo_util::paths::write(
+ &cargo_home.join("credentials.toml"),
+ r#"
+ [registry]
+ token = "crates-io-token"
+
+ [registries.dummy-registry]
+ token = "dummy-token"
+ "#,
+ )
+ .unwrap();
+ check_token(Some("dummy-token"), Some("dummy-registry"));
+ check_token(Some("crates-io-token"), None);
+
+ cargo_process("logout -Zunstable-options")
+ .masquerade_as_nightly_cargo(&["cargo-logout"])
+ .with_stderr(
+ "\
+[LOGOUT] token for `dummy-registry` has been removed from local storage
+[NOTE] This does not revoke the token on the registry server.
+ If you need to revoke the token, visit the `dummy-registry` website \
+ and follow the instructions there.
+",
+ )
+ .run();
+ check_token(None, Some("dummy-registry"));
+ check_token(Some("crates-io-token"), None);
+
+ cargo_process("logout -Zunstable-options")
+ .masquerade_as_nightly_cargo(&["cargo-logout"])
+ .with_stderr("[LOGOUT] not currently logged in to `dummy-registry`")
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/lto.rs b/src/tools/cargo/tests/testsuite/lto.rs
new file mode 100644
index 000000000..40b4f7ca2
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/lto.rs
@@ -0,0 +1,850 @@
+use cargo::core::compiler::Lto;
+use cargo_test_support::registry::Package;
+use cargo_test_support::{basic_manifest, project, Project};
+use std::process::Output;
+
+#[cargo_test]
+fn with_deps() {
+ Package::new("bar", "0.0.1").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "test"
+ version = "0.0.0"
+
+ [dependencies]
+ bar = "*"
+
+ [profile.release]
+ lto = true
+ "#,
+ )
+ .file("src/main.rs", "extern crate bar; fn main() {}")
+ .build();
+ p.cargo("build -v --release")
+ .with_stderr_contains("[..]`rustc[..]--crate-name bar[..]-C linker-plugin-lto[..]`")
+ .with_stderr_contains("[..]`rustc[..]--crate-name test[..]-C lto[..]`")
+ .run();
+}
+
+#[cargo_test]
+fn shared_deps() {
+ Package::new("bar", "0.0.1").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "test"
+ version = "0.0.0"
+
+ [dependencies]
+ bar = "*"
+
+ [build-dependencies]
+ bar = "*"
+
+ [profile.release]
+ lto = true
+ "#,
+ )
+ .file("build.rs", "extern crate bar; fn main() {}")
+ .file("src/main.rs", "extern crate bar; fn main() {}")
+ .build();
+ p.cargo("build -v --release")
+ .with_stderr_contains("[..]`rustc[..]--crate-name test[..]-C lto[..]`")
+ .run();
+}
+
+#[cargo_test]
+fn build_dep_not_ltod() {
+ Package::new("bar", "0.0.1").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "test"
+ version = "0.0.0"
+
+ [build-dependencies]
+ bar = "*"
+
+ [profile.release]
+ lto = true
+ "#,
+ )
+ .file("build.rs", "extern crate bar; fn main() {}")
+ .file("src/main.rs", "fn main() {}")
+ .build();
+ p.cargo("build -v --release")
+ .with_stderr_contains("[..]`rustc[..]--crate-name bar[..]-C embed-bitcode=no[..]`")
+ .with_stderr_contains("[..]`rustc[..]--crate-name test[..]-C lto[..]`")
+ .run();
+}
+
+#[cargo_test]
+fn complicated() {
+ Package::new("dep-shared", "0.0.1")
+ .file("src/lib.rs", "pub fn foo() {}")
+ .publish();
+ Package::new("dep-normal2", "0.0.1")
+ .file("src/lib.rs", "pub fn foo() {}")
+ .publish();
+ Package::new("dep-normal", "0.0.1")
+ .dep("dep-shared", "*")
+ .dep("dep-normal2", "*")
+ .file(
+ "src/lib.rs",
+ "
+ pub fn foo() {
+ dep_shared::foo();
+ dep_normal2::foo();
+ }
+ ",
+ )
+ .publish();
+ Package::new("dep-build2", "0.0.1")
+ .file("src/lib.rs", "pub fn foo() {}")
+ .publish();
+ Package::new("dep-build", "0.0.1")
+ .dep("dep-shared", "*")
+ .dep("dep-build2", "*")
+ .file(
+ "src/lib.rs",
+ "
+ pub fn foo() {
+ dep_shared::foo();
+ dep_build2::foo();
+ }
+ ",
+ )
+ .publish();
+ Package::new("dep-proc-macro2", "0.0.1")
+ .file("src/lib.rs", "pub fn foo() {}")
+ .publish();
+ Package::new("dep-proc-macro", "0.0.1")
+ .proc_macro(true)
+ .dep("dep-shared", "*")
+ .dep("dep-proc-macro2", "*")
+ .file(
+ "src/lib.rs",
+ "
+ extern crate proc_macro;
+ use proc_macro::TokenStream;
+
+ #[proc_macro_attribute]
+ pub fn foo(_: TokenStream, a: TokenStream) -> TokenStream {
+ dep_shared::foo();
+ dep_proc_macro2::foo();
+ a
+ }
+ ",
+ )
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "test"
+ version = "0.0.0"
+
+ [lib]
+ crate-type = ['cdylib', 'staticlib']
+
+ [dependencies]
+ dep-normal = "*"
+ dep-proc-macro = "*"
+
+ [build-dependencies]
+ dep-build = "*"
+
+ [profile.release]
+ lto = true
+
+ # force build deps to share an opt-level with the rest of the
+ # graph so they only get built once.
+ [profile.release.build-override]
+ opt-level = 3
+ "#,
+ )
+ .file("build.rs", "fn main() { dep_build::foo() }")
+ .file(
+ "src/bin/foo-bin.rs",
+ "#[dep_proc_macro::foo] fn main() { dep_normal::foo() }",
+ )
+ .file(
+ "src/lib.rs",
+ "#[dep_proc_macro::foo] pub fn foo() { dep_normal::foo() }",
+ )
+ .build();
+ p.cargo("build -v --release")
+ // normal deps and their transitive dependencies do not need object
+ // code, so they should have linker-plugin-lto specified
+ .with_stderr_contains(
+ "[..]`rustc[..]--crate-name dep_normal2 [..]-C linker-plugin-lto[..]`",
+ )
+ .with_stderr_contains("[..]`rustc[..]--crate-name dep_normal [..]-C linker-plugin-lto[..]`")
+ // build dependencies and their transitive deps don't need any bitcode,
+ // so embedding should be turned off
+ .with_stderr_contains("[..]`rustc[..]--crate-name dep_build2 [..]-C embed-bitcode=no[..]`")
+ .with_stderr_contains("[..]`rustc[..]--crate-name dep_build [..]-C embed-bitcode=no[..]`")
+ .with_stderr_contains(
+ "[..]`rustc[..]--crate-name build_script_build [..]-C embed-bitcode=no[..]`",
+ )
+ // proc macro deps are the same as build deps here
+ .with_stderr_contains(
+ "[..]`rustc[..]--crate-name dep_proc_macro2 [..]-C embed-bitcode=no[..]`",
+ )
+ .with_stderr_contains(
+ "[..]`rustc[..]--crate-name dep_proc_macro [..]-C embed-bitcode=no[..]`",
+ )
+ .with_stderr_contains(
+ "[..]`rustc[..]--crate-name foo_bin [..]--crate-type bin[..]-C lto[..]`",
+ )
+ .with_stderr_contains(
+ "[..]`rustc[..]--crate-name test [..]--crate-type cdylib[..]-C lto[..]`",
+ )
+ .with_stderr_contains("[..]`rustc[..]--crate-name dep_shared [..]`")
+ .with_stderr_does_not_contain("[..]--crate-name dep_shared[..]-C lto[..]")
+ .with_stderr_does_not_contain("[..]--crate-name dep_shared[..]-C linker-plugin-lto[..]")
+ .with_stderr_does_not_contain("[..]--crate-name dep_shared[..]-C embed-bitcode[..]")
+ .run();
+}
+
+#[cargo_test]
+fn off_in_manifest_works() {
+ Package::new("bar", "0.0.1")
+ .file("src/lib.rs", "pub fn foo() {}")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "test"
+ version = "0.0.0"
+
+ [dependencies]
+ bar = "*"
+
+ [profile.release]
+ lto = "off"
+ "#,
+ )
+ .file("src/lib.rs", "pub fn foo() {}")
+ .file(
+ "src/main.rs",
+ "fn main() {
+ test::foo();
+ bar::foo();
+ }",
+ )
+ .build();
+ p.cargo("build -v --release")
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[DOWNLOADING] [..]
+[DOWNLOADED] [..]
+[COMPILING] bar v0.0.1
+[RUNNING] `rustc --crate-name bar [..]--crate-type lib [..]-C lto=off -C embed-bitcode=no[..]
+[COMPILING] test [..]
+[RUNNING] `rustc --crate-name test [..]--crate-type lib [..]-C lto=off -C embed-bitcode=no[..]
+[RUNNING] `rustc --crate-name test src/main.rs [..]--crate-type bin [..]-C lto=off[..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn between_builds() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "test"
+ version = "0.0.0"
+
+ [profile.release]
+ lto = true
+ "#,
+ )
+ .file("src/lib.rs", "pub fn foo() {}")
+ .file("src/main.rs", "fn main() { test::foo() }")
+ .build();
+ p.cargo("build -v --release --lib")
+ .with_stderr(
+ "\
+[COMPILING] test [..]
+[RUNNING] `rustc [..]--crate-type lib[..]-C linker-plugin-lto[..]
+[FINISHED] [..]
+",
+ )
+ .run();
+ p.cargo("build -v --release")
+ .with_stderr_contains(
+ "\
+[COMPILING] test [..]
+[RUNNING] `rustc [..]--crate-type bin[..]-C lto[..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn test_all() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+
+ [profile.release]
+ lto = true
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("tests/a.rs", "")
+ .file("tests/b.rs", "")
+ .build();
+ p.cargo("test --release -v")
+ .with_stderr_contains("[RUNNING] `rustc[..]--crate-name foo[..]-C lto[..]")
+ .run();
+}
+
+#[cargo_test]
+fn test_all_and_bench() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+
+ [profile.release]
+ lto = true
+ [profile.bench]
+ lto = true
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("tests/a.rs", "")
+ .file("tests/b.rs", "")
+ .build();
+ p.cargo("test --release -v")
+ .with_stderr_contains("[RUNNING] `rustc[..]--crate-name a[..]-C lto[..]")
+ .with_stderr_contains("[RUNNING] `rustc[..]--crate-name b[..]-C lto[..]")
+ .with_stderr_contains("[RUNNING] `rustc[..]--crate-name foo[..]-C lto[..]")
+ .run();
+}
+
+/// Basic setup:
+///
+/// foo v0.0.0
+/// ├── bar v0.0.0
+/// │ ├── registry v0.0.1
+/// │ └── registry-shared v0.0.1
+/// └── registry-shared v0.0.1
+///
+/// Where `bar` will have the given crate types.
+fn project_with_dep(crate_types: &str) -> Project {
+ Package::new("registry", "0.0.1")
+ .file("src/lib.rs", r#"pub fn foo() { println!("registry"); }"#)
+ .publish();
+ Package::new("registry-shared", "0.0.1")
+ .file("src/lib.rs", r#"pub fn foo() { println!("shared"); }"#)
+ .publish();
+
+ project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+
+ [workspace]
+
+ [dependencies]
+ bar = { path = 'bar' }
+ registry-shared = "*"
+
+ [profile.release]
+ lto = true
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ "
+ fn main() {
+ bar::foo();
+ registry_shared::foo();
+ }
+ ",
+ )
+ .file(
+ "bar/Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.0"
+
+ [dependencies]
+ registry = "*"
+ registry-shared = "*"
+
+ [lib]
+ crate-type = [{}]
+ "#,
+ crate_types
+ ),
+ )
+ .file(
+ "bar/src/lib.rs",
+ r#"
+ pub fn foo() {
+ println!("bar");
+ registry::foo();
+ registry_shared::foo();
+ }
+ "#,
+ )
+ .file("tests/a.rs", "")
+ .file("bar/tests/b.rs", "")
+ .build()
+}
+
+/// Helper for checking which LTO behavior is used for a specific crate.
+///
+/// `krate_info` is extra compiler flags used to distinguish this if the same
+/// crate name is being built multiple times.
+fn verify_lto(output: &Output, krate: &str, krate_info: &str, expected_lto: Lto) {
+ let stderr = std::str::from_utf8(&output.stderr).unwrap();
+ let mut matches = stderr.lines().filter(|line| {
+ line.contains("Running")
+ && line.contains(&format!("--crate-name {} ", krate))
+ && line.contains(krate_info)
+ });
+ let line = matches.next().unwrap_or_else(|| {
+ panic!(
+ "expected to find crate `{}` info: `{}`, not found in output:\n{}",
+ krate, krate_info, stderr
+ );
+ });
+ if let Some(line2) = matches.next() {
+ panic!(
+ "found multiple lines matching crate `{}` info: `{}`:\nline1:{}\nline2:{}\noutput:\n{}",
+ krate, krate_info, line, line2, stderr
+ );
+ }
+ let actual_lto = if let Some(index) = line.find("-C lto=") {
+ let s = &line[index..];
+ let end = s.find(' ').unwrap();
+ let mode = &line[index..index + end];
+ if mode == "off" {
+ Lto::Off
+ } else {
+ Lto::Run(Some(mode.into()))
+ }
+ } else if line.contains("-C lto") {
+ Lto::Run(None)
+ } else if line.contains("-C linker-plugin-lto") {
+ Lto::OnlyBitcode
+ } else if line.contains("-C embed-bitcode=no") {
+ Lto::OnlyObject
+ } else {
+ Lto::ObjectAndBitcode
+ };
+ assert_eq!(
+ actual_lto, expected_lto,
+ "did not find expected LTO in line: {}",
+ line
+ );
+}
+
+#[cargo_test]
+fn cdylib_and_rlib() {
+ let p = project_with_dep("'cdylib', 'rlib'");
+ let output = p.cargo("build --release -v").exec_with_output().unwrap();
+ // `registry` is ObjectAndBitcode because it needs Object for the
+ // rlib, and Bitcode for the cdylib (which doesn't support LTO).
+ verify_lto(
+ &output,
+ "registry",
+ "--crate-type lib",
+ Lto::ObjectAndBitcode,
+ );
+ // Same as `registry`
+ verify_lto(
+ &output,
+ "registry_shared",
+ "--crate-type lib",
+ Lto::ObjectAndBitcode,
+ );
+ // Same as `registry`
+ verify_lto(
+ &output,
+ "bar",
+ "--crate-type cdylib --crate-type rlib",
+ Lto::ObjectAndBitcode,
+ );
+ verify_lto(&output, "foo", "--crate-type bin", Lto::Run(None));
+ p.cargo("test --release -v")
+ .with_stderr_unordered(
+ "\
+[FRESH] registry v0.0.1
+[FRESH] registry-shared v0.0.1
+[FRESH] bar v0.0.0 [..]
+[COMPILING] foo [..]
+[RUNNING] `rustc --crate-name foo [..]-C lto [..]--test[..]
+[RUNNING] `rustc --crate-name a [..]-C lto [..]--test[..]
+[FINISHED] [..]
+[RUNNING] [..]
+[RUNNING] [..]
+",
+ )
+ .run();
+ p.cargo("build --release -v --manifest-path bar/Cargo.toml")
+ .with_stderr_unordered(
+ "\
+[FRESH] registry-shared v0.0.1
+[FRESH] registry v0.0.1
+[FRESH] bar v0.0.0 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+ p.cargo("test --release -v --manifest-path bar/Cargo.toml")
+ .with_stderr_unordered(
+ "\
+[FRESH] registry-shared v0.0.1
+[FRESH] registry v0.0.1
+[COMPILING] bar [..]
+[RUNNING] `rustc --crate-name bar [..]-C lto[..]--test[..]
+[RUNNING] `rustc --crate-name b [..]-C lto[..]--test[..]
+[FINISHED] [..]
+[RUNNING] [..]target/release/deps/bar-[..]
+[RUNNING] [..]target/release/deps/b-[..]
+[DOCTEST] bar
+[RUNNING] `rustdoc --crate-type cdylib --crate-type rlib --crate-name bar --test [..]-C lto[..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn dylib() {
+ let p = project_with_dep("'dylib'");
+ let output = p.cargo("build --release -v").exec_with_output().unwrap();
+ // `registry` is OnlyObject because rustc doesn't support LTO with dylibs.
+ verify_lto(&output, "registry", "--crate-type lib", Lto::OnlyObject);
+ // `registry_shared` is both because it is needed by both bar (Object) and
+ // foo (Bitcode for LTO).
+ verify_lto(
+ &output,
+ "registry_shared",
+ "--crate-type lib",
+ Lto::ObjectAndBitcode,
+ );
+ // `bar` is OnlyObject because rustc doesn't support LTO with dylibs.
+ verify_lto(&output, "bar", "--crate-type dylib", Lto::OnlyObject);
+ // `foo` is LTO because it is a binary, and the profile specifies `lto=true`.
+ verify_lto(&output, "foo", "--crate-type bin", Lto::Run(None));
+ // `cargo test` should not rebuild dependencies. It builds the test
+ // executables with `lto=true` because the tests are built with the
+ // `--release` flag.
+ p.cargo("test --release -v")
+ .with_stderr_unordered(
+ "\
+[FRESH] registry v0.0.1
+[FRESH] registry-shared v0.0.1
+[FRESH] bar v0.0.0 [..]
+[COMPILING] foo [..]
+[RUNNING] `rustc --crate-name foo [..]-C lto [..]--test[..]
+[RUNNING] `rustc --crate-name a [..]-C lto [..]--test[..]
+[FINISHED] [..]
+[RUNNING] [..]
+[RUNNING] [..]
+",
+ )
+ .run();
+ // Building just `bar` causes `registry-shared` to get rebuilt because it
+ // switches to OnlyObject because it is now only being used with a dylib
+ // which does not support LTO.
+ //
+ // `bar` gets rebuilt because `registry_shared` got rebuilt.
+ p.cargo("build --release -v --manifest-path bar/Cargo.toml")
+ .with_stderr_unordered(
+ "\
+[COMPILING] registry-shared v0.0.1
+[FRESH] registry v0.0.1
+[RUNNING] `rustc --crate-name registry_shared [..]-C embed-bitcode=no[..]
+[DIRTY] bar v0.0.0 ([..]): dependency info changed
+[COMPILING] bar [..]
+[RUNNING] `rustc --crate-name bar [..]--crate-type dylib [..]-C embed-bitcode=no[..]
+[FINISHED] [..]
+",
+ )
+ .run();
+ // Testing just `bar` causes `registry` to get rebuilt because it switches
+ // to needing both Object (for the `bar` dylib) and Bitcode (for the test
+ // built with LTO).
+ //
+ // `bar` the dylib gets rebuilt because `registry` got rebuilt.
+ p.cargo("test --release -v --manifest-path bar/Cargo.toml")
+ .with_stderr_unordered(
+ "\
+[FRESH] registry-shared v0.0.1
+[COMPILING] registry v0.0.1
+[RUNNING] `rustc --crate-name registry [..]
+[DIRTY] bar v0.0.0 ([..]): dependency info changed
+[COMPILING] bar [..]
+[RUNNING] `rustc --crate-name bar [..]--crate-type dylib [..]-C embed-bitcode=no[..]
+[RUNNING] `rustc --crate-name bar [..]-C lto [..]--test[..]
+[RUNNING] `rustc --crate-name b [..]-C lto [..]--test[..]
+[FINISHED] [..]
+[RUNNING] [..]
+[RUNNING] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+// This is currently broken on windows-gnu, see https://github.com/rust-lang/rust/issues/109797
+#[cfg_attr(
+ all(target_os = "windows", target_env = "gnu"),
+ ignore = "windows-gnu not working"
+)]
+fn test_profile() {
+ Package::new("bar", "0.0.1")
+ .file("src/lib.rs", "pub fn foo() -> i32 { 123 } ")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [profile.test]
+ lto = 'thin'
+
+ [dependencies]
+ bar = "*"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ #[test]
+ fn t1() {
+ assert_eq!(123, bar::foo());
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("test -v")
+ // unordered because the two `foo` builds start in parallel
+ .with_stderr_unordered("\
+[UPDATING] [..]
+[DOWNLOADING] [..]
+[DOWNLOADED] [..]
+[COMPILING] bar v0.0.1
+[RUNNING] `rustc --crate-name bar [..]crate-type lib[..]
+[COMPILING] foo [..]
+[RUNNING] `rustc --crate-name foo [..]--crate-type lib --emit=dep-info,metadata,link -C linker-plugin-lto[..]
+[RUNNING] `rustc --crate-name foo [..]--emit=dep-info,link -C lto=thin [..]--test[..]
+[FINISHED] [..]
+[RUNNING] [..]
+[DOCTEST] foo
+[RUNNING] `rustdoc [..]
+")
+ .run();
+}
+
+#[cargo_test]
+fn doctest() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [profile.release]
+ lto = true
+
+ [dependencies]
+ bar = { path = "bar" }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ /// Foo!
+ ///
+ /// ```
+ /// foo::foo();
+ /// ```
+ pub fn foo() { bar::bar(); }
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file(
+ "bar/src/lib.rs",
+ r#"
+ pub fn bar() { println!("hi!"); }
+ "#,
+ )
+ .build();
+
+ p.cargo("test --doc --release -v")
+ .with_stderr_contains("[..]`rustc --crate-name bar[..]-C linker-plugin-lto[..]")
+ .with_stderr_contains("[..]`rustc --crate-name foo[..]-C linker-plugin-lto[..]")
+ // embed-bitcode should be harmless here
+ .with_stderr_contains("[..]`rustdoc [..]-C lto[..]")
+ .run();
+
+ // Try with bench profile.
+ p.cargo("test --doc --release -v")
+ .env("CARGO_PROFILE_BENCH_LTO", "true")
+ .with_stderr_unordered(
+ "\
+[FRESH] bar v0.1.0 [..]
+[FRESH] foo v0.1.0 [..]
+[FINISHED] release [..]
+[DOCTEST] foo
+[RUNNING] `rustdoc [..]-C lto[..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn dylib_rlib_bin() {
+ // dylib+rlib linked with a binary
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [lib]
+ crate-type = ["dylib", "rlib"]
+
+ [profile.release]
+ lto = true
+ "#,
+ )
+ .file("src/lib.rs", "pub fn foo() { println!(\"hi!\"); }")
+ .file("src/bin/ferret.rs", "fn main() { foo::foo(); }")
+ .build();
+
+ let output = p.cargo("build --release -v").exec_with_output().unwrap();
+ verify_lto(
+ &output,
+ "foo",
+ "--crate-type dylib --crate-type rlib",
+ Lto::ObjectAndBitcode,
+ );
+ verify_lto(&output, "ferret", "--crate-type bin", Lto::Run(None));
+}
+
+#[cargo_test]
+fn fresh_swapping_commands() {
+ // In some rare cases, different commands end up building dependencies
+ // with different LTO settings. This checks that it doesn't cause the
+ // cache to thrash in that scenario.
+ Package::new("bar", "1.0.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "1.0"
+
+ [profile.release]
+ lto = true
+ "#,
+ )
+ .file("src/lib.rs", "pub fn foo() { println!(\"hi!\"); }")
+ .build();
+
+ p.cargo("build --release -v")
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v1.0.0 [..]
+[COMPILING] bar v1.0.0
+[RUNNING] `rustc --crate-name bar [..]-C linker-plugin-lto[..]
+[COMPILING] foo v0.1.0 [..]
+[RUNNING] `rustc --crate-name foo src/lib.rs [..]-C linker-plugin-lto[..]
+[FINISHED] [..]
+",
+ )
+ .run();
+ p.cargo("test --release -v")
+ .with_stderr_unordered(
+ "\
+[FRESH] bar v1.0.0
+[COMPILING] foo v0.1.0 [..]
+[RUNNING] `rustc --crate-name foo src/lib.rs [..]-C lto[..]--test[..]
+[FINISHED] [..]
+[RUNNING] `[..]/foo[..]`
+[DOCTEST] foo
+[RUNNING] `rustdoc [..]-C lto[..]
+",
+ )
+ .run();
+
+ p.cargo("build --release -v")
+ .with_stderr(
+ "\
+[FRESH] bar v1.0.0
+[FRESH] foo [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+ p.cargo("test --release -v --no-run -v")
+ .with_stderr(
+ "\
+[FRESH] bar v1.0.0
+[FRESH] foo [..]
+[FINISHED] [..]
+[EXECUTABLE] `[..]/target/release/deps/foo-[..][EXE]`
+",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/main.rs b/src/tools/cargo/tests/testsuite/main.rs
new file mode 100644
index 000000000..a1e293acd
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/main.rs
@@ -0,0 +1,146 @@
+// See src/cargo/lib.rs for notes on these lint settings.
+#![warn(rust_2018_idioms)]
+#![allow(clippy::all)]
+
+#[macro_use]
+extern crate cargo_test_macro;
+
+mod advanced_env;
+mod alt_registry;
+mod artifact_dep;
+mod bad_config;
+mod bad_manifest_path;
+mod bench;
+mod binary_name;
+mod build;
+mod build_plan;
+mod build_script;
+mod build_script_env;
+mod build_script_extra_link_arg;
+mod cache_messages;
+mod cargo_add;
+mod cargo_alias_config;
+mod cargo_command;
+mod cargo_config;
+mod cargo_env_config;
+mod cargo_features;
+mod cargo_remove;
+mod cargo_targets;
+mod cfg;
+mod check;
+mod check_cfg;
+mod clean;
+mod collisions;
+mod concurrent;
+mod config;
+mod config_cli;
+mod config_include;
+mod corrupt_git;
+mod credential_process;
+mod cross_compile;
+mod cross_publish;
+mod custom_target;
+mod death;
+mod dep_info;
+mod direct_minimal_versions;
+mod directory;
+mod doc;
+mod docscrape;
+mod edition;
+mod error;
+mod features;
+mod features2;
+mod features_namespaced;
+mod fetch;
+mod fix;
+mod freshness;
+mod future_incompat_report;
+mod generate_lockfile;
+mod git;
+mod git_auth;
+mod git_gc;
+mod glob_targets;
+mod help;
+mod https;
+mod inheritable_workspace_fields;
+mod init;
+mod install;
+mod install_upgrade;
+mod jobserver;
+mod list_availables;
+mod local_registry;
+mod locate_project;
+mod lockfile_compat;
+mod login;
+mod logout;
+mod lto;
+mod member_discovery;
+mod member_errors;
+mod message_format;
+mod messages;
+mod metabuild;
+mod metadata;
+mod minimal_versions;
+mod multitarget;
+mod net_config;
+mod new;
+mod offline;
+mod old_cargos;
+mod out_dir;
+mod owner;
+mod package;
+mod package_features;
+mod patch;
+mod path;
+mod paths;
+mod pkgid;
+mod plugins;
+mod proc_macro;
+mod profile_config;
+mod profile_custom;
+mod profile_overrides;
+mod profile_targets;
+mod profiles;
+mod progress;
+mod pub_priv;
+mod publish;
+mod publish_lockfile;
+mod read_manifest;
+mod registry;
+mod registry_auth;
+mod rename_deps;
+mod replace;
+mod required_features;
+mod run;
+mod rust_version;
+mod rustc;
+mod rustc_info_cache;
+mod rustdoc;
+mod rustdoc_extern_html;
+mod rustdocflags;
+mod rustflags;
+mod search;
+mod shell_quoting;
+mod source_replacement;
+mod ssh;
+mod standard_lib;
+mod test;
+mod timings;
+mod tool_paths;
+mod tree;
+mod tree_graph_features;
+mod unit_graph;
+mod update;
+mod vendor;
+mod verify_project;
+mod version;
+mod warn_on_failure;
+mod weak_dep_features;
+mod workspaces;
+mod yank;
+
+#[cargo_test]
+fn aaa_trigger_cross_compile_disabled_check() {
+ // This triggers the cross compile disabled check to run ASAP, see #5141
+ cargo_test_support::cross_compile::disabled();
+}
diff --git a/src/tools/cargo/tests/testsuite/member_discovery.rs b/src/tools/cargo/tests/testsuite/member_discovery.rs
new file mode 100644
index 000000000..5377443b6
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/member_discovery.rs
@@ -0,0 +1,44 @@
+//! Tests for workspace member discovery.
+
+use cargo::core::{Shell, Workspace};
+use cargo::util::config::Config;
+
+use cargo_test_support::install::cargo_home;
+use cargo_test_support::project;
+use cargo_test_support::registry;
+
+/// Tests exclusion of non-directory files from workspace member discovery using glob `*`.
+#[cargo_test]
+fn bad_file_member_exclusion() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = [ "crates/*" ]
+ "#,
+ )
+ .file("crates/.DS_Store", "PLACEHOLDER")
+ .file(
+ "crates/bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+ "#,
+ )
+ .file("crates/bar/src/main.rs", "fn main() {}")
+ .build();
+
+ // Prevent this test from accessing the network by setting up .cargo/config.
+ registry::init();
+ let config = Config::new(
+ Shell::from_write(Box::new(Vec::new())),
+ cargo_home(),
+ cargo_home(),
+ );
+ let ws = Workspace::new(&p.root().join("Cargo.toml"), &config).unwrap();
+ assert_eq!(ws.members().count(), 1);
+ assert_eq!(ws.members().next().unwrap().name(), "bar");
+}
diff --git a/src/tools/cargo/tests/testsuite/member_errors.rs b/src/tools/cargo/tests/testsuite/member_errors.rs
new file mode 100644
index 000000000..c3c340ce0
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/member_errors.rs
@@ -0,0 +1,164 @@
+//! Tests for workspace member errors.
+
+use cargo::core::resolver::ResolveError;
+use cargo::core::{compiler::CompileMode, Shell, Workspace};
+use cargo::ops::{self, CompileOptions};
+use cargo::util::{config::Config, errors::ManifestError};
+
+use cargo_test_support::install::cargo_home;
+use cargo_test_support::project;
+use cargo_test_support::registry;
+
+/// Tests inclusion of a `ManifestError` pointing to a member manifest
+/// when that manifest fails to deserialize.
+#[cargo_test]
+fn toml_deserialize_manifest_error() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ bar = { path = "bar" }
+
+ [workspace]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ foobar == "0.55"
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}")
+ .build();
+
+ let root_manifest_path = p.root().join("Cargo.toml");
+ let member_manifest_path = p.root().join("bar").join("Cargo.toml");
+
+ let error = Workspace::new(&root_manifest_path, &Config::default().unwrap()).unwrap_err();
+ eprintln!("{:?}", error);
+
+ let manifest_err: &ManifestError = error.downcast_ref().expect("Not a ManifestError");
+ assert_eq!(manifest_err.manifest_path(), &root_manifest_path);
+
+ let causes: Vec<_> = manifest_err.manifest_causes().collect();
+ assert_eq!(causes.len(), 1, "{:?}", causes);
+ assert_eq!(causes[0].manifest_path(), &member_manifest_path);
+}
+
+/// Tests inclusion of a `ManifestError` pointing to a member manifest
+/// when that manifest has an invalid dependency path.
+#[cargo_test]
+fn member_manifest_path_io_error() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ bar = { path = "bar" }
+
+ [workspace]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ foobar = { path = "nosuch" }
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}")
+ .build();
+
+ let root_manifest_path = p.root().join("Cargo.toml");
+ let member_manifest_path = p.root().join("bar").join("Cargo.toml");
+ let missing_manifest_path = p.root().join("bar").join("nosuch").join("Cargo.toml");
+
+ let error = Workspace::new(&root_manifest_path, &Config::default().unwrap()).unwrap_err();
+ eprintln!("{:?}", error);
+
+ let manifest_err: &ManifestError = error.downcast_ref().expect("Not a ManifestError");
+ assert_eq!(manifest_err.manifest_path(), &root_manifest_path);
+
+ let causes: Vec<_> = manifest_err.manifest_causes().collect();
+ assert_eq!(causes.len(), 2, "{:?}", causes);
+ assert_eq!(causes[0].manifest_path(), &member_manifest_path);
+ assert_eq!(causes[1].manifest_path(), &missing_manifest_path);
+}
+
+/// Tests dependency version errors provide which package failed via a `ResolveError`.
+#[cargo_test]
+fn member_manifest_version_error() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ bar = { path = "bar" }
+
+ [workspace]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ i-dont-exist = "0.55"
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}")
+ .build();
+
+ // Prevent this test from accessing the network by setting up .cargo/config.
+ registry::init();
+ let config = Config::new(
+ Shell::from_write(Box::new(Vec::new())),
+ cargo_home(),
+ cargo_home(),
+ );
+ let ws = Workspace::new(&p.root().join("Cargo.toml"), &config).unwrap();
+ let compile_options = CompileOptions::new(&config, CompileMode::Build).unwrap();
+ let member_bar = ws.members().find(|m| &*m.name() == "bar").unwrap();
+
+ let error = ops::compile(&ws, &compile_options).map(|_| ()).unwrap_err();
+ eprintln!("{:?}", error);
+
+ let resolve_err: &ResolveError = error.downcast_ref().expect("Not a ResolveError");
+ let package_path = resolve_err.package_path();
+ assert_eq!(package_path.len(), 1, "package_path: {:?}", package_path);
+ assert_eq!(package_path[0], member_bar.package_id());
+}
diff --git a/src/tools/cargo/tests/testsuite/message_format.rs b/src/tools/cargo/tests/testsuite/message_format.rs
new file mode 100644
index 000000000..e9310b261
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/message_format.rs
@@ -0,0 +1,133 @@
+//! Tests for --message-format flag.
+
+use cargo_test_support::{basic_lib_manifest, basic_manifest, project};
+
+#[cargo_test]
+fn cannot_specify_two() {
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ let formats = ["human", "json", "short"];
+
+ let two_kinds = "error: cannot specify two kinds of `message-format` arguments\n";
+ for a in formats.iter() {
+ for b in formats.iter() {
+ p.cargo(&format!("build --message-format {},{}", a, b))
+ .with_status(101)
+ .with_stderr(two_kinds)
+ .run();
+ }
+ }
+}
+
+#[cargo_test]
+fn double_json_works() {
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check --message-format json,json-render-diagnostics")
+ .run();
+ p.cargo("check --message-format json,json-diagnostic-short")
+ .run();
+ p.cargo("check --message-format json,json-diagnostic-rendered-ansi")
+ .run();
+ p.cargo("check --message-format json --message-format json-diagnostic-rendered-ansi")
+ .run();
+ p.cargo("check --message-format json-diagnostic-rendered-ansi")
+ .run();
+ p.cargo("check --message-format json-diagnostic-short,json-diagnostic-rendered-ansi")
+ .run();
+}
+
+#[cargo_test]
+fn cargo_renders() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = 'foo'
+ version = '0.1.0'
+
+ [dependencies]
+ bar = { path = 'bar' }
+ "#,
+ )
+ .file("src/main.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("check --message-format json-render-diagnostics")
+ .with_status(101)
+ .with_stdout(
+ "{\"reason\":\"compiler-artifact\",[..]\n\
+ {\"reason\":\"build-finished\",\"success\":false}",
+ )
+ .with_stderr_contains(
+ "\
+[CHECKING] bar [..]
+[CHECKING] foo [..]
+error[..]`main`[..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_renders_short() {
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("src/main.rs", "")
+ .build();
+
+ p.cargo("check --message-format json-render-diagnostics,json-diagnostic-short")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+[CHECKING] foo [..]
+error[..]`main`[..]
+",
+ )
+ .with_stderr_does_not_contain("note:")
+ .run();
+}
+
+#[cargo_test]
+fn cargo_renders_ansi() {
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("src/main.rs", "")
+ .build();
+
+ p.cargo("check --message-format json-diagnostic-rendered-ansi")
+ .with_status(101)
+ .with_stdout_contains("[..]\\u001b[38;5;9merror[..]")
+ .run();
+}
+
+#[cargo_test]
+fn cargo_renders_doctests() {
+ let p = project()
+ .file("Cargo.toml", &basic_lib_manifest("foo"))
+ .file(
+ "src/lib.rs",
+ "\
+ /// ```rust
+ /// bar()
+ /// ```
+ pub fn bar() {}
+ ",
+ )
+ .build();
+
+ p.cargo("test --doc --message-format short")
+ .with_status(101)
+ .with_stdout_contains("src/lib.rs:2:1: error[E0425]:[..]")
+ .with_stdout_contains("[..]src/lib.rs - bar (line 1)[..]")
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/messages.rs b/src/tools/cargo/tests/testsuite/messages.rs
new file mode 100644
index 000000000..2c534d8f0
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/messages.rs
@@ -0,0 +1,144 @@
+//! General tests specifically about diagnostics and other messages.
+//!
+//! Tests for message caching can be found in `cache_messages`.
+
+use cargo_test_support::{process, project, Project};
+use cargo_util::ProcessError;
+
+/// Captures the actual diagnostics displayed by rustc. This is done to avoid
+/// relying on the exact message formatting in rustc.
+pub fn raw_rustc_output(project: &Project, path: &str, extra: &[&str]) -> String {
+ let mut proc = process("rustc");
+ if cfg!(windows) {
+ // Sanitize in case the caller wants to do direct string comparison with Cargo's output.
+ proc.arg(path.replace('/', "\\"));
+ } else {
+ proc.arg(path);
+ }
+ let rustc_output = match proc
+ .arg("--crate-type=lib")
+ .args(extra)
+ .cwd(project.root())
+ .exec_with_output()
+ {
+ Ok(output) => output.stderr,
+ Err(e) => e.downcast::<ProcessError>().unwrap().stderr.unwrap(),
+ };
+ // Do a little dance to remove rustc's "warnings emitted" message and the subsequent newline.
+ let stderr = std::str::from_utf8(&rustc_output).expect("utf8");
+ let mut lines = stderr.lines();
+ let mut result = String::new();
+ while let Some(line) = lines.next() {
+ if line.contains("warning emitted")
+ || line.contains("warnings emitted")
+ || line.contains("aborting due to")
+ {
+ // Eat blank line.
+ match lines.next() {
+ None | Some("") => continue,
+ Some(s) => panic!("unexpected str {}", s),
+ }
+ }
+ result.push_str(line);
+ result.push('\n');
+ }
+ result
+}
+
+#[cargo_test]
+fn deduplicate_messages_basic() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn foo() {
+ let x = 1;
+ }
+ "#,
+ )
+ .build();
+ let rustc_message = raw_rustc_output(&p, "src/lib.rs", &[]);
+ let expected_output = format!(
+ "{}\
+warning: `foo` (lib) generated 1 warning (run `cargo fix --lib -p foo` to apply 1 suggestion)
+warning: `foo` (lib test) generated 1 warning (1 duplicate)
+[FINISHED] [..]
+[EXECUTABLE] unittests src/lib.rs (target/debug/deps/foo-[..][EXE])
+",
+ rustc_message
+ );
+ p.cargo("test --no-run -j1")
+ .with_stderr(&format!("[COMPILING] foo [..]\n{}", expected_output))
+ .run();
+ // Run again, to check for caching behavior.
+ p.cargo("test --no-run -j1")
+ .with_stderr(expected_output)
+ .run();
+}
+
+#[cargo_test]
+fn deduplicate_messages_mismatched_warnings() {
+ // One execution prints 1 warning, the other prints 2 where there is an overlap.
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn foo() {
+ let x = 1;
+ }
+
+ #[test]
+ fn t1() {
+ let MY_VALUE = 1;
+ assert_eq!(MY_VALUE, 1);
+ }
+ "#,
+ )
+ .build();
+ let lib_output = raw_rustc_output(&p, "src/lib.rs", &[]);
+ let mut lib_test_output = raw_rustc_output(&p, "src/lib.rs", &["--test"]);
+ // Remove the duplicate warning.
+ let start = lib_test_output.find(&lib_output).expect("same warning");
+ lib_test_output.replace_range(start..start + lib_output.len(), "");
+ let expected_output = format!(
+ "\
+{}\
+warning: `foo` (lib) generated 1 warning (run `cargo fix --lib -p foo` to apply 1 suggestion)
+{}\
+warning: `foo` (lib test) generated 2 warnings (1 duplicate)
+[FINISHED] [..]
+[EXECUTABLE] unittests src/lib.rs (target/debug/deps/foo-[..][EXE])
+",
+ lib_output, lib_test_output
+ );
+ p.cargo("test --no-run -j1")
+ .with_stderr(&format!("[COMPILING] foo v0.0.1 [..]\n{}", expected_output))
+ .run();
+ // Run again, to check for caching behavior.
+ p.cargo("test --no-run -j1")
+ .with_stderr(expected_output)
+ .run();
+}
+
+#[cargo_test]
+fn deduplicate_errors() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ this should not compile
+ "#,
+ )
+ .build();
+ let rustc_message = raw_rustc_output(&p, "src/lib.rs", &[]);
+ p.cargo("test -j1")
+ .with_status(101)
+ .with_stderr(&format!(
+ "\
+[COMPILING] foo v0.0.1 [..]
+{}error: could not compile `foo` (lib) due to previous error
+",
+ rustc_message
+ ))
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/metabuild.rs b/src/tools/cargo/tests/testsuite/metabuild.rs
new file mode 100644
index 000000000..022d0bff0
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/metabuild.rs
@@ -0,0 +1,771 @@
+//! Tests for the metabuild feature (declarative build scripts).
+
+use cargo_test_support::{
+ basic_lib_manifest, basic_manifest, is_coarse_mtime, project, registry::Package, rustc_host,
+ Project,
+};
+
+use std::str;
+
+#[cargo_test]
+fn metabuild_gated() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ metabuild = ["mb"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .masquerade_as_nightly_cargo(&["metabuild"])
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[..]`
+
+Caused by:
+ feature `metabuild` is required
+
+ The package requires the Cargo feature called `metabuild`, \
+ but that feature is not stabilized in this version of Cargo (1.[..]).
+ Consider adding `cargo-features = [\"metabuild\"]` to the top of Cargo.toml \
+ (above the [package] table) to tell Cargo you are opting in to use this unstable feature.
+ See https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#metabuild \
+ for more information about the status of this feature.
+",
+ )
+ .run();
+}
+
+fn basic_project() -> Project {
+ project()
+ .file(
+ "Cargo.toml",
+ r#"
+ cargo-features = ["metabuild"]
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ metabuild = ["mb", "mb-other"]
+
+ [build-dependencies]
+ mb = {path="mb"}
+ mb-other = {path="mb-other"}
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("mb/Cargo.toml", &basic_lib_manifest("mb"))
+ .file(
+ "mb/src/lib.rs",
+ r#"pub fn metabuild() { println!("Hello mb"); }"#,
+ )
+ .file(
+ "mb-other/Cargo.toml",
+ r#"
+ [package]
+ name = "mb-other"
+ version = "0.0.1"
+ "#,
+ )
+ .file(
+ "mb-other/src/lib.rs",
+ r#"pub fn metabuild() { println!("Hello mb-other"); }"#,
+ )
+ .build()
+}
+
+#[cargo_test]
+fn metabuild_basic() {
+ let p = basic_project();
+ p.cargo("check -vv")
+ .masquerade_as_nightly_cargo(&["metabuild"])
+ .with_stdout_contains("[foo 0.0.1] Hello mb")
+ .with_stdout_contains("[foo 0.0.1] Hello mb-other")
+ .run();
+}
+
+#[cargo_test]
+fn metabuild_error_both() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ cargo-features = ["metabuild"]
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ metabuild = "mb"
+
+ [build-dependencies]
+ mb = {path="mb"}
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("build.rs", r#"fn main() {}"#)
+ .file("mb/Cargo.toml", &basic_lib_manifest("mb"))
+ .file(
+ "mb/src/lib.rs",
+ r#"pub fn metabuild() { println!("Hello mb"); }"#,
+ )
+ .build();
+
+ p.cargo("check -vv")
+ .masquerade_as_nightly_cargo(&["metabuild"])
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+error: failed to parse manifest at [..]
+
+Caused by:
+ cannot specify both `metabuild` and `build`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn metabuild_missing_dep() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ cargo-features = ["metabuild"]
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ metabuild = "mb"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check -vv")
+ .masquerade_as_nightly_cargo(&["metabuild"])
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+error: failed to parse manifest at [..]
+
+Caused by:
+ metabuild package `mb` must be specified in `build-dependencies`",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn metabuild_optional_dep() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ cargo-features = ["metabuild"]
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ metabuild = "mb"
+
+ [build-dependencies]
+ mb = {path="mb", optional=true}
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("mb/Cargo.toml", &basic_lib_manifest("mb"))
+ .file(
+ "mb/src/lib.rs",
+ r#"pub fn metabuild() { println!("Hello mb"); }"#,
+ )
+ .build();
+
+ p.cargo("check -vv")
+ .masquerade_as_nightly_cargo(&["metabuild"])
+ .with_stdout_does_not_contain("[foo 0.0.1] Hello mb")
+ .run();
+
+ p.cargo("check -vv --features mb")
+ .masquerade_as_nightly_cargo(&["metabuild"])
+ .with_stdout_contains("[foo 0.0.1] Hello mb")
+ .run();
+}
+
+#[cargo_test]
+fn metabuild_lib_name() {
+ // Test when setting `name` on [lib].
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ cargo-features = ["metabuild"]
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ metabuild = "mb"
+
+ [build-dependencies]
+ mb = {path="mb"}
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "mb/Cargo.toml",
+ r#"
+ [package]
+ name = "mb"
+ version = "0.0.1"
+ [lib]
+ name = "other"
+ "#,
+ )
+ .file(
+ "mb/src/lib.rs",
+ r#"pub fn metabuild() { println!("Hello mb"); }"#,
+ )
+ .build();
+
+ p.cargo("check -vv")
+ .masquerade_as_nightly_cargo(&["metabuild"])
+ .with_stdout_contains("[foo 0.0.1] Hello mb")
+ .run();
+}
+
+#[cargo_test]
+fn metabuild_fresh() {
+ if is_coarse_mtime() {
+ // This test doesn't work on coarse mtimes very well. Because the
+ // metabuild script is created at build time, its mtime is almost
+ // always equal to the mtime of the output. The second call to `build`
+ // will then think it needs to be rebuilt when it should be fresh.
+ return;
+ }
+
+ // Check that rebuild is fresh.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ cargo-features = ["metabuild"]
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ metabuild = "mb"
+
+ [build-dependencies]
+ mb = {path="mb"}
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("mb/Cargo.toml", &basic_lib_manifest("mb"))
+ .file(
+ "mb/src/lib.rs",
+ r#"pub fn metabuild() { println!("Hello mb"); }"#,
+ )
+ .build();
+
+ p.cargo("check -vv")
+ .masquerade_as_nightly_cargo(&["metabuild"])
+ .with_stdout_contains("[foo 0.0.1] Hello mb")
+ .run();
+
+ p.cargo("check -vv")
+ .masquerade_as_nightly_cargo(&["metabuild"])
+ .with_stdout_does_not_contain("[foo 0.0.1] Hello mb")
+ .with_stderr(
+ "\
+[FRESH] mb [..]
+[FRESH] foo [..]
+[FINISHED] dev [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn metabuild_links() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ cargo-features = ["metabuild"]
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ links = "cat"
+ metabuild = "mb"
+
+ [build-dependencies]
+ mb = {path="mb"}
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("mb/Cargo.toml", &basic_lib_manifest("mb"))
+ .file(
+ "mb/src/lib.rs",
+ r#"
+ pub fn metabuild() {
+ assert_eq!(std::env::var("CARGO_MANIFEST_LINKS"),
+ Ok("cat".to_string()));
+ println!("Hello mb");
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("check -vv")
+ .masquerade_as_nightly_cargo(&["metabuild"])
+ .with_stdout_contains("[foo 0.0.1] Hello mb")
+ .run();
+}
+
+#[cargo_test]
+fn metabuild_override() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ cargo-features = ["metabuild"]
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ links = "cat"
+ metabuild = "mb"
+
+ [build-dependencies]
+ mb = {path="mb"}
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("mb/Cargo.toml", &basic_lib_manifest("mb"))
+ .file(
+ "mb/src/lib.rs",
+ r#"pub fn metabuild() { panic!("should not run"); }"#,
+ )
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [target.{}.cat]
+ rustc-link-lib = ["a"]
+ "#,
+ rustc_host()
+ ),
+ )
+ .build();
+
+ p.cargo("check -vv")
+ .masquerade_as_nightly_cargo(&["metabuild"])
+ .run();
+}
+
+#[cargo_test]
+fn metabuild_workspace() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["member1", "member2"]
+ "#,
+ )
+ .file(
+ "member1/Cargo.toml",
+ r#"
+ cargo-features = ["metabuild"]
+ [package]
+ name = "member1"
+ version = "0.0.1"
+ metabuild = ["mb1", "mb2"]
+
+ [build-dependencies]
+ mb1 = {path="../../mb1"}
+ mb2 = {path="../../mb2"}
+ "#,
+ )
+ .file("member1/src/lib.rs", "")
+ .file(
+ "member2/Cargo.toml",
+ r#"
+ cargo-features = ["metabuild"]
+ [package]
+ name = "member2"
+ version = "0.0.1"
+ metabuild = ["mb1"]
+
+ [build-dependencies]
+ mb1 = {path="../../mb1"}
+ "#,
+ )
+ .file("member2/src/lib.rs", "")
+ .build();
+
+ project()
+ .at("mb1")
+ .file("Cargo.toml", &basic_lib_manifest("mb1"))
+ .file(
+ "src/lib.rs",
+ r#"pub fn metabuild() { println!("Hello mb1 {}", std::env::var("CARGO_MANIFEST_DIR").unwrap()); }"#,
+ )
+ .build();
+
+ project()
+ .at("mb2")
+ .file("Cargo.toml", &basic_lib_manifest("mb2"))
+ .file(
+ "src/lib.rs",
+ r#"pub fn metabuild() { println!("Hello mb2 {}", std::env::var("CARGO_MANIFEST_DIR").unwrap()); }"#,
+ )
+ .build();
+
+ p.cargo("check -vv --workspace")
+ .masquerade_as_nightly_cargo(&["metabuild"])
+ .with_stdout_contains("[member1 0.0.1] Hello mb1 [..]member1")
+ .with_stdout_contains("[member1 0.0.1] Hello mb2 [..]member1")
+ .with_stdout_contains("[member2 0.0.1] Hello mb1 [..]member2")
+ .with_stdout_does_not_contain("[member2 0.0.1] Hello mb2 [..]member2")
+ .run();
+}
+
+#[cargo_test]
+fn metabuild_metadata() {
+ // The metabuild Target is filtered out of the `metadata` results.
+ let p = basic_project();
+
+ let meta = p
+ .cargo("metadata --format-version=1")
+ .masquerade_as_nightly_cargo(&["metabuild"])
+ .run_json();
+ let mb_info: Vec<&str> = meta["packages"]
+ .as_array()
+ .unwrap()
+ .iter()
+ .find(|p| p["name"].as_str().unwrap() == "foo")
+ .unwrap()["metabuild"]
+ .as_array()
+ .unwrap()
+ .iter()
+ .map(|s| s.as_str().unwrap())
+ .collect();
+ assert_eq!(mb_info, ["mb", "mb-other"]);
+}
+
+#[cargo_test]
+fn metabuild_build_plan() {
+ let p = basic_project();
+
+ p.cargo("build --build-plan -Zunstable-options")
+ .masquerade_as_nightly_cargo(&["metabuild", "build-plan"])
+ .with_json(
+ r#"
+ {
+ "invocations": [
+ {
+ "package_name": "mb",
+ "package_version": "0.5.0",
+ "target_kind": ["lib"],
+ "compile_mode": "build",
+ "kind": null,
+ "deps": [],
+ "outputs": [
+ "[..]/target/debug/deps/libmb-[..].rlib",
+ "[..]/target/debug/deps/libmb-[..].rmeta"
+ ],
+ "links": {},
+ "program": "rustc",
+ "args": "{...}",
+ "env": "{...}",
+ "cwd": "[..]"
+ },
+ {
+ "package_name": "mb-other",
+ "package_version": "0.0.1",
+ "target_kind": ["lib"],
+ "compile_mode": "build",
+ "kind": null,
+ "deps": [],
+ "outputs": [
+ "[..]/target/debug/deps/libmb_other-[..].rlib",
+ "[..]/target/debug/deps/libmb_other-[..].rmeta"
+ ],
+ "links": {},
+ "program": "rustc",
+ "args": "{...}",
+ "env": "{...}",
+ "cwd": "[..]"
+ },
+ {
+ "package_name": "foo",
+ "package_version": "0.0.1",
+ "target_kind": ["custom-build"],
+ "compile_mode": "build",
+ "kind": null,
+ "deps": [0, 1],
+ "outputs": "{...}",
+ "links": "{...}",
+ "program": "rustc",
+ "args": "{...}",
+ "env": "{...}",
+ "cwd": "[..]"
+ },
+ {
+ "package_name": "foo",
+ "package_version": "0.0.1",
+ "target_kind": ["custom-build"],
+ "compile_mode": "run-custom-build",
+ "kind": null,
+ "deps": [2],
+ "outputs": [],
+ "links": {},
+ "program": "[..]/foo/target/debug/build/foo-[..]/metabuild-foo",
+ "args": [],
+ "env": "{...}",
+ "cwd": "[..]"
+ },
+ {
+ "package_name": "foo",
+ "package_version": "0.0.1",
+ "target_kind": ["lib"],
+ "compile_mode": "build",
+ "kind": null,
+ "deps": [3],
+ "outputs": [
+ "[..]/foo/target/debug/deps/libfoo-[..].rlib",
+ "[..]/foo/target/debug/deps/libfoo-[..].rmeta"
+ ],
+ "links": "{...}",
+ "program": "rustc",
+ "args": "{...}",
+ "env": "{...}",
+ "cwd": "[..]"
+ }
+ ],
+ "inputs": [
+ "[..]/foo/Cargo.toml",
+ "[..]/foo/mb/Cargo.toml",
+ "[..]/foo/mb-other/Cargo.toml"
+ ]
+ }
+ "#,
+ )
+ .run();
+
+ assert_eq!(p.glob("target/.metabuild/metabuild-foo-*.rs").count(), 1);
+}
+
+#[cargo_test]
+fn metabuild_two_versions() {
+ // Two versions of a metabuild dep with the same name.
+ let p = project()
+ .at("ws")
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["member1", "member2"]
+ "#,
+ )
+ .file(
+ "member1/Cargo.toml",
+ r#"
+ cargo-features = ["metabuild"]
+ [package]
+ name = "member1"
+ version = "0.0.1"
+ metabuild = ["mb"]
+
+ [build-dependencies]
+ mb = {path="../../mb1"}
+ "#,
+ )
+ .file("member1/src/lib.rs", "")
+ .file(
+ "member2/Cargo.toml",
+ r#"
+ cargo-features = ["metabuild"]
+ [package]
+ name = "member2"
+ version = "0.0.1"
+ metabuild = ["mb"]
+
+ [build-dependencies]
+ mb = {path="../../mb2"}
+ "#,
+ )
+ .file("member2/src/lib.rs", "")
+ .build();
+
+ project().at("mb1")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "mb"
+ version = "0.0.1"
+ "#)
+ .file(
+ "src/lib.rs",
+ r#"pub fn metabuild() { println!("Hello mb1 {}", std::env::var("CARGO_MANIFEST_DIR").unwrap()); }"#,
+ )
+ .build();
+
+ project().at("mb2")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "mb"
+ version = "0.0.2"
+ "#)
+ .file(
+ "src/lib.rs",
+ r#"pub fn metabuild() { println!("Hello mb2 {}", std::env::var("CARGO_MANIFEST_DIR").unwrap()); }"#,
+ )
+ .build();
+
+ p.cargo("check -vv --workspace")
+ .masquerade_as_nightly_cargo(&["metabuild"])
+ .with_stdout_contains("[member1 0.0.1] Hello mb1 [..]member1")
+ .with_stdout_contains("[member2 0.0.1] Hello mb2 [..]member2")
+ .run();
+
+ assert_eq!(
+ p.glob("target/.metabuild/metabuild-member?-*.rs").count(),
+ 2
+ );
+}
+
+#[cargo_test]
+fn metabuild_external_dependency() {
+ Package::new("mb", "1.0.0")
+ .file("Cargo.toml", &basic_manifest("mb", "1.0.0"))
+ .file(
+ "src/lib.rs",
+ r#"pub fn metabuild() { println!("Hello mb"); }"#,
+ )
+ .publish();
+ Package::new("dep", "1.0.0")
+ .file(
+ "Cargo.toml",
+ r#"
+ cargo-features = ["metabuild"]
+ [package]
+ name = "dep"
+ version = "1.0.0"
+ metabuild = ["mb"]
+
+ [build-dependencies]
+ mb = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build_dep("mb", "1.0.0")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ [dependencies]
+ dep = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "extern crate dep;")
+ .build();
+
+ p.cargo("check -vv")
+ .masquerade_as_nightly_cargo(&["metabuild"])
+ .with_stdout_contains("[dep 1.0.0] Hello mb")
+ .run();
+
+ assert_eq!(p.glob("target/.metabuild/metabuild-dep-*.rs").count(), 1);
+}
+
+#[cargo_test]
+fn metabuild_json_artifact() {
+ let p = basic_project();
+ p.cargo("check --message-format=json")
+ .masquerade_as_nightly_cargo(&["metabuild"])
+ .with_json_contains_unordered(
+ r#"
+ {
+ "executable": null,
+ "features": [],
+ "filenames": "{...}",
+ "fresh": false,
+ "package_id": "foo [..]",
+ "manifest_path": "[..]",
+ "profile": "{...}",
+ "reason": "compiler-artifact",
+ "target": {
+ "crate_types": [
+ "bin"
+ ],
+ "doc": false,
+ "doctest": false,
+ "edition": "2018",
+ "kind": [
+ "custom-build"
+ ],
+ "name": "metabuild-foo",
+ "src_path": "[..]/foo/target/.metabuild/metabuild-foo-[..].rs",
+ "test": false
+ }
+ }
+
+ {
+ "cfgs": [],
+ "env": [],
+ "linked_libs": [],
+ "linked_paths": [],
+ "package_id": "foo [..]",
+ "out_dir": "[..]",
+ "reason": "build-script-executed"
+ }
+ "#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn metabuild_failed_build_json() {
+ let p = basic_project();
+ // Modify the metabuild dep so that it fails to compile.
+ p.change_file("mb/src/lib.rs", "");
+ p.cargo("check --message-format=json")
+ .masquerade_as_nightly_cargo(&["metabuild"])
+ .with_status(101)
+ .with_json_contains_unordered(
+ r#"
+ {
+ "message": {
+ "children": "{...}",
+ "code": "{...}",
+ "level": "error",
+ "message": "cannot find function `metabuild` in [..] `mb`",
+ "rendered": "{...}",
+ "spans": "{...}"
+ },
+ "package_id": "foo [..]",
+ "manifest_path": "[..]",
+ "reason": "compiler-message",
+ "target": {
+ "crate_types": [
+ "bin"
+ ],
+ "doc": false,
+ "doctest": false,
+ "edition": "2018",
+ "kind": [
+ "custom-build"
+ ],
+ "name": "metabuild-foo",
+ "src_path": null,
+ "test": false
+ }
+ }
+ "#,
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/metadata.rs b/src/tools/cargo/tests/testsuite/metadata.rs
new file mode 100644
index 000000000..547916e7a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/metadata.rs
@@ -0,0 +1,4192 @@
+//! Tests for the `cargo metadata` command.
+
+use cargo_test_support::install::cargo_home;
+use cargo_test_support::paths::CargoPathExt;
+use cargo_test_support::registry::Package;
+use cargo_test_support::{basic_bin_manifest, basic_lib_manifest, main_file, project, rustc_host};
+use serde_json::json;
+
+#[cargo_test]
+fn cargo_metadata_simple() {
+ let p = project()
+ .file("src/foo.rs", "")
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .build();
+
+ p.cargo("metadata")
+ .with_json(
+ r#"
+ {
+ "packages": [
+ {
+ "authors": [
+ "wycats@example.com"
+ ],
+ "categories": [],
+ "default_run": null,
+ "name": "foo",
+ "version": "0.5.0",
+ "id": "foo[..]",
+ "keywords": [],
+ "source": null,
+ "dependencies": [],
+ "edition": "2015",
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "description": null,
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "homepage": null,
+ "documentation": null,
+ "targets": [
+ {
+ "kind": [
+ "bin"
+ ],
+ "crate_types": [
+ "bin"
+ ],
+ "doc": true,
+ "doctest": false,
+ "test": true,
+ "edition": "2015",
+ "name": "foo",
+ "src_path": "[..]/foo/src/foo.rs"
+ }
+ ],
+ "features": {},
+ "manifest_path": "[..]Cargo.toml",
+ "metadata": null,
+ "publish": null
+ }
+ ],
+ "workspace_members": ["foo 0.5.0 (path+file:[..]foo)"],
+ "resolve": {
+ "nodes": [
+ {
+ "dependencies": [],
+ "deps": [],
+ "features": [],
+ "id": "foo 0.5.0 (path+file:[..]foo)"
+ }
+ ],
+ "root": "foo 0.5.0 (path+file:[..]foo)"
+ },
+ "target_directory": "[..]foo/target",
+ "version": 1,
+ "workspace_root": "[..]/foo",
+ "metadata": null
+ }"#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_metadata_warns_on_implicit_version() {
+ let p = project()
+ .file("src/foo.rs", "")
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .build();
+
+ p.cargo("metadata").with_stderr("[WARNING] please specify `--format-version` flag explicitly to avoid compatibility problems").run();
+
+ p.cargo("metadata --format-version 1").with_stderr("").run();
+}
+
+#[cargo_test]
+fn library_with_several_crate_types() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(
+ "Cargo.toml",
+ r#"
+[package]
+name = "foo"
+version = "0.5.0"
+
+[lib]
+crate-type = ["lib", "staticlib"]
+ "#,
+ )
+ .build();
+
+ p.cargo("metadata")
+ .with_json(
+ r#"
+ {
+ "packages": [
+ {
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "name": "foo",
+ "readme": null,
+ "repository": null,
+ "homepage": null,
+ "documentation": null,
+ "version": "0.5.0",
+ "rust_version": null,
+ "id": "foo[..]",
+ "keywords": [],
+ "source": null,
+ "dependencies": [],
+ "edition": "2015",
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "description": null,
+ "targets": [
+ {
+ "kind": [
+ "lib",
+ "staticlib"
+ ],
+ "crate_types": [
+ "lib",
+ "staticlib"
+ ],
+ "doc": true,
+ "doctest": true,
+ "test": true,
+ "edition": "2015",
+ "name": "foo",
+ "src_path": "[..]/foo/src/lib.rs"
+ }
+ ],
+ "features": {},
+ "manifest_path": "[..]Cargo.toml",
+ "metadata": null,
+ "publish": null
+ }
+ ],
+ "workspace_members": ["foo 0.5.0 (path+file:[..]foo)"],
+ "resolve": {
+ "nodes": [
+ {
+ "dependencies": [],
+ "deps": [],
+ "features": [],
+ "id": "foo 0.5.0 (path+file:[..]foo)"
+ }
+ ],
+ "root": "foo 0.5.0 (path+file:[..]foo)"
+ },
+ "target_directory": "[..]foo/target",
+ "version": 1,
+ "workspace_root": "[..]/foo",
+ "metadata": null
+ }"#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn library_with_features() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(
+ "Cargo.toml",
+ r#"
+[package]
+name = "foo"
+version = "0.5.0"
+
+[features]
+default = ["default_feat"]
+default_feat = []
+optional_feat = []
+ "#,
+ )
+ .build();
+
+ p.cargo("metadata")
+ .with_json(
+ r#"
+ {
+ "packages": [
+ {
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "name": "foo",
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "version": "0.5.0",
+ "id": "foo[..]",
+ "keywords": [],
+ "source": null,
+ "dependencies": [],
+ "edition": "2015",
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "description": null,
+ "targets": [
+ {
+ "kind": [
+ "lib"
+ ],
+ "crate_types": [
+ "lib"
+ ],
+ "doc": true,
+ "doctest": true,
+ "test": true,
+ "edition": "2015",
+ "name": "foo",
+ "src_path": "[..]/foo/src/lib.rs"
+ }
+ ],
+ "features": {
+ "default": [
+ "default_feat"
+ ],
+ "default_feat": [],
+ "optional_feat": []
+ },
+ "manifest_path": "[..]Cargo.toml",
+ "metadata": null,
+ "publish": null
+ }
+ ],
+ "workspace_members": ["foo 0.5.0 (path+file:[..]foo)"],
+ "resolve": {
+ "nodes": [
+ {
+ "dependencies": [],
+ "deps": [],
+ "features": [
+ "default",
+ "default_feat"
+ ],
+ "id": "foo 0.5.0 (path+file:[..]foo)"
+ }
+ ],
+ "root": "foo 0.5.0 (path+file:[..]foo)"
+ },
+ "target_directory": "[..]foo/target",
+ "version": 1,
+ "workspace_root": "[..]/foo",
+ "metadata": null
+ }"#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_metadata_with_deps_and_version() {
+ let p = project()
+ .file("src/foo.rs", "")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ license = "MIT"
+ description = "foo"
+
+ [[bin]]
+ name = "foo"
+
+ [dependencies]
+ bar = "*"
+ [dev-dependencies]
+ foobar = "*"
+ "#,
+ )
+ .build();
+ Package::new("baz", "0.0.1").publish();
+ Package::new("foobar", "0.0.1").publish();
+ Package::new("bar", "0.0.1").dep("baz", "0.0.1").publish();
+
+ p.cargo("metadata -q --format-version 1")
+ .with_json(
+ r#"
+ {
+ "packages": [
+ {
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "dependencies": [
+ {
+ "features": [],
+ "kind": null,
+ "name": "baz",
+ "optional": false,
+ "registry": null,
+ "rename": null,
+ "req": "^0.0.1",
+ "source": "registry+https://github.com/rust-lang/crates.io-index",
+ "target": null,
+ "uses_default_features": true
+ }
+ ],
+ "description": null,
+ "edition": "2015",
+ "features": {},
+ "id": "bar 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "keywords": [],
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "manifest_path": "[..]Cargo.toml",
+ "metadata": null,
+ "publish": null,
+ "name": "bar",
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "source": "registry+https://github.com/rust-lang/crates.io-index",
+ "targets": [
+ {
+ "crate_types": [
+ "lib"
+ ],
+ "doc": true,
+ "doctest": true,
+ "test": true,
+ "edition": "2015",
+ "kind": [
+ "lib"
+ ],
+ "name": "bar",
+ "src_path": "[..]src/lib.rs"
+ }
+ ],
+ "version": "0.0.1"
+ },
+ {
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "dependencies": [],
+ "description": null,
+ "edition": "2015",
+ "features": {},
+ "id": "baz 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "keywords": [],
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "manifest_path": "[..]Cargo.toml",
+ "metadata": null,
+ "publish": null,
+ "name": "baz",
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "source": "registry+https://github.com/rust-lang/crates.io-index",
+ "targets": [
+ {
+ "crate_types": [
+ "lib"
+ ],
+ "doc": true,
+ "doctest": true,
+ "test": true,
+ "edition": "2015",
+ "kind": [
+ "lib"
+ ],
+ "name": "baz",
+ "src_path": "[..]src/lib.rs"
+ }
+ ],
+ "version": "0.0.1"
+ },
+ {
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "dependencies": [
+ {
+ "features": [],
+ "kind": null,
+ "name": "bar",
+ "optional": false,
+ "registry": null,
+ "rename": null,
+ "req": "*",
+ "source": "registry+https://github.com/rust-lang/crates.io-index",
+ "target": null,
+ "uses_default_features": true
+ },
+ {
+ "features": [],
+ "kind": "dev",
+ "name": "foobar",
+ "optional": false,
+ "registry": null,
+ "rename": null,
+ "req": "*",
+ "source": "registry+https://github.com/rust-lang/crates.io-index",
+ "target": null,
+ "uses_default_features": true
+ }
+ ],
+ "description": "foo",
+ "edition": "2015",
+ "features": {},
+ "id": "foo 0.5.0 (path+file:[..]foo)",
+ "keywords": [],
+ "license": "MIT",
+ "license_file": null,
+ "links": null,
+ "manifest_path": "[..]Cargo.toml",
+ "metadata": null,
+ "publish": null,
+ "name": "foo",
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "source": null,
+ "targets": [
+ {
+ "crate_types": [
+ "bin"
+ ],
+ "doc": true,
+ "doctest": false,
+ "test": true,
+ "edition": "2015",
+ "kind": [
+ "bin"
+ ],
+ "name": "foo",
+ "src_path": "[..]src/foo.rs"
+ }
+ ],
+ "version": "0.5.0"
+ },
+ {
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "dependencies": [],
+ "description": null,
+ "edition": "2015",
+ "features": {},
+ "id": "foobar 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "keywords": [],
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "manifest_path": "[..]Cargo.toml",
+ "metadata": null,
+ "publish": null,
+ "name": "foobar",
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "source": "registry+https://github.com/rust-lang/crates.io-index",
+ "targets": [
+ {
+ "crate_types": [
+ "lib"
+ ],
+ "doc": true,
+ "doctest": true,
+ "test": true,
+ "edition": "2015",
+ "kind": [
+ "lib"
+ ],
+ "name": "foobar",
+ "src_path": "[..]src/lib.rs"
+ }
+ ],
+ "version": "0.0.1"
+ }
+ ],
+ "resolve": {
+ "nodes": [
+ {
+ "dependencies": [
+ "baz 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)"
+ ],
+ "deps": [
+ {
+ "dep_kinds": [
+ {
+ "kind": null,
+ "target": null
+ }
+ ],
+ "name": "baz",
+ "pkg": "baz 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)"
+ }
+ ],
+ "features": [],
+ "id": "bar 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)"
+ },
+ {
+ "dependencies": [],
+ "deps": [],
+ "features": [],
+ "id": "baz 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)"
+ },
+ {
+ "dependencies": [
+ "bar 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "foobar 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)"
+ ],
+ "deps": [
+ {
+ "dep_kinds": [
+ {
+ "kind": null,
+ "target": null
+ }
+ ],
+ "name": "bar",
+ "pkg": "bar 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)"
+ },
+ {
+ "dep_kinds": [
+ {
+ "kind": "dev",
+ "target": null
+ }
+ ],
+ "name": "foobar",
+ "pkg": "foobar 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)"
+ }
+ ],
+ "features": [],
+ "id": "foo 0.5.0 (path+file:[..]foo)"
+ },
+ {
+ "dependencies": [],
+ "deps": [],
+ "features": [],
+ "id": "foobar 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)"
+ }
+ ],
+ "root": "foo 0.5.0 (path+file:[..]foo)"
+ },
+ "target_directory": "[..]foo/target",
+ "version": 1,
+ "workspace_members": [
+ "foo 0.5.0 (path+file:[..]foo)"
+ ],
+ "workspace_root": "[..]/foo",
+ "metadata": null
+ }"#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn example() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file("examples/ex.rs", "")
+ .file(
+ "Cargo.toml",
+ r#"
+[package]
+name = "foo"
+version = "0.1.0"
+
+[[example]]
+name = "ex"
+ "#,
+ )
+ .build();
+
+ p.cargo("metadata")
+ .with_json(
+ r#"
+ {
+ "packages": [
+ {
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "name": "foo",
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "version": "0.1.0",
+ "id": "foo[..]",
+ "keywords": [],
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "description": null,
+ "edition": "2015",
+ "source": null,
+ "dependencies": [],
+ "targets": [
+ {
+ "kind": [ "lib" ],
+ "crate_types": [ "lib" ],
+ "doc": true,
+ "doctest": true,
+ "test": true,
+ "edition": "2015",
+ "name": "foo",
+ "src_path": "[..]/foo/src/lib.rs"
+ },
+ {
+ "kind": [ "example" ],
+ "crate_types": [ "bin" ],
+ "doc": false,
+ "doctest": false,
+ "test": false,
+ "edition": "2015",
+ "name": "ex",
+ "src_path": "[..]/foo/examples/ex.rs"
+ }
+ ],
+ "features": {},
+ "manifest_path": "[..]Cargo.toml",
+ "metadata": null,
+ "publish": null
+ }
+ ],
+ "workspace_members": [
+ "foo 0.1.0 (path+file:[..]foo)"
+ ],
+ "resolve": {
+ "root": "foo 0.1.0 (path+file://[..]foo)",
+ "nodes": [
+ {
+ "id": "foo 0.1.0 (path+file:[..]foo)",
+ "features": [],
+ "dependencies": [],
+ "deps": []
+ }
+ ]
+ },
+ "target_directory": "[..]foo/target",
+ "version": 1,
+ "workspace_root": "[..]/foo",
+ "metadata": null
+ }"#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn example_lib() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file("examples/ex.rs", "")
+ .file(
+ "Cargo.toml",
+ r#"
+[package]
+name = "foo"
+version = "0.1.0"
+
+[[example]]
+name = "ex"
+crate-type = ["rlib", "dylib"]
+ "#,
+ )
+ .build();
+
+ p.cargo("metadata")
+ .with_json(
+ r#"
+ {
+ "packages": [
+ {
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "name": "foo",
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "version": "0.1.0",
+ "id": "foo[..]",
+ "keywords": [],
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "description": null,
+ "edition": "2015",
+ "source": null,
+ "dependencies": [],
+ "targets": [
+ {
+ "kind": [ "lib" ],
+ "crate_types": [ "lib" ],
+ "doc": true,
+ "doctest": true,
+ "test": true,
+ "edition": "2015",
+ "name": "foo",
+ "src_path": "[..]/foo/src/lib.rs"
+ },
+ {
+ "kind": [ "example" ],
+ "crate_types": [ "rlib", "dylib" ],
+ "doc": false,
+ "doctest": false,
+ "test": false,
+ "edition": "2015",
+ "name": "ex",
+ "src_path": "[..]/foo/examples/ex.rs"
+ }
+ ],
+ "features": {},
+ "manifest_path": "[..]Cargo.toml",
+ "metadata": null,
+ "publish": null
+ }
+ ],
+ "workspace_members": [
+ "foo 0.1.0 (path+file:[..]foo)"
+ ],
+ "resolve": {
+ "root": "foo 0.1.0 (path+file://[..]foo)",
+ "nodes": [
+ {
+ "id": "foo 0.1.0 (path+file:[..]foo)",
+ "features": [],
+ "dependencies": [],
+ "deps": []
+ }
+ ]
+ },
+ "target_directory": "[..]foo/target",
+ "version": 1,
+ "workspace_root": "[..]/foo",
+ "metadata": null
+ }"#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn workspace_metadata() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar", "baz"]
+
+ [workspace.metadata]
+ tool1 = "hello"
+ tool2 = [1, 2, 3]
+
+ [workspace.metadata.foo]
+ bar = 3
+
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_lib_manifest("bar"))
+ .file("bar/src/lib.rs", "")
+ .file("baz/Cargo.toml", &basic_lib_manifest("baz"))
+ .file("baz/src/lib.rs", "")
+ .build();
+
+ p.cargo("metadata")
+ .with_json(
+ r#"
+ {
+ "packages": [
+ {
+ "authors": [
+ "wycats@example.com"
+ ],
+ "categories": [],
+ "default_run": null,
+ "name": "bar",
+ "version": "0.5.0",
+ "id": "bar[..]",
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "keywords": [],
+ "source": null,
+ "dependencies": [],
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "description": null,
+ "edition": "2015",
+ "targets": [
+ {
+ "kind": [ "lib" ],
+ "crate_types": [ "lib" ],
+ "doc": true,
+ "doctest": true,
+ "test": true,
+ "edition": "2015",
+ "name": "bar",
+ "src_path": "[..]bar/src/lib.rs"
+ }
+ ],
+ "features": {},
+ "manifest_path": "[..]bar/Cargo.toml",
+ "metadata": null,
+ "publish": null
+ },
+ {
+ "authors": [
+ "wycats@example.com"
+ ],
+ "categories": [],
+ "default_run": null,
+ "name": "baz",
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "version": "0.5.0",
+ "id": "baz[..]",
+ "keywords": [],
+ "source": null,
+ "dependencies": [],
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "description": null,
+ "edition": "2015",
+ "targets": [
+ {
+ "kind": [ "lib" ],
+ "crate_types": [ "lib" ],
+ "doc": true,
+ "doctest": true,
+ "test": true,
+ "edition": "2015",
+ "name": "baz",
+ "src_path": "[..]baz/src/lib.rs"
+ }
+ ],
+ "features": {},
+ "manifest_path": "[..]baz/Cargo.toml",
+ "metadata": null,
+ "publish": null
+ }
+ ],
+ "workspace_members": ["bar 0.5.0 (path+file:[..]bar)", "baz 0.5.0 (path+file:[..]baz)"],
+ "resolve": {
+ "nodes": [
+ {
+ "dependencies": [],
+ "deps": [],
+ "features": [],
+ "id": "bar 0.5.0 (path+file:[..]bar)"
+ },
+ {
+ "dependencies": [],
+ "deps": [],
+ "features": [],
+ "id": "baz 0.5.0 (path+file:[..]baz)"
+ }
+ ],
+ "root": null
+ },
+ "target_directory": "[..]foo/target",
+ "version": 1,
+ "workspace_root": "[..]/foo",
+ "metadata": {
+ "tool1": "hello",
+ "tool2": [1, 2, 3],
+ "foo": {
+ "bar": 3
+ }
+ }
+ }"#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn workspace_metadata_with_dependencies_no_deps() {
+ let p = project()
+ // NOTE that 'artifact' isn't mentioned in the workspace here, yet it shows up as member.
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar", "baz"]
+ "#,
+ )
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+
+ name = "bar"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies]
+ baz = { path = "../baz/" }
+ artifact = { path = "../artifact/", artifact = "bin" }
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .file("baz/Cargo.toml", &basic_lib_manifest("baz"))
+ .file("baz/src/lib.rs", "")
+ .file("artifact/Cargo.toml", &basic_bin_manifest("artifact"))
+ .file("artifact/src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("metadata --no-deps -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_json(
+ r#"
+ {
+ "packages": [
+ {
+ "authors": [
+ "wycats@example.com"
+ ],
+ "categories": [],
+ "default_run": null,
+ "name": "bar",
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "version": "0.5.0",
+ "id": "bar[..]",
+ "keywords": [],
+ "source": null,
+ "license": null,
+ "dependencies": [
+ {
+ "features": [],
+ "kind": null,
+ "name": "artifact",
+ "optional": false,
+ "path": "[..]/foo/artifact",
+ "registry": null,
+ "rename": null,
+ "req": "*",
+ "source": null,
+ "target": null,
+ "uses_default_features": true,
+ "artifact": {
+ "kinds": [
+ "bin"
+ ],
+ "lib": false,
+ "target": null
+ }
+ },
+ {
+ "features": [],
+ "kind": null,
+ "name": "baz",
+ "optional": false,
+ "path": "[..]/foo/baz",
+ "registry": null,
+ "rename": null,
+ "req": "*",
+ "source": null,
+ "target": null,
+ "uses_default_features": true
+ }
+ ],
+ "license_file": null,
+ "links": null,
+ "description": null,
+ "edition": "2015",
+ "targets": [
+ {
+ "kind": [ "lib" ],
+ "crate_types": [ "lib" ],
+ "doc": true,
+ "doctest": true,
+ "test": true,
+ "edition": "2015",
+ "name": "bar",
+ "src_path": "[..]bar/src/lib.rs"
+ }
+ ],
+ "features": {},
+ "manifest_path": "[..]bar/Cargo.toml",
+ "metadata": null,
+ "publish": null
+ },
+ {
+ "authors": [
+ "wycats@example.com"
+ ],
+ "categories": [],
+ "default_run": null,
+ "dependencies": [],
+ "description": null,
+ "documentation": null,
+ "edition": "2015",
+ "features": {},
+ "homepage": null,
+ "id": "artifact 0.5.0 (path+file:[..]/foo/artifact)",
+ "keywords": [],
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "manifest_path": "[..]/foo/artifact/Cargo.toml",
+ "metadata": null,
+ "name": "artifact",
+ "publish": null,
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "source": null,
+ "targets": [
+ {
+ "crate_types": [
+ "bin"
+ ],
+ "doc": true,
+ "doctest": false,
+ "edition": "2015",
+ "kind": [
+ "bin"
+ ],
+ "name": "artifact",
+ "src_path": "[..]/foo/artifact/src/main.rs",
+ "test": true
+ }
+ ],
+ "version": "0.5.0"
+ },
+ {
+ "authors": [
+ "wycats@example.com"
+ ],
+ "categories": [],
+ "default_run": null,
+ "name": "baz",
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "version": "0.5.0",
+ "id": "baz[..]",
+ "keywords": [],
+ "source": null,
+ "dependencies": [],
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "description": null,
+ "edition": "2015",
+ "targets": [
+ {
+ "kind": [ "lib" ],
+ "crate_types": ["lib"],
+ "doc": true,
+ "doctest": true,
+ "test": true,
+ "edition": "2015",
+ "name": "baz",
+ "src_path": "[..]baz/src/lib.rs"
+ }
+ ],
+ "features": {},
+ "manifest_path": "[..]baz/Cargo.toml",
+ "metadata": null,
+ "publish": null
+ }
+ ],
+ "workspace_members": [
+ "bar 0.5.0 (path+file:[..]bar)",
+ "artifact 0.5.0 (path+file:[..]/foo/artifact)",
+ "baz 0.5.0 (path+file:[..]baz)"
+ ],
+ "resolve": null,
+ "target_directory": "[..]foo/target",
+ "version": 1,
+ "workspace_root": "[..]/foo",
+ "metadata": null
+ }"#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn workspace_metadata_with_dependencies_and_resolve() {
+ let alt_target = "wasm32-unknown-unknown";
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar", "artifact", "non-artifact", "bin-only-artifact"]
+ "#,
+ )
+ .file(
+ "bar/Cargo.toml",
+ &r#"
+ [package]
+
+ name = "bar"
+ version = "0.5.0"
+ authors = []
+
+ [build-dependencies]
+ artifact = { path = "../artifact/", artifact = "bin", target = "target" }
+ bin-only-artifact = { path = "../bin-only-artifact/", artifact = "bin", target = "$ALT_TARGET" }
+ non-artifact = { path = "../non-artifact" }
+
+ [dependencies]
+ artifact = { path = "../artifact/", artifact = ["cdylib", "staticlib", "bin:baz-name"], lib = true, target = "$ALT_TARGET" }
+ bin-only-artifact = { path = "../bin-only-artifact/", artifact = "bin:a-name" }
+ non-artifact = { path = "../non-artifact" }
+
+ [dev-dependencies]
+ artifact = { path = "../artifact/" }
+ non-artifact = { path = "../non-artifact" }
+ bin-only-artifact = { path = "../bin-only-artifact/", artifact = "bin:b-name" }
+ "#.replace("$ALT_TARGET", alt_target),
+ )
+ .file("bar/src/lib.rs", "")
+ .file("bar/build.rs", "fn main() {}")
+ .file(
+ "artifact/Cargo.toml",
+ r#"
+ [package]
+ name = "artifact"
+ version = "0.5.0"
+ authors = []
+
+ [lib]
+ crate-type = ["staticlib", "cdylib", "rlib"]
+
+ [[bin]]
+ name = "bar-name"
+
+ [[bin]]
+ name = "baz-name"
+ "#,
+ )
+ .file("artifact/src/main.rs", "fn main() {}")
+ .file("artifact/src/lib.rs", "")
+ .file(
+ "bin-only-artifact/Cargo.toml",
+ r#"
+ [package]
+ name = "bin-only-artifact"
+ version = "0.5.0"
+ authors = []
+
+ [[bin]]
+ name = "a-name"
+
+ [[bin]]
+ name = "b-name"
+ "#,
+ )
+ .file("bin-only-artifact/src/main.rs", "fn main() {}")
+ .file("non-artifact/Cargo.toml",
+ r#"
+ [package]
+
+ name = "non-artifact"
+ version = "0.5.0"
+ authors = []
+ "#,
+ )
+ .file("non-artifact/src/lib.rs", "")
+ .build();
+
+ p.cargo("metadata -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_json(
+ r#"
+ {
+ "metadata": null,
+ "packages": [
+ {
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "dependencies": [],
+ "description": null,
+ "documentation": null,
+ "edition": "2015",
+ "features": {},
+ "homepage": null,
+ "id": "artifact 0.5.0 (path+file://[..]/foo/artifact)",
+ "keywords": [],
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "manifest_path": "[..]/foo/artifact/Cargo.toml",
+ "metadata": null,
+ "name": "artifact",
+ "publish": null,
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "source": null,
+ "targets": [
+ {
+ "crate_types": [
+ "staticlib",
+ "cdylib",
+ "rlib"
+ ],
+ "doc": true,
+ "doctest": true,
+ "edition": "2015",
+ "kind": [
+ "staticlib",
+ "cdylib",
+ "rlib"
+ ],
+ "name": "artifact",
+ "src_path": "[..]/foo/artifact/src/lib.rs",
+ "test": true
+ },
+ {
+ "crate_types": [
+ "bin"
+ ],
+ "doc": true,
+ "doctest": false,
+ "edition": "2015",
+ "kind": [
+ "bin"
+ ],
+ "name": "bar-name",
+ "src_path": "[..]/foo/artifact/src/main.rs",
+ "test": true
+ },
+ {
+ "crate_types": [
+ "bin"
+ ],
+ "doc": true,
+ "doctest": false,
+ "edition": "2015",
+ "kind": [
+ "bin"
+ ],
+ "name": "baz-name",
+ "src_path": "[..]/foo/artifact/src/main.rs",
+ "test": true
+ }
+ ],
+ "version": "0.5.0"
+ },
+ {
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "dependencies": [
+ {
+ "artifact": {
+ "kinds": [
+ "cdylib",
+ "staticlib",
+ "bin:baz-name"
+ ],
+ "lib": true,
+ "target": "wasm32-unknown-unknown"
+ },
+ "features": [],
+ "kind": null,
+ "name": "artifact",
+ "optional": false,
+ "path": "[..]/foo/artifact",
+ "registry": null,
+ "rename": null,
+ "req": "*",
+ "source": null,
+ "target": null,
+ "uses_default_features": true
+ },
+ {
+ "artifact": {
+ "kinds": [
+ "bin:a-name"
+ ],
+ "lib": false,
+ "target": null
+ },
+ "features": [],
+ "kind": null,
+ "name": "bin-only-artifact",
+ "optional": false,
+ "path": "[..]/foo/bin-only-artifact",
+ "registry": null,
+ "rename": null,
+ "req": "*",
+ "source": null,
+ "target": null,
+ "uses_default_features": true
+ },
+ {
+ "features": [],
+ "kind": null,
+ "name": "non-artifact",
+ "optional": false,
+ "path": "[..]/foo/non-artifact",
+ "registry": null,
+ "rename": null,
+ "req": "*",
+ "source": null,
+ "target": null,
+ "uses_default_features": true
+ },
+ {
+ "features": [],
+ "kind": "dev",
+ "name": "artifact",
+ "optional": false,
+ "path": "[..]/foo/artifact",
+ "registry": null,
+ "rename": null,
+ "req": "*",
+ "source": null,
+ "target": null,
+ "uses_default_features": true
+ },
+ {
+ "artifact": {
+ "kinds": [
+ "bin:b-name"
+ ],
+ "lib": false,
+ "target": null
+ },
+ "features": [],
+ "kind": "dev",
+ "name": "bin-only-artifact",
+ "optional": false,
+ "path": "[..]/foo/bin-only-artifact",
+ "registry": null,
+ "rename": null,
+ "req": "*",
+ "source": null,
+ "target": null,
+ "uses_default_features": true
+ },
+ {
+ "features": [],
+ "kind": "dev",
+ "name": "non-artifact",
+ "optional": false,
+ "path": "[..]/foo/non-artifact",
+ "registry": null,
+ "rename": null,
+ "req": "*",
+ "source": null,
+ "target": null,
+ "uses_default_features": true
+ },
+ {
+ "artifact": {
+ "kinds": [
+ "bin"
+ ],
+ "lib": false,
+ "target": "target"
+ },
+ "features": [],
+ "kind": "build",
+ "name": "artifact",
+ "optional": false,
+ "path": "[..]/foo/artifact",
+ "registry": null,
+ "rename": null,
+ "req": "*",
+ "source": null,
+ "target": null,
+ "uses_default_features": true
+ },
+ {
+ "artifact": {
+ "kinds": [
+ "bin"
+ ],
+ "lib": false,
+ "target": "wasm32-unknown-unknown"
+ },
+ "features": [],
+ "kind": "build",
+ "name": "bin-only-artifact",
+ "optional": false,
+ "path": "[..]/foo/bin-only-artifact",
+ "registry": null,
+ "rename": null,
+ "req": "*",
+ "source": null,
+ "target": null,
+ "uses_default_features": true
+ },
+ {
+ "features": [],
+ "kind": "build",
+ "name": "non-artifact",
+ "optional": false,
+ "path": "[..]/foo/non-artifact",
+ "registry": null,
+ "rename": null,
+ "req": "*",
+ "source": null,
+ "target": null,
+ "uses_default_features": true
+ }
+ ],
+ "description": null,
+ "documentation": null,
+ "edition": "2015",
+ "features": {},
+ "homepage": null,
+ "id": "bar 0.5.0 (path+file://[..]/foo/bar)",
+ "keywords": [],
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "manifest_path": "[..]/foo/bar/Cargo.toml",
+ "metadata": null,
+ "name": "bar",
+ "publish": null,
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "source": null,
+ "targets": [
+ {
+ "crate_types": [
+ "lib"
+ ],
+ "doc": true,
+ "doctest": true,
+ "edition": "2015",
+ "kind": [
+ "lib"
+ ],
+ "name": "bar",
+ "src_path": "[..]/foo/bar/src/lib.rs",
+ "test": true
+ },
+ {
+ "crate_types": [
+ "bin"
+ ],
+ "doc": false,
+ "doctest": false,
+ "edition": "2015",
+ "kind": [
+ "custom-build"
+ ],
+ "name": "build-script-build",
+ "src_path": "[..]/foo/bar/build.rs",
+ "test": false
+ }
+ ],
+ "version": "0.5.0"
+ },
+ {
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "dependencies": [],
+ "description": null,
+ "documentation": null,
+ "edition": "2015",
+ "features": {},
+ "homepage": null,
+ "id": "bin-only-artifact 0.5.0 (path+file://[..]/foo/bin-only-artifact)",
+ "keywords": [],
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "manifest_path": "[..]/foo/bin-only-artifact/Cargo.toml",
+ "metadata": null,
+ "name": "bin-only-artifact",
+ "publish": null,
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "source": null,
+ "targets": [
+ {
+ "crate_types": [
+ "bin"
+ ],
+ "doc": true,
+ "doctest": false,
+ "edition": "2015",
+ "kind": [
+ "bin"
+ ],
+ "name": "a-name",
+ "src_path": "[..]/foo/bin-only-artifact/src/main.rs",
+ "test": true
+ },
+ {
+ "crate_types": [
+ "bin"
+ ],
+ "doc": true,
+ "doctest": false,
+ "edition": "2015",
+ "kind": [
+ "bin"
+ ],
+ "name": "b-name",
+ "src_path": "[..]/foo/bin-only-artifact/src/main.rs",
+ "test": true
+ }
+ ],
+ "version": "0.5.0"
+ },
+ {
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "dependencies": [],
+ "description": null,
+ "documentation": null,
+ "edition": "2015",
+ "features": {},
+ "homepage": null,
+ "id": "non-artifact 0.5.0 (path+file://[..]/foo/non-artifact)",
+ "keywords": [],
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "manifest_path": "[..]/foo/non-artifact/Cargo.toml",
+ "metadata": null,
+ "name": "non-artifact",
+ "publish": null,
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "source": null,
+ "targets": [
+ {
+ "crate_types": [
+ "lib"
+ ],
+ "doc": true,
+ "doctest": true,
+ "edition": "2015",
+ "kind": [
+ "lib"
+ ],
+ "name": "non-artifact",
+ "src_path": "[..]/foo/non-artifact/src/lib.rs",
+ "test": true
+ }
+ ],
+ "version": "0.5.0"
+ }
+ ],
+ "resolve": {
+ "nodes": [
+ {
+ "dependencies": [],
+ "deps": [],
+ "features": [],
+ "id": "artifact 0.5.0 (path+file://[..]/foo/artifact)"
+ },
+ {
+ "dependencies": [
+ "artifact 0.5.0 (path+file://[..]/foo/artifact)",
+ "bin-only-artifact 0.5.0 (path+file://[..]/foo/bin-only-artifact)",
+ "non-artifact 0.5.0 (path+file://[..]/foo/non-artifact)"
+ ],
+ "deps": [
+ {
+ "dep_kinds": [
+ {
+ "extern_name": "artifact",
+ "kind": null,
+ "target": null
+ },
+ {
+ "artifact": "cdylib",
+ "compile_target": "wasm32-unknown-unknown",
+ "extern_name": "artifact",
+ "kind": null,
+ "target": null
+ },
+ {
+ "artifact": "staticlib",
+ "compile_target": "wasm32-unknown-unknown",
+ "extern_name": "artifact",
+ "kind": null,
+ "target": null
+ },
+ {
+ "artifact": "bin",
+ "bin_name": "baz-name",
+ "compile_target": "wasm32-unknown-unknown",
+ "extern_name": "baz_name",
+ "kind": null,
+ "target": null
+ },
+ {
+ "kind": "dev",
+ "target": null
+ },
+ {
+ "artifact": "bin",
+ "bin_name": "bar-name",
+ "compile_target": "<target>",
+ "extern_name": "bar_name",
+ "kind": "build",
+ "target": null
+ },
+ {
+ "artifact": "bin",
+ "bin_name": "baz-name",
+ "compile_target": "<target>",
+ "extern_name": "baz_name",
+ "kind": "build",
+ "target": null
+ }
+ ],
+ "name": "artifact",
+ "pkg": "artifact 0.5.0 (path+file://[..]/foo/artifact)"
+ },
+ {
+ "dep_kinds": [
+ {
+ "artifact": "bin",
+ "bin_name": "a-name",
+ "extern_name": "a_name",
+ "kind": null,
+ "target": null
+ },
+ {
+ "artifact": "bin",
+ "bin_name": "b-name",
+ "extern_name": "b_name",
+ "kind": "dev",
+ "target": null
+ },
+ {
+ "artifact": "bin",
+ "bin_name": "a-name",
+ "compile_target": "wasm32-unknown-unknown",
+ "extern_name": "a_name",
+ "kind": "build",
+ "target": null
+ },
+ {
+ "artifact": "bin",
+ "bin_name": "b-name",
+ "compile_target": "wasm32-unknown-unknown",
+ "extern_name": "b_name",
+ "kind": "build",
+ "target": null
+ }
+ ],
+ "name": "",
+ "pkg": "bin-only-artifact 0.5.0 (path+file://[..]/foo/bin-only-artifact)"
+ },
+ {
+ "dep_kinds": [
+ {
+ "kind": null,
+ "target": null
+ },
+ {
+ "kind": "dev",
+ "target": null
+ },
+ {
+ "kind": "build",
+ "target": null
+ }
+ ],
+ "name": "non_artifact",
+ "pkg": "non-artifact 0.5.0 (path+file://[..]/foo/non-artifact)"
+ }
+ ],
+ "features": [],
+ "id": "bar 0.5.0 (path+file://[..]/foo/bar)"
+ },
+ {
+ "dependencies": [],
+ "deps": [],
+ "features": [],
+ "id": "bin-only-artifact 0.5.0 (path+file://[..]/foo/bin-only-artifact)"
+ },
+ {
+ "dependencies": [],
+ "deps": [],
+ "features": [],
+ "id": "non-artifact 0.5.0 (path+file://[..]/foo/non-artifact)"
+ }
+ ],
+ "root": null
+ },
+ "target_directory": "[..]/foo/target",
+ "version": 1,
+ "workspace_members": [
+ "bar 0.5.0 (path+file://[..]/foo/bar)",
+ "artifact 0.5.0 (path+file://[..]/foo/artifact)",
+ "bin-only-artifact 0.5.0 (path+file://[..]/foo/bin-only-artifact)",
+ "non-artifact 0.5.0 (path+file://[..]/foo/non-artifact)"
+ ],
+ "workspace_root": "[..]/foo"
+ }
+ "#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_metadata_with_invalid_manifest() {
+ let p = project().file("Cargo.toml", "").build();
+
+ p.cargo("metadata --format-version 1")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ virtual manifests must be configured with [workspace]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_metadata_with_invalid_authors_field() {
+ let p = project()
+ .file("src/foo.rs", "")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ authors = ""
+ "#,
+ )
+ .build();
+
+ p.cargo("metadata")
+ .with_status(101)
+ .with_stderr(
+ r#"[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ invalid type: string "", expected a vector of strings or workspace
+ in `package.authors`"#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_metadata_with_invalid_version_field() {
+ let p = project()
+ .file("src/foo.rs", "")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ version = 1
+ "#,
+ )
+ .build();
+
+ p.cargo("metadata")
+ .with_status(101)
+ .with_stderr(
+ r#"[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ invalid type: integer `1`, expected SemVer version
+ in `package.version`"#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_metadata_with_invalid_publish_field() {
+ let p = project()
+ .file("src/foo.rs", "")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ publish = "foo"
+ "#,
+ )
+ .build();
+
+ p.cargo("metadata")
+ .with_status(101)
+ .with_stderr(
+ r#"[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ invalid type: string "foo", expected a boolean, a vector of strings, or workspace
+ in `package.publish`"#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_metadata_with_invalid_artifact_deps() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+
+ [dependencies]
+ artifact = { path = "artifact", artifact = "bin:notfound" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("artifact/Cargo.toml", &basic_bin_manifest("artifact"))
+ .file("artifact/src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("metadata -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_status(101)
+ .with_stderr(
+ "\
+[WARNING] please specify `--format-version` flag explicitly to avoid compatibility problems
+[ERROR] dependency `artifact` in package `foo` requires a `bin:notfound` artifact to be present.",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_metadata_with_invalid_duplicate_renamed_deps() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+
+ [dependencies]
+ bar = { path = "bar" }
+ baz = { path = "bar", package = "bar" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_lib_manifest("bar"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("metadata")
+ .with_status(101)
+ .with_stderr(
+ "\
+[WARNING] please specify `--format-version` flag explicitly to avoid compatibility problems
+[ERROR] the crate `foo v0.5.0 ([..])` depends on crate `bar v0.5.0 ([..])` multiple times with different names",
+ )
+ .run();
+}
+
+const MANIFEST_OUTPUT: &str = r#"
+{
+ "packages": [{
+ "authors": [
+ "wycats@example.com"
+ ],
+ "categories": [],
+ "default_run": null,
+ "name":"foo",
+ "version":"0.5.0",
+ "id":"foo[..]0.5.0[..](path+file://[..]/foo)",
+ "source":null,
+ "dependencies":[],
+ "keywords": [],
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "description": null,
+ "edition": "2015",
+ "targets":[{
+ "kind":["bin"],
+ "crate_types":["bin"],
+ "doc": true,
+ "doctest": false,
+ "test": true,
+ "edition": "2015",
+ "name":"foo",
+ "src_path":"[..]/foo/src/foo.rs"
+ }],
+ "features":{},
+ "manifest_path":"[..]Cargo.toml",
+ "metadata": null,
+ "publish": null,
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null
+ }],
+ "workspace_members": [ "foo 0.5.0 (path+file:[..]foo)" ],
+ "resolve": null,
+ "target_directory": "[..]foo/target",
+ "version": 1,
+ "workspace_root": "[..]/foo",
+ "metadata": null
+}"#;
+
+#[cargo_test]
+fn cargo_metadata_no_deps_path_to_cargo_toml_relative() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .build();
+
+ p.cargo("metadata --no-deps --manifest-path foo/Cargo.toml")
+ .cwd(p.root().parent().unwrap())
+ .with_json(MANIFEST_OUTPUT)
+ .run();
+}
+
+#[cargo_test]
+fn cargo_metadata_no_deps_path_to_cargo_toml_absolute() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .build();
+
+ p.cargo("metadata --no-deps --manifest-path")
+ .arg(p.root().join("Cargo.toml"))
+ .cwd(p.root().parent().unwrap())
+ .with_json(MANIFEST_OUTPUT)
+ .run();
+}
+
+#[cargo_test]
+fn cargo_metadata_no_deps_path_to_cargo_toml_parent_relative() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .build();
+
+ p.cargo("metadata --no-deps --manifest-path foo")
+ .cwd(p.root().parent().unwrap())
+ .with_status(101)
+ .with_stderr(
+ "[ERROR] the manifest-path must be \
+ a path to a Cargo.toml file",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_metadata_no_deps_path_to_cargo_toml_parent_absolute() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .build();
+
+ p.cargo("metadata --no-deps --manifest-path")
+ .arg(p.root())
+ .cwd(p.root().parent().unwrap())
+ .with_status(101)
+ .with_stderr(
+ "[ERROR] the manifest-path must be \
+ a path to a Cargo.toml file",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_metadata_no_deps_cwd() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .build();
+
+ p.cargo("metadata --no-deps")
+ .with_json(MANIFEST_OUTPUT)
+ .run();
+}
+
+#[cargo_test]
+fn cargo_metadata_bad_version() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .build();
+
+ p.cargo("metadata --no-deps --format-version 2")
+ .with_status(1)
+ .with_stderr_contains(
+ "\
+error: invalid value '2' for '--format-version <VERSION>'
+ [possible values: 1]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn multiple_features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [features]
+ a = []
+ b = []
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("metadata --features").arg("a b").run();
+}
+
+#[cargo_test]
+fn package_metadata() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = ["wycats@example.com"]
+ categories = ["database"]
+ keywords = ["database"]
+ readme = "README.md"
+ repository = "https://github.com/rust-lang/cargo"
+ homepage = "https://rust-lang.org"
+ documentation = "https://doc.rust-lang.org/stable/std/"
+
+ [package.metadata.bar]
+ baz = "quux"
+ "#,
+ )
+ .file("README.md", "")
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("metadata --no-deps")
+ .with_json(
+ r#"
+ {
+ "packages": [
+ {
+ "authors": ["wycats@example.com"],
+ "categories": ["database"],
+ "default_run": null,
+ "name": "foo",
+ "readme": "README.md",
+ "repository": "https://github.com/rust-lang/cargo",
+ "rust_version": null,
+ "homepage": "https://rust-lang.org",
+ "documentation": "https://doc.rust-lang.org/stable/std/",
+ "version": "0.1.0",
+ "id": "foo[..]",
+ "keywords": ["database"],
+ "source": null,
+ "dependencies": [],
+ "edition": "2015",
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "description": null,
+ "targets": [
+ {
+ "kind": [ "lib" ],
+ "crate_types": [ "lib" ],
+ "doc": true,
+ "doctest": true,
+ "test": true,
+ "edition": "2015",
+ "name": "foo",
+ "src_path": "[..]foo/src/lib.rs"
+ }
+ ],
+ "features": {},
+ "manifest_path": "[..]foo/Cargo.toml",
+ "metadata": {
+ "bar": {
+ "baz": "quux"
+ }
+ },
+ "publish": null
+ }
+ ],
+ "workspace_members": ["foo[..]"],
+ "resolve": null,
+ "target_directory": "[..]foo/target",
+ "version": 1,
+ "workspace_root": "[..]/foo",
+ "metadata": null
+ }"#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn package_publish() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = ["wycats@example.com"]
+ categories = ["database"]
+ keywords = ["database"]
+ readme = "README.md"
+ repository = "https://github.com/rust-lang/cargo"
+ publish = ["my-registry"]
+ "#,
+ )
+ .file("README.md", "")
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("metadata --no-deps")
+ .with_json(
+ r#"
+ {
+ "packages": [
+ {
+ "authors": ["wycats@example.com"],
+ "categories": ["database"],
+ "default_run": null,
+ "name": "foo",
+ "readme": "README.md",
+ "repository": "https://github.com/rust-lang/cargo",
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "version": "0.1.0",
+ "id": "foo[..]",
+ "keywords": ["database"],
+ "source": null,
+ "dependencies": [],
+ "edition": "2015",
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "description": null,
+ "targets": [
+ {
+ "kind": [ "lib" ],
+ "crate_types": [ "lib" ],
+ "doc": true,
+ "doctest": true,
+ "test": true,
+ "edition": "2015",
+ "name": "foo",
+ "src_path": "[..]foo/src/lib.rs"
+ }
+ ],
+ "features": {},
+ "manifest_path": "[..]foo/Cargo.toml",
+ "metadata": null,
+ "publish": ["my-registry"]
+ }
+ ],
+ "workspace_members": ["foo[..]"],
+ "resolve": null,
+ "target_directory": "[..]foo/target",
+ "version": 1,
+ "workspace_root": "[..]/foo",
+ "metadata": null
+ }"#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_metadata_path_to_cargo_toml_project() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_lib_manifest("bar"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("package --manifest-path")
+ .arg(p.root().join("bar/Cargo.toml"))
+ .cwd(p.root().parent().unwrap())
+ .run();
+
+ p.cargo("metadata --manifest-path")
+ .arg(p.root().join("target/package/bar-0.5.0/Cargo.toml"))
+ .with_json(
+ r#"
+ {
+ "packages": [
+ {
+ "authors": [
+ "wycats@example.com"
+ ],
+ "categories": [],
+ "default_run": null,
+ "dependencies": [],
+ "description": null,
+ "edition": "2015",
+ "features": {},
+ "id": "bar 0.5.0 ([..])",
+ "keywords": [],
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "manifest_path": "[..]Cargo.toml",
+ "metadata": null,
+ "publish": null,
+ "name": "bar",
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "source": null,
+ "targets": [
+ {
+ "crate_types": [
+ "lib"
+ ],
+ "doc": true,
+ "doctest": true,
+ "test": true,
+ "edition": "2015",
+ "kind": [
+ "lib"
+ ],
+ "name": "bar",
+ "src_path": "[..]src/lib.rs"
+ }
+ ],
+ "version": "0.5.0"
+ }
+ ],
+ "resolve": {
+ "nodes": [
+ {
+ "dependencies": [],
+ "deps": [],
+ "features": [],
+ "id": "bar 0.5.0 ([..])"
+ }
+ ],
+ "root": "bar 0.5.0 (path+file:[..])"
+ },
+ "target_directory": "[..]",
+ "version": 1,
+ "workspace_members": [
+ "bar 0.5.0 (path+file:[..])"
+ ],
+ "workspace_root": "[..]",
+ "metadata": null
+ }
+ "#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn package_edition_2018() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = ["wycats@example.com"]
+ edition = "2018"
+ "#,
+ )
+ .build();
+ p.cargo("metadata")
+ .with_json(
+ r#"
+ {
+ "packages": [
+ {
+ "authors": [
+ "wycats@example.com"
+ ],
+ "categories": [],
+ "default_run": null,
+ "dependencies": [],
+ "description": null,
+ "edition": "2018",
+ "features": {},
+ "id": "foo 0.1.0 (path+file:[..])",
+ "keywords": [],
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "manifest_path": "[..]Cargo.toml",
+ "metadata": null,
+ "publish": null,
+ "name": "foo",
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "source": null,
+ "targets": [
+ {
+ "crate_types": [
+ "lib"
+ ],
+ "doc": true,
+ "doctest": true,
+ "test": true,
+ "edition": "2018",
+ "kind": [
+ "lib"
+ ],
+ "name": "foo",
+ "src_path": "[..]src/lib.rs"
+ }
+ ],
+ "version": "0.1.0"
+ }
+ ],
+ "resolve": {
+ "nodes": [
+ {
+ "dependencies": [],
+ "deps": [],
+ "features": [],
+ "id": "foo 0.1.0 (path+file:[..])"
+ }
+ ],
+ "root": "foo 0.1.0 (path+file:[..])"
+ },
+ "target_directory": "[..]",
+ "version": 1,
+ "workspace_members": [
+ "foo 0.1.0 (path+file:[..])"
+ ],
+ "workspace_root": "[..]",
+ "metadata": null
+ }
+ "#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn package_default_run() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file("src/bin/a.rs", r#"fn main() { println!("hello A"); }"#)
+ .file("src/bin/b.rs", r#"fn main() { println!("hello B"); }"#)
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = ["wycats@example.com"]
+ edition = "2018"
+ default-run = "a"
+ "#,
+ )
+ .build();
+ let json = p.cargo("metadata").run_json();
+ assert_eq!(json["packages"][0]["default_run"], json!("a"));
+}
+
+#[cargo_test]
+fn package_rust_version() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = ["wycats@example.com"]
+ edition = "2018"
+ rust-version = "1.56"
+ "#,
+ )
+ .build();
+ let json = p.cargo("metadata").run_json();
+ assert_eq!(json["packages"][0]["rust_version"], json!("1.56"));
+}
+
+#[cargo_test]
+fn target_edition_2018() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file("src/main.rs", "")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = ["wycats@example.com"]
+ edition = "2015"
+
+ [lib]
+ edition = "2018"
+ "#,
+ )
+ .build();
+ p.cargo("metadata")
+ .with_json(
+ r#"
+ {
+ "packages": [
+ {
+ "authors": [
+ "wycats@example.com"
+ ],
+ "categories": [],
+ "default_run": null,
+ "dependencies": [],
+ "description": null,
+ "edition": "2015",
+ "features": {},
+ "id": "foo 0.1.0 (path+file:[..])",
+ "keywords": [],
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "manifest_path": "[..]Cargo.toml",
+ "metadata": null,
+ "publish": null,
+ "name": "foo",
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "source": null,
+ "targets": [
+ {
+ "crate_types": [
+ "lib"
+ ],
+ "doc": true,
+ "doctest": true,
+ "test": true,
+ "edition": "2018",
+ "kind": [
+ "lib"
+ ],
+ "name": "foo",
+ "src_path": "[..]src/lib.rs"
+ },
+ {
+ "crate_types": [
+ "bin"
+ ],
+ "doc": true,
+ "doctest": false,
+ "test": true,
+ "edition": "2015",
+ "kind": [
+ "bin"
+ ],
+ "name": "foo",
+ "src_path": "[..]src/main.rs"
+ }
+ ],
+ "version": "0.1.0"
+ }
+ ],
+ "resolve": {
+ "nodes": [
+ {
+ "dependencies": [],
+ "deps": [],
+ "features": [],
+ "id": "foo 0.1.0 (path+file:[..])"
+ }
+ ],
+ "root": "foo 0.1.0 (path+file:[..])"
+ },
+ "target_directory": "[..]",
+ "version": 1,
+ "workspace_members": [
+ "foo 0.1.0 (path+file:[..])"
+ ],
+ "workspace_root": "[..]",
+ "metadata": null
+ }
+ "#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rename_dependency() {
+ Package::new("bar", "0.1.0").publish();
+ Package::new("bar", "0.2.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = { version = "0.1.0" }
+ baz = { version = "0.2.0", package = "bar" }
+ "#,
+ )
+ .file("src/lib.rs", "extern crate bar; extern crate baz;")
+ .build();
+
+ p.cargo("metadata")
+ .with_json(
+ r#"
+{
+ "packages": [
+ {
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "dependencies": [],
+ "description": null,
+ "edition": "2015",
+ "features": {},
+ "id": "bar 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "keywords": [],
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "manifest_path": "[..]",
+ "metadata": null,
+ "publish": null,
+ "name": "bar",
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "source": "registry+https://github.com/rust-lang/crates.io-index",
+ "targets": [
+ {
+ "crate_types": [
+ "lib"
+ ],
+ "doc": true,
+ "doctest": true,
+ "test": true,
+ "edition": "2015",
+ "kind": [
+ "lib"
+ ],
+ "name": "bar",
+ "src_path": "[..]"
+ }
+ ],
+ "version": "0.1.0"
+ },
+ {
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "dependencies": [],
+ "description": null,
+ "edition": "2015",
+ "features": {},
+ "id": "bar 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "keywords": [],
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "manifest_path": "[..]",
+ "metadata": null,
+ "publish": null,
+ "name": "bar",
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "source": "registry+https://github.com/rust-lang/crates.io-index",
+ "targets": [
+ {
+ "crate_types": [
+ "lib"
+ ],
+ "doc": true,
+ "doctest": true,
+ "test": true,
+ "edition": "2015",
+ "kind": [
+ "lib"
+ ],
+ "name": "bar",
+ "src_path": "[..]"
+ }
+ ],
+ "version": "0.2.0"
+ },
+ {
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "dependencies": [
+ {
+ "features": [],
+ "kind": null,
+ "name": "bar",
+ "optional": false,
+ "rename": null,
+ "registry": null,
+ "req": "^0.1.0",
+ "source": "registry+https://github.com/rust-lang/crates.io-index",
+ "target": null,
+ "uses_default_features": true
+ },
+ {
+ "features": [],
+ "kind": null,
+ "name": "bar",
+ "optional": false,
+ "rename": "baz",
+ "registry": null,
+ "req": "^0.2.0",
+ "source": "registry+https://github.com/rust-lang/crates.io-index",
+ "target": null,
+ "uses_default_features": true
+ }
+ ],
+ "description": null,
+ "edition": "2015",
+ "features": {},
+ "id": "foo 0.0.1[..]",
+ "keywords": [],
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "manifest_path": "[..]",
+ "metadata": null,
+ "publish": null,
+ "name": "foo",
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "source": null,
+ "targets": [
+ {
+ "crate_types": [
+ "lib"
+ ],
+ "doc": true,
+ "doctest": true,
+ "test": true,
+ "edition": "2015",
+ "kind": [
+ "lib"
+ ],
+ "name": "foo",
+ "src_path": "[..]"
+ }
+ ],
+ "version": "0.0.1"
+ }
+ ],
+ "resolve": {
+ "nodes": [
+ {
+ "dependencies": [],
+ "deps": [],
+ "features": [],
+ "id": "bar 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)"
+ },
+ {
+ "dependencies": [],
+ "deps": [],
+ "features": [],
+ "id": "bar 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)"
+ },
+ {
+ "dependencies": [
+ "bar 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bar 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)"
+ ],
+ "deps": [
+ {
+ "dep_kinds": [
+ {
+ "kind": null,
+ "target": null
+ }
+ ],
+ "name": "bar",
+ "pkg": "bar 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)"
+ },
+ {
+ "dep_kinds": [
+ {
+ "kind": null,
+ "target": null
+ }
+ ],
+ "name": "baz",
+ "pkg": "bar 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)"
+ }
+ ],
+ "features": [],
+ "id": "foo 0.0.1[..]"
+ }
+ ],
+ "root": "foo 0.0.1[..]"
+ },
+ "target_directory": "[..]",
+ "version": 1,
+ "workspace_members": [
+ "foo 0.0.1[..]"
+ ],
+ "workspace_root": "[..]",
+ "metadata": null
+}"#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn metadata_links() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ links = "a"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("build.rs", "fn main() {}")
+ .build();
+
+ p.cargo("metadata")
+ .with_json(
+ r#"
+ {
+ "packages": [
+ {
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "dependencies": [],
+ "description": null,
+ "edition": "2015",
+ "features": {},
+ "id": "foo 0.5.0 [..]",
+ "keywords": [],
+ "license": null,
+ "license_file": null,
+ "links": "a",
+ "manifest_path": "[..]/foo/Cargo.toml",
+ "metadata": null,
+ "publish": null,
+ "name": "foo",
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "source": null,
+ "targets": [
+ {
+ "crate_types": [
+ "lib"
+ ],
+ "doc": true,
+ "doctest": true,
+ "test": true,
+ "edition": "2015",
+ "kind": [
+ "lib"
+ ],
+ "name": "foo",
+ "src_path": "[..]/foo/src/lib.rs"
+ },
+ {
+ "crate_types": [
+ "bin"
+ ],
+ "doc": false,
+ "doctest": false,
+ "test": false,
+ "edition": "2015",
+ "kind": [
+ "custom-build"
+ ],
+ "name": "build-script-build",
+ "src_path": "[..]/foo/build.rs"
+ }
+ ],
+ "version": "0.5.0"
+ }
+ ],
+ "resolve": {
+ "nodes": [
+ {
+ "dependencies": [],
+ "deps": [],
+ "features": [],
+ "id": "foo 0.5.0 [..]"
+ }
+ ],
+ "root": "foo 0.5.0 [..]"
+ },
+ "target_directory": "[..]/foo/target",
+ "version": 1,
+ "workspace_members": [
+ "foo 0.5.0 [..]"
+ ],
+ "workspace_root": "[..]/foo",
+ "metadata": null
+ }
+ "#,
+ )
+ .run()
+}
+
+#[cargo_test]
+fn deps_with_bin_only() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ [dependencies]
+ bdep = { path = "bdep" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bdep/Cargo.toml", &basic_bin_manifest("bdep"))
+ .file("bdep/src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("metadata")
+ .with_json(
+ r#"
+ {
+ "packages": [
+ {
+ "name": "foo",
+ "version": "0.1.0",
+ "id": "foo 0.1.0 ([..])",
+ "license": null,
+ "license_file": null,
+ "description": null,
+ "source": null,
+ "dependencies": [
+ {
+ "name": "bdep",
+ "source": null,
+ "req": "*",
+ "kind": null,
+ "rename": null,
+ "optional": false,
+ "uses_default_features": true,
+ "path": "[..]/foo/bdep",
+ "features": [],
+ "target": null,
+ "registry": null
+ }
+ ],
+ "targets": [
+ {
+ "kind": [
+ "lib"
+ ],
+ "crate_types": [
+ "lib"
+ ],
+ "name": "foo",
+ "src_path": "[..]/foo/src/lib.rs",
+ "edition": "2015",
+ "doc": true,
+ "doctest": true,
+ "test": true
+ }
+ ],
+ "features": {},
+ "manifest_path": "[..]/foo/Cargo.toml",
+ "metadata": null,
+ "publish": null,
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "keywords": [],
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "edition": "2015",
+ "links": null
+ }
+ ],
+ "workspace_members": [
+ "foo 0.1.0 ([..])"
+ ],
+ "resolve": {
+ "nodes": [
+ {
+ "id": "foo 0.1.0 ([..])",
+ "dependencies": [],
+ "deps": [],
+ "features": []
+ }
+ ],
+ "root": "foo 0.1.0 ([..])"
+ },
+ "target_directory": "[..]/foo/target",
+ "version": 1,
+ "workspace_root": "[..]foo",
+ "metadata": null
+ }
+ "#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn filter_platform() {
+ // Testing the --filter-platform flag.
+ Package::new("normal-dep", "0.0.1").publish();
+ Package::new("host-dep", "0.0.1").publish();
+ Package::new("alt-dep", "0.0.1").publish();
+ Package::new("cfg-dep", "0.0.1").publish();
+ // Just needs to be a valid target that is different from host.
+ // Presumably nobody runs these tests on wasm. 🙃
+ let alt_target = "wasm32-unknown-unknown";
+ let host_target = rustc_host();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ normal-dep = "0.0.1"
+
+ [target.{}.dependencies]
+ host-dep = "0.0.1"
+
+ [target.{}.dependencies]
+ alt-dep = "0.0.1"
+
+ [target.'cfg(foobar)'.dependencies]
+ cfg-dep = "0.0.1"
+ "#,
+ host_target, alt_target
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ let alt_dep = r#"
+ {
+ "name": "alt-dep",
+ "version": "0.0.1",
+ "id": "alt-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "license": null,
+ "license_file": null,
+ "description": null,
+ "source": "registry+https://github.com/rust-lang/crates.io-index",
+ "dependencies": [],
+ "targets": [
+ {
+ "kind": [
+ "lib"
+ ],
+ "crate_types": [
+ "lib"
+ ],
+ "name": "alt-dep",
+ "src_path": "[..]/alt-dep-0.0.1/src/lib.rs",
+ "edition": "2015",
+ "test": true,
+ "doc": true,
+ "doctest": true
+ }
+ ],
+ "features": {},
+ "manifest_path": "[..]/alt-dep-0.0.1/Cargo.toml",
+ "metadata": null,
+ "publish": null,
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "keywords": [],
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "edition": "2015",
+ "links": null
+ }
+ "#;
+
+ let cfg_dep = r#"
+ {
+ "name": "cfg-dep",
+ "version": "0.0.1",
+ "id": "cfg-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "license": null,
+ "license_file": null,
+ "description": null,
+ "source": "registry+https://github.com/rust-lang/crates.io-index",
+ "dependencies": [],
+ "targets": [
+ {
+ "kind": [
+ "lib"
+ ],
+ "crate_types": [
+ "lib"
+ ],
+ "name": "cfg-dep",
+ "src_path": "[..]/cfg-dep-0.0.1/src/lib.rs",
+ "edition": "2015",
+ "test": true,
+ "doc": true,
+ "doctest": true
+ }
+ ],
+ "features": {},
+ "manifest_path": "[..]/cfg-dep-0.0.1/Cargo.toml",
+ "metadata": null,
+ "publish": null,
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "keywords": [],
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "edition": "2015",
+ "links": null
+ }
+ "#;
+
+ let host_dep = r#"
+ {
+ "name": "host-dep",
+ "version": "0.0.1",
+ "id": "host-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "license": null,
+ "license_file": null,
+ "description": null,
+ "source": "registry+https://github.com/rust-lang/crates.io-index",
+ "dependencies": [],
+ "targets": [
+ {
+ "kind": [
+ "lib"
+ ],
+ "crate_types": [
+ "lib"
+ ],
+ "name": "host-dep",
+ "src_path": "[..]/host-dep-0.0.1/src/lib.rs",
+ "edition": "2015",
+ "test": true,
+ "doc": true,
+ "doctest": true
+ }
+ ],
+ "features": {},
+ "manifest_path": "[..]/host-dep-0.0.1/Cargo.toml",
+ "metadata": null,
+ "publish": null,
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "keywords": [],
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "edition": "2015",
+ "links": null
+ }
+ "#;
+
+ let normal_dep = r#"
+ {
+ "name": "normal-dep",
+ "version": "0.0.1",
+ "id": "normal-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "license": null,
+ "license_file": null,
+ "description": null,
+ "source": "registry+https://github.com/rust-lang/crates.io-index",
+ "dependencies": [],
+ "targets": [
+ {
+ "kind": [
+ "lib"
+ ],
+ "crate_types": [
+ "lib"
+ ],
+ "name": "normal-dep",
+ "src_path": "[..]/normal-dep-0.0.1/src/lib.rs",
+ "edition": "2015",
+ "test": true,
+ "doc": true,
+ "doctest": true
+ }
+ ],
+ "features": {},
+ "manifest_path": "[..]/normal-dep-0.0.1/Cargo.toml",
+ "metadata": null,
+ "publish": null,
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "keywords": [],
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "edition": "2015",
+ "links": null
+ }
+ "#;
+
+ // The dependencies are stored in sorted order by target and then by name.
+ // Since the testsuite may run on different targets, this needs to be
+ // sorted before it can be compared.
+ let mut foo_deps = serde_json::json!([
+ {
+ "name": "normal-dep",
+ "source": "registry+https://github.com/rust-lang/crates.io-index",
+ "req": "^0.0.1",
+ "kind": null,
+ "rename": null,
+ "optional": false,
+ "uses_default_features": true,
+ "features": [],
+ "target": null,
+ "registry": null
+ },
+ {
+ "name": "cfg-dep",
+ "source": "registry+https://github.com/rust-lang/crates.io-index",
+ "req": "^0.0.1",
+ "kind": null,
+ "rename": null,
+ "optional": false,
+ "uses_default_features": true,
+ "features": [],
+ "target": "cfg(foobar)",
+ "registry": null
+ },
+ {
+ "name": "alt-dep",
+ "source": "registry+https://github.com/rust-lang/crates.io-index",
+ "req": "^0.0.1",
+ "kind": null,
+ "rename": null,
+ "optional": false,
+ "uses_default_features": true,
+ "features": [],
+ "target": alt_target,
+ "registry": null
+ },
+ {
+ "name": "host-dep",
+ "source": "registry+https://github.com/rust-lang/crates.io-index",
+ "req": "^0.0.1",
+ "kind": null,
+ "rename": null,
+ "optional": false,
+ "uses_default_features": true,
+ "features": [],
+ "target": host_target,
+ "registry": null
+ }
+ ]);
+ foo_deps.as_array_mut().unwrap().sort_by(|a, b| {
+ // This really should be `rename`, but not needed here.
+ // Also, sorting on `name` isn't really necessary since this test
+ // only has one package per target, but leaving it here to be safe.
+ let a = (a["target"].as_str(), a["name"].as_str());
+ let b = (b["target"].as_str(), b["name"].as_str());
+ a.cmp(&b)
+ });
+
+ let foo = r#"
+ {
+ "name": "foo",
+ "version": "0.1.0",
+ "id": "foo 0.1.0 (path+file:[..]foo)",
+ "license": null,
+ "license_file": null,
+ "description": null,
+ "source": null,
+ "dependencies":
+ $FOO_DEPS,
+ "targets": [
+ {
+ "kind": [
+ "lib"
+ ],
+ "crate_types": [
+ "lib"
+ ],
+ "name": "foo",
+ "src_path": "[..]/foo/src/lib.rs",
+ "edition": "2015",
+ "test": true,
+ "doc": true,
+ "doctest": true
+ }
+ ],
+ "features": {},
+ "manifest_path": "[..]/foo/Cargo.toml",
+ "metadata": null,
+ "publish": null,
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "keywords": [],
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "homepage": null,
+ "documentation": null,
+ "edition": "2015",
+ "links": null
+ }
+ "#
+ .replace("$ALT_TRIPLE", alt_target)
+ .replace("$HOST_TRIPLE", host_target)
+ .replace("$FOO_DEPS", &foo_deps.to_string());
+
+ // We're going to be checking that we don't download excessively,
+ // so we need to ensure that downloads will happen.
+ let clear = || {
+ cargo_home().join("registry/cache").rm_rf();
+ cargo_home().join("registry/src").rm_rf();
+ p.build_dir().rm_rf();
+ };
+
+ // Normal metadata, no filtering, returns *everything*.
+ p.cargo("metadata")
+ .with_stderr_unordered(
+ "\
+[UPDATING] [..]
+[WARNING] please specify `--format-version` flag explicitly to avoid compatibility problems
+[DOWNLOADING] crates ...
+[DOWNLOADED] normal-dep v0.0.1 [..]
+[DOWNLOADED] host-dep v0.0.1 [..]
+[DOWNLOADED] alt-dep v0.0.1 [..]
+[DOWNLOADED] cfg-dep v0.0.1 [..]
+",
+ )
+ .with_json(
+ &r#"
+{
+ "packages": [
+ $ALT_DEP,
+ $CFG_DEP,
+ $FOO,
+ $HOST_DEP,
+ $NORMAL_DEP
+ ],
+ "workspace_members": [
+ "foo 0.1.0 (path+file:[..]foo)"
+ ],
+ "resolve": {
+ "nodes": [
+ {
+ "id": "alt-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "dependencies": [],
+ "deps": [],
+ "features": []
+ },
+ {
+ "id": "cfg-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "dependencies": [],
+ "deps": [],
+ "features": []
+ },
+ {
+ "id": "foo 0.1.0 (path+file:[..]foo)",
+ "dependencies": [
+ "alt-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "host-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "normal-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)"
+ ],
+ "deps": [
+ {
+ "name": "alt_dep",
+ "pkg": "alt-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "dep_kinds": [
+ {
+ "kind": null,
+ "target": "$ALT_TRIPLE"
+ }
+ ]
+ },
+ {
+ "name": "cfg_dep",
+ "pkg": "cfg-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "dep_kinds": [
+ {
+ "kind": null,
+ "target": "cfg(foobar)"
+ }
+ ]
+ },
+ {
+ "name": "host_dep",
+ "pkg": "host-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "dep_kinds": [
+ {
+ "kind": null,
+ "target": "$HOST_TRIPLE"
+ }
+ ]
+ },
+ {
+ "name": "normal_dep",
+ "pkg": "normal-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "dep_kinds": [
+ {
+ "kind": null,
+ "target": null
+ }
+ ]
+ }
+ ],
+ "features": []
+ },
+ {
+ "id": "host-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "dependencies": [],
+ "deps": [],
+ "features": []
+ },
+ {
+ "id": "normal-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "dependencies": [],
+ "deps": [],
+ "features": []
+ }
+ ],
+ "root": "foo 0.1.0 (path+file:[..]foo)"
+ },
+ "target_directory": "[..]/foo/target",
+ "version": 1,
+ "workspace_root": "[..]/foo",
+ "metadata": null
+}
+"#
+ .replace("$ALT_TRIPLE", alt_target)
+ .replace("$HOST_TRIPLE", host_target)
+ .replace("$ALT_DEP", alt_dep)
+ .replace("$CFG_DEP", cfg_dep)
+ .replace("$HOST_DEP", host_dep)
+ .replace("$NORMAL_DEP", normal_dep)
+ .replace("$FOO", &foo),
+ )
+ .run();
+ clear();
+
+ // Filter on alternate, removes cfg and host.
+ p.cargo("metadata --filter-platform")
+ .arg(alt_target)
+ .with_stderr_unordered(
+ "\
+[WARNING] please specify `--format-version` flag explicitly to avoid compatibility problems
+[DOWNLOADING] crates ...
+[DOWNLOADED] normal-dep v0.0.1 [..]
+[DOWNLOADED] host-dep v0.0.1 [..]
+[DOWNLOADED] alt-dep v0.0.1 [..]
+",
+ )
+ .with_json(
+ &r#"
+{
+ "packages": [
+ $ALT_DEP,
+ $FOO,
+ $NORMAL_DEP
+ ],
+ "workspace_members": "{...}",
+ "resolve": {
+ "nodes": [
+ {
+ "id": "alt-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "dependencies": [],
+ "deps": [],
+ "features": []
+ },
+ {
+ "id": "foo 0.1.0 (path+file:[..]foo)",
+ "dependencies": [
+ "alt-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "normal-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)"
+ ],
+ "deps": [
+ {
+ "name": "alt_dep",
+ "pkg": "alt-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "dep_kinds": [
+ {
+ "kind": null,
+ "target": "$ALT_TRIPLE"
+ }
+ ]
+ },
+ {
+ "name": "normal_dep",
+ "pkg": "normal-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "dep_kinds": [
+ {
+ "kind": null,
+ "target": null
+ }
+ ]
+ }
+ ],
+ "features": []
+ },
+ {
+ "id": "normal-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "dependencies": [],
+ "deps": [],
+ "features": []
+ }
+ ],
+ "root": "foo 0.1.0 (path+file:[..]foo)"
+ },
+ "target_directory": "[..]foo/target",
+ "version": 1,
+ "workspace_root": "[..]foo",
+ "metadata": null
+}
+"#
+ .replace("$ALT_TRIPLE", alt_target)
+ .replace("$ALT_DEP", alt_dep)
+ .replace("$NORMAL_DEP", normal_dep)
+ .replace("$FOO", &foo),
+ )
+ .run();
+ clear();
+
+ // Filter on host, removes alt and cfg.
+ p.cargo("metadata --filter-platform")
+ .arg(&host_target)
+ .with_stderr_unordered(
+ "\
+[WARNING] please specify `--format-version` flag explicitly to avoid compatibility problems
+[DOWNLOADING] crates ...
+[DOWNLOADED] normal-dep v0.0.1 [..]
+[DOWNLOADED] host-dep v0.0.1 [..]
+",
+ )
+ .with_json(
+ &r#"
+{
+ "packages": [
+ $FOO,
+ $HOST_DEP,
+ $NORMAL_DEP
+ ],
+ "workspace_members": "{...}",
+ "resolve": {
+ "nodes": [
+ {
+ "id": "foo 0.1.0 (path+file:[..]foo)",
+ "dependencies": [
+ "host-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "normal-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)"
+ ],
+ "deps": [
+ {
+ "name": "host_dep",
+ "pkg": "host-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "dep_kinds": [
+ {
+ "kind": null,
+ "target": "$HOST_TRIPLE"
+ }
+ ]
+ },
+ {
+ "name": "normal_dep",
+ "pkg": "normal-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "dep_kinds": [
+ {
+ "kind": null,
+ "target": null
+ }
+ ]
+ }
+ ],
+ "features": []
+ },
+ {
+ "id": "host-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "dependencies": [],
+ "deps": [],
+ "features": []
+ },
+ {
+ "id": "normal-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "dependencies": [],
+ "deps": [],
+ "features": []
+ }
+ ],
+ "root": "foo 0.1.0 (path+file:[..]foo)"
+ },
+ "target_directory": "[..]foo/target",
+ "version": 1,
+ "workspace_root": "[..]foo",
+ "metadata": null
+}
+"#
+ .replace("$HOST_TRIPLE", host_target)
+ .replace("$HOST_DEP", host_dep)
+ .replace("$NORMAL_DEP", normal_dep)
+ .replace("$FOO", &foo),
+ )
+ .run();
+ clear();
+
+ // Filter host with cfg, removes alt only
+ p.cargo("metadata --filter-platform")
+ .arg(&host_target)
+ .env("RUSTFLAGS", "--cfg=foobar")
+ .with_stderr_unordered(
+ "\
+[WARNING] please specify `--format-version` flag explicitly to avoid compatibility problems
+[DOWNLOADING] crates ...
+[DOWNLOADED] normal-dep v0.0.1 [..]
+[DOWNLOADED] host-dep v0.0.1 [..]
+[DOWNLOADED] cfg-dep v0.0.1 [..]
+",
+ )
+ .with_json(
+ &r#"
+{
+ "packages": [
+ $CFG_DEP,
+ $FOO,
+ $HOST_DEP,
+ $NORMAL_DEP
+ ],
+ "workspace_members": "{...}",
+ "resolve": {
+ "nodes": [
+ {
+ "id": "cfg-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "dependencies": [],
+ "deps": [],
+ "features": []
+ },
+ {
+ "id": "foo 0.1.0 (path+file:[..]/foo)",
+ "dependencies": [
+ "cfg-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "host-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "normal-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)"
+ ],
+ "deps": [
+ {
+ "name": "cfg_dep",
+ "pkg": "cfg-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "dep_kinds": [
+ {
+ "kind": null,
+ "target": "cfg(foobar)"
+ }
+ ]
+ },
+ {
+ "name": "host_dep",
+ "pkg": "host-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "dep_kinds": [
+ {
+ "kind": null,
+ "target": "$HOST_TRIPLE"
+ }
+ ]
+ },
+ {
+ "name": "normal_dep",
+ "pkg": "normal-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "dep_kinds": [
+ {
+ "kind": null,
+ "target": null
+ }
+ ]
+ }
+ ],
+ "features": []
+ },
+ {
+ "id": "host-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "dependencies": [],
+ "deps": [],
+ "features": []
+ },
+ {
+ "id": "normal-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "dependencies": [],
+ "deps": [],
+ "features": []
+ }
+ ],
+ "root": "foo 0.1.0 (path+file:[..]/foo)"
+ },
+ "target_directory": "[..]/foo/target",
+ "version": 1,
+ "workspace_root": "[..]/foo",
+ "metadata": null
+}
+"#
+ .replace("$HOST_TRIPLE", host_target)
+ .replace("$CFG_DEP", cfg_dep)
+ .replace("$HOST_DEP", host_dep)
+ .replace("$NORMAL_DEP", normal_dep)
+ .replace("$FOO", &foo),
+ )
+ .run();
+}
+
+#[cargo_test]
+fn dep_kinds() {
+ Package::new("bar", "0.1.0").publish();
+ Package::new("winapi", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "0.1"
+
+ [dev-dependencies]
+ bar = "0.1"
+
+ [build-dependencies]
+ bar = "0.1"
+
+ [target.'cfg(windows)'.dependencies]
+ winapi = "0.1"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("metadata")
+ .with_json(
+ r#"
+ {
+ "packages": "{...}",
+ "workspace_members": "{...}",
+ "target_directory": "{...}",
+ "version": 1,
+ "workspace_root": "{...}",
+ "metadata": null,
+ "resolve": {
+ "nodes": [
+ {
+ "id": "bar 0.1.0 [..]",
+ "dependencies": [],
+ "deps": [],
+ "features": []
+ },
+ {
+ "id": "foo 0.1.0 [..]",
+ "dependencies": [
+ "bar 0.1.0 [..]",
+ "winapi 0.1.0 [..]"
+ ],
+ "deps": [
+ {
+ "name": "bar",
+ "pkg": "bar 0.1.0 [..]",
+ "dep_kinds": [
+ {
+ "kind": null,
+ "target": null
+ },
+ {
+ "kind": "dev",
+ "target": null
+ },
+ {
+ "kind": "build",
+ "target": null
+ }
+ ]
+ },
+ {
+ "name": "winapi",
+ "pkg": "winapi 0.1.0 [..]",
+ "dep_kinds": [
+ {
+ "kind": null,
+ "target": "cfg(windows)"
+ }
+ ]
+ }
+ ],
+ "features": []
+ },
+ {
+ "id": "winapi 0.1.0 [..]",
+ "dependencies": [],
+ "deps": [],
+ "features": []
+ }
+ ],
+ "root": "foo 0.1.0 [..]"
+ }
+ }
+ "#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn dep_kinds_workspace() {
+ // Check for bug with duplicate dep kinds in a workspace.
+ // If different members select different features for the same package,
+ // they show up multiple times in the resolver `deps`.
+ //
+ // Here:
+ // foo -> dep
+ // bar -> foo[feat1] -> dep
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [features]
+ feat1 = []
+
+ [dependencies]
+ dep = { path="dep" }
+
+ [workspace]
+ members = ["bar"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+
+ [dependencies]
+ foo = { path="..", features=["feat1"] }
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .file("dep/Cargo.toml", &basic_lib_manifest("dep"))
+ .file("dep/src/lib.rs", "")
+ .build();
+
+ p.cargo("metadata")
+ .with_json(
+ r#"
+ {
+ "packages": "{...}",
+ "workspace_members": "{...}",
+ "target_directory": "[..]/foo/target",
+ "version": 1,
+ "workspace_root": "[..]/foo",
+ "metadata": null,
+ "resolve": {
+ "nodes": [
+ {
+ "id": "bar 0.1.0 (path+file://[..]/foo/bar)",
+ "dependencies": [
+ "foo 0.1.0 (path+file://[..]/foo)"
+ ],
+ "deps": [
+ {
+ "name": "foo",
+ "pkg": "foo 0.1.0 (path+file://[..]/foo)",
+ "dep_kinds": [
+ {
+ "kind": null,
+ "target": null
+ }
+ ]
+ }
+ ],
+ "features": []
+ },
+ {
+ "id": "dep 0.5.0 (path+file://[..]/foo/dep)",
+ "dependencies": [],
+ "deps": [],
+ "features": []
+ },
+ {
+ "id": "foo 0.1.0 (path+file://[..]/foo)",
+ "dependencies": [
+ "dep 0.5.0 (path+file://[..]/foo/dep)"
+ ],
+ "deps": [
+ {
+ "name": "dep",
+ "pkg": "dep 0.5.0 (path+file://[..]/foo/dep)",
+ "dep_kinds": [
+ {
+ "kind": null,
+ "target": null
+ }
+ ]
+ }
+ ],
+ "features": [
+ "feat1"
+ ]
+ }
+ ],
+ "root": "foo 0.1.0 (path+file://[..]/foo)"
+ }
+ }
+ "#,
+ )
+ .run();
+}
+
+// Creating non-utf8 path is an OS-specific pain, so let's run this only on
+// linux, where arbitrary bytes work.
+#[cfg(target_os = "linux")]
+#[cargo_test]
+fn cargo_metadata_non_utf8() {
+ use std::ffi::OsString;
+ use std::os::unix::ffi::OsStringExt;
+ use std::path::PathBuf;
+
+ let base = PathBuf::from(OsString::from_vec(vec![255]));
+
+ let p = project()
+ .no_manifest()
+ .file(base.join("./src/lib.rs"), "")
+ .file(base.join("./Cargo.toml"), &basic_lib_manifest("foo"))
+ .build();
+
+ p.cargo("metadata")
+ .cwd(p.root().join(base))
+ .arg("--format-version")
+ .arg("1")
+ .with_stderr("error: path contains invalid UTF-8 characters")
+ .with_status(101)
+ .run();
+}
+
+// TODO: Consider using this test instead of the version without the 'artifact' suffix or merge them because they should be pretty much the same.
+#[cargo_test]
+fn workspace_metadata_with_dependencies_no_deps_artifact() {
+ let p = project()
+ // NOTE that 'artifact' isn't mentioned in the workspace here, yet it shows up as member.
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar", "baz"]
+ "#,
+ )
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+
+ name = "bar"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies]
+ baz = { path = "../baz/" }
+ baz-renamed = { path = "../baz/" }
+ artifact = { path = "../artifact/", artifact = "bin" }
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .file("baz/Cargo.toml", &basic_lib_manifest("baz"))
+ .file("baz/src/lib.rs", "")
+ .file("artifact/Cargo.toml", &basic_bin_manifest("artifact"))
+ .file("artifact/src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("metadata --no-deps -Z bindeps")
+ .masquerade_as_nightly_cargo(&["bindeps"])
+ .with_json(
+ r#"
+ {
+ "metadata": null,
+ "packages": [
+ {
+ "authors": [
+ "wycats@example.com"
+ ],
+ "categories": [],
+ "default_run": null,
+ "dependencies": [
+ {
+ "artifact": {
+ "kinds": [
+ "bin"
+ ],
+ "lib": false,
+ "target": null
+ },
+ "features": [],
+ "kind": null,
+ "name": "artifact",
+ "optional": false,
+ "path": "[..]/foo/artifact",
+ "registry": null,
+ "rename": null,
+ "req": "*",
+ "source": null,
+ "target": null,
+ "uses_default_features": true
+ },
+ {
+ "features": [],
+ "kind": null,
+ "name": "baz",
+ "optional": false,
+ "path": "[..]/foo/baz",
+ "registry": null,
+ "rename": null,
+ "req": "*",
+ "source": null,
+ "target": null,
+ "uses_default_features": true
+ },
+ {
+ "features": [],
+ "kind": null,
+ "name": "baz-renamed",
+ "optional": false,
+ "path": "[..]/foo/baz",
+ "registry": null,
+ "rename": null,
+ "req": "*",
+ "source": null,
+ "target": null,
+ "uses_default_features": true
+ }
+ ],
+ "description": null,
+ "documentation": null,
+ "edition": "2015",
+ "features": {},
+ "homepage": null,
+ "id": "bar 0.5.0 (path+file://[..]/foo/bar)",
+ "keywords": [],
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "manifest_path": "[..]/foo/bar/Cargo.toml",
+ "metadata": null,
+ "name": "bar",
+ "publish": null,
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "source": null,
+ "targets": [
+ {
+ "crate_types": [
+ "lib"
+ ],
+ "doc": true,
+ "doctest": true,
+ "edition": "2015",
+ "kind": [
+ "lib"
+ ],
+ "name": "bar",
+ "src_path": "[..]/foo/bar/src/lib.rs",
+ "test": true
+ }
+ ],
+ "version": "0.5.0"
+ },
+ {
+ "authors": [
+ "wycats@example.com"
+ ],
+ "categories": [],
+ "default_run": null,
+ "dependencies": [],
+ "description": null,
+ "documentation": null,
+ "edition": "2015",
+ "features": {},
+ "homepage": null,
+ "id": "artifact 0.5.0 (path+file://[..]/foo/artifact)",
+ "keywords": [],
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "manifest_path": "[..]/foo/artifact/Cargo.toml",
+ "metadata": null,
+ "name": "artifact",
+ "publish": null,
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "source": null,
+ "targets": [
+ {
+ "crate_types": [
+ "bin"
+ ],
+ "doc": true,
+ "doctest": false,
+ "edition": "2015",
+ "kind": [
+ "bin"
+ ],
+ "name": "artifact",
+ "src_path": "[..]/foo/artifact/src/main.rs",
+ "test": true
+ }
+ ],
+ "version": "0.5.0"
+ },
+ {
+ "authors": [
+ "wycats@example.com"
+ ],
+ "categories": [],
+ "default_run": null,
+ "dependencies": [],
+ "description": null,
+ "documentation": null,
+ "edition": "2015",
+ "features": {},
+ "homepage": null,
+ "id": "baz 0.5.0 (path+file://[..]/foo/baz)",
+ "keywords": [],
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "manifest_path": "[..]/foo/baz/Cargo.toml",
+ "metadata": null,
+ "name": "baz",
+ "publish": null,
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "source": null,
+ "targets": [
+ {
+ "crate_types": [
+ "lib"
+ ],
+ "doc": true,
+ "doctest": true,
+ "edition": "2015",
+ "kind": [
+ "lib"
+ ],
+ "name": "baz",
+ "src_path": "[..]/foo/baz/src/lib.rs",
+ "test": true
+ }
+ ],
+ "version": "0.5.0"
+ }
+ ],
+ "resolve": null,
+ "target_directory": "[..]/foo/target",
+ "version": 1,
+ "workspace_members": [
+ "bar 0.5.0 (path+file://[..]/foo/bar)",
+ "artifact 0.5.0 (path+file://[..]/foo/artifact)",
+ "baz 0.5.0 (path+file://[..]/foo/baz)"
+ ],
+ "workspace_root": "[..]/foo"
+ }
+"#,
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/minimal_versions.rs b/src/tools/cargo/tests/testsuite/minimal_versions.rs
new file mode 100644
index 000000000..f814dcb70
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/minimal_versions.rs
@@ -0,0 +1,38 @@
+//! Tests for minimal-version resolution.
+//!
+//! Note: Some tests are located in the resolver-tests package.
+
+use cargo_test_support::project;
+use cargo_test_support::registry::Package;
+
+// Ensure that the "-Z minimal-versions" CLI option works and the minimal
+// version of a dependency ends up in the lock file.
+#[cargo_test]
+fn minimal_version_cli() {
+ Package::new("dep", "1.0.0").publish();
+ Package::new("dep", "1.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.1"
+
+ [dependencies]
+ dep = "1.0"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("generate-lockfile -Zminimal-versions")
+ .masquerade_as_nightly_cargo(&["minimal-versions"])
+ .run();
+
+ let lock = p.read_lockfile();
+
+ assert!(!lock.contains("1.1.0"));
+}
diff --git a/src/tools/cargo/tests/testsuite/mock-std/Cargo.toml b/src/tools/cargo/tests/testsuite/mock-std/Cargo.toml
new file mode 100644
index 000000000..a69aa4b88
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/mock-std/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+members = [
+ "library/alloc",
+ "library/core",
+ "library/proc_macro",
+ "library/std",
+ "library/test",
+]
diff --git a/src/tools/cargo/tests/testsuite/mock-std/library/alloc/Cargo.toml b/src/tools/cargo/tests/testsuite/mock-std/library/alloc/Cargo.toml
new file mode 100644
index 000000000..dc965abff
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/mock-std/library/alloc/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "alloc"
+version = "0.1.0"
+authors = ["Alex Crichton <alex@alexcrichton.com>"]
+edition = "2018"
+
+[dependencies]
+registry-dep-using-core = { version = "*", features = ['mockbuild'] }
diff --git a/src/tools/cargo/tests/testsuite/mock-std/library/alloc/src/lib.rs b/src/tools/cargo/tests/testsuite/mock-std/library/alloc/src/lib.rs
new file mode 100644
index 000000000..823716e40
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/mock-std/library/alloc/src/lib.rs
@@ -0,0 +1,11 @@
+#![feature(staged_api)]
+#![stable(since = "1.0.0", feature = "dummy")]
+
+extern crate alloc;
+
+#[stable(since = "1.0.0", feature = "dummy")]
+pub use alloc::*;
+
+#[stable(since = "1.0.0", feature = "dummy")]
+pub fn custom_api() {
+}
diff --git a/src/tools/cargo/tests/testsuite/mock-std/library/compiler_builtins/Cargo.toml b/src/tools/cargo/tests/testsuite/mock-std/library/compiler_builtins/Cargo.toml
new file mode 100644
index 000000000..d1df281d6
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/mock-std/library/compiler_builtins/Cargo.toml
@@ -0,0 +1,5 @@
+[package]
+name = "compiler_builtins"
+version = "0.1.0"
+authors = ["Alex Crichton <alex@alexcrichton.com>"]
+edition = "2018"
diff --git a/src/tools/cargo/tests/testsuite/mock-std/library/compiler_builtins/src/lib.rs b/src/tools/cargo/tests/testsuite/mock-std/library/compiler_builtins/src/lib.rs
new file mode 100644
index 000000000..65e2cc340
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/mock-std/library/compiler_builtins/src/lib.rs
@@ -0,0 +1 @@
+// intentionally blank
diff --git a/src/tools/cargo/tests/testsuite/mock-std/library/core/Cargo.toml b/src/tools/cargo/tests/testsuite/mock-std/library/core/Cargo.toml
new file mode 100644
index 000000000..3f7de53db
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/mock-std/library/core/Cargo.toml
@@ -0,0 +1,5 @@
+[package]
+name = "core"
+version = "0.1.0"
+authors = ["Alex Crichton <alex@alexcrichton.com>"]
+edition = "2018"
diff --git a/src/tools/cargo/tests/testsuite/mock-std/library/core/src/lib.rs b/src/tools/cargo/tests/testsuite/mock-std/library/core/src/lib.rs
new file mode 100644
index 000000000..b90ed0914
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/mock-std/library/core/src/lib.rs
@@ -0,0 +1,9 @@
+#![feature(staged_api)]
+#![stable(since = "1.0.0", feature = "dummy")]
+
+#[stable(since = "1.0.0", feature = "dummy")]
+pub use core::*;
+
+#[stable(since = "1.0.0", feature = "dummy")]
+pub fn custom_api() {
+}
diff --git a/src/tools/cargo/tests/testsuite/mock-std/library/panic_unwind/Cargo.toml b/src/tools/cargo/tests/testsuite/mock-std/library/panic_unwind/Cargo.toml
new file mode 100644
index 000000000..e7beb923f
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/mock-std/library/panic_unwind/Cargo.toml
@@ -0,0 +1,5 @@
+[package]
+name = "panic_unwind"
+version = "0.1.0"
+authors = ["Alex Crichton <alex@alexcrichton.com>"]
+edition = "2018"
diff --git a/src/tools/cargo/tests/testsuite/mock-std/library/panic_unwind/src/lib.rs b/src/tools/cargo/tests/testsuite/mock-std/library/panic_unwind/src/lib.rs
new file mode 100644
index 000000000..6af65d875
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/mock-std/library/panic_unwind/src/lib.rs
@@ -0,0 +1,5 @@
+#![feature(panic_unwind, panic_runtime)]
+#![panic_runtime]
+#![no_std]
+
+extern crate panic_unwind;
diff --git a/src/tools/cargo/tests/testsuite/mock-std/library/proc_macro/Cargo.toml b/src/tools/cargo/tests/testsuite/mock-std/library/proc_macro/Cargo.toml
new file mode 100644
index 000000000..939a113b2
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/mock-std/library/proc_macro/Cargo.toml
@@ -0,0 +1,5 @@
+[package]
+name = "proc_macro"
+version = "0.1.0"
+authors = ["Alex Crichton <alex@alexcrichton.com>"]
+edition = "2018"
diff --git a/src/tools/cargo/tests/testsuite/mock-std/library/proc_macro/src/lib.rs b/src/tools/cargo/tests/testsuite/mock-std/library/proc_macro/src/lib.rs
new file mode 100644
index 000000000..82a768406
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/mock-std/library/proc_macro/src/lib.rs
@@ -0,0 +1,11 @@
+#![feature(staged_api)]
+#![stable(since = "1.0.0", feature = "dummy")]
+
+extern crate proc_macro;
+
+#[stable(since = "1.0.0", feature = "dummy")]
+pub use proc_macro::*;
+
+#[stable(since = "1.0.0", feature = "dummy")]
+pub fn custom_api() {
+}
diff --git a/src/tools/cargo/tests/testsuite/mock-std/library/rustc-std-workspace-alloc/Cargo.toml b/src/tools/cargo/tests/testsuite/mock-std/library/rustc-std-workspace-alloc/Cargo.toml
new file mode 100644
index 000000000..6b86f22ca
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/mock-std/library/rustc-std-workspace-alloc/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "rustc-std-workspace-alloc"
+version = "1.9.0"
+authors = ["Alex Crichton <alex@alexcrichton.com>"]
+edition = "2018"
+
+[lib]
+path = "lib.rs"
+
+[dependencies]
+alloc = { path = "../alloc" }
diff --git a/src/tools/cargo/tests/testsuite/mock-std/library/rustc-std-workspace-alloc/lib.rs b/src/tools/cargo/tests/testsuite/mock-std/library/rustc-std-workspace-alloc/lib.rs
new file mode 100644
index 000000000..2bbfa1a49
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/mock-std/library/rustc-std-workspace-alloc/lib.rs
@@ -0,0 +1,3 @@
+#![no_std]
+
+pub use alloc::*;
diff --git a/src/tools/cargo/tests/testsuite/mock-std/library/rustc-std-workspace-core/Cargo.toml b/src/tools/cargo/tests/testsuite/mock-std/library/rustc-std-workspace-core/Cargo.toml
new file mode 100644
index 000000000..8d1921600
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/mock-std/library/rustc-std-workspace-core/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "rustc-std-workspace-core"
+version = "1.9.0"
+authors = ["Alex Crichton <alex@alexcrichton.com>"]
+edition = "2018"
+
+[lib]
+path = "lib.rs"
+
+[dependencies]
+core = { path = "../core" }
diff --git a/src/tools/cargo/tests/testsuite/mock-std/library/rustc-std-workspace-core/lib.rs b/src/tools/cargo/tests/testsuite/mock-std/library/rustc-std-workspace-core/lib.rs
new file mode 100644
index 000000000..816251790
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/mock-std/library/rustc-std-workspace-core/lib.rs
@@ -0,0 +1,3 @@
+#![no_std]
+
+pub use core::*;
diff --git a/src/tools/cargo/tests/testsuite/mock-std/library/rustc-std-workspace-std/Cargo.toml b/src/tools/cargo/tests/testsuite/mock-std/library/rustc-std-workspace-std/Cargo.toml
new file mode 100644
index 000000000..91572b815
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/mock-std/library/rustc-std-workspace-std/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "rustc-std-workspace-std"
+version = "1.9.0"
+authors = ["Alex Crichton <alex@alexcrichton.com>"]
+edition = "2018"
+
+[lib]
+path = "lib.rs"
+
+[dependencies]
+std = { path = "../std" }
diff --git a/src/tools/cargo/tests/testsuite/mock-std/library/rustc-std-workspace-std/lib.rs b/src/tools/cargo/tests/testsuite/mock-std/library/rustc-std-workspace-std/lib.rs
new file mode 100644
index 000000000..f40d09caf
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/mock-std/library/rustc-std-workspace-std/lib.rs
@@ -0,0 +1 @@
+pub use std::*;
diff --git a/src/tools/cargo/tests/testsuite/mock-std/library/std/Cargo.toml b/src/tools/cargo/tests/testsuite/mock-std/library/std/Cargo.toml
new file mode 100644
index 000000000..d2cfdea39
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/mock-std/library/std/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "std"
+version = "0.1.0"
+authors = ["Alex Crichton <alex@alexcrichton.com>"]
+edition = "2018"
+
+[dependencies]
+registry-dep-using-alloc = { version = "*", features = ['mockbuild'] }
+
+[features]
+feature1 = []
diff --git a/src/tools/cargo/tests/testsuite/mock-std/library/std/src/lib.rs b/src/tools/cargo/tests/testsuite/mock-std/library/std/src/lib.rs
new file mode 100644
index 000000000..146d4c42c
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/mock-std/library/std/src/lib.rs
@@ -0,0 +1,12 @@
+#![feature(staged_api)]
+#![stable(since = "1.0.0", feature = "dummy")]
+
+#[stable(since = "1.0.0", feature = "dummy")]
+pub use std::*;
+
+#[stable(since = "1.0.0", feature = "dummy")]
+pub fn custom_api() {}
+
+#[cfg(feature = "feature1")]
+#[stable(since = "1.0.0", feature = "dummy")]
+pub fn conditional_function() {}
diff --git a/src/tools/cargo/tests/testsuite/mock-std/library/test/Cargo.toml b/src/tools/cargo/tests/testsuite/mock-std/library/test/Cargo.toml
new file mode 100644
index 000000000..299db7bfd
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/mock-std/library/test/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "test"
+version = "0.1.0"
+authors = ["Alex Crichton <alex@alexcrichton.com>"]
+edition = "2018"
+
+[dependencies]
+proc_macro = { path = "../proc_macro" }
+std = { path = "../std" }
+panic_unwind = { path = "../panic_unwind" }
+compiler_builtins = { path = "../compiler_builtins" }
+registry-dep-using-std = { version = "*", features = ['mockbuild'] }
+
+[features]
+panic-unwind = []
+backtrace = []
+feature1 = ["std/feature1"]
+default = []
diff --git a/src/tools/cargo/tests/testsuite/mock-std/library/test/src/lib.rs b/src/tools/cargo/tests/testsuite/mock-std/library/test/src/lib.rs
new file mode 100644
index 000000000..a112855f5
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/mock-std/library/test/src/lib.rs
@@ -0,0 +1,10 @@
+#![feature(staged_api)]
+#![feature(test)]
+#![unstable(feature = "test", issue = "none")]
+
+extern crate test;
+
+pub use test::*;
+
+pub fn custom_api() {
+}
diff --git a/src/tools/cargo/tests/testsuite/multitarget.rs b/src/tools/cargo/tests/testsuite/multitarget.rs
new file mode 100644
index 000000000..5f3543f01
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/multitarget.rs
@@ -0,0 +1,231 @@
+//! Tests for multiple `--target` flags to subcommands
+
+use cargo_test_support::{basic_manifest, cross_compile, project, rustc_host};
+
+#[cargo_test]
+fn simple_build() {
+ if cross_compile::disabled() {
+ return;
+ }
+ let t1 = cross_compile::alternate();
+ let t2 = rustc_host();
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo", "1.0.0"))
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build")
+ .arg("--target")
+ .arg(&t1)
+ .arg("--target")
+ .arg(&t2)
+ .run();
+
+ assert!(p.target_bin(t1, "foo").is_file());
+ assert!(p.target_bin(t2, "foo").is_file());
+}
+
+#[cargo_test]
+fn simple_build_with_config() {
+ if cross_compile::disabled() {
+ return;
+ }
+ let t1 = cross_compile::alternate();
+ let t2 = rustc_host();
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo", "1.0.0"))
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ ".cargo/config.toml",
+ &format!(
+ r#"
+ [build]
+ target = ["{t1}", "{t2}"]
+ "#
+ ),
+ )
+ .build();
+
+ p.cargo("build").run();
+
+ assert!(p.target_bin(t1, "foo").is_file());
+ assert!(p.target_bin(t2, "foo").is_file());
+}
+
+#[cargo_test]
+fn simple_test() {
+ if !cross_compile::can_run_on_host() {
+ return;
+ }
+ let t1 = cross_compile::alternate();
+ let t2 = rustc_host();
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo", "1.0.0"))
+ .file("src/lib.rs", "fn main() {}")
+ .build();
+
+ p.cargo("test")
+ .arg("--target")
+ .arg(&t1)
+ .arg("--target")
+ .arg(&t2)
+ .with_stderr_contains(&format!("[RUNNING] [..]{}[..]", t1))
+ .with_stderr_contains(&format!("[RUNNING] [..]{}[..]", t2))
+ .run();
+}
+
+#[cargo_test]
+fn simple_run() {
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo", "1.0.0"))
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("run --target a --target b")
+ .with_stderr("[ERROR] only one `--target` argument is supported")
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn simple_doc() {
+ if cross_compile::disabled() {
+ return;
+ }
+ let t1 = cross_compile::alternate();
+ let t2 = rustc_host();
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo", "1.0.0"))
+ .file("src/lib.rs", "//! empty lib")
+ .build();
+
+ p.cargo("doc")
+ .arg("--target")
+ .arg(&t1)
+ .arg("--target")
+ .arg(&t2)
+ .run();
+
+ assert!(p.build_dir().join(&t1).join("doc/foo/index.html").is_file());
+ assert!(p.build_dir().join(&t2).join("doc/foo/index.html").is_file());
+}
+
+#[cargo_test]
+fn simple_check() {
+ if cross_compile::disabled() {
+ return;
+ }
+ let t1 = cross_compile::alternate();
+ let t2 = rustc_host();
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo", "1.0.0"))
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check")
+ .arg("--target")
+ .arg(&t1)
+ .arg("--target")
+ .arg(&t2)
+ .run();
+}
+
+#[cargo_test]
+fn same_value_twice() {
+ if cross_compile::disabled() {
+ return;
+ }
+ let t = rustc_host();
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo", "1.0.0"))
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build")
+ .arg("--target")
+ .arg(&t)
+ .arg("--target")
+ .arg(&t)
+ .run();
+
+ assert!(p.target_bin(t, "foo").is_file());
+}
+
+#[cargo_test]
+fn same_value_twice_with_config() {
+ if cross_compile::disabled() {
+ return;
+ }
+ let t = rustc_host();
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo", "1.0.0"))
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ ".cargo/config.toml",
+ &format!(
+ r#"
+ [build]
+ target = ["{t}", "{t}"]
+ "#
+ ),
+ )
+ .build();
+
+ p.cargo("build").run();
+
+ assert!(p.target_bin(t, "foo").is_file());
+}
+
+#[cargo_test]
+fn works_with_config_in_both_string_or_list() {
+ if cross_compile::disabled() {
+ return;
+ }
+ let t = rustc_host();
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo", "1.0.0"))
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ ".cargo/config.toml",
+ &format!(
+ r#"
+ [build]
+ target = "{t}"
+ "#
+ ),
+ )
+ .build();
+
+ p.cargo("build").run();
+
+ assert!(p.target_bin(t, "foo").is_file());
+
+ p.cargo("clean").run();
+
+ p.change_file(
+ ".cargo/config.toml",
+ &format!(
+ r#"
+ [build]
+ target = ["{t}"]
+ "#
+ ),
+ );
+
+ p.cargo("build").run();
+
+ assert!(p.target_bin(t, "foo").is_file());
+}
+
+#[cargo_test]
+fn works_with_env() {
+ let t = rustc_host();
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo", "1.0.0"))
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build").env("CARGO_BUILD_TARGET", t).run();
+
+ assert!(p.target_bin(t, "foo").is_file());
+}
diff --git a/src/tools/cargo/tests/testsuite/net_config.rs b/src/tools/cargo/tests/testsuite/net_config.rs
new file mode 100644
index 000000000..569ec552c
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/net_config.rs
@@ -0,0 +1,74 @@
+//! Tests for network configuration.
+
+use cargo_test_support::project;
+
+#[cargo_test]
+fn net_retry_loads_from_config() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ git = "http://127.0.0.1:11/foo/bar"
+ "#,
+ )
+ .file("src/main.rs", "")
+ .file(
+ ".cargo/config",
+ r#"
+ [net]
+ retry=1
+ [http]
+ timeout=1
+ "#,
+ )
+ .build();
+
+ p.cargo("check -v")
+ .with_status(101)
+ .with_stderr_contains(
+ "[WARNING] spurious network error \
+ (1 tries remaining): [..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn net_retry_git_outputs_warning() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ git = "http://127.0.0.1:11/foo/bar"
+ "#,
+ )
+ .file(
+ ".cargo/config",
+ r#"
+ [http]
+ timeout=1
+ "#,
+ )
+ .file("src/main.rs", "")
+ .build();
+
+ p.cargo("check -v -j 1")
+ .with_status(101)
+ .with_stderr_contains(
+ "[WARNING] spurious network error \
+ (2 tries remaining): [..]",
+ )
+ .with_stderr_contains("[WARNING] spurious network error (1 tries remaining): [..]")
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/new.rs b/src/tools/cargo/tests/testsuite/new.rs
new file mode 100644
index 000000000..b9ddcf2d7
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/new.rs
@@ -0,0 +1,560 @@
+//! Tests for the `cargo new` command.
+
+use cargo_test_support::cargo_process;
+use cargo_test_support::paths;
+use std::env;
+use std::fs::{self, File};
+
+fn create_default_gitconfig() {
+ // This helps on Windows where libgit2 is very aggressive in attempting to
+ // find a git config file.
+ let gitconfig = paths::home().join(".gitconfig");
+ File::create(gitconfig).unwrap();
+
+ // If we're running this under a user account that has a different default branch set up
+ // then tests that assume the default branch is master will fail. We set the default branch
+ // to master explicitly so that tests that rely on this behavior still pass.
+ fs::write(
+ paths::home().join(".gitconfig"),
+ r#"
+ [init]
+ defaultBranch = master
+ "#,
+ )
+ .unwrap();
+}
+
+#[cargo_test]
+fn simple_lib() {
+ cargo_process("new --lib foo --vcs none --edition 2015")
+ .with_stderr("[CREATED] library `foo` package")
+ .run();
+
+ assert!(paths::root().join("foo").is_dir());
+ assert!(paths::root().join("foo/Cargo.toml").is_file());
+ assert!(paths::root().join("foo/src/lib.rs").is_file());
+ assert!(!paths::root().join("foo/.gitignore").is_file());
+
+ let lib = paths::root().join("foo/src/lib.rs");
+ let contents = fs::read_to_string(&lib).unwrap();
+ assert_eq!(
+ contents,
+ r#"pub fn add(left: usize, right: usize) -> usize {
+ left + right
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn it_works() {
+ let result = add(2, 2);
+ assert_eq!(result, 4);
+ }
+}
+"#
+ );
+
+ cargo_process("build").cwd(&paths::root().join("foo")).run();
+}
+
+#[cargo_test]
+fn simple_bin() {
+ cargo_process("new --bin foo --edition 2015")
+ .with_stderr("[CREATED] binary (application) `foo` package")
+ .run();
+
+ assert!(paths::root().join("foo").is_dir());
+ assert!(paths::root().join("foo/Cargo.toml").is_file());
+ assert!(paths::root().join("foo/src/main.rs").is_file());
+
+ cargo_process("build").cwd(&paths::root().join("foo")).run();
+ assert!(paths::root()
+ .join(&format!("foo/target/debug/foo{}", env::consts::EXE_SUFFIX))
+ .is_file());
+}
+
+#[cargo_test]
+fn both_lib_and_bin() {
+ cargo_process("new --lib --bin foo")
+ .with_status(101)
+ .with_stderr("[ERROR] can't specify both lib and binary outputs")
+ .run();
+}
+
+#[cargo_test]
+fn simple_git() {
+ cargo_process("new --lib foo --edition 2015").run();
+
+ assert!(paths::root().is_dir());
+ assert!(paths::root().join("foo/Cargo.toml").is_file());
+ assert!(paths::root().join("foo/src/lib.rs").is_file());
+ assert!(paths::root().join("foo/.git").is_dir());
+ assert!(paths::root().join("foo/.gitignore").is_file());
+
+ let fp = paths::root().join("foo/.gitignore");
+ let contents = fs::read_to_string(&fp).unwrap();
+ assert_eq!(contents, "/target\n/Cargo.lock\n",);
+
+ cargo_process("build").cwd(&paths::root().join("foo")).run();
+}
+
+#[cargo_test(requires_hg)]
+fn simple_hg() {
+ cargo_process("new --lib foo --edition 2015 --vcs hg").run();
+
+ assert!(paths::root().is_dir());
+ assert!(paths::root().join("foo/Cargo.toml").is_file());
+ assert!(paths::root().join("foo/src/lib.rs").is_file());
+ assert!(paths::root().join("foo/.hg").is_dir());
+ assert!(paths::root().join("foo/.hgignore").is_file());
+
+ let fp = paths::root().join("foo/.hgignore");
+ let contents = fs::read_to_string(&fp).unwrap();
+ assert_eq!(contents, "^target$\n^Cargo.lock$\n",);
+
+ cargo_process("build").cwd(&paths::root().join("foo")).run();
+}
+
+#[cargo_test]
+fn no_argument() {
+ cargo_process("new")
+ .with_status(1)
+ .with_stderr_contains(
+ "\
+error: the following required arguments were not provided:
+ <path>
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn existing() {
+ let dst = paths::root().join("foo");
+ fs::create_dir(&dst).unwrap();
+ cargo_process("new foo")
+ .with_status(101)
+ .with_stderr(
+ "[ERROR] destination `[CWD]/foo` already exists\n\n\
+ Use `cargo init` to initialize the directory",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn invalid_characters() {
+ cargo_process("new foo.rs")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] invalid character `.` in package name: `foo.rs`, [..]
+If you need a package name to not match the directory name, consider using --name flag.
+If you need a binary with the name \"foo.rs\", use a valid package name, \
+and set the binary name to be different from the package. \
+This can be done by setting the binary filename to `src/bin/foo.rs.rs` \
+or change the name in Cargo.toml with:
+
+ [[bin]]
+ name = \"foo.rs\"
+ path = \"src/main.rs\"
+
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn reserved_name() {
+ cargo_process("new test")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] the name `test` cannot be used as a package name, it conflicts [..]
+If you need a package name to not match the directory name, consider using --name flag.
+If you need a binary with the name \"test\", use a valid package name, \
+and set the binary name to be different from the package. \
+This can be done by setting the binary filename to `src/bin/test.rs` \
+or change the name in Cargo.toml with:
+
+ [[bin]]
+ name = \"test\"
+ path = \"src/main.rs\"
+
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn reserved_binary_name() {
+ cargo_process("new --bin incremental")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] the name `incremental` cannot be used as a package name, it conflicts [..]
+If you need a package name to not match the directory name, consider using --name flag.
+",
+ )
+ .run();
+
+ cargo_process("new --lib incremental")
+ .with_stderr(
+ "\
+[WARNING] the name `incremental` will not support binary executables with that name, \
+it conflicts with cargo's build directory names
+[CREATED] library `incremental` package
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn keyword_name() {
+ cargo_process("new pub")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] the name `pub` cannot be used as a package name, it is a Rust keyword
+If you need a package name to not match the directory name, consider using --name flag.
+If you need a binary with the name \"pub\", use a valid package name, \
+and set the binary name to be different from the package. \
+This can be done by setting the binary filename to `src/bin/pub.rs` \
+or change the name in Cargo.toml with:
+
+ [[bin]]
+ name = \"pub\"
+ path = \"src/main.rs\"
+
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn std_name() {
+ cargo_process("new core")
+ .with_stderr(
+ "\
+[WARNING] the name `core` is part of Rust's standard library
+It is recommended to use a different name to avoid problems.
+If you need a package name to not match the directory name, consider using --name flag.
+If you need a binary with the name \"core\", use a valid package name, \
+and set the binary name to be different from the package. \
+This can be done by setting the binary filename to `src/bin/core.rs` \
+or change the name in Cargo.toml with:
+
+ [[bin]]
+ name = \"core\"
+ path = \"src/main.rs\"
+
+[CREATED] binary (application) `core` package
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn git_prefers_command_line() {
+ let root = paths::root();
+ fs::create_dir(&root.join(".cargo")).unwrap();
+ fs::write(
+ &root.join(".cargo/config"),
+ r#"
+ [cargo-new]
+ vcs = "none"
+ name = "foo"
+ email = "bar"
+ "#,
+ )
+ .unwrap();
+
+ cargo_process("new foo --vcs git").run();
+ assert!(paths::root().join("foo/.gitignore").exists());
+ assert!(!fs::read_to_string(paths::root().join("foo/Cargo.toml"))
+ .unwrap()
+ .contains("authors ="));
+}
+
+#[cargo_test]
+fn subpackage_no_git() {
+ cargo_process("new foo").run();
+
+ assert!(paths::root().join("foo/.git").is_dir());
+ assert!(paths::root().join("foo/.gitignore").is_file());
+
+ let subpackage = paths::root().join("foo").join("components");
+ fs::create_dir(&subpackage).unwrap();
+ cargo_process("new foo/components/subcomponent").run();
+
+ assert!(!paths::root()
+ .join("foo/components/subcomponent/.git")
+ .is_file());
+ assert!(!paths::root()
+ .join("foo/components/subcomponent/.gitignore")
+ .is_file());
+}
+
+#[cargo_test]
+fn subpackage_git_with_gitignore() {
+ cargo_process("new foo").run();
+
+ assert!(paths::root().join("foo/.git").is_dir());
+ assert!(paths::root().join("foo/.gitignore").is_file());
+
+ let gitignore = paths::root().join("foo/.gitignore");
+ fs::write(gitignore, b"components").unwrap();
+
+ let subpackage = paths::root().join("foo/components");
+ fs::create_dir(&subpackage).unwrap();
+ cargo_process("new foo/components/subcomponent").run();
+
+ assert!(paths::root()
+ .join("foo/components/subcomponent/.git")
+ .is_dir());
+ assert!(paths::root()
+ .join("foo/components/subcomponent/.gitignore")
+ .is_file());
+}
+
+#[cargo_test]
+fn subpackage_git_with_vcs_arg() {
+ cargo_process("new foo").run();
+
+ let subpackage = paths::root().join("foo").join("components");
+ fs::create_dir(&subpackage).unwrap();
+ cargo_process("new foo/components/subcomponent --vcs git").run();
+
+ assert!(paths::root()
+ .join("foo/components/subcomponent/.git")
+ .is_dir());
+ assert!(paths::root()
+ .join("foo/components/subcomponent/.gitignore")
+ .is_file());
+}
+
+#[cargo_test]
+fn unknown_flags() {
+ cargo_process("new foo --flag")
+ .with_status(1)
+ .with_stderr_contains("error: unexpected argument '--flag' found")
+ .run();
+}
+
+#[cargo_test]
+fn explicit_invalid_name_not_suggested() {
+ cargo_process("new --name 10-invalid a")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] the name `10-invalid` cannot be used as a package name, \
+the name cannot start with a digit\n\
+If you need a binary with the name \"10-invalid\", use a valid package name, \
+and set the binary name to be different from the package. \
+This can be done by setting the binary filename to `src/bin/10-invalid.rs` \
+or change the name in Cargo.toml with:
+
+ [[bin]]
+ name = \"10-invalid\"
+ path = \"src/main.rs\"
+
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn explicit_project_name() {
+ cargo_process("new --lib foo --name bar")
+ .with_stderr("[CREATED] library `bar` package")
+ .run();
+}
+
+#[cargo_test]
+fn new_with_edition_2015() {
+ cargo_process("new --edition 2015 foo").run();
+ let manifest = fs::read_to_string(paths::root().join("foo/Cargo.toml")).unwrap();
+ assert!(manifest.contains("edition = \"2015\""));
+}
+
+#[cargo_test]
+fn new_with_edition_2018() {
+ cargo_process("new --edition 2018 foo").run();
+ let manifest = fs::read_to_string(paths::root().join("foo/Cargo.toml")).unwrap();
+ assert!(manifest.contains("edition = \"2018\""));
+}
+
+#[cargo_test]
+fn new_default_edition() {
+ cargo_process("new foo").run();
+ let manifest = fs::read_to_string(paths::root().join("foo/Cargo.toml")).unwrap();
+ assert!(manifest.contains("edition = \"2021\""));
+}
+
+#[cargo_test]
+fn new_with_bad_edition() {
+ cargo_process("new --edition something_else foo")
+ .with_stderr_contains("error: invalid value 'something_else' for '--edition <YEAR>'")
+ .with_status(1)
+ .run();
+}
+
+#[cargo_test]
+fn new_with_reference_link() {
+ cargo_process("new foo").run();
+
+ let contents = fs::read_to_string(paths::root().join("foo/Cargo.toml")).unwrap();
+ assert!(contents.contains("# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html"))
+}
+
+#[cargo_test]
+fn lockfile_constant_during_new() {
+ cargo_process("new foo").run();
+
+ cargo_process("build").cwd(&paths::root().join("foo")).run();
+ let before = fs::read_to_string(paths::root().join("foo/Cargo.lock")).unwrap();
+ cargo_process("build").cwd(&paths::root().join("foo")).run();
+ let after = fs::read_to_string(paths::root().join("foo/Cargo.lock")).unwrap();
+ assert_eq!(before, after);
+}
+
+#[cargo_test]
+fn restricted_windows_name() {
+ if cfg!(windows) {
+ cargo_process("new nul")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] cannot use name `nul`, it is a reserved Windows filename
+If you need a package name to not match the directory name, consider using --name flag.
+",
+ )
+ .run();
+ } else {
+ cargo_process("new nul")
+ .with_stderr(
+ "\
+[WARNING] the name `nul` is a reserved Windows filename
+This package will not work on Windows platforms.
+[CREATED] binary (application) `nul` package
+",
+ )
+ .run();
+ }
+}
+
+#[cargo_test]
+fn non_ascii_name() {
+ cargo_process("new Привет")
+ .with_stderr(
+ "\
+[WARNING] the name `Привет` contains non-ASCII characters
+Non-ASCII crate names are not supported by Rust.
+[CREATED] binary (application) `Привет` package
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn non_ascii_name_invalid() {
+ // These are alphanumeric characters, but not Unicode XID.
+ cargo_process("new ⒶⒷⒸ")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] invalid character `Ⓐ` in package name: `ⒶⒷⒸ`, \
+the first character must be a Unicode XID start character (most letters or `_`)
+If you need a package name to not match the directory name, consider using --name flag.
+If you need a binary with the name \"ⒶⒷⒸ\", use a valid package name, \
+and set the binary name to be different from the package. \
+This can be done by setting the binary filename to `src/bin/ⒶⒷⒸ.rs` \
+or change the name in Cargo.toml with:
+
+ [[bin]]
+ name = \"ⒶⒷⒸ\"
+ path = \"src/main.rs\"
+
+",
+ )
+ .run();
+
+ cargo_process("new a¼")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] invalid character `¼` in package name: `a¼`, \
+characters must be Unicode XID characters (numbers, `-`, `_`, or most letters)
+If you need a package name to not match the directory name, consider using --name flag.
+If you need a binary with the name \"a¼\", use a valid package name, \
+and set the binary name to be different from the package. \
+This can be done by setting the binary filename to `src/bin/a¼.rs` \
+or change the name in Cargo.toml with:
+
+ [[bin]]
+ name = \"a¼\"
+ path = \"src/main.rs\"
+
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn git_default_branch() {
+ // Check for init.defaultBranch support.
+ create_default_gitconfig();
+
+ cargo_process("new foo").run();
+ let repo = git2::Repository::open(paths::root().join("foo")).unwrap();
+ let head = repo.find_reference("HEAD").unwrap();
+ assert_eq!(head.symbolic_target().unwrap(), "refs/heads/master");
+
+ fs::write(
+ paths::home().join(".gitconfig"),
+ r#"
+ [init]
+ defaultBranch = hello
+ "#,
+ )
+ .unwrap();
+ cargo_process("new bar").run();
+ let repo = git2::Repository::open(paths::root().join("bar")).unwrap();
+ let head = repo.find_reference("HEAD").unwrap();
+ assert_eq!(head.symbolic_target().unwrap(), "refs/heads/hello");
+}
+
+#[cargo_test]
+fn non_utf8_str_in_ignore_file() {
+ let gitignore = paths::home().join(".gitignore");
+ File::create(gitignore).unwrap();
+
+ fs::write(paths::home().join(".gitignore"), &[0xFF, 0xFE]).unwrap();
+
+ cargo_process(&format!("init {} --vcs git", paths::home().display()))
+ .with_status(101)
+ .with_stderr(
+ "\
+error: Failed to create package `home` at `[..]`
+
+Caused by:
+ Character at line 0 is invalid. Cargo only supports UTF-8.
+",
+ )
+ .run();
+}
+
+#[cfg(unix)]
+#[cargo_test]
+fn path_with_invalid_character() {
+ cargo_process("new --name testing test:ing")
+ .with_stderr(
+ "\
+[WARNING] the path `[CWD]/test:ing` contains invalid PATH characters (usually `:`, `;`, or `\"`)
+It is recommended to use a different name to avoid problems.
+[CREATED] binary (application) `testing` package
+",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/offline.rs b/src/tools/cargo/tests/testsuite/offline.rs
new file mode 100644
index 000000000..fe54fc59d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/offline.rs
@@ -0,0 +1,728 @@
+//! Tests for --offline flag.
+
+use cargo_test_support::{
+ basic_manifest, git, main_file, path2url, project,
+ registry::{Package, RegistryBuilder},
+};
+use std::fs;
+
+#[cargo_test]
+fn offline_unused_target_dep() {
+ // --offline with a target dependency that is not used and not downloaded.
+ Package::new("unused_dep", "1.0.0").publish();
+ Package::new("used_dep", "1.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ [dependencies]
+ used_dep = "1.0"
+ [target.'cfg(unused)'.dependencies]
+ unused_dep = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ // Do a build that downloads only what is necessary.
+ p.cargo("check")
+ .with_stderr_contains("[DOWNLOADED] used_dep [..]")
+ .with_stderr_does_not_contain("[DOWNLOADED] unused_dep [..]")
+ .run();
+ p.cargo("clean").run();
+ // Build offline, make sure it works.
+ p.cargo("check --offline").run();
+}
+
+#[cargo_test]
+fn offline_missing_optional() {
+ Package::new("opt_dep", "1.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ [dependencies]
+ opt_dep = { version = "1.0", optional = true }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ // Do a build that downloads only what is necessary.
+ p.cargo("check")
+ .with_stderr_does_not_contain("[DOWNLOADED] opt_dep [..]")
+ .run();
+ p.cargo("clean").run();
+ // Build offline, make sure it works.
+ p.cargo("check --offline").run();
+ p.cargo("check --offline --features=opt_dep")
+ .with_stderr(
+ "\
+[ERROR] failed to download `opt_dep v1.0.0`
+
+Caused by:
+ attempting to make an HTTP request, but --offline was specified
+",
+ )
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn cargo_compile_path_with_offline() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "bar"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("check --offline").run();
+}
+
+#[cargo_test]
+fn cargo_compile_with_downloaded_dependency_with_offline() {
+ Package::new("present_dep", "1.2.3")
+ .file("Cargo.toml", &basic_manifest("present_dep", "1.2.3"))
+ .file("src/lib.rs", "")
+ .publish();
+
+ // make package downloaded
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ present_dep = "1.2.3"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("check").run();
+
+ let p2 = project()
+ .at("bar")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+
+ [dependencies]
+ present_dep = "1.2.3"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p2.cargo("check --offline")
+ .with_stderr(
+ "\
+[CHECKING] present_dep v1.2.3
+[CHECKING] bar v0.1.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_compile_offline_not_try_update() {
+ // When --offline needs to download the registry, provide a reasonable
+ // error hint to run without --offline.
+ let p = project()
+ .at("bar")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+
+ [dependencies]
+ not_cached_dep = "1.2.5"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ let msg = "\
+[ERROR] no matching package named `not_cached_dep` found
+location searched: registry `crates-io`
+required by package `bar v0.1.0 ([..]/bar)`
+As a reminder, you're using offline mode (--offline) which can sometimes cause \
+surprising resolution failures, if this error is too confusing you may wish to \
+retry without the offline flag.
+";
+
+ p.cargo("check --offline")
+ .with_status(101)
+ .with_stderr(msg)
+ .run();
+
+ // While we're here, also check the config works.
+ p.change_file(".cargo/config", "net.offline = true");
+ p.cargo("check").with_status(101).with_stderr(msg).run();
+}
+
+#[cargo_test]
+fn compile_offline_without_maxvers_cached() {
+ Package::new("present_dep", "1.2.1").publish();
+ Package::new("present_dep", "1.2.2").publish();
+
+ Package::new("present_dep", "1.2.3")
+ .file("Cargo.toml", &basic_manifest("present_dep", "1.2.3"))
+ .file(
+ "src/lib.rs",
+ r#"pub fn get_version()->&'static str {"1.2.3"}"#,
+ )
+ .publish();
+
+ Package::new("present_dep", "1.2.5")
+ .file("Cargo.toml", &basic_manifest("present_dep", "1.2.5"))
+ .file("src/lib.rs", r#"pub fn get_version(){"1.2.5"}"#)
+ .publish();
+
+ // make package cached
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ present_dep = "=1.2.3"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("build").run();
+
+ let p2 = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ present_dep = "1.2"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ "\
+extern crate present_dep;
+fn main(){
+ println!(\"{}\", present_dep::get_version());
+}",
+ )
+ .build();
+
+ p2.cargo("run --offline")
+ .with_stderr(
+ "\
+[COMPILING] present_dep v1.2.3
+[COMPILING] foo v0.1.0 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+ Running `[..]`",
+ )
+ .with_stdout("1.2.3")
+ .run();
+}
+
+#[cargo_test]
+fn cargo_compile_forbird_git_httpsrepo_offline() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = ["chabapok@example.com"]
+
+ [dependencies.dep1]
+ git = 'https://github.com/some_user/dep1.git'
+ "#,
+ )
+ .file("src/main.rs", "")
+ .build();
+
+ p.cargo("check --offline").with_status(101).with_stderr("\
+[ERROR] failed to get `dep1` as a dependency of package `foo v0.5.0 [..]`
+
+Caused by:
+ failed to load source for dependency `dep1`
+
+Caused by:
+ Unable to update https://github.com/some_user/dep1.git
+
+Caused by:
+ can't checkout from 'https://github.com/some_user/dep1.git': you are in the offline mode (--offline)").run();
+}
+
+#[cargo_test]
+fn compile_offline_while_transitive_dep_not_cached() {
+ let baz = Package::new("baz", "1.0.0");
+ let baz_path = baz.archive_dst();
+ baz.publish();
+
+ let baz_content = fs::read(&baz_path).unwrap();
+ // Truncate the file to simulate a download failure.
+ fs::write(&baz_path, &[]).unwrap();
+
+ Package::new("bar", "0.1.0").dep("baz", "1.0.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file("src/main.rs", "fn main(){}")
+ .build();
+
+ // simulate download bar, but fail to download baz
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr_contains("[..]failed to verify the checksum of `baz[..]")
+ .run();
+
+ // Restore the file contents.
+ fs::write(&baz_path, &baz_content).unwrap();
+
+ p.cargo("check --offline")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to download `bar v0.1.0`
+
+Caused by:
+ attempting to make an HTTP request, but --offline was specified
+",
+ )
+ .run();
+}
+
+fn update_offline_not_cached() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "*"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+ p.cargo("update --offline")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] no matching package named `bar` found
+location searched: registry `[..]`
+required by package `foo v0.0.1 ([..]/foo)`
+As a reminder, you're using offline mode (--offline) which can sometimes cause \
+surprising resolution failures, if this error is too confusing you may wish to \
+retry without the offline flag.",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn update_offline_not_cached_sparse() {
+ let _registry = RegistryBuilder::new().http_index().build();
+ update_offline_not_cached()
+}
+
+#[cargo_test]
+fn update_offline_not_cached_git() {
+ update_offline_not_cached()
+}
+
+#[cargo_test]
+fn cargo_compile_offline_with_cached_git_dep() {
+ let git_project = git::new("dep1", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("dep1", "0.5.0"))
+ .file(
+ "src/lib.rs",
+ r#"
+ pub static COOL_STR:&str = "cached git repo rev1";
+ "#,
+ )
+ });
+
+ let repo = git2::Repository::open(&git_project.root()).unwrap();
+ let rev1 = repo.revparse_single("HEAD").unwrap().id();
+
+ // Commit the changes and make sure we trigger a recompile
+ git_project.change_file(
+ "src/lib.rs",
+ r#"pub static COOL_STR:&str = "cached git repo rev2";"#,
+ );
+ git::add(&repo);
+ let rev2 = git::commit(&repo);
+
+ // cache to registry rev1 and rev2
+ let prj = project()
+ .at("cache_git_dep")
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "cache_git_dep"
+ version = "0.5.0"
+
+ [dependencies.dep1]
+ git = '{}'
+ rev = "{}"
+ "#,
+ git_project.url(),
+ rev1
+ ),
+ )
+ .file("src/main.rs", "fn main(){}")
+ .build();
+ prj.cargo("build").run();
+
+ prj.change_file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "cache_git_dep"
+ version = "0.5.0"
+
+ [dependencies.dep1]
+ git = '{}'
+ rev = "{}"
+ "#,
+ git_project.url(),
+ rev2
+ ),
+ );
+ prj.cargo("build").run();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+
+ [dependencies.dep1]
+ git = '{}'
+ "#,
+ git_project.url()
+ ),
+ )
+ .file(
+ "src/main.rs",
+ &main_file(r#""hello from {}", dep1::COOL_STR"#, &["dep1"]),
+ )
+ .build();
+
+ let git_root = git_project.root();
+
+ p.cargo("build --offline")
+ .with_stderr(format!(
+ "\
+[COMPILING] dep1 v0.5.0 ({}#[..])
+[COMPILING] foo v0.5.0 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ path2url(git_root),
+ ))
+ .run();
+
+ assert!(p.bin("foo").is_file());
+
+ p.process(&p.bin("foo"))
+ .with_stdout("hello from cached git repo rev2\n")
+ .run();
+
+ p.change_file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+
+ [dependencies.dep1]
+ git = '{}'
+ rev = "{}"
+ "#,
+ git_project.url(),
+ rev1
+ ),
+ );
+
+ p.cargo("build --offline").run();
+ p.process(&p.bin("foo"))
+ .with_stdout("hello from cached git repo rev1\n")
+ .run();
+}
+
+#[cargo_test]
+fn offline_resolve_optional_fail() {
+ // Example where resolve fails offline.
+ //
+ // This happens if at least 1 version of an optional dependency is
+ // available, but none of them satisfy the requirements. The current logic
+ // that handles this is `RegistryIndex::query_inner`, and it doesn't know
+ // if the package being queried is an optional one. This is not ideal, it
+ // would be best if it just ignored optional (unselected) dependencies.
+ Package::new("dep", "1.0.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ dep = { version = "1.0", optional = true }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("fetch").run();
+
+ // Change dep to 2.0.
+ p.change_file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ dep = { version = "2.0", optional = true }
+ "#,
+ );
+
+ p.cargo("check --offline")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to select a version for the requirement `dep = \"^2.0\"`
+candidate versions found which didn't match: 1.0.0
+location searched: `[..]` index (which is replacing registry `crates-io`)
+required by package `foo v0.1.0 ([..]/foo)`
+perhaps a crate was updated and forgotten to be re-vendored?
+As a reminder, you're using offline mode (--offline) which can sometimes cause \
+surprising resolution failures, if this error is too confusing you may wish to \
+retry without the offline flag.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn offline_with_all_patched() {
+ // Offline works if everything is patched.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ dep = "1.0"
+
+ [patch.crates-io]
+ dep = {path = "dep"}
+ "#,
+ )
+ .file("src/lib.rs", "pub fn f() { dep::foo(); }")
+ .file("dep/Cargo.toml", &basic_manifest("dep", "1.0.0"))
+ .file("dep/src/lib.rs", "pub fn foo() {}")
+ .build();
+
+ p.cargo("check --offline").run();
+}
+
+#[cargo_test]
+fn update_offline_cached() {
+ // Cache a few versions to update against
+ let p = project().file("src/lib.rs", "").build();
+ let versions = ["1.2.3", "1.2.5", "1.2.9"];
+ for vers in versions.iter() {
+ Package::new("present_dep", vers)
+ .file("Cargo.toml", &basic_manifest("present_dep", vers))
+ .file(
+ "src/lib.rs",
+ format!(r#"pub fn get_version()->&'static str {{ "{}" }}"#, vers).as_str(),
+ )
+ .publish();
+ // make package cached
+ p.change_file(
+ "Cargo.toml",
+ format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ present_dep = "={}"
+ "#,
+ vers
+ )
+ .as_str(),
+ );
+ p.cargo("build").run();
+ }
+
+ let p2 = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ present_dep = "1.2"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ "\
+extern crate present_dep;
+fn main(){
+ println!(\"{}\", present_dep::get_version());
+}",
+ )
+ .build();
+
+ p2.cargo("build --offline")
+ .with_stderr(
+ "\
+[COMPILING] present_dep v1.2.9
+[COMPILING] foo v0.1.0 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ p2.rename_run("foo", "with_1_2_9")
+ .with_stdout("1.2.9")
+ .run();
+ // updates happen without updating the index
+ p2.cargo("update -p present_dep --precise 1.2.3 --offline")
+ .with_status(0)
+ .with_stderr(
+ "\
+[DOWNGRADING] present_dep v1.2.9 -> v1.2.3
+",
+ )
+ .run();
+
+ p2.cargo("build --offline")
+ .with_stderr(
+ "\
+[COMPILING] present_dep v1.2.3
+[COMPILING] foo v0.1.0 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ p2.rename_run("foo", "with_1_2_3")
+ .with_stdout("1.2.3")
+ .run();
+
+ // Offline update should only print package details and not index updating
+ p2.cargo("update --offline")
+ .with_status(0)
+ .with_stderr(
+ "\
+[UPDATING] present_dep v1.2.3 -> v1.2.9
+",
+ )
+ .run();
+
+ // No v1.2.8 loaded into the cache so expect failure.
+ p2.cargo("update -p present_dep --precise 1.2.8 --offline")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] no matching package named `present_dep` found
+location searched: registry `[..]`
+required by package `foo v0.1.0 ([..]/foo)`
+As a reminder, you're using offline mode (--offline) which can sometimes cause \
+surprising resolution failures, if this error is too confusing you may wish to \
+retry without the offline flag.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn offline_and_frozen_and_no_lock() {
+ let p = project().file("src/lib.rs", "").build();
+ p.cargo("check --frozen --offline")
+ .with_status(101)
+ .with_stderr("\
+error: the lock file [ROOT]/foo/Cargo.lock needs to be updated but --frozen was passed to prevent this
+If you want to try to generate the lock file without accessing the network, \
+remove the --frozen flag and use --offline instead.
+")
+ .run();
+}
+
+#[cargo_test]
+fn offline_and_locked_and_no_frozen() {
+ let p = project().file("src/lib.rs", "").build();
+ p.cargo("check --locked --offline")
+ .with_status(101)
+ .with_stderr("\
+error: the lock file [ROOT]/foo/Cargo.lock needs to be updated but --locked was passed to prevent this
+If you want to try to generate the lock file without accessing the network, \
+remove the --locked flag and use --offline instead.
+")
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/old_cargos.rs b/src/tools/cargo/tests/testsuite/old_cargos.rs
new file mode 100644
index 000000000..a85e13d3b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/old_cargos.rs
@@ -0,0 +1,679 @@
+//! Tests for checking behavior of old cargos.
+//!
+//! These tests are ignored because it is intended to be run on a developer
+//! system with a bunch of toolchains installed. This requires `rustup` to be
+//! installed. It will iterate over installed toolchains, and run some tests
+//! over each one, producing a report at the end. As of this writing, I have
+//! tested 1.0 to 1.51. Run this with:
+//!
+//! ```console
+//! cargo test --test testsuite -- old_cargos --nocapture --ignored
+//! ```
+
+use cargo::CargoResult;
+use cargo_test_support::paths::CargoPathExt;
+use cargo_test_support::registry::{self, Dependency, Package};
+use cargo_test_support::{cargo_exe, execs, paths, process, project, rustc_host};
+use cargo_util::{ProcessBuilder, ProcessError};
+use semver::Version;
+use std::fs;
+
+fn tc_process(cmd: &str, toolchain: &str) -> ProcessBuilder {
+ let mut p = if toolchain == "this" {
+ if cmd == "cargo" {
+ process(&cargo_exe())
+ } else {
+ process(cmd)
+ }
+ } else {
+ let mut cmd = process(cmd);
+ cmd.arg(format!("+{}", toolchain));
+ cmd
+ };
+ // Reset PATH since `process` modifies it to remove rustup.
+ p.env("PATH", std::env::var_os("PATH").unwrap());
+ p
+}
+
+/// Returns a sorted list of all toolchains.
+///
+/// The returned value includes the parsed version, and the rustup toolchain
+/// name as a string.
+fn collect_all_toolchains() -> Vec<(Version, String)> {
+ let rustc_version = |tc| {
+ let mut cmd = tc_process("rustc", tc);
+ cmd.arg("-V");
+ let output = cmd.exec_with_output().expect("rustc installed");
+ let version = std::str::from_utf8(&output.stdout).unwrap();
+ let parts: Vec<_> = version.split_whitespace().collect();
+ assert_eq!(parts[0], "rustc");
+ assert!(parts[1].starts_with("1."));
+ Version::parse(parts[1]).expect("valid version")
+ };
+
+ // Provide a way to override the list.
+ if let Ok(tcs) = std::env::var("OLD_CARGO") {
+ return tcs
+ .split(',')
+ .map(|tc| (rustc_version(tc), tc.to_string()))
+ .collect();
+ }
+
+ let host = rustc_host();
+ // I tend to have lots of toolchains installed, but I don't want to test
+ // all of them (like dated nightlies, or toolchains for non-host targets).
+ let valid_names = &[
+ format!("stable-{}", host),
+ format!("beta-{}", host),
+ format!("nightly-{}", host),
+ ];
+
+ let output = ProcessBuilder::new("rustup")
+ .args(&["toolchain", "list"])
+ .exec_with_output()
+ .expect("rustup should be installed");
+ let stdout = std::str::from_utf8(&output.stdout).unwrap();
+ let mut toolchains: Vec<_> = stdout
+ .lines()
+ .map(|line| {
+ // Some lines say things like (default), just get the version.
+ line.split_whitespace().next().expect("non-empty line")
+ })
+ .filter(|line| {
+ line.ends_with(&host)
+ && (line.starts_with("1.") || valid_names.iter().any(|name| name == line))
+ })
+ .map(|line| (rustc_version(line), line.to_string()))
+ .collect();
+
+ toolchains.sort_by(|a, b| a.0.cmp(&b.0));
+ toolchains
+}
+
+/// Returns whether the default toolchain is the stable version.
+fn default_toolchain_is_stable() -> bool {
+ let default = tc_process("rustc", "this").arg("-V").exec_with_output();
+ let stable = tc_process("rustc", "stable").arg("-V").exec_with_output();
+ match (default, stable) {
+ (Ok(d), Ok(s)) => d.stdout == s.stdout,
+ _ => false,
+ }
+}
+
+// This is a test for exercising the behavior of older versions of cargo with
+// the new feature syntax.
+//
+// The test involves a few dependencies with different feature requirements:
+//
+// * `bar` 1.0.0 is the base version that does not use the new syntax.
+// * `bar` 1.0.1 has a feature with the new syntax, but the feature is unused.
+// The optional dependency `new-baz-dep` should not be activated.
+// * `bar` 1.0.2 has a dependency on `baz` that *requires* the new feature
+// syntax.
+#[ignore = "must be run manually, requires old cargo installations"]
+#[cargo_test]
+fn new_features() {
+ let registry = registry::init();
+ if std::process::Command::new("rustup").output().is_err() {
+ panic!("old_cargos requires rustup to be installed");
+ }
+ Package::new("new-baz-dep", "1.0.0").publish();
+
+ Package::new("baz", "1.0.0").publish();
+ let baz101_cksum = Package::new("baz", "1.0.1")
+ .add_dep(Dependency::new("new-baz-dep", "1.0").optional(true))
+ .feature("new-feat", &["dep:new-baz-dep"])
+ .publish();
+
+ let bar100_cksum = Package::new("bar", "1.0.0")
+ .add_dep(Dependency::new("baz", "1.0").optional(true))
+ .feature("feat", &["baz"])
+ .publish();
+ let bar101_cksum = Package::new("bar", "1.0.1")
+ .add_dep(Dependency::new("baz", "1.0").optional(true))
+ .feature("feat", &["dep:baz"])
+ .publish();
+ let bar102_cksum = Package::new("bar", "1.0.2")
+ .add_dep(Dependency::new("baz", "1.0").enable_features(&["new-feat"]))
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ let lock_bar_to = |toolchain_version: &Version, bar_version| {
+ let lock = if toolchain_version < &Version::new(1, 12, 0) {
+ let url = registry.index_url();
+ match bar_version {
+ 100 => format!(
+ r#"
+ [root]
+ name = "foo"
+ version = "0.1.0"
+ dependencies = [
+ "bar 1.0.0 (registry+{url})",
+ ]
+
+ [[package]]
+ name = "bar"
+ version = "1.0.0"
+ source = "registry+{url}"
+ "#,
+ url = url
+ ),
+ 101 => format!(
+ r#"
+ [root]
+ name = "foo"
+ version = "0.1.0"
+ dependencies = [
+ "bar 1.0.1 (registry+{url})",
+ ]
+
+ [[package]]
+ name = "bar"
+ version = "1.0.1"
+ source = "registry+{url}"
+ "#,
+ url = url
+ ),
+ 102 => format!(
+ r#"
+ [root]
+ name = "foo"
+ version = "0.1.0"
+ dependencies = [
+ "bar 1.0.2 (registry+{url})",
+ ]
+
+ [[package]]
+ name = "bar"
+ version = "1.0.2"
+ source = "registry+{url}"
+ dependencies = [
+ "baz 1.0.1 (registry+{url})",
+ ]
+
+ [[package]]
+ name = "baz"
+ version = "1.0.1"
+ source = "registry+{url}"
+ "#,
+ url = url
+ ),
+ _ => panic!("unexpected version"),
+ }
+ } else {
+ match bar_version {
+ 100 => format!(
+ r#"
+ [root]
+ name = "foo"
+ version = "0.1.0"
+ dependencies = [
+ "bar 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ ]
+
+ [[package]]
+ name = "bar"
+ version = "1.0.0"
+ source = "registry+https://github.com/rust-lang/crates.io-index"
+
+ [metadata]
+ "checksum bar 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "{}"
+ "#,
+ bar100_cksum
+ ),
+ 101 => format!(
+ r#"
+ [root]
+ name = "foo"
+ version = "0.1.0"
+ dependencies = [
+ "bar 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ ]
+
+ [[package]]
+ name = "bar"
+ version = "1.0.1"
+ source = "registry+https://github.com/rust-lang/crates.io-index"
+
+ [metadata]
+ "checksum bar 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "{}"
+ "#,
+ bar101_cksum
+ ),
+ 102 => format!(
+ r#"
+ [root]
+ name = "foo"
+ version = "0.1.0"
+ dependencies = [
+ "bar 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ ]
+
+ [[package]]
+ name = "bar"
+ version = "1.0.2"
+ source = "registry+https://github.com/rust-lang/crates.io-index"
+ dependencies = [
+ "baz 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ ]
+
+ [[package]]
+ name = "baz"
+ version = "1.0.1"
+ source = "registry+https://github.com/rust-lang/crates.io-index"
+
+ [metadata]
+ "checksum bar 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "{bar102_cksum}"
+ "checksum baz 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "{baz101_cksum}"
+ "#,
+ bar102_cksum = bar102_cksum,
+ baz101_cksum = baz101_cksum
+ ),
+ _ => panic!("unexpected version"),
+ }
+ };
+ p.change_file("Cargo.lock", &lock);
+ };
+
+ let toolchains = collect_all_toolchains();
+
+ let config_path = paths::home().join(".cargo/config");
+ let lock_path = p.root().join("Cargo.lock");
+
+ struct ToolchainBehavior {
+ bar: Option<Version>,
+ baz: Option<Version>,
+ new_baz_dep: Option<Version>,
+ }
+
+ // Collect errors to print at the end. One entry per toolchain, a list of
+ // strings to print.
+ let mut unexpected_results: Vec<Vec<String>> = Vec::new();
+
+ for (version, toolchain) in &toolchains {
+ let mut tc_result = Vec::new();
+ // Write a config appropriate for this version.
+ if version < &Version::new(1, 12, 0) {
+ fs::write(
+ &config_path,
+ format!(
+ r#"
+ [registry]
+ index = "{}"
+ "#,
+ registry.index_url()
+ ),
+ )
+ .unwrap();
+ } else {
+ fs::write(
+ &config_path,
+ format!(
+ "
+ [source.crates-io]
+ registry = 'https://wut' # only needed by 1.12
+ replace-with = 'dummy-registry'
+
+ [source.dummy-registry]
+ registry = '{}'
+ ",
+ registry.index_url()
+ ),
+ )
+ .unwrap();
+ }
+
+ // Fetches the version of a package in the lock file.
+ let pkg_version = |pkg| -> Option<Version> {
+ let output = tc_process("cargo", toolchain)
+ .args(&["pkgid", pkg])
+ .cwd(p.root())
+ .exec_with_output()
+ .ok()?;
+ let stdout = std::str::from_utf8(&output.stdout).unwrap();
+ let version = stdout
+ .trim()
+ .rsplitn(2, ':')
+ .next()
+ .expect("version after colon");
+ Some(Version::parse(version).expect("parseable version"))
+ };
+
+ // Runs `cargo build` and returns the versions selected in the lock.
+ let run_cargo = || -> CargoResult<ToolchainBehavior> {
+ match tc_process("cargo", toolchain)
+ .args(&["build", "--verbose"])
+ .cwd(p.root())
+ .exec_with_output()
+ {
+ Ok(_output) => {
+ eprintln!("{} ok", toolchain);
+ let bar = pkg_version("bar");
+ let baz = pkg_version("baz");
+ let new_baz_dep = pkg_version("new-baz-dep");
+ Ok(ToolchainBehavior {
+ bar,
+ baz,
+ new_baz_dep,
+ })
+ }
+ Err(e) => {
+ eprintln!("{} err {}", toolchain, e);
+ Err(e)
+ }
+ }
+ };
+
+ macro_rules! check_lock {
+ ($tc_result:ident, $pkg:expr, $which:expr, $actual:expr, None) => {
+ check_lock!(= $tc_result, $pkg, $which, $actual, None);
+ };
+ ($tc_result:ident, $pkg:expr, $which:expr, $actual:expr, $expected:expr) => {
+ check_lock!(= $tc_result, $pkg, $which, $actual, Some(Version::parse($expected).unwrap()));
+ };
+ (= $tc_result:ident, $pkg:expr, $which:expr, $actual:expr, $expected:expr) => {
+ let exp: Option<Version> = $expected;
+ if $actual != $expected {
+ $tc_result.push(format!(
+ "{} for {} saw {:?} but expected {:?}",
+ $which, $pkg, $actual, exp
+ ));
+ }
+ };
+ }
+
+ let check_err_contains = |tc_result: &mut Vec<_>, err: anyhow::Error, contents| {
+ if let Some(ProcessError {
+ stderr: Some(stderr),
+ ..
+ }) = err.downcast_ref::<ProcessError>()
+ {
+ let stderr = std::str::from_utf8(stderr).unwrap();
+ if !stderr.contains(contents) {
+ tc_result.push(format!(
+ "{} expected to see error contents:\n{}\nbut saw:\n{}",
+ toolchain, contents, stderr
+ ));
+ }
+ } else {
+ panic!("{} unexpected error {}", toolchain, err);
+ }
+ };
+
+ // Unlocked behavior.
+ let which = "unlocked";
+ lock_path.rm_rf();
+ p.build_dir().rm_rf();
+ match run_cargo() {
+ Ok(behavior) => {
+ if version < &Version::new(1, 51, 0) {
+ check_lock!(tc_result, "bar", which, behavior.bar, "1.0.2");
+ check_lock!(tc_result, "baz", which, behavior.baz, "1.0.1");
+ check_lock!(tc_result, "new-baz-dep", which, behavior.new_baz_dep, None);
+ } else if version >= &Version::new(1, 51, 0) && version <= &Version::new(1, 59, 0) {
+ check_lock!(tc_result, "bar", which, behavior.bar, "1.0.0");
+ check_lock!(tc_result, "baz", which, behavior.baz, None);
+ check_lock!(tc_result, "new-baz-dep", which, behavior.new_baz_dep, None);
+ }
+ // Starting with 1.60, namespaced-features has been stabilized.
+ else {
+ check_lock!(tc_result, "bar", which, behavior.bar, "1.0.2");
+ check_lock!(tc_result, "baz", which, behavior.baz, "1.0.1");
+ check_lock!(
+ tc_result,
+ "new-baz-dep",
+ which,
+ behavior.new_baz_dep,
+ "1.0.0"
+ );
+ }
+ }
+ Err(e) => {
+ tc_result.push(format!("unlocked build failed: {}", e));
+ }
+ }
+
+ let which = "locked bar 1.0.0";
+ lock_bar_to(version, 100);
+ match run_cargo() {
+ Ok(behavior) => {
+ check_lock!(tc_result, "bar", which, behavior.bar, "1.0.0");
+ check_lock!(tc_result, "baz", which, behavior.baz, None);
+ check_lock!(tc_result, "new-baz-dep", which, behavior.new_baz_dep, None);
+ }
+ Err(e) => {
+ tc_result.push(format!("bar 1.0.0 locked build failed: {}", e));
+ }
+ }
+
+ let which = "locked bar 1.0.1";
+ lock_bar_to(version, 101);
+ match run_cargo() {
+ Ok(behavior) => {
+ check_lock!(tc_result, "bar", which, behavior.bar, "1.0.1");
+ check_lock!(tc_result, "baz", which, behavior.baz, None);
+ check_lock!(tc_result, "new-baz-dep", which, behavior.new_baz_dep, None);
+ }
+ Err(e) => {
+ // When version >= 1.51 and <= 1.59,
+ // 1.0.1 can't be used without -Znamespaced-features
+ // It gets filtered out of the index.
+ check_err_contains(
+ &mut tc_result,
+ e,
+ "candidate versions found which didn't match: 1.0.2, 1.0.0",
+ );
+ }
+ }
+
+ let which = "locked bar 1.0.2";
+ lock_bar_to(version, 102);
+ match run_cargo() {
+ Ok(behavior) => {
+ if version <= &Version::new(1, 59, 0) {
+ check_lock!(tc_result, "bar", which, behavior.bar, "1.0.2");
+ check_lock!(tc_result, "baz", which, behavior.baz, "1.0.1");
+ check_lock!(tc_result, "new-baz-dep", which, behavior.new_baz_dep, None);
+ }
+ // Starting with 1.60, namespaced-features has been stabilized.
+ else {
+ check_lock!(tc_result, "bar", which, behavior.bar, "1.0.2");
+ check_lock!(tc_result, "baz", which, behavior.baz, "1.0.1");
+ check_lock!(
+ tc_result,
+ "new-baz-dep",
+ which,
+ behavior.new_baz_dep,
+ "1.0.0"
+ );
+ }
+ }
+ Err(e) => {
+ // When version >= 1.51 and <= 1.59,
+ // baz can't lock to 1.0.1, it requires -Znamespaced-features
+ check_err_contains(
+ &mut tc_result,
+ e,
+ "candidate versions found which didn't match: 1.0.0",
+ );
+ }
+ }
+
+ unexpected_results.push(tc_result);
+ }
+
+ // Generate a report.
+ let mut has_err = false;
+ for ((tc_vers, tc_name), errs) in toolchains.iter().zip(unexpected_results) {
+ if errs.is_empty() {
+ continue;
+ }
+ eprintln!("error: toolchain {} (version {}):", tc_name, tc_vers);
+ for err in errs {
+ eprintln!(" {}", err);
+ }
+ has_err = true;
+ }
+ if has_err {
+ panic!("at least one toolchain did not run as expected");
+ }
+}
+
+#[cargo_test]
+#[ignore = "must be run manually, requires old cargo installations"]
+fn index_cache_rebuild() {
+ // Checks that the index cache gets rebuilt.
+ //
+ // 1.48 will not cache entries with features with the same name as a
+ // dependency. If the cache does not get rebuilt, then running with
+ // `-Znamespaced-features` would prevent the new cargo from seeing those
+ // entries. The index cache version was changed to prevent this from
+ // happening, and switching between versions should work correctly
+ // (although it will thrash the cash, that's better than not working
+ // correctly.
+ Package::new("baz", "1.0.0").publish();
+ Package::new("bar", "1.0.0").publish();
+ Package::new("bar", "1.0.1")
+ .add_dep(Dependency::new("baz", "1.0").optional(true))
+ .feature("baz", &["dep:baz"])
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ // This version of Cargo errors on index entries that have overlapping
+ // feature names, so 1.0.1 will be missing.
+ execs()
+ .with_process_builder(tc_process("cargo", "1.48.0"))
+ .arg("check")
+ .cwd(p.root())
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v1.0.0 [..]
+[CHECKING] bar v1.0.0
+[CHECKING] foo v0.1.0 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ fs::remove_file(p.root().join("Cargo.lock")).unwrap();
+
+ // This should rebuild the cache and use 1.0.1.
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v1.0.1 [..]
+[CHECKING] bar v1.0.1
+[CHECKING] foo v0.1.0 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ fs::remove_file(p.root().join("Cargo.lock")).unwrap();
+
+ // Verify 1.48 can still resolve, and is at 1.0.0.
+ execs()
+ .with_process_builder(tc_process("cargo", "1.48.0"))
+ .arg("tree")
+ .cwd(p.root())
+ .with_stdout(
+ "\
+foo v0.1.0 [..]
+└── bar v1.0.0
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+#[ignore = "must be run manually, requires old cargo installations"]
+fn avoids_split_debuginfo_collision() {
+ // Test needs two different toolchains.
+ // If the default toolchain is stable, then it won't work.
+ if default_toolchain_is_stable() {
+ return;
+ }
+ // Checks for a bug where .o files were being incorrectly shared between
+ // different toolchains using incremental and split-debuginfo on macOS.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [profile.dev]
+ split-debuginfo = "unpacked"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ execs()
+ .with_process_builder(tc_process("cargo", "stable"))
+ .arg("build")
+ .env("CARGO_INCREMENTAL", "1")
+ .cwd(p.root())
+ .with_stderr(
+ "\
+[COMPILING] foo v0.1.0 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ p.cargo("build")
+ .env("CARGO_INCREMENTAL", "1")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.1.0 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ execs()
+ .with_process_builder(tc_process("cargo", "stable"))
+ .arg("build")
+ .env("CARGO_INCREMENTAL", "1")
+ .cwd(p.root())
+ .with_stderr(
+ "\
+[FINISHED] [..]
+",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/out_dir.rs b/src/tools/cargo/tests/testsuite/out_dir.rs
new file mode 100644
index 000000000..fe647f56e
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/out_dir.rs
@@ -0,0 +1,317 @@
+//! Tests for --out-dir flag.
+
+use cargo_test_support::sleep_ms;
+use cargo_test_support::{basic_manifest, project};
+use std::env;
+use std::fs;
+use std::path::Path;
+
+#[cargo_test]
+fn binary_with_debug() {
+ let p = project()
+ .file("src/main.rs", r#"fn main() { println!("Hello, World!") }"#)
+ .build();
+
+ p.cargo("build -Z unstable-options --out-dir out")
+ .masquerade_as_nightly_cargo(&["out-dir"])
+ .enable_mac_dsym()
+ .run();
+ check_dir_contents(
+ &p.root().join("out"),
+ &["foo"],
+ &["foo", "foo.dSYM"],
+ &["foo.exe", "foo.pdb"],
+ &["foo.exe"],
+ );
+}
+
+#[cargo_test]
+fn static_library_with_debug() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ crate-type = ["staticlib"]
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ #[no_mangle]
+ pub extern "C" fn foo() { println!("Hello, World!") }
+ "#,
+ )
+ .build();
+
+ p.cargo("build -Z unstable-options --out-dir out")
+ .masquerade_as_nightly_cargo(&["out-dir"])
+ .run();
+ check_dir_contents(
+ &p.root().join("out"),
+ &["libfoo.a"],
+ &["libfoo.a"],
+ &["foo.lib"],
+ &["libfoo.a"],
+ );
+}
+
+#[cargo_test]
+fn dynamic_library_with_debug() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ crate-type = ["cdylib"]
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ #[no_mangle]
+ pub extern "C" fn foo() { println!("Hello, World!") }
+ "#,
+ )
+ .build();
+
+ p.cargo("build -Z unstable-options --out-dir out")
+ .masquerade_as_nightly_cargo(&["out-dir"])
+ .enable_mac_dsym()
+ .run();
+ check_dir_contents(
+ &p.root().join("out"),
+ &["libfoo.so"],
+ &["libfoo.dylib", "libfoo.dylib.dSYM"],
+ &["foo.dll", "foo.dll.exp", "foo.dll.lib", "foo.pdb"],
+ &["foo.dll", "libfoo.dll.a"],
+ );
+}
+
+#[cargo_test]
+fn rlib_with_debug() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ crate-type = ["rlib"]
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn foo() { println!("Hello, World!") }
+ "#,
+ )
+ .build();
+
+ p.cargo("build -Z unstable-options --out-dir out")
+ .masquerade_as_nightly_cargo(&["out-dir"])
+ .run();
+ check_dir_contents(
+ &p.root().join("out"),
+ &["libfoo.rlib"],
+ &["libfoo.rlib"],
+ &["libfoo.rlib"],
+ &["libfoo.rlib"],
+ );
+}
+
+#[cargo_test]
+fn include_only_the_binary_from_the_current_package() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [workspace]
+
+ [dependencies]
+ utils = { path = "./utils" }
+ "#,
+ )
+ .file("src/lib.rs", "extern crate utils;")
+ .file(
+ "src/main.rs",
+ r#"
+ extern crate foo;
+ extern crate utils;
+ fn main() {
+ println!("Hello, World!")
+ }
+ "#,
+ )
+ .file("utils/Cargo.toml", &basic_manifest("utils", "0.0.1"))
+ .file("utils/src/lib.rs", "")
+ .build();
+
+ p.cargo("build -Z unstable-options --bin foo --out-dir out")
+ .masquerade_as_nightly_cargo(&["out-dir"])
+ .enable_mac_dsym()
+ .run();
+ check_dir_contents(
+ &p.root().join("out"),
+ &["foo"],
+ &["foo", "foo.dSYM"],
+ &["foo.exe", "foo.pdb"],
+ &["foo.exe"],
+ );
+}
+
+#[cargo_test]
+fn out_dir_is_a_file() {
+ let p = project()
+ .file("src/main.rs", r#"fn main() { println!("Hello, World!") }"#)
+ .file("out", "")
+ .build();
+
+ p.cargo("build -Z unstable-options --out-dir out")
+ .masquerade_as_nightly_cargo(&["out-dir"])
+ .with_status(101)
+ .with_stderr_contains("[ERROR] failed to create directory [..]")
+ .run();
+}
+
+#[cargo_test]
+fn replaces_artifacts() {
+ let p = project()
+ .file("src/main.rs", r#"fn main() { println!("foo") }"#)
+ .build();
+
+ p.cargo("build -Z unstable-options --out-dir out")
+ .masquerade_as_nightly_cargo(&["out-dir"])
+ .run();
+ p.process(
+ &p.root()
+ .join(&format!("out/foo{}", env::consts::EXE_SUFFIX)),
+ )
+ .with_stdout("foo")
+ .run();
+
+ sleep_ms(1000);
+ p.change_file("src/main.rs", r#"fn main() { println!("bar") }"#);
+
+ p.cargo("build -Z unstable-options --out-dir out")
+ .masquerade_as_nightly_cargo(&["out-dir"])
+ .run();
+ p.process(
+ &p.root()
+ .join(&format!("out/foo{}", env::consts::EXE_SUFFIX)),
+ )
+ .with_stdout("bar")
+ .run();
+}
+
+#[cargo_test]
+fn avoid_build_scripts() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["a", "b"]
+ "#,
+ )
+ .file("a/Cargo.toml", &basic_manifest("a", "0.0.1"))
+ .file("a/src/main.rs", "fn main() {}")
+ .file("a/build.rs", r#"fn main() { println!("hello-build-a"); }"#)
+ .file("b/Cargo.toml", &basic_manifest("b", "0.0.1"))
+ .file("b/src/main.rs", "fn main() {}")
+ .file("b/build.rs", r#"fn main() { println!("hello-build-b"); }"#)
+ .build();
+
+ p.cargo("build -Z unstable-options --out-dir out -vv")
+ .masquerade_as_nightly_cargo(&["out-dir"])
+ .enable_mac_dsym()
+ .with_stdout_contains("[a 0.0.1] hello-build-a")
+ .with_stdout_contains("[b 0.0.1] hello-build-b")
+ .run();
+ check_dir_contents(
+ &p.root().join("out"),
+ &["a", "b"],
+ &["a", "a.dSYM", "b", "b.dSYM"],
+ &["a.exe", "a.pdb", "b.exe", "b.pdb"],
+ &["a.exe", "b.exe"],
+ );
+}
+
+#[cargo_test]
+fn cargo_build_out_dir() {
+ let p = project()
+ .file("src/main.rs", r#"fn main() { println!("Hello, World!") }"#)
+ .file(
+ ".cargo/config",
+ r#"
+ [build]
+ out-dir = "out"
+ "#,
+ )
+ .build();
+
+ p.cargo("build -Z unstable-options")
+ .masquerade_as_nightly_cargo(&["out-dir"])
+ .enable_mac_dsym()
+ .run();
+ check_dir_contents(
+ &p.root().join("out"),
+ &["foo"],
+ &["foo", "foo.dSYM"],
+ &["foo.exe", "foo.pdb"],
+ &["foo.exe"],
+ );
+}
+
+fn check_dir_contents(
+ out_dir: &Path,
+ expected_linux: &[&str],
+ expected_mac: &[&str],
+ expected_win_msvc: &[&str],
+ expected_win_gnu: &[&str],
+) {
+ let expected = if cfg!(target_os = "windows") {
+ if cfg!(target_env = "msvc") {
+ expected_win_msvc
+ } else {
+ expected_win_gnu
+ }
+ } else if cfg!(target_os = "macos") {
+ expected_mac
+ } else {
+ expected_linux
+ };
+
+ let actual = list_dir(out_dir);
+ let mut expected = expected.iter().map(|s| s.to_string()).collect::<Vec<_>>();
+ expected.sort_unstable();
+ assert_eq!(actual, expected);
+}
+
+fn list_dir(dir: &Path) -> Vec<String> {
+ let mut res = Vec::new();
+ for entry in fs::read_dir(dir).unwrap() {
+ let entry = entry.unwrap();
+ res.push(entry.file_name().into_string().unwrap());
+ }
+ res.sort_unstable();
+ res
+}
diff --git a/src/tools/cargo/tests/testsuite/owner.rs b/src/tools/cargo/tests/testsuite/owner.rs
new file mode 100644
index 000000000..9fc960c92
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/owner.rs
@@ -0,0 +1,192 @@
+//! Tests for the `cargo owner` command.
+
+use std::fs;
+
+use cargo_test_support::paths::CargoPathExt;
+use cargo_test_support::project;
+use cargo_test_support::registry::{self, api_path};
+
+fn setup(name: &str, content: Option<&str>) {
+ let dir = api_path().join(format!("api/v1/crates/{}", name));
+ dir.mkdir_p();
+ if let Some(body) = content {
+ fs::write(dir.join("owners"), body).unwrap();
+ }
+}
+
+#[cargo_test]
+fn simple_list() {
+ let registry = registry::init();
+ let content = r#"{
+ "users": [
+ {
+ "id": 70,
+ "login": "github:rust-lang:core",
+ "name": "Core"
+ },
+ {
+ "id": 123,
+ "login": "octocat"
+ }
+ ]
+ }"#;
+ setup("foo", Some(content));
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("owner -l")
+ .replace_crates_io(registry.index_url())
+ .with_stdout(
+ "\
+github:rust-lang:core (Core)
+octocat
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn simple_add() {
+ let registry = registry::init();
+ setup("foo", None);
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("owner -a username")
+ .replace_crates_io(registry.index_url())
+ .with_status(101)
+ .with_stderr(
+ " Updating crates.io index
+error: failed to invite owners to crate `foo` on registry at file://[..]
+
+Caused by:
+ EOF while parsing a value at line 1 column 0",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn simple_add_with_asymmetric() {
+ let registry = registry::RegistryBuilder::new()
+ .http_api()
+ .token(cargo_test_support::registry::Token::rfc_key())
+ .build();
+ setup("foo", None);
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [project]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ // The http_api server will check that the authorization is correct.
+ // If the authorization was not sent then we would get an unauthorized error.
+ p.cargo("owner -a username")
+ .arg("-Zregistry-auth")
+ .masquerade_as_nightly_cargo(&["registry-auth"])
+ .replace_crates_io(registry.index_url())
+ .with_status(0)
+ .run();
+}
+
+#[cargo_test]
+fn simple_remove() {
+ let registry = registry::init();
+ setup("foo", None);
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("owner -r username")
+ .replace_crates_io(registry.index_url())
+ .with_status(101)
+ .with_stderr(
+ " Updating crates.io index
+ Owner removing [\"username\"] from crate foo
+error: failed to remove owners from crate `foo` on registry at file://[..]
+
+Caused by:
+ EOF while parsing a value at line 1 column 0",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn simple_remove_with_asymmetric() {
+ let registry = registry::RegistryBuilder::new()
+ .http_api()
+ .token(cargo_test_support::registry::Token::rfc_key())
+ .build();
+ setup("foo", None);
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [project]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ // The http_api server will check that the authorization is correct.
+ // If the authorization was not sent then we would get an unauthorized error.
+ p.cargo("owner -r username")
+ .arg("-Zregistry-auth")
+ .replace_crates_io(registry.index_url())
+ .masquerade_as_nightly_cargo(&["registry-auth"])
+ .with_status(0)
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/package.rs b/src/tools/cargo/tests/testsuite/package.rs
new file mode 100644
index 000000000..14bac6618
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/package.rs
@@ -0,0 +1,2764 @@
+//! Tests for the `cargo package` command.
+
+use cargo_test_support::paths::CargoPathExt;
+use cargo_test_support::publish::validate_crate_contents;
+use cargo_test_support::registry::{self, Package};
+use cargo_test_support::{
+ basic_manifest, cargo_process, git, path2url, paths, project, symlink_supported, t,
+};
+use flate2::read::GzDecoder;
+use std::fs::{self, read_to_string, File};
+use std::path::Path;
+use tar::Archive;
+
+#[cargo_test]
+fn simple() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ exclude = ["*.txt"]
+ license = "MIT"
+ description = "foo"
+ "#,
+ )
+ .file("src/main.rs", r#"fn main() { println!("hello"); }"#)
+ .file("src/bar.txt", "") // should be ignored when packaging
+ .build();
+
+ p.cargo("package")
+ .with_stderr(
+ "\
+[WARNING] manifest has no documentation[..]
+See [..]
+[PACKAGING] foo v0.0.1 ([CWD])
+[VERIFYING] foo v0.0.1 ([CWD])
+[COMPILING] foo v0.0.1 ([CWD][..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[PACKAGED] 4 files, [..] ([..] compressed)
+",
+ )
+ .run();
+ assert!(p.root().join("target/package/foo-0.0.1.crate").is_file());
+ p.cargo("package -l")
+ .with_stdout(
+ "\
+Cargo.lock
+Cargo.toml
+Cargo.toml.orig
+src/main.rs
+",
+ )
+ .run();
+ p.cargo("package").with_stdout("").run();
+
+ let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap();
+ validate_crate_contents(
+ f,
+ "foo-0.0.1.crate",
+ &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"],
+ &[],
+ );
+}
+
+#[cargo_test]
+fn metadata_warning() {
+ let p = project().file("src/main.rs", "fn main() {}").build();
+ p.cargo("package")
+ .with_stderr(
+ "\
+warning: manifest has no description, license, license-file, documentation, \
+homepage or repository.
+See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info.
+[PACKAGING] foo v0.0.1 ([CWD])
+[VERIFYING] foo v0.0.1 ([CWD])
+[COMPILING] foo v0.0.1 ([CWD][..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[PACKAGED] [..] files, [..] ([..] compressed)
+",
+ )
+ .run();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+ p.cargo("package")
+ .with_stderr(
+ "\
+warning: manifest has no description, documentation, homepage or repository.
+See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info.
+[PACKAGING] foo v0.0.1 ([CWD])
+[VERIFYING] foo v0.0.1 ([CWD])
+[COMPILING] foo v0.0.1 ([CWD][..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[PACKAGED] [..] files, [..] ([..] compressed)
+",
+ )
+ .run();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ repository = "bar"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+ p.cargo("package")
+ .with_stderr(
+ "\
+[PACKAGING] foo v0.0.1 ([CWD])
+[VERIFYING] foo v0.0.1 ([CWD])
+[COMPILING] foo v0.0.1 ([CWD][..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[PACKAGED] [..] files, [..] ([..] compressed)
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn package_verbose() {
+ let root = paths::root().join("all");
+ let repo = git::repo(&root)
+ .file("Cargo.toml", &basic_manifest("foo", "0.0.1"))
+ .file("src/main.rs", "fn main() {}")
+ .file("a/a/Cargo.toml", &basic_manifest("a", "0.0.1"))
+ .file("a/a/src/lib.rs", "")
+ .build();
+ cargo_process("build").cwd(repo.root()).run();
+
+ println!("package main repo");
+ cargo_process("package -v --no-verify")
+ .cwd(repo.root())
+ .with_stderr(
+ "\
+[WARNING] manifest has no description[..]
+See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info.
+[PACKAGING] foo v0.0.1 ([..])
+[ARCHIVING] .cargo_vcs_info.json
+[ARCHIVING] Cargo.lock
+[ARCHIVING] Cargo.toml
+[ARCHIVING] Cargo.toml.orig
+[ARCHIVING] src/main.rs
+[PACKAGED] 5 files, [..] ([..] compressed)
+",
+ )
+ .run();
+
+ let f = File::open(&repo.root().join("target/package/foo-0.0.1.crate")).unwrap();
+ let vcs_contents = format!(
+ r#"{{
+ "git": {{
+ "sha1": "{}"
+ }},
+ "path_in_vcs": ""
+}}
+"#,
+ repo.revparse_head()
+ );
+ validate_crate_contents(
+ f,
+ "foo-0.0.1.crate",
+ &[
+ "Cargo.lock",
+ "Cargo.toml",
+ "Cargo.toml.orig",
+ "src/main.rs",
+ ".cargo_vcs_info.json",
+ ],
+ &[(".cargo_vcs_info.json", &vcs_contents)],
+ );
+
+ println!("package sub-repo");
+ cargo_process("package -v --no-verify")
+ .cwd(repo.root().join("a/a"))
+ .with_stderr(
+ "\
+[WARNING] manifest has no description[..]
+See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info.
+[PACKAGING] a v0.0.1 ([..])
+[ARCHIVING] .cargo_vcs_info.json
+[ARCHIVING] Cargo.toml
+[ARCHIVING] Cargo.toml.orig
+[ARCHIVING] src/lib.rs
+[PACKAGED] 4 files, [..] ([..] compressed)
+",
+ )
+ .run();
+
+ let f = File::open(&repo.root().join("a/a/target/package/a-0.0.1.crate")).unwrap();
+ let vcs_contents = format!(
+ r#"{{
+ "git": {{
+ "sha1": "{}"
+ }},
+ "path_in_vcs": "a/a"
+}}
+"#,
+ repo.revparse_head()
+ );
+ validate_crate_contents(
+ f,
+ "a-0.0.1.crate",
+ &[
+ "Cargo.toml",
+ "Cargo.toml.orig",
+ "src/lib.rs",
+ ".cargo_vcs_info.json",
+ ],
+ &[(".cargo_vcs_info.json", &vcs_contents)],
+ );
+}
+
+#[cargo_test]
+fn package_verification() {
+ let p = project().file("src/main.rs", "fn main() {}").build();
+ p.cargo("build").run();
+ p.cargo("package")
+ .with_stderr(
+ "\
+[WARNING] manifest has no description[..]
+See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info.
+[PACKAGING] foo v0.0.1 ([CWD])
+[VERIFYING] foo v0.0.1 ([CWD])
+[COMPILING] foo v0.0.1 ([CWD][..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[PACKAGED] [..] files, [..] ([..] compressed)
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn vcs_file_collision() {
+ let p = project().build();
+ let _ = git::repo(&paths::root().join("foo"))
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ description = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ documentation = "foo"
+ homepage = "foo"
+ repository = "foo"
+ exclude = ["*.no-existe"]
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {}
+ "#,
+ )
+ .file(".cargo_vcs_info.json", "foo")
+ .build();
+ p.cargo("package")
+ .arg("--no-verify")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] invalid inclusion of reserved file name .cargo_vcs_info.json \
+in package source
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn orig_file_collision() {
+ let p = project().build();
+ let _ = git::repo(&paths::root().join("foo"))
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ description = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ documentation = "foo"
+ homepage = "foo"
+ repository = "foo"
+ exclude = ["*.no-existe"]
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {}
+ "#,
+ )
+ .file("Cargo.toml.orig", "oops")
+ .build();
+ p.cargo("package")
+ .arg("--no-verify")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] invalid inclusion of reserved file name Cargo.toml.orig \
+in package source
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn path_dependency_no_version() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+
+ [dependencies.bar]
+ path = "bar"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("package")
+ .with_status(101)
+ .with_stderr(
+ "\
+[WARNING] manifest has no documentation, homepage or repository.
+See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info.
+[ERROR] all dependencies must have a version specified when packaging.
+dependency `bar` does not specify a version\n\
+Note: The packaged dependency will use the version from crates.io,
+the `path` specification will be removed from the dependency declaration.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn git_dependency_no_version() {
+ registry::init();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+
+ [dependencies.foo]
+ git = "git://path/to/nowhere"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("package")
+ .with_status(101)
+ .with_stderr(
+ "\
+[WARNING] manifest has no documentation, homepage or repository.
+See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info.
+[ERROR] all dependencies must have a version specified when packaging.
+dependency `foo` does not specify a version
+Note: The packaged dependency will use the version from crates.io,
+the `git` specification will be removed from the dependency declaration.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn exclude() {
+ let root = paths::root().join("exclude");
+ let repo = git::repo(&root)
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ exclude = [
+ "*.txt",
+ # file in root
+ "file_root_1", # NO_CHANGE (ignored)
+ "/file_root_2", # CHANGING (packaged -> ignored)
+ "file_root_3/", # NO_CHANGE (packaged)
+ "file_root_4/*", # NO_CHANGE (packaged)
+ "file_root_5/**", # NO_CHANGE (packaged)
+ # file in sub-dir
+ "file_deep_1", # CHANGING (packaged -> ignored)
+ "/file_deep_2", # NO_CHANGE (packaged)
+ "file_deep_3/", # NO_CHANGE (packaged)
+ "file_deep_4/*", # NO_CHANGE (packaged)
+ "file_deep_5/**", # NO_CHANGE (packaged)
+ # dir in root
+ "dir_root_1", # CHANGING (packaged -> ignored)
+ "/dir_root_2", # CHANGING (packaged -> ignored)
+ "dir_root_3/", # CHANGING (packaged -> ignored)
+ "dir_root_4/*", # NO_CHANGE (ignored)
+ "dir_root_5/**", # NO_CHANGE (ignored)
+ # dir in sub-dir
+ "dir_deep_1", # CHANGING (packaged -> ignored)
+ "/dir_deep_2", # NO_CHANGE
+ "dir_deep_3/", # CHANGING (packaged -> ignored)
+ "dir_deep_4/*", # CHANGING (packaged -> ignored)
+ "dir_deep_5/**", # CHANGING (packaged -> ignored)
+ ]
+ "#,
+ )
+ .file("src/main.rs", r#"fn main() { println!("hello"); }"#)
+ .file("bar.txt", "")
+ .file("src/bar.txt", "")
+ // File in root.
+ .file("file_root_1", "")
+ .file("file_root_2", "")
+ .file("file_root_3", "")
+ .file("file_root_4", "")
+ .file("file_root_5", "")
+ // File in sub-dir.
+ .file("some_dir/file_deep_1", "")
+ .file("some_dir/file_deep_2", "")
+ .file("some_dir/file_deep_3", "")
+ .file("some_dir/file_deep_4", "")
+ .file("some_dir/file_deep_5", "")
+ // Dir in root.
+ .file("dir_root_1/some_dir/file", "")
+ .file("dir_root_2/some_dir/file", "")
+ .file("dir_root_3/some_dir/file", "")
+ .file("dir_root_4/some_dir/file", "")
+ .file("dir_root_5/some_dir/file", "")
+ // Dir in sub-dir.
+ .file("some_dir/dir_deep_1/some_dir/file", "")
+ .file("some_dir/dir_deep_2/some_dir/file", "")
+ .file("some_dir/dir_deep_3/some_dir/file", "")
+ .file("some_dir/dir_deep_4/some_dir/file", "")
+ .file("some_dir/dir_deep_5/some_dir/file", "")
+ .build();
+
+ cargo_process("package --no-verify -v")
+ .cwd(repo.root())
+ .with_stdout("")
+ .with_stderr(
+ "\
+[WARNING] manifest has no description[..]
+See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info.
+[PACKAGING] foo v0.0.1 ([..])
+[ARCHIVING] .cargo_vcs_info.json
+[ARCHIVING] Cargo.lock
+[ARCHIVING] Cargo.toml
+[ARCHIVING] Cargo.toml.orig
+[ARCHIVING] file_root_3
+[ARCHIVING] file_root_4
+[ARCHIVING] file_root_5
+[ARCHIVING] some_dir/dir_deep_2/some_dir/file
+[ARCHIVING] some_dir/dir_deep_4/some_dir/file
+[ARCHIVING] some_dir/dir_deep_5/some_dir/file
+[ARCHIVING] some_dir/file_deep_2
+[ARCHIVING] some_dir/file_deep_3
+[ARCHIVING] some_dir/file_deep_4
+[ARCHIVING] some_dir/file_deep_5
+[ARCHIVING] src/main.rs
+[PACKAGED] 15 files, [..] ([..] compressed)
+",
+ )
+ .run();
+
+ assert!(repo.root().join("target/package/foo-0.0.1.crate").is_file());
+
+ cargo_process("package -l")
+ .cwd(repo.root())
+ .with_stdout(
+ "\
+.cargo_vcs_info.json
+Cargo.lock
+Cargo.toml
+Cargo.toml.orig
+file_root_3
+file_root_4
+file_root_5
+some_dir/dir_deep_2/some_dir/file
+some_dir/dir_deep_4/some_dir/file
+some_dir/dir_deep_5/some_dir/file
+some_dir/file_deep_2
+some_dir/file_deep_3
+some_dir/file_deep_4
+some_dir/file_deep_5
+src/main.rs
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn include() {
+ let root = paths::root().join("include");
+ let repo = git::repo(&root)
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ exclude = ["*.txt"]
+ include = ["foo.txt", "**/*.rs", "Cargo.toml", ".dotfile"]
+ "#,
+ )
+ .file("foo.txt", "")
+ .file("src/main.rs", r#"fn main() { println!("hello"); }"#)
+ .file(".dotfile", "")
+ // Should be ignored when packaging.
+ .file("src/bar.txt", "")
+ .build();
+
+ cargo_process("package --no-verify -v")
+ .cwd(repo.root())
+ .with_stderr(
+ "\
+[WARNING] manifest has no description[..]
+See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info.
+[WARNING] both package.include and package.exclude are specified; the exclude list will be ignored
+[PACKAGING] foo v0.0.1 ([..])
+[ARCHIVING] .cargo_vcs_info.json
+[ARCHIVING] .dotfile
+[ARCHIVING] Cargo.lock
+[ARCHIVING] Cargo.toml
+[ARCHIVING] Cargo.toml.orig
+[ARCHIVING] foo.txt
+[ARCHIVING] src/main.rs
+[PACKAGED] 7 files, [..] ([..] compressed)
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn package_lib_with_bin() {
+ let p = project()
+ .file("src/main.rs", "extern crate foo; fn main() {}")
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("package -v").run();
+}
+
+#[cargo_test]
+fn package_git_submodule() {
+ let project = git::new("foo", |project| {
+ project
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = ["foo@example.com"]
+ license = "MIT"
+ description = "foo"
+ repository = "foo"
+ "#,
+ )
+ .file("src/lib.rs", "pub fn foo() {}")
+ });
+ let library = git::new("bar", |library| {
+ library.no_manifest().file("Makefile", "all:")
+ });
+
+ let repository = git2::Repository::open(&project.root()).unwrap();
+ let url = path2url(library.root()).to_string();
+ git::add_submodule(&repository, &url, Path::new("bar"));
+ git::commit(&repository);
+
+ let repository = git2::Repository::open(&project.root().join("bar")).unwrap();
+ repository
+ .reset(
+ &repository.revparse_single("HEAD").unwrap(),
+ git2::ResetType::Hard,
+ None,
+ )
+ .unwrap();
+
+ project
+ .cargo("package --no-verify -v")
+ .with_stderr_contains("[ARCHIVING] bar/Makefile")
+ .run();
+}
+
+#[cargo_test]
+/// Tests if a symlink to a git submodule is properly handled.
+///
+/// This test requires you to be able to make symlinks.
+/// For windows, this may require you to enable developer mode.
+fn package_symlink_to_submodule() {
+ #[cfg(unix)]
+ use std::os::unix::fs::symlink;
+ #[cfg(windows)]
+ use std::os::windows::fs::symlink_dir as symlink;
+
+ if !symlink_supported() {
+ return;
+ }
+
+ let project = git::new("foo", |project| {
+ project.file("src/lib.rs", "pub fn foo() {}")
+ });
+
+ let library = git::new("submodule", |library| {
+ library.no_manifest().file("Makefile", "all:")
+ });
+
+ let repository = git2::Repository::open(&project.root()).unwrap();
+ let url = path2url(library.root()).to_string();
+ git::add_submodule(&repository, &url, Path::new("submodule"));
+ t!(symlink(
+ &project.root().join("submodule"),
+ &project.root().join("submodule-link")
+ ));
+ git::add(&repository);
+ git::commit(&repository);
+
+ let repository = git2::Repository::open(&project.root().join("submodule")).unwrap();
+ repository
+ .reset(
+ &repository.revparse_single("HEAD").unwrap(),
+ git2::ResetType::Hard,
+ None,
+ )
+ .unwrap();
+
+ project
+ .cargo("package --no-verify -v")
+ .with_stderr_contains("[ARCHIVING] submodule/Makefile")
+ .run();
+}
+
+#[cargo_test]
+fn no_duplicates_from_modified_tracked_files() {
+ let p = git::new("all", |p| p.file("src/main.rs", "fn main() {}"));
+ p.change_file("src/main.rs", r#"fn main() { println!("A change!"); }"#);
+ p.cargo("build").run();
+ p.cargo("package --list --allow-dirty")
+ .with_stdout(
+ "\
+Cargo.lock
+Cargo.toml
+Cargo.toml.orig
+src/main.rs
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn ignore_nested() {
+ let cargo_toml = r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ "#;
+ let main_rs = r#"
+ fn main() { println!("hello"); }
+ "#;
+ let p = project()
+ .file("Cargo.toml", cargo_toml)
+ .file("src/main.rs", main_rs)
+ // If a project happens to contain a copy of itself, we should
+ // ignore it.
+ .file("a_dir/foo/Cargo.toml", cargo_toml)
+ .file("a_dir/foo/src/main.rs", main_rs)
+ .build();
+
+ p.cargo("package")
+ .with_stderr(
+ "\
+[WARNING] manifest has no documentation[..]
+See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info.
+[PACKAGING] foo v0.0.1 ([CWD])
+[VERIFYING] foo v0.0.1 ([CWD])
+[COMPILING] foo v0.0.1 ([CWD][..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[PACKAGED] 4 files, [..] ([..] compressed)
+",
+ )
+ .run();
+ assert!(p.root().join("target/package/foo-0.0.1.crate").is_file());
+ p.cargo("package -l")
+ .with_stdout(
+ "\
+Cargo.lock
+Cargo.toml
+Cargo.toml.orig
+src/main.rs
+",
+ )
+ .run();
+ p.cargo("package").with_stdout("").run();
+
+ let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap();
+ validate_crate_contents(
+ f,
+ "foo-0.0.1.crate",
+ &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"],
+ &[],
+ );
+}
+
+// Windows doesn't allow these characters in filenames.
+#[cfg(unix)]
+#[cargo_test]
+fn package_weird_characters() {
+ let p = project()
+ .file("src/main.rs", r#"fn main() { println!("hello"); }"#)
+ .file("src/:foo", "")
+ .build();
+
+ p.cargo("package")
+ .with_status(101)
+ .with_stderr(
+ "\
+warning: [..]
+See [..]
+[ERROR] cannot package a filename with a special character `:`: src/:foo
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn repackage_on_source_change() {
+ let p = project()
+ .file("src/main.rs", r#"fn main() { println!("hello"); }"#)
+ .build();
+
+ p.cargo("package").run();
+
+ // Add another source file
+ p.change_file("src/foo.rs", r#"fn main() { println!("foo"); }"#);
+
+ // Check that cargo rebuilds the tarball
+ p.cargo("package")
+ .with_stderr(
+ "\
+[WARNING] [..]
+See [..]
+[PACKAGING] foo v0.0.1 ([CWD])
+[VERIFYING] foo v0.0.1 ([CWD])
+[COMPILING] foo v0.0.1 ([CWD][..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[PACKAGED] 5 files, [..] ([..] compressed)
+",
+ )
+ .run();
+
+ // Check that the tarball contains the added file
+ let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap();
+ validate_crate_contents(
+ f,
+ "foo-0.0.1.crate",
+ &[
+ "Cargo.lock",
+ "Cargo.toml",
+ "Cargo.toml.orig",
+ "src/main.rs",
+ "src/foo.rs",
+ ],
+ &[],
+ );
+}
+
+#[cargo_test]
+/// Tests if a broken symlink is properly handled when packaging.
+///
+/// This test requires you to be able to make symlinks.
+/// For windows, this may require you to enable developer mode.
+fn broken_symlink() {
+ #[cfg(unix)]
+ use std::os::unix::fs::symlink;
+ #[cfg(windows)]
+ use std::os::windows::fs::symlink_dir as symlink;
+
+ if !symlink_supported() {
+ return;
+ }
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = 'foo'
+ documentation = 'foo'
+ homepage = 'foo'
+ repository = 'foo'
+ "#,
+ )
+ .file("src/main.rs", r#"fn main() { println!("hello"); }"#)
+ .build();
+ t!(symlink("nowhere", &p.root().join("src/foo.rs")));
+
+ p.cargo("package -v")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+[ERROR] failed to prepare local package for uploading
+
+Caused by:
+ failed to open for archiving: `[..]foo.rs`
+
+Caused by:
+ [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+/// Tests if a broken but excluded symlink is ignored.
+/// See issue rust-lang/cargo#10917
+///
+/// This test requires you to be able to make symlinks.
+/// For windows, this may require you to enable developer mode.
+fn broken_but_excluded_symlink() {
+ #[cfg(unix)]
+ use std::os::unix::fs::symlink;
+ #[cfg(windows)]
+ use std::os::windows::fs::symlink_dir as symlink;
+
+ if !symlink_supported() {
+ return;
+ }
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = 'foo'
+ documentation = 'foo'
+ homepage = 'foo'
+ repository = 'foo'
+ exclude = ["src/foo.rs"]
+ "#,
+ )
+ .file("src/main.rs", r#"fn main() { println!("hello"); }"#)
+ .build();
+ t!(symlink("nowhere", &p.root().join("src/foo.rs")));
+
+ p.cargo("package -v --list")
+ // `src/foo.rs` is excluded.
+ .with_stdout(
+ "\
+Cargo.lock
+Cargo.toml
+Cargo.toml.orig
+src/main.rs
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+#[cfg(not(windows))] // https://github.com/libgit2/libgit2/issues/6250
+/// Test that /dir and /dir/ matches symlinks to directories.
+fn gitignore_symlink_dir() {
+ if !symlink_supported() {
+ return;
+ }
+
+ let (p, _repo) = git::new_repo("foo", |p| {
+ p.file("src/main.rs", r#"fn main() { println!("hello"); }"#)
+ .symlink_dir("src", "src1")
+ .symlink_dir("src", "src2")
+ .symlink_dir("src", "src3")
+ .symlink_dir("src", "src4")
+ .file(".gitignore", "/src1\n/src2/\nsrc3\nsrc4/")
+ });
+
+ p.cargo("package -l --no-metadata")
+ .with_stderr("")
+ .with_stdout(
+ "\
+.cargo_vcs_info.json
+.gitignore
+Cargo.lock
+Cargo.toml
+Cargo.toml.orig
+src/main.rs
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+#[cfg(not(windows))] // https://github.com/libgit2/libgit2/issues/6250
+/// Test that /dir and /dir/ matches symlinks to directories in dirty working directory.
+fn gitignore_symlink_dir_dirty() {
+ if !symlink_supported() {
+ return;
+ }
+
+ let (p, _repo) = git::new_repo("foo", |p| {
+ p.file("src/main.rs", r#"fn main() { println!("hello"); }"#)
+ .file(".gitignore", "/src1\n/src2/\nsrc3\nsrc4/")
+ });
+
+ p.symlink("src", "src1");
+ p.symlink("src", "src2");
+ p.symlink("src", "src3");
+ p.symlink("src", "src4");
+
+ p.cargo("package -l --no-metadata")
+ .with_stderr("")
+ .with_stdout(
+ "\
+.cargo_vcs_info.json
+.gitignore
+Cargo.lock
+Cargo.toml
+Cargo.toml.orig
+src/main.rs
+",
+ )
+ .run();
+
+ p.cargo("package -l --no-metadata --allow-dirty")
+ .with_stderr("")
+ .with_stdout(
+ "\
+.gitignore
+Cargo.lock
+Cargo.toml
+Cargo.toml.orig
+src/main.rs
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+/// Tests if a symlink to a directory is properly included.
+///
+/// This test requires you to be able to make symlinks.
+/// For windows, this may require you to enable developer mode.
+fn package_symlink_to_dir() {
+ if !symlink_supported() {
+ return;
+ }
+
+ project()
+ .file("src/main.rs", r#"fn main() { println!("hello"); }"#)
+ .file("bla/Makefile", "all:")
+ .symlink_dir("bla", "foo")
+ .build()
+ .cargo("package -v")
+ .with_stderr_contains("[ARCHIVING] foo/Makefile")
+ .run();
+}
+
+#[cargo_test]
+/// Tests if a symlink to ancestor causes filesystem loop error.
+///
+/// This test requires you to be able to make symlinks.
+/// For windows, this may require you to enable developer mode.
+fn filesystem_loop() {
+ if !symlink_supported() {
+ return;
+ }
+
+ project()
+ .file("src/main.rs", r#"fn main() { println!("hello"); }"#)
+ .symlink_dir("a/b", "a/b/c/d/foo")
+ .build()
+ .cargo("package -v")
+ .with_stderr_contains(
+ "[WARNING] File system loop found: [..]/a/b/c/d/foo points to an ancestor [..]/a/b",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn do_not_package_if_repository_is_dirty() {
+ let p = project().build();
+
+ // Create a Git repository containing a minimal Rust project.
+ let _ = git::repo(&paths::root().join("foo"))
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ license = "MIT"
+ description = "foo"
+ documentation = "foo"
+ homepage = "foo"
+ repository = "foo"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ // Modify Cargo.toml without committing the change.
+ p.change_file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ license = "MIT"
+ description = "foo"
+ documentation = "foo"
+ homepage = "foo"
+ repository = "foo"
+ # change
+ "#,
+ );
+
+ p.cargo("package")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: 1 files in the working directory contain changes that were not yet \
+committed into git:
+
+Cargo.toml
+
+to proceed despite this and include the uncommitted changes, pass the `--allow-dirty` flag
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn dirty_ignored() {
+ // Cargo warns about an ignored file that will be published.
+ let (p, repo) = git::new_repo("foo", |p| {
+ p.file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ description = "foo"
+ license = "foo"
+ documentation = "foo"
+ include = ["src", "build"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(".gitignore", "build")
+ });
+ // Example of adding a file that is confusingly ignored by an overzealous
+ // gitignore rule.
+ p.change_file("src/build/mod.rs", "");
+ p.cargo("package --list")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: 1 files in the working directory contain changes that were not yet committed into git:
+
+src/build/mod.rs
+
+to proceed despite this and include the uncommitted changes, pass the `--allow-dirty` flag
+",
+ )
+ .run();
+ // Add the ignored file and make sure it is included.
+ let mut index = t!(repo.index());
+ t!(index.add_path(Path::new("src/build/mod.rs")));
+ t!(index.write());
+ git::commit(&repo);
+ p.cargo("package --list")
+ .with_stderr("")
+ .with_stdout(
+ "\
+.cargo_vcs_info.json
+Cargo.toml
+Cargo.toml.orig
+src/build/mod.rs
+src/lib.rs
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn generated_manifest() {
+ let registry = registry::alt_init();
+ Package::new("abc", "1.0.0").publish();
+ Package::new("def", "1.0.0").alternative(true).publish();
+ Package::new("ghi", "1.0.0").publish();
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ exclude = ["*.txt"]
+ license = "MIT"
+ description = "foo"
+
+ [package.metadata]
+ foo = 'bar'
+
+ [workspace]
+
+ [dependencies]
+ bar = { path = "bar", version = "0.1" }
+ def = { version = "1.0", registry = "alternative" }
+ ghi = "1.0"
+ abc = "1.0"
+ "#,
+ )
+ .file("src/main.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("package --no-verify").run();
+
+ let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap();
+ let rewritten_toml = format!(
+ r#"{}
+[package]
+name = "foo"
+version = "0.0.1"
+authors = []
+exclude = ["*.txt"]
+description = "foo"
+license = "MIT"
+
+[package.metadata]
+foo = "bar"
+
+[dependencies.abc]
+version = "1.0"
+
+[dependencies.bar]
+version = "0.1"
+
+[dependencies.def]
+version = "1.0"
+registry-index = "{}"
+
+[dependencies.ghi]
+version = "1.0"
+"#,
+ cargo::core::package::MANIFEST_PREAMBLE,
+ registry.index_url()
+ );
+
+ validate_crate_contents(
+ f,
+ "foo-0.0.1.crate",
+ &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"],
+ &[("Cargo.toml", &rewritten_toml)],
+ );
+}
+
+#[cargo_test]
+fn ignore_workspace_specifier() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ authors = []
+
+ [workspace]
+
+ [dependencies]
+ bar = { path = "bar", version = "0.1" }
+ "#,
+ )
+ .file("src/main.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+ workspace = ".."
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("package --no-verify").cwd("bar").run();
+
+ let f = File::open(&p.root().join("target/package/bar-0.1.0.crate")).unwrap();
+ let rewritten_toml = format!(
+ r#"{}
+[package]
+name = "bar"
+version = "0.1.0"
+authors = []
+"#,
+ cargo::core::package::MANIFEST_PREAMBLE
+ );
+ validate_crate_contents(
+ f,
+ "bar-0.1.0.crate",
+ &["Cargo.toml", "Cargo.toml.orig", "src/lib.rs"],
+ &[("Cargo.toml", &rewritten_toml)],
+ );
+}
+
+#[cargo_test]
+fn package_two_kinds_of_deps() {
+ Package::new("other", "1.0.0").publish();
+ Package::new("other1", "1.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ other = "1.0"
+ other1 = { version = "1.0" }
+ "#,
+ )
+ .file("src/main.rs", "")
+ .build();
+
+ p.cargo("package --no-verify").run();
+}
+
+#[cargo_test]
+fn test_edition() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ cargo-features = ["edition"]
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ edition = "2018"
+ "#,
+ )
+ .file("src/lib.rs", r#" "#)
+ .build();
+
+ p.cargo("check -v")
+ .with_stderr_contains(
+ "\
+[CHECKING] foo v0.0.1 ([..])
+[RUNNING] `rustc [..]--edition=2018 [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn edition_with_metadata() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ edition = "2018"
+
+ [package.metadata.docs.rs]
+ features = ["foobar"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("package").run();
+}
+
+#[cargo_test]
+fn test_edition_malformed() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ edition = "chicken"
+ "#,
+ )
+ .file("src/lib.rs", r#" "#)
+ .build();
+
+ p.cargo("check -v")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[..]`
+
+Caused by:
+ failed to parse the `edition` key
+
+Caused by:
+ supported edition values are `2015`, `2018`, or `2021`, but `chicken` is unknown
+"
+ .to_string(),
+ )
+ .run();
+}
+
+#[cargo_test]
+fn test_edition_from_the_future() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"[package]
+ edition = "2038"
+ name = "foo"
+ version = "99.99.99"
+ authors = []
+ "#,
+ )
+ .file("src/main.rs", r#""#)
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[..]`
+
+Caused by:
+ failed to parse the `edition` key
+
+Caused by:
+ this version of Cargo is older than the `2038` edition, and only supports `2015`, `2018`, and `2021` editions.
+"
+ .to_string(),
+ )
+ .run();
+}
+
+#[cargo_test]
+fn do_not_package_if_src_was_modified() {
+ let p = project()
+ .file("src/main.rs", r#"fn main() { println!("hello"); }"#)
+ .file("dir/foo.txt", "")
+ .file("bar.txt", "")
+ .file(
+ "build.rs",
+ r#"
+ use std::fs;
+
+ fn main() {
+ fs::write("src/generated.txt",
+ "Hello, world of generated files."
+ ).expect("failed to create file");
+ fs::remove_file("dir/foo.txt").expect("failed to remove file");
+ fs::remove_dir("dir").expect("failed to remove dir");
+ fs::write("bar.txt", "updated content").expect("failed to update");
+ fs::create_dir("new-dir").expect("failed to create dir");
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("package")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+error: failed to verify package tarball
+
+Caused by:
+ Source directory was modified by build.rs during cargo publish. \
+ Build scripts should not modify anything outside of OUT_DIR.
+ Changed: [CWD]/target/package/foo-0.0.1/bar.txt
+ Added: [CWD]/target/package/foo-0.0.1/new-dir
+ <tab>[CWD]/target/package/foo-0.0.1/src/generated.txt
+ Removed: [CWD]/target/package/foo-0.0.1/dir
+ <tab>[CWD]/target/package/foo-0.0.1/dir/foo.txt
+
+ To proceed despite this, pass the `--no-verify` flag.",
+ )
+ .run();
+
+ p.cargo("package --no-verify").run();
+}
+
+#[cargo_test]
+fn package_with_select_features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+
+ [features]
+ required = []
+ optional = []
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ "#[cfg(not(feature = \"required\"))]
+ compile_error!(\"This crate requires `required` feature!\");
+ fn main() {}",
+ )
+ .build();
+
+ p.cargo("package --features required").run();
+}
+
+#[cargo_test]
+fn package_with_all_features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+
+ [features]
+ required = []
+ optional = []
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ "#[cfg(not(feature = \"required\"))]
+ compile_error!(\"This crate requires `required` feature!\");
+ fn main() {}",
+ )
+ .build();
+
+ p.cargo("package --all-features").run();
+}
+
+#[cargo_test]
+fn package_no_default_features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+
+ [features]
+ default = ["required"]
+ required = []
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ "#[cfg(not(feature = \"required\"))]
+ compile_error!(\"This crate requires `required` feature!\");
+ fn main() {}",
+ )
+ .build();
+
+ p.cargo("package --no-default-features")
+ .with_stderr_contains("error: This crate requires `required` feature!")
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn include_cargo_toml_implicit() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ include = ["src/lib.rs"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("package --list")
+ .with_stdout("Cargo.toml\nCargo.toml.orig\nsrc/lib.rs\n")
+ .run();
+}
+
+fn include_exclude_test(include: &str, exclude: &str, files: &[&str], expected: &str) {
+ let mut pb = project().file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ documentation = "foo"
+ homepage = "foo"
+ repository = "foo"
+ include = {}
+ exclude = {}
+ "#,
+ include, exclude
+ ),
+ );
+ for file in files {
+ pb = pb.file(file, "");
+ }
+ let p = pb.build();
+
+ p.cargo("package --list")
+ .with_stderr("")
+ .with_stdout(expected)
+ .run();
+ p.root().rm_rf();
+}
+
+#[cargo_test]
+fn package_include_ignore_only() {
+ // Test with a gitignore pattern that fails to parse with glob.
+ // This is a somewhat nonsense pattern, but is an example of something git
+ // allows and glob does not.
+ assert!(glob::Pattern::new("src/abc**").is_err());
+
+ include_exclude_test(
+ r#"["Cargo.toml", "src/abc**", "src/lib.rs"]"#,
+ "[]",
+ &["src/lib.rs", "src/abc1.rs", "src/abc2.rs", "src/abc/mod.rs"],
+ "Cargo.toml\n\
+ Cargo.toml.orig\n\
+ src/abc/mod.rs\n\
+ src/abc1.rs\n\
+ src/abc2.rs\n\
+ src/lib.rs\n\
+ ",
+ )
+}
+
+#[cargo_test]
+fn gitignore_patterns() {
+ include_exclude_test(
+ r#"["Cargo.toml", "foo"]"#, // include
+ "[]",
+ &["src/lib.rs", "foo", "a/foo", "a/b/foo", "x/foo/y", "bar"],
+ "Cargo.toml\n\
+ Cargo.toml.orig\n\
+ a/b/foo\n\
+ a/foo\n\
+ foo\n\
+ x/foo/y\n\
+ ",
+ );
+
+ include_exclude_test(
+ r#"["Cargo.toml", "/foo"]"#, // include
+ "[]",
+ &["src/lib.rs", "foo", "a/foo", "a/b/foo", "x/foo/y", "bar"],
+ "Cargo.toml\n\
+ Cargo.toml.orig\n\
+ foo\n\
+ ",
+ );
+
+ include_exclude_test(
+ "[]",
+ r#"["foo/"]"#, // exclude
+ &["src/lib.rs", "foo", "a/foo", "x/foo/y", "bar"],
+ "Cargo.toml\n\
+ Cargo.toml.orig\n\
+ a/foo\n\
+ bar\n\
+ foo\n\
+ src/lib.rs\n\
+ ",
+ );
+
+ include_exclude_test(
+ "[]",
+ r#"["*.txt", "[ab]", "[x-z]"]"#, // exclude
+ &[
+ "src/lib.rs",
+ "foo.txt",
+ "bar/foo.txt",
+ "other",
+ "a",
+ "b",
+ "c",
+ "x",
+ "y",
+ "z",
+ ],
+ "Cargo.toml\n\
+ Cargo.toml.orig\n\
+ c\n\
+ other\n\
+ src/lib.rs\n\
+ ",
+ );
+
+ include_exclude_test(
+ r#"["Cargo.toml", "**/foo/bar"]"#, // include
+ "[]",
+ &["src/lib.rs", "a/foo/bar", "foo", "bar"],
+ "Cargo.toml\n\
+ Cargo.toml.orig\n\
+ a/foo/bar\n\
+ ",
+ );
+
+ include_exclude_test(
+ r#"["Cargo.toml", "foo/**"]"#, // include
+ "[]",
+ &["src/lib.rs", "a/foo/bar", "foo/x/y/z"],
+ "Cargo.toml\n\
+ Cargo.toml.orig\n\
+ foo/x/y/z\n\
+ ",
+ );
+
+ include_exclude_test(
+ r#"["Cargo.toml", "a/**/b"]"#, // include
+ "[]",
+ &["src/lib.rs", "a/b", "a/x/b", "a/x/y/b"],
+ "Cargo.toml\n\
+ Cargo.toml.orig\n\
+ a/b\n\
+ a/x/b\n\
+ a/x/y/b\n\
+ ",
+ );
+}
+
+#[cargo_test]
+fn gitignore_negate() {
+ include_exclude_test(
+ r#"["Cargo.toml", "*.rs", "!foo.rs", "\\!important"]"#, // include
+ "[]",
+ &["src/lib.rs", "foo.rs", "!important"],
+ "!important\n\
+ Cargo.toml\n\
+ Cargo.toml.orig\n\
+ src/lib.rs\n\
+ ",
+ );
+
+ // NOTE: This is unusual compared to git. Git treats `src/` as a
+ // short-circuit which means rules like `!src/foo.rs` would never run.
+ // However, because Cargo only works by iterating over *files*, it doesn't
+ // short-circuit.
+ include_exclude_test(
+ r#"["Cargo.toml", "src/", "!src/foo.rs"]"#, // include
+ "[]",
+ &["src/lib.rs", "src/foo.rs"],
+ "Cargo.toml\n\
+ Cargo.toml.orig\n\
+ src/lib.rs\n\
+ ",
+ );
+
+ include_exclude_test(
+ r#"["Cargo.toml", "src/*.rs", "!foo.rs"]"#, // include
+ "[]",
+ &["src/lib.rs", "foo.rs", "src/foo.rs", "src/bar/foo.rs"],
+ "Cargo.toml\n\
+ Cargo.toml.orig\n\
+ src/lib.rs\n\
+ ",
+ );
+
+ include_exclude_test(
+ "[]",
+ r#"["*.rs", "!foo.rs", "\\!important"]"#, // exclude
+ &["src/lib.rs", "foo.rs", "!important"],
+ "Cargo.toml\n\
+ Cargo.toml.orig\n\
+ foo.rs\n\
+ ",
+ );
+}
+
+#[cargo_test]
+fn exclude_dot_files_and_directories_by_default() {
+ include_exclude_test(
+ "[]",
+ "[]",
+ &["src/lib.rs", ".dotfile", ".dotdir/file"],
+ "Cargo.toml\n\
+ Cargo.toml.orig\n\
+ src/lib.rs\n\
+ ",
+ );
+
+ include_exclude_test(
+ r#"["Cargo.toml", "src/lib.rs", ".dotfile", ".dotdir/file"]"#,
+ "[]",
+ &["src/lib.rs", ".dotfile", ".dotdir/file"],
+ ".dotdir/file\n\
+ .dotfile\n\
+ Cargo.toml\n\
+ Cargo.toml.orig\n\
+ src/lib.rs\n\
+ ",
+ );
+}
+
+#[cargo_test]
+fn invalid_license_file_path() {
+ // Test warning when license-file points to a non-existent file.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "1.0.0"
+ license-file = "does-not-exist"
+ description = "foo"
+ homepage = "foo"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("package --no-verify")
+ .with_stderr(
+ "\
+[WARNING] license-file `does-not-exist` does not appear to exist (relative to `[..]/foo`).
+Please update the license-file setting in the manifest at `[..]/foo/Cargo.toml`
+This may become a hard error in the future.
+[PACKAGING] foo v1.0.0 ([..]/foo)
+[PACKAGED] [..] files, [..] ([..] compressed)
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn license_file_implicit_include() {
+ // license-file should be automatically included even if not listed.
+ let p = git::new("foo", |p| {
+ p.file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "1.0.0"
+ license-file = "subdir/LICENSE"
+ description = "foo"
+ homepage = "foo"
+ include = ["src"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("subdir/LICENSE", "license text")
+ });
+
+ p.cargo("package --list")
+ .with_stdout(
+ "\
+.cargo_vcs_info.json
+Cargo.toml
+Cargo.toml.orig
+src/lib.rs
+subdir/LICENSE
+",
+ )
+ .with_stderr("")
+ .run();
+
+ p.cargo("package --no-verify -v")
+ .with_stderr(
+ "\
+[PACKAGING] foo v1.0.0 [..]
+[ARCHIVING] .cargo_vcs_info.json
+[ARCHIVING] Cargo.toml
+[ARCHIVING] Cargo.toml.orig
+[ARCHIVING] src/lib.rs
+[ARCHIVING] subdir/LICENSE
+[PACKAGED] 5 files, [..] ([..] compressed)
+",
+ )
+ .run();
+ let f = File::open(&p.root().join("target/package/foo-1.0.0.crate")).unwrap();
+ validate_crate_contents(
+ f,
+ "foo-1.0.0.crate",
+ &[
+ ".cargo_vcs_info.json",
+ "Cargo.toml",
+ "Cargo.toml.orig",
+ "subdir/LICENSE",
+ "src/lib.rs",
+ ],
+ &[("subdir/LICENSE", "license text")],
+ );
+}
+
+#[cargo_test]
+fn relative_license_included() {
+ // license-file path outside of package will copy into root.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "1.0.0"
+ license-file = "../LICENSE"
+ description = "foo"
+ homepage = "foo"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("../LICENSE", "license text")
+ .build();
+
+ p.cargo("package --list")
+ .with_stdout(
+ "\
+Cargo.toml
+Cargo.toml.orig
+LICENSE
+src/lib.rs
+",
+ )
+ .with_stderr("")
+ .run();
+
+ p.cargo("package")
+ .with_stderr(
+ "\
+[PACKAGING] foo v1.0.0 [..]
+[VERIFYING] foo v1.0.0 [..]
+[COMPILING] foo v1.0.0 [..]
+[FINISHED] [..]
+[PACKAGED] 4 files, [..] ([..] compressed)
+",
+ )
+ .run();
+ let f = File::open(&p.root().join("target/package/foo-1.0.0.crate")).unwrap();
+ validate_crate_contents(
+ f,
+ "foo-1.0.0.crate",
+ &["Cargo.toml", "Cargo.toml.orig", "LICENSE", "src/lib.rs"],
+ &[("LICENSE", "license text")],
+ );
+ let manifest =
+ std::fs::read_to_string(p.root().join("target/package/foo-1.0.0/Cargo.toml")).unwrap();
+ assert!(manifest.contains("license-file = \"LICENSE\""));
+ let orig =
+ std::fs::read_to_string(p.root().join("target/package/foo-1.0.0/Cargo.toml.orig")).unwrap();
+ assert!(orig.contains("license-file = \"../LICENSE\""));
+}
+
+#[cargo_test]
+fn relative_license_include_collision() {
+ // Can't copy a relative license-file if there is a file with that name already.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "1.0.0"
+ license-file = "../LICENSE"
+ description = "foo"
+ homepage = "foo"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("../LICENSE", "outer license")
+ .file("LICENSE", "inner license")
+ .build();
+
+ p.cargo("package --list")
+ .with_stdout(
+ "\
+Cargo.toml
+Cargo.toml.orig
+LICENSE
+src/lib.rs
+",
+ )
+ .with_stderr("[WARNING] license-file `../LICENSE` appears to be [..]")
+ .run();
+
+ p.cargo("package")
+ .with_stderr(
+ "\
+[WARNING] license-file `../LICENSE` appears to be [..]
+[PACKAGING] foo v1.0.0 [..]
+[VERIFYING] foo v1.0.0 [..]
+[COMPILING] foo v1.0.0 [..]
+[FINISHED] [..]
+[PACKAGED] 4 files, [..] ([..] compressed)
+",
+ )
+ .run();
+ let f = File::open(&p.root().join("target/package/foo-1.0.0.crate")).unwrap();
+ validate_crate_contents(
+ f,
+ "foo-1.0.0.crate",
+ &["Cargo.toml", "Cargo.toml.orig", "LICENSE", "src/lib.rs"],
+ &[("LICENSE", "inner license")],
+ );
+ let manifest = read_to_string(p.root().join("target/package/foo-1.0.0/Cargo.toml")).unwrap();
+ assert!(manifest.contains("license-file = \"LICENSE\""));
+ let orig = read_to_string(p.root().join("target/package/foo-1.0.0/Cargo.toml.orig")).unwrap();
+ assert!(orig.contains("license-file = \"../LICENSE\""));
+}
+
+#[cargo_test]
+#[cfg(not(windows))] // Don't want to create invalid files on Windows.
+fn package_restricted_windows() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ license = "MIT"
+ description = "foo"
+ homepage = "foo"
+ "#,
+ )
+ .file("src/lib.rs", "pub mod con;\npub mod aux;")
+ .file("src/con.rs", "pub fn f() {}")
+ .file("src/aux/mod.rs", "pub fn f() {}")
+ .build();
+
+ p.cargo("package")
+ // use unordered here because the order of the warning is different on each platform.
+ .with_stderr_unordered(
+ "\
+[WARNING] file src/aux/mod.rs is a reserved Windows filename, it will not work on Windows platforms
+[WARNING] file src/con.rs is a reserved Windows filename, it will not work on Windows platforms
+[PACKAGING] foo [..]
+[VERIFYING] foo [..]
+[COMPILING] foo [..]
+[PACKAGED] [..] files, [..] ([..] compressed)
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn finds_git_in_parent() {
+ // Test where `Cargo.toml` is not in the root of the git repo.
+ let repo_path = paths::root().join("repo");
+ fs::create_dir(&repo_path).unwrap();
+ let p = project()
+ .at("repo/foo")
+ .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("src/lib.rs", "")
+ .build();
+ let repo = git::init(&repo_path);
+ git::add(&repo);
+ git::commit(&repo);
+ p.change_file("ignoreme", "");
+ p.change_file("ignoreme2", "");
+ p.cargo("package --list --allow-dirty")
+ .with_stdout(
+ "\
+Cargo.toml
+Cargo.toml.orig
+ignoreme
+ignoreme2
+src/lib.rs
+",
+ )
+ .run();
+
+ p.change_file(".gitignore", "ignoreme");
+ p.cargo("package --list --allow-dirty")
+ .with_stdout(
+ "\
+.gitignore
+Cargo.toml
+Cargo.toml.orig
+ignoreme2
+src/lib.rs
+",
+ )
+ .run();
+
+ fs::write(repo_path.join(".gitignore"), "ignoreme2").unwrap();
+ p.cargo("package --list --allow-dirty")
+ .with_stdout(
+ "\
+.gitignore
+Cargo.toml
+Cargo.toml.orig
+src/lib.rs
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+#[cfg(windows)]
+fn reserved_windows_name() {
+ // If we are running on a version of Windows that allows these reserved filenames,
+ // skip this test.
+ if paths::windows_reserved_names_are_allowed() {
+ return;
+ }
+
+ Package::new("bar", "1.0.0")
+ .file("src/lib.rs", "pub mod aux;")
+ .file("src/aux.rs", "")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+
+ [dependencies]
+ bar = "1.0.0"
+ "#,
+ )
+ .file("src/main.rs", "extern crate bar;\nfn main() { }")
+ .build();
+ p.cargo("package")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+error: failed to verify package tarball
+
+Caused by:
+ failed to download replaced source registry `[..]`
+
+Caused by:
+ failed to unpack package `[..] `[..]`)`
+
+Caused by:
+ failed to unpack entry at `[..]aux.rs`
+
+Caused by:
+ `[..]aux.rs` appears to contain a reserved Windows path, it cannot be extracted on Windows
+
+Caused by:
+ failed to unpack `[..]aux.rs`
+
+Caused by:
+ failed to unpack `[..]aux.rs` into `[..]aux.rs`",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn list_with_path_and_lock() {
+ // Allow --list even for something that isn't packageable.
+
+ // Init an empty registry because a versionless path dep will search for
+ // the package on crates.io.
+ registry::init();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ license = "MIT"
+ description = "foo"
+ homepage = "foo"
+
+ [dependencies]
+ bar = {path="bar"}
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("package --list")
+ .with_stdout(
+ "\
+Cargo.lock
+Cargo.toml
+Cargo.toml.orig
+src/main.rs
+",
+ )
+ .run();
+
+ p.cargo("package")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] all dependencies must have a version specified when packaging.
+dependency `bar` does not specify a version
+Note: The packaged dependency will use the version from crates.io,
+the `path` specification will be removed from the dependency declaration.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn long_file_names() {
+ // Filenames over 100 characters require a GNU extension tarfile.
+ // See #8453.
+
+ registry::init();
+ let long_name = concat!(
+ "012345678901234567890123456789012345678901234567890123456789",
+ "012345678901234567890123456789012345678901234567890123456789",
+ "012345678901234567890123456789012345678901234567890123456789"
+ );
+ if cfg!(windows) {
+ // Long paths on Windows require a special registry entry that is
+ // disabled by default (even on Windows 10).
+ // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
+ // If the directory where Cargo runs happens to be more than 80 characters
+ // long, then it will bump into this limit.
+ //
+ // First create a directory to account for various paths Cargo will
+ // be using in the target directory (such as "target/package/foo-0.1.0").
+ let test_path = paths::root().join("test-dir-probe-long-path-support");
+ test_path.mkdir_p();
+ let test_path = test_path.join(long_name);
+ if let Err(e) = File::create(&test_path) {
+ // write to stderr directly to avoid output from being captured
+ // and always display text, even without --nocapture
+ use std::io::Write;
+ writeln!(
+ std::io::stderr(),
+ "\nSkipping long_file_names test, this OS or filesystem does not \
+ appear to support long file paths: {:?}\n{:?}",
+ e,
+ test_path
+ )
+ .unwrap();
+ return;
+ }
+ }
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ license = "MIT"
+ description = "foo"
+ homepage = "foo"
+
+ [dependencies]
+ "#,
+ )
+ .file(long_name, "something")
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("package").run();
+ p.cargo("package --list")
+ .with_stdout(&format!(
+ "\
+{}
+Cargo.lock
+Cargo.toml
+Cargo.toml.orig
+src/main.rs
+",
+ long_name
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn reproducible_output() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ exclude = ["*.txt"]
+ license = "MIT"
+ description = "foo"
+ "#,
+ )
+ .file("src/main.rs", r#"fn main() { println!("hello"); }"#)
+ .build();
+
+ p.cargo("package").run();
+ assert!(p.root().join("target/package/foo-0.0.1.crate").is_file());
+
+ let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap();
+ let decoder = GzDecoder::new(f);
+ let mut archive = Archive::new(decoder);
+ for ent in archive.entries().unwrap() {
+ let ent = ent.unwrap();
+ println!("checking {:?}", ent.path());
+ let header = ent.header();
+ assert_eq!(header.mode().unwrap(), 0o644);
+ assert!(header.mtime().unwrap() != 0);
+ assert_eq!(header.username().unwrap().unwrap(), "");
+ assert_eq!(header.groupname().unwrap().unwrap(), "");
+ }
+}
+
+#[cargo_test]
+fn package_with_resolver_and_metadata() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ resolver = '2'
+
+ [package.metadata.docs.rs]
+ all-features = true
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("package").run();
+}
+
+#[cargo_test]
+fn deleted_git_working_tree() {
+ // When deleting a file, but not staged, cargo should ignore the file.
+ let (p, repo) = git::new_repo("foo", |p| {
+ p.file("src/lib.rs", "").file("src/main.rs", "fn main() {}")
+ });
+ p.root().join("src/lib.rs").rm_rf();
+ p.cargo("package --allow-dirty --list")
+ .with_stdout(
+ "\
+Cargo.lock
+Cargo.toml
+Cargo.toml.orig
+src/main.rs
+",
+ )
+ .run();
+ p.cargo("package --allow-dirty").run();
+ let mut index = t!(repo.index());
+ t!(index.remove(Path::new("src/lib.rs"), 0));
+ t!(index.write());
+ p.cargo("package --allow-dirty --list")
+ .with_stdout(
+ "\
+Cargo.lock
+Cargo.toml
+Cargo.toml.orig
+src/main.rs
+",
+ )
+ .run();
+ p.cargo("package --allow-dirty").run();
+}
+
+#[cargo_test]
+fn in_workspace() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+
+ [workspace]
+ members = ["bar"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "bar"
+ workspace = ".."
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("package --workspace")
+ .with_stderr(
+ "\
+[WARNING] manifest has no documentation, [..]
+See [..]
+[PACKAGING] bar v0.0.1 ([CWD]/bar)
+[VERIFYING] bar v0.0.1 ([CWD]/bar)
+[COMPILING] bar v0.0.1 ([CWD][..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[PACKAGED] [..] files, [..] ([..] compressed)
+[WARNING] manifest has no documentation, [..]
+See [..]
+[PACKAGING] foo v0.0.1 ([CWD])
+[VERIFYING] foo v0.0.1 ([CWD])
+[COMPILING] foo v0.0.1 ([CWD][..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[PACKAGED] [..] files, [..] ([..] compressed)
+",
+ )
+ .run();
+
+ assert!(p.root().join("target/package/foo-0.0.1.crate").is_file());
+ assert!(p.root().join("target/package/bar-0.0.1.crate").is_file());
+}
+
+#[cargo_test]
+fn workspace_overrides_resolver() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar", "baz"]
+ "#,
+ )
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ edition = "2021"
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .file(
+ "baz/Cargo.toml",
+ r#"
+ [package]
+ name = "baz"
+ version = "0.1.0"
+ edition = "2015"
+ "#,
+ )
+ .file("baz/src/lib.rs", "")
+ .build();
+
+ p.cargo("package --no-verify -p bar -p baz").run();
+
+ let f = File::open(&p.root().join("target/package/bar-0.1.0.crate")).unwrap();
+ let rewritten_toml = format!(
+ r#"{}
+[package]
+edition = "2021"
+name = "bar"
+version = "0.1.0"
+resolver = "1"
+"#,
+ cargo::core::package::MANIFEST_PREAMBLE
+ );
+ validate_crate_contents(
+ f,
+ "bar-0.1.0.crate",
+ &["Cargo.toml", "Cargo.toml.orig", "src/lib.rs"],
+ &[("Cargo.toml", &rewritten_toml)],
+ );
+
+ // When the crate has the same implicit resolver as the workspace it is not overridden
+ let f = File::open(&p.root().join("target/package/baz-0.1.0.crate")).unwrap();
+ let rewritten_toml = format!(
+ r#"{}
+[package]
+edition = "2015"
+name = "baz"
+version = "0.1.0"
+"#,
+ cargo::core::package::MANIFEST_PREAMBLE
+ );
+ validate_crate_contents(
+ f,
+ "baz-0.1.0.crate",
+ &["Cargo.toml", "Cargo.toml.orig", "src/lib.rs"],
+ &[("Cargo.toml", &rewritten_toml)],
+ );
+}
+
+fn verify_packaged_status_line(
+ output: std::process::Output,
+ num_files: usize,
+ uncompressed_size: u64,
+ compressed_size: u64,
+) {
+ use cargo::util::human_readable_bytes;
+
+ let stderr = String::from_utf8(output.stderr).unwrap();
+ let mut packaged_lines = stderr
+ .lines()
+ .filter(|line| line.trim().starts_with("Packaged"));
+ let packaged_line = packaged_lines
+ .next()
+ .expect("`Packaged` status line should appear in stderr");
+ assert!(
+ packaged_lines.next().is_none(),
+ "Only one `Packaged` status line should appear in stderr"
+ );
+ let size_info = packaged_line.trim().trim_start_matches("Packaged").trim();
+ let uncompressed = human_readable_bytes(uncompressed_size);
+ let compressed = human_readable_bytes(compressed_size);
+ let expected = format!(
+ "{} files, {:.1}{} ({:.1}{} compressed)",
+ num_files, uncompressed.0, uncompressed.1, compressed.0, compressed.1
+ );
+ assert_eq!(size_info, expected);
+}
+
+#[cargo_test]
+fn basic_filesizes() {
+ let cargo_toml_orig_contents = r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ exclude = ["*.txt"]
+ license = "MIT"
+ description = "foo"
+ "#;
+ let main_rs_contents = r#"fn main() { println!("🦀"); }"#;
+ let cargo_toml_contents = format!(
+ r#"{}
+[package]
+name = "foo"
+version = "0.0.1"
+authors = []
+exclude = ["*.txt"]
+description = "foo"
+license = "MIT"
+"#,
+ cargo::core::package::MANIFEST_PREAMBLE
+ );
+ let cargo_lock_contents = r#"# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "foo"
+version = "0.0.1"
+"#;
+ let p = project()
+ .file("Cargo.toml", cargo_toml_orig_contents)
+ .file("src/main.rs", main_rs_contents)
+ .file("src/bar.txt", "Ignored text file contents") // should be ignored when packaging
+ .build();
+
+ let uncompressed_size = (cargo_toml_orig_contents.len()
+ + main_rs_contents.len()
+ + cargo_toml_contents.len()
+ + cargo_lock_contents.len()) as u64;
+ let output = p.cargo("package").exec_with_output().unwrap();
+
+ assert!(p.root().join("target/package/foo-0.0.1.crate").is_file());
+ p.cargo("package -l")
+ .with_stdout(
+ "\
+Cargo.lock
+Cargo.toml
+Cargo.toml.orig
+src/main.rs
+",
+ )
+ .run();
+ p.cargo("package").with_stdout("").run();
+
+ let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap();
+ let compressed_size = f.metadata().unwrap().len();
+ verify_packaged_status_line(output, 4, uncompressed_size, compressed_size);
+ validate_crate_contents(
+ f,
+ "foo-0.0.1.crate",
+ &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"],
+ &[
+ ("Cargo.lock", cargo_lock_contents),
+ ("Cargo.toml", &cargo_toml_contents),
+ ("Cargo.toml.orig", cargo_toml_orig_contents),
+ ("src/main.rs", main_rs_contents),
+ ],
+ );
+}
+
+#[cargo_test]
+fn larger_filesizes() {
+ let cargo_toml_orig_contents = r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ "#;
+ let lots_of_crabs = std::iter::repeat("🦀").take(1337).collect::<String>();
+ let main_rs_contents = format!(r#"fn main() {{ println!("{}"); }}"#, lots_of_crabs);
+ let bar_txt_contents = "This file is relatively uncompressible, to increase the compressed
+ package size beyond 1KiB.
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
+ ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
+ ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
+ reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur
+ sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est
+ laborum.";
+ let cargo_toml_contents = format!(
+ r#"{}
+[package]
+name = "foo"
+version = "0.0.1"
+authors = []
+description = "foo"
+license = "MIT"
+"#,
+ cargo::core::package::MANIFEST_PREAMBLE
+ );
+ let cargo_lock_contents = r#"# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "foo"
+version = "0.0.1"
+"#;
+ let p = project()
+ .file("Cargo.toml", cargo_toml_orig_contents)
+ .file("src/main.rs", &main_rs_contents)
+ .file("src/bar.txt", bar_txt_contents)
+ .build();
+
+ let uncompressed_size = (cargo_toml_orig_contents.len()
+ + main_rs_contents.len()
+ + cargo_toml_contents.len()
+ + cargo_lock_contents.len()
+ + bar_txt_contents.len()) as u64;
+
+ let output = p.cargo("package").exec_with_output().unwrap();
+ assert!(p.root().join("target/package/foo-0.0.1.crate").is_file());
+ p.cargo("package -l")
+ .with_stdout(
+ "\
+Cargo.lock
+Cargo.toml
+Cargo.toml.orig
+src/bar.txt
+src/main.rs
+",
+ )
+ .run();
+ p.cargo("package").with_stdout("").run();
+
+ let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap();
+ let compressed_size = f.metadata().unwrap().len();
+ verify_packaged_status_line(output, 5, uncompressed_size, compressed_size);
+ validate_crate_contents(
+ f,
+ "foo-0.0.1.crate",
+ &[
+ "Cargo.lock",
+ "Cargo.toml",
+ "Cargo.toml.orig",
+ "src/bar.txt",
+ "src/main.rs",
+ ],
+ &[
+ ("Cargo.lock", cargo_lock_contents),
+ ("Cargo.toml", &cargo_toml_contents),
+ ("Cargo.toml.orig", cargo_toml_orig_contents),
+ ("src/bar.txt", bar_txt_contents),
+ ("src/main.rs", &main_rs_contents),
+ ],
+ );
+}
+
+#[cargo_test]
+fn symlink_filesizes() {
+ if !symlink_supported() {
+ return;
+ }
+
+ let cargo_toml_orig_contents = r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ "#;
+ let lots_of_crabs = std::iter::repeat("🦀").take(1337).collect::<String>();
+ let main_rs_contents = format!(r#"fn main() {{ println!("{}"); }}"#, lots_of_crabs);
+ let bar_txt_contents = "This file is relatively uncompressible, to increase the compressed
+ package size beyond 1KiB.
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
+ ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
+ ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
+ reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur
+ sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est
+ laborum.";
+ let cargo_toml_contents = format!(
+ r#"{}
+[package]
+name = "foo"
+version = "0.0.1"
+authors = []
+description = "foo"
+license = "MIT"
+"#,
+ cargo::core::package::MANIFEST_PREAMBLE
+ );
+ let cargo_lock_contents = r#"# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "foo"
+version = "0.0.1"
+"#;
+
+ let p = project()
+ .file("Cargo.toml", cargo_toml_orig_contents)
+ .file("src/main.rs", &main_rs_contents)
+ .file("bla/bar.txt", bar_txt_contents)
+ .symlink("src/main.rs", "src/main.rs.bak")
+ .symlink_dir("bla", "foo")
+ .build();
+
+ let uncompressed_size = (cargo_toml_orig_contents.len()
+ + main_rs_contents.len() * 2
+ + cargo_toml_contents.len()
+ + cargo_lock_contents.len()
+ + bar_txt_contents.len() * 2) as u64;
+
+ let output = p.cargo("package").exec_with_output().unwrap();
+ assert!(p.root().join("target/package/foo-0.0.1.crate").is_file());
+ p.cargo("package -l")
+ .with_stdout(
+ "\
+Cargo.lock
+Cargo.toml
+Cargo.toml.orig
+bla/bar.txt
+foo/bar.txt
+src/main.rs
+src/main.rs.bak
+",
+ )
+ .run();
+ p.cargo("package").with_stdout("").run();
+
+ let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap();
+ let compressed_size = f.metadata().unwrap().len();
+ verify_packaged_status_line(output, 7, uncompressed_size, compressed_size);
+ validate_crate_contents(
+ f,
+ "foo-0.0.1.crate",
+ &[
+ "Cargo.lock",
+ "Cargo.toml",
+ "Cargo.toml.orig",
+ "bla/bar.txt",
+ "foo/bar.txt",
+ "src/main.rs",
+ "src/main.rs.bak",
+ ],
+ &[
+ ("Cargo.lock", cargo_lock_contents),
+ ("Cargo.toml", &cargo_toml_contents),
+ ("Cargo.toml.orig", cargo_toml_orig_contents),
+ ("bla/bar.txt", bar_txt_contents),
+ ("foo/bar.txt", bar_txt_contents),
+ ("src/main.rs", &main_rs_contents),
+ ("src/main.rs.bak", &main_rs_contents),
+ ],
+ );
+}
diff --git a/src/tools/cargo/tests/testsuite/package_features.rs b/src/tools/cargo/tests/testsuite/package_features.rs
new file mode 100644
index 000000000..15f726be5
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/package_features.rs
@@ -0,0 +1,704 @@
+//! Tests for feature selection on the command-line.
+
+use super::features2::switch_to_resolver_2;
+use cargo_test_support::registry::{Dependency, Package};
+use cargo_test_support::{basic_manifest, project};
+use std::fmt::Write;
+
+#[cargo_test]
+fn virtual_no_default_features() {
+ // --no-default-features in root of virtual workspace.
+ Package::new("dep1", "1.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["a", "b"]
+ "#,
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+
+ [dependencies]
+ dep1 = {version = "1.0", optional = true}
+
+ [features]
+ default = ["dep1"]
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.1.0"
+
+ [features]
+ default = ["f1"]
+ f1 = []
+ "#,
+ )
+ .file(
+ "b/src/lib.rs",
+ r#"
+ #[cfg(feature = "f1")]
+ compile_error!{"expected f1 off"}
+ "#,
+ )
+ .build();
+
+ p.cargo("check --no-default-features")
+ .with_stderr_unordered(
+ "\
+[UPDATING] [..]
+[CHECKING] a v0.1.0 [..]
+[CHECKING] b v0.1.0 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ p.cargo("check --features foo")
+ .with_status(101)
+ .with_stderr(
+ "[ERROR] none of the selected packages contains these features: foo, did you mean: f1?",
+ )
+ .run();
+
+ p.cargo("check --features a/dep1,b/f1,b/f2,f2")
+ .with_status(101)
+ .with_stderr("[ERROR] none of the selected packages contains these features: b/f2, f2, did you mean: f1?")
+ .run();
+
+ p.cargo("check --features a/dep,b/f1,b/f2,f2")
+ .with_status(101)
+ .with_stderr("[ERROR] none of the selected packages contains these features: a/dep, b/f2, f2, did you mean: a/dep1, f1?")
+ .run();
+
+ p.cargo("check --features a/dep,a/dep1")
+ .with_status(101)
+ .with_stderr("[ERROR] none of the selected packages contains these features: a/dep, did you mean: b/f1?")
+ .run();
+}
+
+#[cargo_test]
+fn virtual_typo_member_feature() {
+ project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+ resolver = "2"
+
+ [features]
+ deny-warnings = []
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build()
+ .cargo("check --features a/deny-warning")
+ .with_status(101)
+ .with_stderr(
+ "[ERROR] none of the selected packages contains these features: a/deny-warning, did you mean: a/deny-warnings?",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn virtual_features() {
+ // --features in root of virtual workspace.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["a", "b"]
+ "#,
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+
+ [features]
+ f1 = []
+ "#,
+ )
+ .file(
+ "a/src/lib.rs",
+ r#"
+ #[cfg(not(feature = "f1"))]
+ compile_error!{"f1 is missing"}
+ "#,
+ )
+ .file("b/Cargo.toml", &basic_manifest("b", "0.1.0"))
+ .file("b/src/lib.rs", "")
+ .build();
+
+ p.cargo("check --features f1")
+ .with_stderr_unordered(
+ "\
+[CHECKING] a [..]
+[CHECKING] b [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn virtual_with_specific() {
+ // -p flags with --features in root of virtual.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["a", "b"]
+ "#,
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+
+ [features]
+ f1 = []
+ f2 = []
+ "#,
+ )
+ .file(
+ "a/src/lib.rs",
+ r#"
+ #[cfg(not_feature = "f1")]
+ compile_error!{"f1 is missing"}
+ #[cfg(not_feature = "f2")]
+ compile_error!{"f2 is missing"}
+ "#,
+ )
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.1.0"
+
+ [features]
+ f2 = []
+ f3 = []
+ "#,
+ )
+ .file(
+ "b/src/lib.rs",
+ r#"
+ #[cfg(not_feature = "f2")]
+ compile_error!{"f2 is missing"}
+ #[cfg(not_feature = "f3")]
+ compile_error!{"f3 is missing"}
+ "#,
+ )
+ .build();
+
+ p.cargo("check -p a -p b --features f1,f2,f3")
+ .with_stderr_unordered(
+ "\
+[CHECKING] a [..]
+[CHECKING] b [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn other_member_from_current() {
+ // -p for another member while in the current directory.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar"]
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { path="bar", features=["f3"] }
+
+ [features]
+ f1 = ["bar/f4"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+
+ [features]
+ f1 = []
+ f2 = []
+ f3 = []
+ f4 = []
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .file(
+ "bar/src/main.rs",
+ r#"
+ fn main() {
+ if cfg!(feature = "f1") {
+ print!("f1");
+ }
+ if cfg!(feature = "f2") {
+ print!("f2");
+ }
+ if cfg!(feature = "f3") {
+ print!("f3");
+ }
+ if cfg!(feature = "f4") {
+ print!("f4");
+ }
+ println!();
+ }
+ "#,
+ )
+ .build();
+
+ // Old behavior.
+ p.cargo("run -p bar --features f1")
+ .with_stdout("f3f4")
+ .run();
+
+ p.cargo("run -p bar --features f1,f2")
+ .with_status(101)
+ .with_stderr("[ERROR] Package `foo[..]` does not have the feature `f2`")
+ .run();
+
+ p.cargo("run -p bar --features bar/f1")
+ .with_stdout("f1f3")
+ .run();
+
+ // New behavior.
+ switch_to_resolver_2(&p);
+ p.cargo("run -p bar --features f1").with_stdout("f1").run();
+
+ p.cargo("run -p bar --features f1,f2")
+ .with_stdout("f1f2")
+ .run();
+
+ p.cargo("run -p bar --features bar/f1")
+ .with_stdout("f1")
+ .run();
+}
+
+#[cargo_test]
+fn feature_default_resolver() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+
+ [features]
+ test = []
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ if cfg!(feature = "test") {
+ println!("feature set");
+ }
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("check --features testt")
+ .with_status(101)
+ .with_stderr("[ERROR] Package `a[..]` does not have the feature `testt`")
+ .run();
+
+ p.cargo("run --features test")
+ .with_status(0)
+ .with_stdout("feature set")
+ .run();
+
+ p.cargo("run --features a/test")
+ .with_status(101)
+ .with_stderr("[ERROR] package `a[..]` does not have a dependency named `a`")
+ .run();
+}
+
+#[cargo_test]
+fn virtual_member_slash() {
+ // member slash feature syntax
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["a"]
+ "#,
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+
+ [dependencies]
+ b = {path="../b", optional=true}
+
+ [features]
+ default = ["f1"]
+ f1 = []
+ f2 = []
+ "#,
+ )
+ .file(
+ "a/src/lib.rs",
+ r#"
+ #[cfg(feature = "f1")]
+ compile_error!{"f1 is set"}
+
+ #[cfg(feature = "f2")]
+ compile_error!{"f2 is set"}
+
+ #[cfg(feature = "b")]
+ compile_error!{"b is set"}
+ "#,
+ )
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.1.0"
+
+ [features]
+ bfeat = []
+ "#,
+ )
+ .file(
+ "b/src/lib.rs",
+ r#"
+ #[cfg(feature = "bfeat")]
+ compile_error!{"bfeat is set"}
+ "#,
+ )
+ .build();
+
+ p.cargo("check -p a")
+ .with_status(101)
+ .with_stderr_contains("[..]f1 is set[..]")
+ .with_stderr_does_not_contain("[..]f2 is set[..]")
+ .with_stderr_does_not_contain("[..]b is set[..]")
+ .run();
+
+ p.cargo("check -p a --features a/f1")
+ .with_status(101)
+ .with_stderr_contains("[..]f1 is set[..]")
+ .with_stderr_does_not_contain("[..]f2 is set[..]")
+ .with_stderr_does_not_contain("[..]b is set[..]")
+ .run();
+
+ p.cargo("check -p a --features a/f2")
+ .with_status(101)
+ .with_stderr_contains("[..]f1 is set[..]")
+ .with_stderr_contains("[..]f2 is set[..]")
+ .with_stderr_does_not_contain("[..]b is set[..]")
+ .run();
+
+ p.cargo("check -p a --features b/bfeat")
+ .with_status(101)
+ .with_stderr_contains("[..]bfeat is set[..]")
+ .run();
+
+ p.cargo("check -p a --no-default-features").run();
+
+ p.cargo("check -p a --no-default-features --features b")
+ .with_status(101)
+ .with_stderr_contains("[..]b is set[..]")
+ .run();
+}
+
+#[cargo_test]
+fn non_member() {
+ // -p for a non-member
+ Package::new("dep", "1.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ resolver = "2"
+
+ [dependencies]
+ dep = "1.0"
+
+ [features]
+ f1 = []
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check -p dep --features f1")
+ .with_status(101)
+ .with_stderr("[ERROR] cannot specify features for packages outside of workspace")
+ .run();
+
+ p.cargo("check -p dep --all-features")
+ .with_status(101)
+ .with_stderr("[ERROR] cannot specify features for packages outside of workspace")
+ .run();
+
+ p.cargo("check -p dep --no-default-features")
+ .with_status(101)
+ .with_stderr("[ERROR] cannot specify features for packages outside of workspace")
+ .run();
+
+ p.cargo("check -p dep")
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[DOWNLOADING] [..]
+[DOWNLOADED] [..]
+[CHECKING] dep [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn resolver1_member_features() {
+ // --features member-name/feature-name with resolver="1"
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["member1", "member2"]
+ "#,
+ )
+ .file(
+ "member1/Cargo.toml",
+ r#"
+ [package]
+ name = "member1"
+ version = "0.1.0"
+
+ [features]
+ m1-feature = []
+ "#,
+ )
+ .file(
+ "member1/src/main.rs",
+ r#"
+ fn main() {
+ if cfg!(feature = "m1-feature") {
+ println!("m1-feature set");
+ }
+ }
+ "#,
+ )
+ .file("member2/Cargo.toml", &basic_manifest("member2", "0.1.0"))
+ .file("member2/src/lib.rs", "")
+ .build();
+
+ p.cargo("run -p member1 --features member1/m1-feature")
+ .cwd("member2")
+ .with_stdout("m1-feature set")
+ .run();
+
+ p.cargo("check -p member1 --features member1/m2-feature")
+ .cwd("member2")
+ .with_status(101)
+ .with_stderr("[ERROR] Package `member1[..]` does not have the feature `m2-feature`")
+ .run();
+}
+
+#[cargo_test]
+fn non_member_feature() {
+ // --features for a non-member
+ Package::new("jazz", "1.0.0").publish();
+ Package::new("bar", "1.0.0")
+ .add_dep(Dependency::new("jazz", "1.0").optional(true))
+ .publish();
+ let make_toml = |resolver, optional| {
+ let mut s = String::new();
+ write!(
+ s,
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ resolver = "{}"
+
+ [dependencies]
+ "#,
+ resolver
+ )
+ .unwrap();
+ if optional {
+ s.push_str(r#"bar = { version = "1.0", optional = true } "#);
+ } else {
+ s.push_str(r#"bar = "1.0""#)
+ }
+ s.push('\n');
+ s
+ };
+ let p = project()
+ .file("Cargo.toml", &make_toml("1", false))
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("fetch").run();
+ ///////////////////////// V1 non-optional
+ eprintln!("V1 non-optional");
+ p.cargo("check -p bar")
+ .with_stderr(
+ "\
+[CHECKING] bar v1.0.0
+[FINISHED] [..]
+",
+ )
+ .run();
+ // TODO: This should not be allowed (future warning?)
+ p.cargo("check --features bar/jazz")
+ .with_stderr(
+ "\
+[DOWNLOADING] crates ...
+[DOWNLOADED] jazz v1.0.0 [..]
+[CHECKING] jazz v1.0.0
+[CHECKING] bar v1.0.0
+[CHECKING] foo v0.1.0 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+ // TODO: This should not be allowed (future warning?)
+ p.cargo("check -p bar --features bar/jazz -v")
+ .with_stderr(
+ "\
+[FRESH] jazz v1.0.0
+[FRESH] bar v1.0.0
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ ///////////////////////// V1 optional
+ eprintln!("V1 optional");
+ p.change_file("Cargo.toml", &make_toml("1", true));
+
+ // This error isn't great, but is probably unlikely to be common in
+ // practice, so I'm not going to put much effort into improving it.
+ p.cargo("check -p bar")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: package ID specification `bar` did not match any packages
+
+<tab>Did you mean `foo`?
+",
+ )
+ .run();
+
+ p.cargo("check -p bar --features bar -v")
+ .with_stderr(
+ "\
+[FRESH] bar v1.0.0
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ // TODO: This should not be allowed (future warning?)
+ p.cargo("check -p bar --features bar/jazz -v")
+ .with_stderr(
+ "\
+[FRESH] jazz v1.0.0
+[FRESH] bar v1.0.0
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ ///////////////////////// V2 non-optional
+ eprintln!("V2 non-optional");
+ p.change_file("Cargo.toml", &make_toml("2", false));
+ // TODO: This should not be allowed (future warning?)
+ p.cargo("check --features bar/jazz -v")
+ .with_stderr(
+ "\
+[FRESH] jazz v1.0.0
+[FRESH] bar v1.0.0
+[FRESH] foo v0.1.0 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+ p.cargo("check -p bar -v")
+ .with_stderr(
+ "\
+[FRESH] bar v1.0.0
+[FINISHED] [..]
+",
+ )
+ .run();
+ p.cargo("check -p bar --features bar/jazz")
+ .with_status(101)
+ .with_stderr("error: cannot specify features for packages outside of workspace")
+ .run();
+
+ ///////////////////////// V2 optional
+ eprintln!("V2 optional");
+ p.change_file("Cargo.toml", &make_toml("2", true));
+ p.cargo("check -p bar")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: package ID specification `bar` did not match any packages
+
+<tab>Did you mean `foo`?
+",
+ )
+ .run();
+ // New --features behavior does not look at cwd.
+ p.cargo("check -p bar --features bar")
+ .with_status(101)
+ .with_stderr("error: cannot specify features for packages outside of workspace")
+ .run();
+ p.cargo("check -p bar --features bar/jazz")
+ .with_status(101)
+ .with_stderr("error: cannot specify features for packages outside of workspace")
+ .run();
+ p.cargo("check -p bar --features foo/bar")
+ .with_status(101)
+ .with_stderr("error: cannot specify features for packages outside of workspace")
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/patch.rs b/src/tools/cargo/tests/testsuite/patch.rs
new file mode 100644
index 000000000..681c02416
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/patch.rs
@@ -0,0 +1,2645 @@
+//! Tests for `[patch]` table source replacement.
+
+use cargo_test_support::git;
+use cargo_test_support::paths;
+use cargo_test_support::registry::{self, Package};
+use cargo_test_support::{basic_manifest, project};
+use std::fs;
+
+#[cargo_test]
+fn replace() {
+ Package::new("bar", "0.1.0").publish();
+ Package::new("baz", "0.1.0")
+ .file(
+ "src/lib.rs",
+ "extern crate bar; pub fn baz() { bar::bar(); }",
+ )
+ .dep("bar", "0.1.0")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+ baz = "0.1.0"
+
+ [patch.crates-io]
+ bar = { path = "bar" }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "
+ extern crate bar;
+ extern crate baz;
+ pub fn bar() {
+ bar::bar();
+ baz::baz();
+ }
+ ",
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] baz v0.1.0 ([..])
+[CHECKING] bar v0.1.0 ([CWD]/bar)
+[CHECKING] baz v0.1.0
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("check").with_stderr("[FINISHED] [..]").run();
+}
+
+#[cargo_test]
+fn from_config() {
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file(
+ ".cargo/config.toml",
+ r#"
+ [patch.crates-io]
+ bar = { path = 'bar' }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.1"))
+ .file("bar/src/lib.rs", r#""#)
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[CHECKING] bar v0.1.1 ([..])
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn from_config_relative() {
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file(
+ "../.cargo/config.toml",
+ r#"
+ [patch.crates-io]
+ bar = { path = 'foo/bar' }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.1"))
+ .file("bar/src/lib.rs", r#""#)
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[CHECKING] bar v0.1.1 ([..])
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn from_config_precedence() {
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+
+ [patch.crates-io]
+ bar = { path = 'no-such-path' }
+ "#,
+ )
+ .file(
+ ".cargo/config.toml",
+ r#"
+ [patch.crates-io]
+ bar = { path = 'bar' }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.1"))
+ .file("bar/src/lib.rs", r#""#)
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[CHECKING] bar v0.1.1 ([..])
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn nonexistent() {
+ Package::new("baz", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+
+ [patch.crates-io]
+ bar = { path = "bar" }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "extern crate bar; pub fn foo() { bar::bar(); }",
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[CHECKING] bar v0.1.0 ([CWD]/bar)
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ p.cargo("check").with_stderr("[FINISHED] [..]").run();
+}
+
+#[cargo_test]
+fn patch_git() {
+ let bar = git::repo(&paths::root().join("override"))
+ .file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("src/lib.rs", "")
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = {{ git = '{}' }}
+
+ [patch.'{0}']
+ bar = {{ path = "bar" }}
+ "#,
+ bar.url()
+ ),
+ )
+ .file(
+ "src/lib.rs",
+ "extern crate bar; pub fn foo() { bar::bar(); }",
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] git repository `file://[..]`
+[CHECKING] bar v0.1.0 ([CWD]/bar)
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ p.cargo("check").with_stderr("[FINISHED] [..]").run();
+}
+
+#[cargo_test]
+fn patch_to_git() {
+ let bar = git::repo(&paths::root().join("override"))
+ .file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1"
+
+ [patch.crates-io]
+ bar = {{ git = '{}' }}
+ "#,
+ bar.url()
+ ),
+ )
+ .file(
+ "src/lib.rs",
+ "extern crate bar; pub fn foo() { bar::bar(); }",
+ )
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] git repository `file://[..]`
+[UPDATING] `dummy-registry` index
+[CHECKING] bar v0.1.0 (file://[..])
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ p.cargo("check").with_stderr("[FINISHED] [..]").run();
+}
+
+#[cargo_test]
+fn unused() {
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+
+ [patch.crates-io]
+ bar = { path = "bar" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.2.0"))
+ .file("bar/src/lib.rs", "not rust code")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[WARNING] Patch `bar v0.2.0 ([CWD]/bar)` was not used in the crate graph.
+Check that [..]
+with the [..]
+what is [..]
+version. [..]
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.1.0 [..]
+[CHECKING] bar v0.1.0
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ p.cargo("check")
+ .with_stderr(
+ "\
+[WARNING] Patch `bar v0.2.0 ([CWD]/bar)` was not used in the crate graph.
+Check that [..]
+with the [..]
+what is [..]
+version. [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ // unused patch should be in the lock file
+ let lock = p.read_lockfile();
+ let toml: toml::Table = toml::from_str(&lock).unwrap();
+ assert_eq!(toml["patch"]["unused"].as_array().unwrap().len(), 1);
+ assert_eq!(toml["patch"]["unused"][0]["name"].as_str(), Some("bar"));
+ assert_eq!(
+ toml["patch"]["unused"][0]["version"].as_str(),
+ Some("0.2.0")
+ );
+}
+
+#[cargo_test]
+fn unused_with_mismatch_source_being_patched() {
+ registry::alt_init();
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+
+ [patch.alternative]
+ bar = { path = "bar" }
+
+ [patch.crates-io]
+ bar = { path = "baz" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.2.0"))
+ .file("bar/src/lib.rs", "not rust code")
+ .file("baz/Cargo.toml", &basic_manifest("bar", "0.3.0"))
+ .file("baz/src/lib.rs", "not rust code")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[WARNING] Patch `bar v0.2.0 ([CWD]/bar)` was not used in the crate graph.
+Perhaps you misspelled the source URL being patched.
+Possible URLs for `[patch.<URL>]`:
+ crates-io
+[WARNING] Patch `bar v0.3.0 ([CWD]/baz)` was not used in the crate graph.
+Check that [..]
+with the [..]
+what is [..]
+version. [..]
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.1.0 [..]
+[CHECKING] bar v0.1.0
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn prefer_patch_version() {
+ Package::new("bar", "0.1.2").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+
+ [patch.crates-io]
+ bar = { path = "bar" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.1"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[CHECKING] bar v0.1.1 ([CWD]/bar)
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ p.cargo("check")
+ .with_stderr(
+ "\
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ // there should be no patch.unused in the toml file
+ let lock = p.read_lockfile();
+ let toml: toml::Table = toml::from_str(&lock).unwrap();
+ assert!(toml.get("patch").is_none());
+}
+
+#[cargo_test]
+fn unused_from_config() {
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file(
+ ".cargo/config.toml",
+ r#"
+ [patch.crates-io]
+ bar = { path = "bar" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.2.0"))
+ .file("bar/src/lib.rs", "not rust code")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[WARNING] Patch `bar v0.2.0 ([CWD]/bar)` was not used in the crate graph.
+Check that [..]
+with the [..]
+what is [..]
+version. [..]
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.1.0 [..]
+[CHECKING] bar v0.1.0
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ p.cargo("check")
+ .with_stderr(
+ "\
+[WARNING] Patch `bar v0.2.0 ([CWD]/bar)` was not used in the crate graph.
+Check that [..]
+with the [..]
+what is [..]
+version. [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ // unused patch should be in the lock file
+ let lock = p.read_lockfile();
+ let toml: toml::Table = toml::from_str(&lock).unwrap();
+ assert_eq!(toml["patch"]["unused"].as_array().unwrap().len(), 1);
+ assert_eq!(toml["patch"]["unused"][0]["name"].as_str(), Some("bar"));
+ assert_eq!(
+ toml["patch"]["unused"][0]["version"].as_str(),
+ Some("0.2.0")
+ );
+}
+
+#[cargo_test]
+fn unused_git() {
+ Package::new("bar", "0.1.0").publish();
+
+ let foo = git::repo(&paths::root().join("override"))
+ .file("Cargo.toml", &basic_manifest("bar", "0.2.0"))
+ .file("src/lib.rs", "")
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1"
+
+ [patch.crates-io]
+ bar = {{ git = '{}' }}
+ "#,
+ foo.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] git repository `file://[..]`
+[UPDATING] `dummy-registry` index
+[WARNING] Patch `bar v0.2.0 ([..])` was not used in the crate graph.
+Check that [..]
+with the [..]
+what is [..]
+version. [..]
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.1.0 [..]
+[CHECKING] bar v0.1.0
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ p.cargo("check")
+ .with_stderr(
+ "\
+[WARNING] Patch `bar v0.2.0 ([..])` was not used in the crate graph.
+Check that [..]
+with the [..]
+what is [..]
+version. [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn add_patch() {
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", r#""#)
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.1.0 [..]
+[CHECKING] bar v0.1.0
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ p.cargo("check").with_stderr("[FINISHED] [..]").run();
+
+ p.change_file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+
+ [patch.crates-io]
+ bar = { path = 'bar' }
+ "#,
+ );
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[CHECKING] bar v0.1.0 ([CWD]/bar)
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ p.cargo("check").with_stderr("[FINISHED] [..]").run();
+}
+
+#[cargo_test]
+fn add_patch_from_config() {
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", r#""#)
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.1.0 [..]
+[CHECKING] bar v0.1.0
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ p.cargo("check").with_stderr("[FINISHED] [..]").run();
+
+ p.change_file(
+ ".cargo/config.toml",
+ r#"
+ [patch.crates-io]
+ bar = { path = 'bar' }
+ "#,
+ );
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[CHECKING] bar v0.1.0 ([CWD]/bar)
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ p.cargo("check").with_stderr("[FINISHED] [..]").run();
+}
+
+#[cargo_test]
+fn add_ignored_patch() {
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.1"))
+ .file("bar/src/lib.rs", r#""#)
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.1.0 [..]
+[CHECKING] bar v0.1.0
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ p.cargo("check").with_stderr("[FINISHED] [..]").run();
+
+ p.change_file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+
+ [patch.crates-io]
+ bar = { path = 'bar' }
+ "#,
+ );
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[WARNING] Patch `bar v0.1.1 ([CWD]/bar)` was not used in the crate graph.
+Check that [..]
+with the [..]
+what is [..]
+version. [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ )
+ .run();
+ p.cargo("check")
+ .with_stderr(
+ "\
+[WARNING] Patch `bar v0.1.1 ([CWD]/bar)` was not used in the crate graph.
+Check that [..]
+with the [..]
+what is [..]
+version. [..]
+[FINISHED] [..]",
+ )
+ .run();
+
+ p.cargo("update").run();
+ p.cargo("check")
+ .with_stderr(
+ "\
+[CHECKING] bar v0.1.1 ([CWD]/bar)
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn add_patch_with_features() {
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+
+ [patch.crates-io]
+ bar = { path = 'bar', features = ["some_feature"] }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", r#""#)
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[WARNING] patch for `bar` uses the features mechanism. \
+default-features and features will not take effect because the patch dependency does not support this mechanism
+[UPDATING] `dummy-registry` index
+[CHECKING] bar v0.1.0 ([CWD]/bar)
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ p.cargo("check")
+ .with_stderr(
+ "\
+[WARNING] patch for `bar` uses the features mechanism. \
+default-features and features will not take effect because the patch dependency does not support this mechanism
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn add_patch_with_setting_default_features() {
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+
+ [patch.crates-io]
+ bar = { path = 'bar', default-features = false, features = ["none_default_feature"] }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", r#""#)
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[WARNING] patch for `bar` uses the features mechanism. \
+default-features and features will not take effect because the patch dependency does not support this mechanism
+[UPDATING] `dummy-registry` index
+[CHECKING] bar v0.1.0 ([CWD]/bar)
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ p.cargo("check")
+ .with_stderr(
+ "\
+[WARNING] patch for `bar` uses the features mechanism. \
+default-features and features will not take effect because the patch dependency does not support this mechanism
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn no_warn_ws_patch() {
+ Package::new("c", "0.1.0").publish();
+
+ // Don't issue an unused patch warning when the patch isn't used when
+ // partially building a workspace.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["a", "b", "c"]
+
+ [patch.crates-io]
+ c = { path = "c" }
+ "#,
+ )
+ .file("a/Cargo.toml", &basic_manifest("a", "0.1.0"))
+ .file("a/src/lib.rs", "")
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.1.0"
+ [dependencies]
+ c = "0.1.0"
+ "#,
+ )
+ .file("b/src/lib.rs", "")
+ .file("c/Cargo.toml", &basic_manifest("c", "0.1.0"))
+ .file("c/src/lib.rs", "")
+ .build();
+
+ p.cargo("check -p a")
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[CHECKING] a [..]
+[FINISHED] [..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn new_minor() {
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+
+ [patch.crates-io]
+ bar = { path = 'bar' }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.1"))
+ .file("bar/src/lib.rs", r#""#)
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[CHECKING] bar v0.1.1 [..]
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn transitive_new_minor() {
+ Package::new("baz", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = { path = 'bar' }
+
+ [patch.crates-io]
+ baz = { path = 'baz' }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ baz = '0.1.0'
+ "#,
+ )
+ .file("bar/src/lib.rs", r#""#)
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.1"))
+ .file("baz/src/lib.rs", r#""#)
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[CHECKING] baz v0.1.1 [..]
+[CHECKING] bar v0.1.0 [..]
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn new_major() {
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.2.0"
+
+ [patch.crates-io]
+ bar = { path = 'bar' }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.2.0"))
+ .file("bar/src/lib.rs", r#""#)
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[CHECKING] bar v0.2.0 [..]
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ Package::new("bar", "0.2.0").publish();
+ p.cargo("update").run();
+ p.cargo("check")
+ .with_stderr("[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]")
+ .run();
+
+ p.change_file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.2.0"
+ "#,
+ );
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.2.0 [..]
+[CHECKING] bar v0.2.0
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn transitive_new_major() {
+ Package::new("baz", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = { path = 'bar' }
+
+ [patch.crates-io]
+ baz = { path = 'baz' }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ baz = '0.2.0'
+ "#,
+ )
+ .file("bar/src/lib.rs", r#""#)
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.2.0"))
+ .file("baz/src/lib.rs", r#""#)
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[CHECKING] baz v0.2.0 [..]
+[CHECKING] bar v0.1.0 [..]
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn shared_by_transitive() {
+ Package::new("baz", "0.1.1").publish();
+
+ let baz = git::repo(&paths::root().join("override"))
+ .file("Cargo.toml", &basic_manifest("baz", "0.1.2"))
+ .file("src/lib.rs", "")
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = " 0.1.0"
+
+ [dependencies]
+ bar = {{ path = "bar" }}
+ baz = "0.1"
+
+ [patch.crates-io]
+ baz = {{ git = "{}", version = "0.1" }}
+ "#,
+ baz.url(),
+ ),
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+
+ [dependencies]
+ baz = "0.1.1"
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] git repository `file://[..]`
+[UPDATING] `dummy-registry` index
+[CHECKING] baz v0.1.2 [..]
+[CHECKING] bar v0.1.0 [..]
+[CHECKING] foo v0.1.0 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn remove_patch() {
+ Package::new("foo", "0.1.0").publish();
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1"
+
+ [patch.crates-io]
+ foo = { path = 'foo' }
+ bar = { path = 'bar' }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", r#""#)
+ .file("foo/Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("foo/src/lib.rs", r#""#)
+ .build();
+
+ // Generate a lock file where `foo` is unused
+ p.cargo("check").run();
+ let lock_file1 = p.read_lockfile();
+
+ // Remove `foo` and generate a new lock file form the old one
+ p.change_file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1"
+
+ [patch.crates-io]
+ bar = { path = 'bar' }
+ "#,
+ );
+ p.cargo("check").run();
+ let lock_file2 = p.read_lockfile();
+
+ // Remove the lock file and build from scratch
+ fs::remove_file(p.root().join("Cargo.lock")).unwrap();
+ p.cargo("check").run();
+ let lock_file3 = p.read_lockfile();
+
+ assert!(lock_file1.contains("foo"));
+ assert_eq!(lock_file2, lock_file3);
+ assert_ne!(lock_file1, lock_file2);
+}
+
+#[cargo_test]
+fn non_crates_io() {
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [patch.some-other-source]
+ bar = { path = 'bar' }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", r#""#)
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[..]`
+
+Caused by:
+ [patch] entry `some-other-source` should be a URL or registry name
+
+Caused by:
+ invalid url `some-other-source`: relative URL without a base
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn replace_with_crates_io() {
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [patch.crates-io]
+ bar = "0.1"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", r#""#)
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] [..]
+error: failed to resolve patches for `[..]`
+
+Caused by:
+ patch for `bar` in `[..]` points to the same source, but patches must point \
+ to different sources
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn patch_in_virtual() {
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["foo"]
+
+ [patch.crates-io]
+ bar = { path = "bar" }
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", r#""#)
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ bar = "0.1"
+ "#,
+ )
+ .file("foo/src/lib.rs", r#""#)
+ .build();
+
+ p.cargo("check").run();
+ p.cargo("check").with_stderr("[FINISHED] [..]").run();
+}
+
+#[cargo_test]
+fn patch_depends_on_another_patch() {
+ Package::new("bar", "0.1.0")
+ .file("src/lib.rs", "broken code")
+ .publish();
+
+ Package::new("baz", "0.1.0")
+ .dep("bar", "0.1")
+ .file("src/lib.rs", "broken code")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "0.1"
+ baz = "0.1"
+
+ [patch.crates-io]
+ bar = { path = "bar" }
+ baz = { path = "baz" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.1"))
+ .file("bar/src/lib.rs", r#""#)
+ .file(
+ "baz/Cargo.toml",
+ r#"
+ [package]
+ name = "baz"
+ version = "0.1.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1"
+ "#,
+ )
+ .file("baz/src/lib.rs", r#""#)
+ .build();
+
+ p.cargo("check").run();
+
+ // Nothing should be rebuilt, no registry should be updated.
+ p.cargo("check").with_stderr("[FINISHED] [..]").run();
+}
+
+#[cargo_test]
+fn replace_prerelease() {
+ Package::new("baz", "1.1.0-pre.1").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar"]
+
+ [patch.crates-io]
+ baz = { path = "./baz" }
+ "#,
+ )
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ baz = "1.1.0-pre.1"
+ "#,
+ )
+ .file(
+ "bar/src/main.rs",
+ "extern crate baz; fn main() { baz::baz() }",
+ )
+ .file(
+ "baz/Cargo.toml",
+ r#"
+ [package]
+ name = "baz"
+ version = "1.1.0-pre.1"
+ authors = []
+ [workspace]
+ "#,
+ )
+ .file("baz/src/lib.rs", "pub fn baz() {}")
+ .build();
+
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn patch_older() {
+ Package::new("baz", "1.0.2").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { path = 'bar' }
+ baz = "=1.0.1"
+
+ [patch.crates-io]
+ baz = { path = "./baz" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ baz = "1.0.0"
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .file(
+ "baz/Cargo.toml",
+ r#"
+ [package]
+ name = "baz"
+ version = "1.0.1"
+ authors = []
+ "#,
+ )
+ .file("baz/src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[CHECKING] baz v1.0.1 [..]
+[CHECKING] bar v0.5.0 [..]
+[CHECKING] foo v0.1.0 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cycle() {
+ Package::new("a", "1.0.0").publish();
+ Package::new("b", "1.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["a", "b"]
+
+ [patch.crates-io]
+ a = {path="a"}
+ b = {path="b"}
+ "#,
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "1.0.0"
+
+ [dependencies]
+ b = "1.0"
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "1.0.0"
+
+ [dependencies]
+ a = "1.0"
+ "#,
+ )
+ .file("b/src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[ERROR] cyclic package dependency: [..]
+package `[..]`
+ ... which satisfies dependency `[..]` of package `[..]`
+ ... which satisfies dependency `[..]` of package `[..]`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn multipatch() {
+ Package::new("a", "1.0.0").publish();
+ Package::new("a", "2.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies]
+ a1 = { version = "1", package = "a" }
+ a2 = { version = "2", package = "a" }
+
+ [patch.crates-io]
+ b1 = { path = "a1", package = "a" }
+ b2 = { path = "a2", package = "a" }
+ "#,
+ )
+ .file("src/lib.rs", "pub fn foo() { a1::f1(); a2::f2(); }")
+ .file(
+ "a1/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "1.0.0"
+ "#,
+ )
+ .file("a1/src/lib.rs", "pub fn f1() {}")
+ .file(
+ "a2/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "2.0.0"
+ "#,
+ )
+ .file("a2/src/lib.rs", "pub fn f2() {}")
+ .build();
+
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn patch_same_version() {
+ let bar = git::repo(&paths::root().join("override"))
+ .file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("src/lib.rs", "")
+ .build();
+
+ cargo_test_support::registry::init();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ [dependencies]
+ bar = "0.1"
+ [patch.crates-io]
+ bar = {{ path = "bar" }}
+ bar2 = {{ git = '{}', package = 'bar' }}
+ "#,
+ bar.url(),
+ ),
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] [..]
+error: cannot have two `[patch]` entries which both resolve to `bar v0.1.0`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn two_semver_compatible() {
+ let bar = git::repo(&paths::root().join("override"))
+ .file("Cargo.toml", &basic_manifest("bar", "0.1.1"))
+ .file("src/lib.rs", "")
+ .build();
+
+ cargo_test_support::registry::init();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ [dependencies]
+ bar = "0.1"
+ [patch.crates-io]
+ bar = {{ path = "bar" }}
+ bar2 = {{ git = '{}', package = 'bar' }}
+ "#,
+ bar.url(),
+ ),
+ )
+ .file("src/lib.rs", "pub fn foo() { bar::foo() }")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.2"
+ "#,
+ )
+ .file("bar/src/lib.rs", "pub fn foo() {}")
+ .build();
+
+ // assert the build succeeds and doesn't panic anywhere, and then afterwards
+ // assert that the build succeeds again without updating anything or
+ // building anything else.
+ p.cargo("check").run();
+ p.cargo("check")
+ .with_stderr(
+ "\
+warning: Patch `bar v0.1.1 [..]` was not used in the crate graph.
+Perhaps you misspelled the source URL being patched.
+Possible URLs for `[patch.<URL>]`:
+ [CWD]/bar
+[FINISHED] [..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn multipatch_select_big() {
+ let bar = git::repo(&paths::root().join("override"))
+ .file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("src/lib.rs", "")
+ .build();
+
+ cargo_test_support::registry::init();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ [dependencies]
+ bar = "*"
+ [patch.crates-io]
+ bar = {{ path = "bar" }}
+ bar2 = {{ git = '{}', package = 'bar' }}
+ "#,
+ bar.url(),
+ ),
+ )
+ .file("src/lib.rs", "pub fn foo() { bar::foo() }")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.2.0"
+ "#,
+ )
+ .file("bar/src/lib.rs", "pub fn foo() {}")
+ .build();
+
+ // assert the build succeeds, which is only possible if 0.2.0 is selected
+ // since 0.1.0 is missing the function we need. Afterwards assert that the
+ // build succeeds again without updating anything or building anything else.
+ p.cargo("check").run();
+ p.cargo("check")
+ .with_stderr(
+ "\
+warning: Patch `bar v0.1.0 [..]` was not used in the crate graph.
+Perhaps you misspelled the source URL being patched.
+Possible URLs for `[patch.<URL>]`:
+ [CWD]/bar
+[FINISHED] [..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn canonicalize_a_bunch() {
+ let base = git::repo(&paths::root().join("base"))
+ .file("Cargo.toml", &basic_manifest("base", "0.1.0"))
+ .file("src/lib.rs", "")
+ .build();
+
+ let intermediate = git::repo(&paths::root().join("intermediate"))
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "intermediate"
+ version = "0.1.0"
+
+ [dependencies]
+ # Note the lack of trailing slash
+ base = {{ git = '{}' }}
+ "#,
+ base.url(),
+ ),
+ )
+ .file("src/lib.rs", "pub fn f() { base::f() }")
+ .build();
+
+ let newbase = git::repo(&paths::root().join("newbase"))
+ .file("Cargo.toml", &basic_manifest("base", "0.1.0"))
+ .file("src/lib.rs", "pub fn f() {}")
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies]
+ # Note the trailing slashes
+ base = {{ git = '{base}/' }}
+ intermediate = {{ git = '{intermediate}/' }}
+
+ [patch.'{base}'] # Note the lack of trailing slash
+ base = {{ git = '{newbase}' }}
+ "#,
+ base = base.url(),
+ intermediate = intermediate.url(),
+ newbase = newbase.url(),
+ ),
+ )
+ .file("src/lib.rs", "pub fn a() { base::f(); intermediate::f() }")
+ .build();
+
+ // Once to make sure it actually works
+ p.cargo("check").run();
+
+ // Then a few more times for good measure to ensure no weird warnings about
+ // `[patch]` are printed.
+ p.cargo("check").with_stderr("[FINISHED] [..]").run();
+ p.cargo("check").with_stderr("[FINISHED] [..]").run();
+}
+
+#[cargo_test]
+fn update_unused_new_version() {
+ // If there is an unused patch entry, and then you update the patch,
+ // make sure `cargo update` will be able to fix the lock file.
+ Package::new("bar", "0.1.5").publish();
+
+ // Start with a lock file to 0.1.5, and an "unused" patch because the
+ // version is too old.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies]
+ bar = "0.1.5"
+
+ [patch.crates-io]
+ bar = { path = "../bar" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ // Patch is too old.
+ let bar = project()
+ .at("bar")
+ .file("Cargo.toml", &basic_manifest("bar", "0.1.4"))
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_stderr_contains("[WARNING] Patch `bar v0.1.4 [..] was not used in the crate graph.")
+ .run();
+ // unused patch should be in the lock file
+ let lock = p.read_lockfile();
+ let toml: toml::Table = toml::from_str(&lock).unwrap();
+ assert_eq!(toml["patch"]["unused"].as_array().unwrap().len(), 1);
+ assert_eq!(toml["patch"]["unused"][0]["name"].as_str(), Some("bar"));
+ assert_eq!(
+ toml["patch"]["unused"][0]["version"].as_str(),
+ Some("0.1.4")
+ );
+
+ // Oh, OK, let's update to the latest version.
+ bar.change_file("Cargo.toml", &basic_manifest("bar", "0.1.6"));
+
+ // Create a backup so we can test it with different options.
+ fs::copy(p.root().join("Cargo.lock"), p.root().join("Cargo.lock.bak")).unwrap();
+
+ // Try to build again, this should automatically update Cargo.lock.
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[CHECKING] bar v0.1.6 ([..]/bar)
+[CHECKING] foo v0.0.1 ([..]/foo)
+[FINISHED] [..]
+",
+ )
+ .run();
+ // This should not update any registry.
+ p.cargo("check").with_stderr("[FINISHED] [..]").run();
+ assert!(!p.read_lockfile().contains("unused"));
+
+ // Restore the lock file, and see if `update` will work, too.
+ fs::copy(p.root().join("Cargo.lock.bak"), p.root().join("Cargo.lock")).unwrap();
+
+ // Try `update -p`.
+ p.cargo("update -p bar")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[ADDING] bar v0.1.6 ([..]/bar)
+[REMOVING] bar v0.1.5
+",
+ )
+ .run();
+
+ // Try with bare `cargo update`.
+ fs::copy(p.root().join("Cargo.lock.bak"), p.root().join("Cargo.lock")).unwrap();
+ p.cargo("update")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[ADDING] bar v0.1.6 ([..]/bar)
+[REMOVING] bar v0.1.5
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn too_many_matches() {
+ // The patch locations has multiple versions that match.
+ registry::alt_init();
+ Package::new("bar", "0.1.0").publish();
+ Package::new("bar", "0.1.0").alternative(true).publish();
+ Package::new("bar", "0.1.1").alternative(true).publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "0.1"
+
+ [patch.crates-io]
+ bar = { version = "0.1", registry = "alternative" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ // Picks 0.1.1, the most recent version.
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] `alternative` index
+[ERROR] failed to resolve patches for `https://github.com/rust-lang/crates.io-index`
+
+Caused by:
+ patch for `bar` in `https://github.com/rust-lang/crates.io-index` failed to resolve
+
+Caused by:
+ patch for `bar` in `registry `alternative`` resolved to more than one candidate
+ Found versions: 0.1.0, 0.1.1
+ Update the patch definition to select only one package.
+ For example, add an `=` version requirement to the patch definition, such as `version = \"=0.1.1\"`.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn no_matches() {
+ // A patch to a location that does not contain the named package.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "0.1"
+
+ [patch.crates-io]
+ bar = { path = "bar" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("abc", "0.1.0"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to resolve patches for `https://github.com/rust-lang/crates.io-index`
+
+Caused by:
+ patch for `bar` in `https://github.com/rust-lang/crates.io-index` failed to resolve
+
+Caused by:
+ The patch location `[..]/foo/bar` does not appear to contain any packages matching the name `bar`.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn mismatched_version() {
+ // A patch to a location that has an old version.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "0.1.1"
+
+ [patch.crates-io]
+ bar = { path = "bar", version = "0.1.1" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to resolve patches for `https://github.com/rust-lang/crates.io-index`
+
+Caused by:
+ patch for `bar` in `https://github.com/rust-lang/crates.io-index` failed to resolve
+
+Caused by:
+ The patch location `[..]/foo/bar` contains a `bar` package with version `0.1.0`, \
+ but the patch definition requires `^0.1.1`.
+ Check that the version in the patch location is what you expect, \
+ and update the patch definition to match.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn patch_walks_backwards() {
+ // Starting with a locked patch, change the patch so it points to an older version.
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "0.1"
+
+ [patch.crates-io]
+ bar = {path="bar"}
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.1"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[CHECKING] bar v0.1.1 ([..]/foo/bar)
+[CHECKING] foo v0.1.0 ([..]/foo)
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ // Somehow the user changes the version backwards.
+ p.change_file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"));
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[CHECKING] bar v0.1.0 ([..]/foo/bar)
+[CHECKING] foo v0.1.0 ([..]/foo)
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn patch_walks_backwards_restricted() {
+ // This is the same as `patch_walks_backwards`, but the patch contains a
+ // `version` qualifier. This is unusual, just checking a strange edge case.
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "0.1"
+
+ [patch.crates-io]
+ bar = {path="bar", version="0.1.1"}
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.1"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[CHECKING] bar v0.1.1 ([..]/foo/bar)
+[CHECKING] foo v0.1.0 ([..]/foo)
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ // Somehow the user changes the version backwards.
+ p.change_file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"));
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to resolve patches for `https://github.com/rust-lang/crates.io-index`
+
+Caused by:
+ patch for `bar` in `https://github.com/rust-lang/crates.io-index` failed to resolve
+
+Caused by:
+ The patch location `[..]/foo/bar` contains a `bar` package with version `0.1.0`, but the patch definition requires `^0.1.1`.
+ Check that the version in the patch location is what you expect, and update the patch definition to match.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn patched_dep_new_version() {
+ // What happens when a patch is locked, and then one of the patched
+ // dependencies needs to be updated. In this case, the baz requirement
+ // gets updated from 0.1.0 to 0.1.1.
+ Package::new("bar", "0.1.0").dep("baz", "0.1.0").publish();
+ Package::new("baz", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "0.1"
+
+ [patch.crates-io]
+ bar = {path="bar"}
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+
+ [dependencies]
+ baz = "0.1"
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ // Lock everything.
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] baz v0.1.0 [..]
+[CHECKING] baz v0.1.0
+[CHECKING] bar v0.1.0 ([..]/foo/bar)
+[CHECKING] foo v0.1.0 ([..]/foo)
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ Package::new("baz", "0.1.1").publish();
+
+ // Just the presence of the new version should not have changed anything.
+ p.cargo("check").with_stderr("[FINISHED] [..]").run();
+
+ // Modify the patch so it requires the new version.
+ p.change_file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+
+ [dependencies]
+ baz = "0.1.1"
+ "#,
+ );
+
+ // Should unlock and update cleanly.
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] baz v0.1.1 (registry `dummy-registry`)
+[CHECKING] baz v0.1.1
+[CHECKING] bar v0.1.0 ([..]/foo/bar)
+[CHECKING] foo v0.1.0 ([..]/foo)
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn patch_update_doesnt_update_other_sources() {
+ // Very extreme edge case, make sure a patch update doesn't update other
+ // sources.
+ registry::alt_init();
+ Package::new("bar", "0.1.0").publish();
+ Package::new("bar", "0.1.0").alternative(true).publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "0.1"
+ bar_alt = { version = "0.1", registry = "alternative", package = "bar" }
+
+ [patch.crates-io]
+ bar = { path = "bar" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_stderr_unordered(
+ "\
+[UPDATING] `dummy-registry` index
+[UPDATING] `alternative` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.1.0 (registry `alternative`)
+[CHECKING] bar v0.1.0 (registry `alternative`)
+[CHECKING] bar v0.1.0 ([..]/foo/bar)
+[CHECKING] foo v0.1.0 ([..]/foo)
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ // Publish new versions in both sources.
+ Package::new("bar", "0.1.1").publish();
+ Package::new("bar", "0.1.1").alternative(true).publish();
+
+ // Since it is locked, nothing should change.
+ p.cargo("check").with_stderr("[FINISHED] [..]").run();
+
+ // Require new version on crates.io.
+ p.change_file("bar/Cargo.toml", &basic_manifest("bar", "0.1.1"));
+
+ // This should not update bar_alt.
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[CHECKING] bar v0.1.1 ([..]/foo/bar)
+[CHECKING] foo v0.1.0 ([..]/foo)
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn can_update_with_alt_reg() {
+ // A patch to an alt reg can update.
+ registry::alt_init();
+ Package::new("bar", "0.1.0").publish();
+ Package::new("bar", "0.1.0").alternative(true).publish();
+ Package::new("bar", "0.1.1").alternative(true).publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "0.1"
+
+ [patch.crates-io]
+ bar = { version = "=0.1.1", registry = "alternative" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `alternative` index
+[UPDATING] `dummy-registry` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.1.1 (registry `alternative`)
+[CHECKING] bar v0.1.1 (registry `alternative`)
+[CHECKING] foo v0.1.0 ([..]/foo)
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ Package::new("bar", "0.1.2").alternative(true).publish();
+
+ // Should remain locked.
+ p.cargo("check").with_stderr("[FINISHED] [..]").run();
+
+ // This does nothing, due to `=` requirement.
+ p.cargo("update -p bar")
+ .with_stderr(
+ "\
+[UPDATING] `alternative` index
+[UPDATING] `dummy-registry` index
+",
+ )
+ .run();
+
+ // Bump to 0.1.2.
+ p.change_file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "0.1"
+
+ [patch.crates-io]
+ bar = { version = "=0.1.2", registry = "alternative" }
+ "#,
+ );
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `alternative` index
+[UPDATING] `dummy-registry` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.1.2 (registry `alternative`)
+[CHECKING] bar v0.1.2 (registry `alternative`)
+[CHECKING] foo v0.1.0 ([..]/foo)
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn old_git_patch() {
+ // Example where an old lockfile with an explicit branch="master" in Cargo.toml.
+ Package::new("bar", "1.0.0").publish();
+ let (bar, bar_repo) = git::new_repo("bar", |p| {
+ p.file("Cargo.toml", &basic_manifest("bar", "1.0.0"))
+ .file("src/lib.rs", "")
+ });
+
+ let bar_oid = bar_repo.head().unwrap().target().unwrap();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "1.0"
+
+ [patch.crates-io]
+ bar = {{ git = "{}", branch = "master" }}
+ "#,
+ bar.url()
+ ),
+ )
+ .file(
+ "Cargo.lock",
+ &format!(
+ r#"
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "bar"
+version = "1.0.0"
+source = "git+{}#{}"
+
+[[package]]
+name = "foo"
+version = "0.1.0"
+dependencies = [
+ "bar",
+]
+ "#,
+ bar.url(),
+ bar_oid
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ bar.change_file("Cargo.toml", &basic_manifest("bar", "2.0.0"));
+ git::add(&bar_repo);
+ git::commit(&bar_repo);
+
+ // This *should* keep the old lock.
+ p.cargo("tree")
+ // .env("CARGO_LOG", "trace")
+ .with_stderr(
+ "\
+[UPDATING] [..]
+",
+ )
+ // .with_status(1)
+ .with_stdout(format!(
+ "\
+foo v0.1.0 [..]
+└── bar v1.0.0 (file:///[..]branch=master#{})
+",
+ &bar_oid.to_string()[..8]
+ ))
+ .run();
+}
+
+// From https://github.com/rust-lang/cargo/issues/7463
+#[cargo_test]
+fn patch_eq_conflict_panic() {
+ Package::new("bar", "0.1.0").publish();
+ Package::new("bar", "0.1.1").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "=0.1.0"
+
+ [dev-dependencies]
+ bar = "=0.1.1"
+
+ [patch.crates-io]
+ bar = {path="bar"}
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.1"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("generate-lockfile")
+ .with_status(101)
+ .with_stderr(
+ r#"[UPDATING] `dummy-registry` index
+[ERROR] failed to select a version for `bar`.
+ ... required by package `foo v0.1.0 ([..])`
+versions that meet the requirements `=0.1.1` are: 0.1.1
+
+all possible versions conflict with previously selected packages.
+
+ previously selected package `bar v0.1.0`
+ ... which satisfies dependency `bar = "=0.1.0"` of package `foo v0.1.0 ([..])`
+
+failed to select a version for `bar` which could resolve this conflict
+"#,
+ )
+ .run();
+}
+
+// From https://github.com/rust-lang/cargo/issues/11336
+#[cargo_test]
+fn mismatched_version2() {
+ Package::new("qux", "0.1.0-beta.1").publish();
+ Package::new("qux", "0.1.0-beta.2").publish();
+ Package::new("bar", "0.1.0")
+ .dep("qux", "=0.1.0-beta.1")
+ .publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "0.1.0"
+ qux = "0.1.0-beta.2"
+
+ [patch.crates-io]
+ qux = { path = "qux" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "qux/Cargo.toml",
+ r#"
+ [package]
+ name = "qux"
+ version = "0.1.0-beta.1"
+ "#,
+ )
+ .file("qux/src/lib.rs", "")
+ .build();
+
+ p.cargo("generate-lockfile")
+ .with_status(101)
+ .with_stderr(
+ r#"[UPDATING] `dummy-registry` index
+[ERROR] failed to select a version for `qux`.
+ ... required by package `bar v0.1.0`
+ ... which satisfies dependency `bar = "^0.1.0"` of package `foo v0.1.0 ([..])`
+versions that meet the requirements `=0.1.0-beta.1` are: 0.1.0-beta.1
+
+all possible versions conflict with previously selected packages.
+
+ previously selected package `qux v0.1.0-beta.2`
+ ... which satisfies dependency `qux = "^0.1.0-beta.2"` of package `foo v0.1.0 ([..])`
+
+failed to select a version for `qux` which could resolve this conflict"#,
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/path.rs b/src/tools/cargo/tests/testsuite/path.rs
new file mode 100644
index 000000000..ebbb72f9a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/path.rs
@@ -0,0 +1,1139 @@
+//! Tests for `path` dependencies.
+
+use cargo_test_support::paths::{self, CargoPathExt};
+use cargo_test_support::registry::Package;
+use cargo_test_support::{basic_lib_manifest, basic_manifest, main_file, project};
+use cargo_test_support::{sleep_ms, t};
+use std::fs;
+
+#[cargo_test]
+// I have no idea why this is failing spuriously on Windows;
+// for more info, see #3466.
+#[cfg(not(windows))]
+fn cargo_compile_with_nested_deps_shorthand() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.bar]
+
+ version = "0.5.0"
+ path = "bar"
+ "#,
+ )
+ .file("src/main.rs", &main_file(r#""{}", bar::gimme()"#, &["bar"]))
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+
+ name = "bar"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.baz]
+
+ version = "0.5.0"
+ path = "baz"
+
+ [lib]
+
+ name = "bar"
+ "#,
+ )
+ .file(
+ "bar/src/bar.rs",
+ r#"
+ extern crate baz;
+
+ pub fn gimme() -> String {
+ baz::gimme()
+ }
+ "#,
+ )
+ .file("bar/baz/Cargo.toml", &basic_lib_manifest("baz"))
+ .file(
+ "bar/baz/src/baz.rs",
+ r#"
+ pub fn gimme() -> String {
+ "test passed".to_string()
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build")
+ .with_stderr(
+ "[COMPILING] baz v0.5.0 ([CWD]/bar/baz)\n\
+ [COMPILING] bar v0.5.0 ([CWD]/bar)\n\
+ [COMPILING] foo v0.5.0 ([CWD])\n\
+ [FINISHED] dev [unoptimized + debuginfo] target(s) \
+ in [..]\n",
+ )
+ .run();
+
+ assert!(p.bin("foo").is_file());
+
+ p.process(&p.bin("foo")).with_stdout("test passed\n").run();
+
+ println!("cleaning");
+ p.cargo("clean -v").with_stdout("").run();
+ println!("building baz");
+ p.cargo("build -p baz")
+ .with_stderr(
+ "[COMPILING] baz v0.5.0 ([CWD]/bar/baz)\n\
+ [FINISHED] dev [unoptimized + debuginfo] target(s) \
+ in [..]\n",
+ )
+ .run();
+ println!("building foo");
+ p.cargo("build -p foo")
+ .with_stderr(
+ "[COMPILING] bar v0.5.0 ([CWD]/bar)\n\
+ [COMPILING] foo v0.5.0 ([CWD])\n\
+ [FINISHED] dev [unoptimized + debuginfo] target(s) \
+ in [..]\n",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_compile_with_root_dev_deps() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dev-dependencies.bar]
+
+ version = "0.5.0"
+ path = "../bar"
+
+ [[bin]]
+ name = "foo"
+ "#,
+ )
+ .file("src/main.rs", &main_file(r#""{}", bar::gimme()"#, &["bar"]))
+ .build();
+ let _p2 = project()
+ .at("bar")
+ .file("Cargo.toml", &basic_manifest("bar", "0.5.0"))
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn gimme() -> &'static str {
+ "zoidberg"
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr_contains("[..]can't find crate for `bar`")
+ .run();
+}
+
+#[cargo_test]
+fn cargo_compile_with_root_dev_deps_with_testing() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dev-dependencies.bar]
+
+ version = "0.5.0"
+ path = "../bar"
+
+ [[bin]]
+ name = "foo"
+ "#,
+ )
+ .file("src/main.rs", &main_file(r#""{}", bar::gimme()"#, &["bar"]))
+ .build();
+ let _p2 = project()
+ .at("bar")
+ .file("Cargo.toml", &basic_manifest("bar", "0.5.0"))
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn gimme() -> &'static str {
+ "zoidberg"
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("test")
+ .with_stderr(
+ "\
+[COMPILING] [..] v0.5.0 ([..])
+[COMPILING] [..] v0.5.0 ([..])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo-[..][EXE])",
+ )
+ .with_stdout_contains("running 0 tests")
+ .run();
+}
+
+#[cargo_test]
+fn cargo_compile_with_transitive_dev_deps() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.bar]
+
+ version = "0.5.0"
+ path = "bar"
+ "#,
+ )
+ .file("src/main.rs", &main_file(r#""{}", bar::gimme()"#, &["bar"]))
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+
+ name = "bar"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dev-dependencies.baz]
+
+ git = "git://example.com/path/to/nowhere"
+
+ [lib]
+
+ name = "bar"
+ "#,
+ )
+ .file(
+ "bar/src/bar.rs",
+ r#"
+ pub fn gimme() -> &'static str {
+ "zoidberg"
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build")
+ .with_stderr(
+ "[COMPILING] bar v0.5.0 ([CWD]/bar)\n\
+ [COMPILING] foo v0.5.0 ([CWD])\n\
+ [FINISHED] dev [unoptimized + debuginfo] target(s) in \
+ [..]\n",
+ )
+ .run();
+
+ assert!(p.bin("foo").is_file());
+
+ p.process(&p.bin("foo")).with_stdout("zoidberg\n").run();
+}
+
+#[cargo_test]
+fn no_rebuild_dependency() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.bar]
+ path = "bar"
+ "#,
+ )
+ .file("src/main.rs", "extern crate bar; fn main() { bar::bar() }")
+ .file("bar/Cargo.toml", &basic_lib_manifest("bar"))
+ .file("bar/src/bar.rs", "pub fn bar() {}")
+ .build();
+ // First time around we should compile both foo and bar
+ p.cargo("check")
+ .with_stderr(
+ "[CHECKING] bar v0.5.0 ([CWD]/bar)\n\
+ [CHECKING] foo v0.5.0 ([CWD])\n\
+ [FINISHED] dev [unoptimized + debuginfo] target(s) \
+ in [..]\n",
+ )
+ .run();
+
+ sleep_ms(1000);
+ p.change_file(
+ "src/main.rs",
+ r#"
+ extern crate bar;
+ fn main() { bar::bar(); }
+ "#,
+ );
+ // Don't compile bar, but do recompile foo.
+ p.cargo("check")
+ .with_stderr(
+ "[CHECKING] foo v0.5.0 ([..])\n\
+ [FINISHED] dev [unoptimized + debuginfo] target(s) \
+ in [..]\n",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn deep_dependencies_trigger_rebuild() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.bar]
+ path = "bar"
+ "#,
+ )
+ .file("src/main.rs", "extern crate bar; fn main() { bar::bar() }")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+
+ name = "bar"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [lib]
+ name = "bar"
+ [dependencies.baz]
+ path = "../baz"
+ "#,
+ )
+ .file(
+ "bar/src/bar.rs",
+ "extern crate baz; pub fn bar() { baz::baz() }",
+ )
+ .file("baz/Cargo.toml", &basic_lib_manifest("baz"))
+ .file("baz/src/baz.rs", "pub fn baz() {}")
+ .build();
+ p.cargo("check")
+ .with_stderr(
+ "[CHECKING] baz v0.5.0 ([CWD]/baz)\n\
+ [CHECKING] bar v0.5.0 ([CWD]/bar)\n\
+ [CHECKING] foo v0.5.0 ([CWD])\n\
+ [FINISHED] dev [unoptimized + debuginfo] target(s) \
+ in [..]\n",
+ )
+ .run();
+ p.cargo("check").with_stdout("").run();
+
+ // Make sure an update to baz triggers a rebuild of bar
+ //
+ // We base recompilation off mtime, so sleep for at least a second to ensure
+ // that this write will change the mtime.
+ sleep_ms(1000);
+ p.change_file("baz/src/baz.rs", r#"pub fn baz() { println!("hello!"); }"#);
+ sleep_ms(1000);
+ p.cargo("check")
+ .with_stderr(
+ "[CHECKING] baz v0.5.0 ([CWD]/baz)\n\
+ [CHECKING] bar v0.5.0 ([CWD]/bar)\n\
+ [CHECKING] foo v0.5.0 ([CWD])\n\
+ [FINISHED] dev [unoptimized + debuginfo] target(s) \
+ in [..]\n",
+ )
+ .run();
+
+ // Make sure an update to bar doesn't trigger baz
+ sleep_ms(1000);
+ p.change_file(
+ "bar/src/bar.rs",
+ r#"
+ extern crate baz;
+ pub fn bar() { println!("hello!"); baz::baz(); }
+ "#,
+ );
+ sleep_ms(1000);
+ p.cargo("check")
+ .with_stderr(
+ "[CHECKING] bar v0.5.0 ([CWD]/bar)\n\
+ [CHECKING] foo v0.5.0 ([CWD])\n\
+ [FINISHED] dev [unoptimized + debuginfo] target(s) \
+ in [..]\n",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn no_rebuild_two_deps() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.bar]
+ path = "bar"
+ [dependencies.baz]
+ path = "baz"
+ "#,
+ )
+ .file("src/main.rs", "extern crate bar; fn main() { bar::bar() }")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+
+ name = "bar"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [lib]
+ name = "bar"
+ [dependencies.baz]
+ path = "../baz"
+ "#,
+ )
+ .file("bar/src/bar.rs", "pub fn bar() {}")
+ .file("baz/Cargo.toml", &basic_lib_manifest("baz"))
+ .file("baz/src/baz.rs", "pub fn baz() {}")
+ .build();
+ p.cargo("build")
+ .with_stderr(
+ "[COMPILING] baz v0.5.0 ([CWD]/baz)\n\
+ [COMPILING] bar v0.5.0 ([CWD]/bar)\n\
+ [COMPILING] foo v0.5.0 ([CWD])\n\
+ [FINISHED] dev [unoptimized + debuginfo] target(s) \
+ in [..]\n",
+ )
+ .run();
+ assert!(p.bin("foo").is_file());
+ p.cargo("build").with_stdout("").run();
+ assert!(p.bin("foo").is_file());
+}
+
+#[cargo_test]
+fn nested_deps_recompile() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.bar]
+
+ version = "0.5.0"
+ path = "src/bar"
+ "#,
+ )
+ .file("src/main.rs", &main_file(r#""{}", bar::gimme()"#, &["bar"]))
+ .file("src/bar/Cargo.toml", &basic_lib_manifest("bar"))
+ .file("src/bar/src/bar.rs", "pub fn gimme() -> i32 { 92 }")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "[CHECKING] bar v0.5.0 ([CWD]/src/bar)\n\
+ [CHECKING] foo v0.5.0 ([CWD])\n\
+ [FINISHED] dev [unoptimized + debuginfo] target(s) \
+ in [..]\n",
+ )
+ .run();
+ sleep_ms(1000);
+
+ p.change_file("src/main.rs", r#"fn main() {}"#);
+
+ // This shouldn't recompile `bar`
+ p.cargo("check")
+ .with_stderr(
+ "[CHECKING] foo v0.5.0 ([CWD])\n\
+ [FINISHED] dev [unoptimized + debuginfo] target(s) \
+ in [..]\n",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn error_message_for_missing_manifest() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.bar]
+
+ path = "src/bar"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("src/bar/not-a-manifest", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to get `bar` as a dependency of package `foo v0.5.0 [..]`
+
+Caused by:
+ failed to load source for dependency `bar`
+
+Caused by:
+ Unable to update [CWD]/src/bar
+
+Caused by:
+ failed to read `[..]bar/Cargo.toml`
+
+Caused by:
+ [..] (os error [..])
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn override_relative() {
+ let bar = project()
+ .at("bar")
+ .file("Cargo.toml", &basic_manifest("bar", "0.5.0"))
+ .file("src/lib.rs", "")
+ .build();
+
+ fs::create_dir(&paths::root().join(".cargo")).unwrap();
+ fs::write(&paths::root().join(".cargo/config"), r#"paths = ["bar"]"#).unwrap();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.bar]
+ path = '{}'
+ "#,
+ bar.root().display()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("check -v").run();
+}
+
+#[cargo_test]
+fn override_self() {
+ let bar = project()
+ .at("bar")
+ .file("Cargo.toml", &basic_manifest("bar", "0.5.0"))
+ .file("src/lib.rs", "")
+ .build();
+
+ let p = project();
+ let root = p.root();
+ let p = p
+ .file(".cargo/config", &format!("paths = ['{}']", root.display()))
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.bar]
+ path = '{}'
+
+ "#,
+ bar.root().display()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn override_path_dep() {
+ let bar = project()
+ .at("bar")
+ .file(
+ "p1/Cargo.toml",
+ r#"
+ [package]
+ name = "p1"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies.p2]
+ path = "../p2"
+ "#,
+ )
+ .file("p1/src/lib.rs", "")
+ .file("p2/Cargo.toml", &basic_manifest("p2", "0.5.0"))
+ .file("p2/src/lib.rs", "")
+ .build();
+
+ let p = project()
+ .file(
+ ".cargo/config",
+ &format!(
+ "paths = ['{}', '{}']",
+ bar.root().join("p1").display(),
+ bar.root().join("p2").display()
+ ),
+ )
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.p2]
+ path = '{}'
+
+ "#,
+ bar.root().join("p2").display()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check -v").run();
+}
+
+#[cargo_test]
+fn path_dep_build_cmd() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.bar]
+
+ version = "0.5.0"
+ path = "bar"
+ "#,
+ )
+ .file("src/main.rs", &main_file(r#""{}", bar::gimme()"#, &["bar"]))
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+
+ name = "bar"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+ build = "build.rs"
+
+ [lib]
+ name = "bar"
+ path = "src/bar.rs"
+ "#,
+ )
+ .file(
+ "bar/build.rs",
+ r#"
+ use std::fs;
+ fn main() {
+ fs::copy("src/bar.rs.in", "src/bar.rs").unwrap();
+ }
+ "#,
+ )
+ .file("bar/src/bar.rs.in", "pub fn gimme() -> i32 { 0 }")
+ .build();
+ p.root().join("bar").move_into_the_past();
+
+ p.cargo("build")
+ .with_stderr(
+ "[COMPILING] bar v0.5.0 ([CWD]/bar)\n\
+ [COMPILING] foo v0.5.0 ([CWD])\n\
+ [FINISHED] dev [unoptimized + debuginfo] target(s) in \
+ [..]\n",
+ )
+ .run();
+
+ assert!(p.bin("foo").is_file());
+
+ p.process(&p.bin("foo")).with_stdout("0\n").run();
+
+ // Touching bar.rs.in should cause the `build` command to run again.
+ p.change_file("bar/src/bar.rs.in", "pub fn gimme() -> i32 { 1 }");
+
+ p.cargo("build")
+ .with_stderr(
+ "[COMPILING] bar v0.5.0 ([CWD]/bar)\n\
+ [COMPILING] foo v0.5.0 ([CWD])\n\
+ [FINISHED] dev [unoptimized + debuginfo] target(s) in \
+ [..]\n",
+ )
+ .run();
+
+ p.process(&p.bin("foo")).with_stdout("1\n").run();
+}
+
+#[cargo_test]
+fn dev_deps_no_rebuild_lib() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+
+ [dev-dependencies.bar]
+ path = "bar"
+
+ [lib]
+ name = "foo"
+ doctest = false
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ #[cfg(test)] #[allow(unused_extern_crates)] extern crate bar;
+ #[cfg(not(test))] pub fn foo() { env!("FOO"); }
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.5.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .build();
+ p.cargo("build")
+ .env("FOO", "bar")
+ .with_stderr(
+ "[COMPILING] foo v0.5.0 ([CWD])\n\
+ [FINISHED] dev [unoptimized + debuginfo] target(s) \
+ in [..]\n",
+ )
+ .run();
+
+ p.cargo("test")
+ .with_stderr(
+ "\
+[COMPILING] [..] v0.5.0 ([CWD][..])
+[COMPILING] [..] v0.5.0 ([CWD][..])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo-[..][EXE])",
+ )
+ .with_stdout_contains("running 0 tests")
+ .run();
+}
+
+#[cargo_test]
+fn custom_target_no_rebuild() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+ [dependencies]
+ a = { path = "a" }
+ [workspace]
+ members = ["a", "b"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("a/Cargo.toml", &basic_manifest("a", "0.5.0"))
+ .file("a/src/lib.rs", "")
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.5.0"
+ authors = []
+ [dependencies]
+ a = { path = "../a" }
+ "#,
+ )
+ .file("b/src/lib.rs", "")
+ .build();
+ p.cargo("check")
+ .with_stderr(
+ "\
+[CHECKING] a v0.5.0 ([..])
+[CHECKING] foo v0.5.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ t!(fs::rename(
+ p.root().join("target"),
+ p.root().join("target_moved")
+ ));
+ p.cargo("check --manifest-path=b/Cargo.toml")
+ .env("CARGO_TARGET_DIR", "target_moved")
+ .with_stderr(
+ "\
+[CHECKING] b v0.5.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn override_and_depend() {
+ let p = project()
+ .no_manifest()
+ .file(
+ "a/a1/Cargo.toml",
+ r#"
+ [package]
+ name = "a1"
+ version = "0.5.0"
+ authors = []
+ [dependencies]
+ a2 = { path = "../a2" }
+ "#,
+ )
+ .file("a/a1/src/lib.rs", "")
+ .file("a/a2/Cargo.toml", &basic_manifest("a2", "0.5.0"))
+ .file("a/a2/src/lib.rs", "")
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.5.0"
+ authors = []
+ [dependencies]
+ a1 = { path = "../a/a1" }
+ a2 = { path = "../a/a2" }
+ "#,
+ )
+ .file("b/src/lib.rs", "")
+ .file("b/.cargo/config", r#"paths = ["../a"]"#)
+ .build();
+ p.cargo("check")
+ .cwd("b")
+ .with_stderr(
+ "\
+[WARNING] skipping duplicate package `a2` found at `[..]`
+[CHECKING] a2 v0.5.0 ([..])
+[CHECKING] a1 v0.5.0 ([..])
+[CHECKING] b v0.5.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn missing_path_dependency() {
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("a", "0.5.0"))
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ r#"paths = ["../whoa-this-does-not-exist"]"#,
+ )
+ .build();
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to update path override `[..]../whoa-this-does-not-exist` \
+(defined in `[..]`)
+
+Caused by:
+ failed to read directory `[..]`
+
+Caused by:
+ [..] (os error [..])
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn invalid_path_dep_in_workspace_with_lockfile() {
+ Package::new("bar", "1.0.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "top"
+ version = "0.5.0"
+ authors = []
+
+ [workspace]
+
+ [dependencies]
+ foo = { path = "foo" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ bar = "*"
+ "#,
+ )
+ .file("foo/src/lib.rs", "")
+ .build();
+
+ // Generate a lock file
+ p.cargo("check").run();
+
+ // Change the dependency on `bar` to an invalid path
+ p.change_file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ bar = { path = "" }
+ "#,
+ );
+
+ // Make sure we get a nice error. In the past this actually stack
+ // overflowed!
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: no matching package found
+searched package name: `bar`
+perhaps you meant: foo
+location searched: [..]
+required by package `foo v0.5.0 ([..])`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn workspace_produces_rlib() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "top"
+ version = "0.5.0"
+ authors = []
+
+ [workspace]
+
+ [dependencies]
+ foo = { path = "foo" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("foo/Cargo.toml", &basic_manifest("foo", "0.5.0"))
+ .file("foo/src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+
+ assert!(p.root().join("target/debug/libtop.rlib").is_file());
+ assert!(!p.root().join("target/debug/libfoo.rlib").is_file());
+}
+
+#[cargo_test]
+fn deep_path_error() {
+ // Test for an error loading a path deep in the dependency graph.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ [dependencies]
+ a = {path="a"}
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+ [dependencies]
+ b = {path="../b"}
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.1.0"
+ [dependencies]
+ c = {path="../c"}
+ "#,
+ )
+ .file("b/src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to get `c` as a dependency of package `b v0.1.0 [..]`
+ ... which satisfies path dependency `b` of package `a v0.1.0 [..]`
+ ... which satisfies path dependency `a` of package `foo v0.1.0 [..]`
+
+Caused by:
+ failed to load source for dependency `c`
+
+Caused by:
+ Unable to update [..]/foo/c
+
+Caused by:
+ failed to read `[..]/foo/c/Cargo.toml`
+
+Caused by:
+ [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn catch_tricky_cycle() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "message"
+ version = "0.1.0"
+
+ [dev-dependencies]
+ test = { path = "test" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "tangle/Cargo.toml",
+ r#"
+ [package]
+ name = "tangle"
+ version = "0.1.0"
+
+ [dependencies]
+ message = { path = ".." }
+ snapshot = { path = "../snapshot" }
+ "#,
+ )
+ .file("tangle/src/lib.rs", "")
+ .file(
+ "snapshot/Cargo.toml",
+ r#"
+ [package]
+ name = "snapshot"
+ version = "0.1.0"
+
+ [dependencies]
+ ledger = { path = "../ledger" }
+ "#,
+ )
+ .file("snapshot/src/lib.rs", "")
+ .file(
+ "ledger/Cargo.toml",
+ r#"
+ [package]
+ name = "ledger"
+ version = "0.1.0"
+
+ [dependencies]
+ tangle = { path = "../tangle" }
+ "#,
+ )
+ .file("ledger/src/lib.rs", "")
+ .file(
+ "test/Cargo.toml",
+ r#"
+ [package]
+ name = "test"
+ version = "0.1.0"
+
+ [dependencies]
+ snapshot = { path = "../snapshot" }
+ "#,
+ )
+ .file("test/src/lib.rs", "")
+ .build();
+
+ p.cargo("test")
+ .with_stderr_contains("[..]cyclic package dependency[..]")
+ .with_status(101)
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/paths.rs b/src/tools/cargo/tests/testsuite/paths.rs
new file mode 100644
index 000000000..31e00ae11
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/paths.rs
@@ -0,0 +1,226 @@
+//! Tests for `paths` overrides.
+
+use cargo_test_support::registry::Package;
+use cargo_test_support::{basic_manifest, project};
+
+#[cargo_test]
+fn broken_path_override_warns() {
+ Package::new("bar", "0.1.0").publish();
+ Package::new("bar", "0.2.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ a = { path = "a1" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "a1/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1"
+ "#,
+ )
+ .file("a1/src/lib.rs", "")
+ .file(
+ "a2/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.2"
+ "#,
+ )
+ .file("a2/src/lib.rs", "")
+ .file(".cargo/config", r#"paths = ["a2"]"#)
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] [..]
+warning: path override for crate `a` has altered the original list of
+dependencies; the dependency on `bar` was either added or
+modified to not match the previously resolved version
+
+This is currently allowed but is known to produce buggy behavior with spurious
+recompiles and changes to the crate graph. Path overrides unfortunately were
+never intended to support this feature, so for now this message is just a
+warning. In the future, however, this message will become a hard error.
+
+To change the dependency graph via an override it's recommended to use the
+`[patch]` feature of Cargo instead of the path override feature. This is
+documented online at the url below for more information.
+
+https://doc.rust-lang.org/cargo/reference/overriding-dependencies.html
+
+[DOWNLOADING] crates ...
+[DOWNLOADED] [..]
+[CHECKING] [..]
+[CHECKING] [..]
+[CHECKING] [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn override_to_path_dep() {
+ Package::new("bar", "0.1.0").dep("baz", "0.1").publish();
+ Package::new("baz", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ baz = { path = "baz" }
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .file("bar/baz/Cargo.toml", &basic_manifest("baz", "0.0.1"))
+ .file("bar/baz/src/lib.rs", "")
+ .file(".cargo/config", r#"paths = ["bar"]"#)
+ .build();
+
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn paths_ok_with_optional() {
+ Package::new("baz", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = { path = "bar" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ baz = { version = "0.1", optional = true }
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .file(
+ "bar2/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ baz = { version = "0.1", optional = true }
+ "#,
+ )
+ .file("bar2/src/lib.rs", "")
+ .file(".cargo/config", r#"paths = ["bar2"]"#)
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[CHECKING] bar v0.1.0 ([..]bar2)
+[CHECKING] foo v0.0.1 ([..])
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn paths_add_optional_bad() {
+ Package::new("baz", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = { path = "bar" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "")
+ .file(
+ "bar2/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ baz = { version = "0.1", optional = true }
+ "#,
+ )
+ .file("bar2/src/lib.rs", "")
+ .file(".cargo/config", r#"paths = ["bar2"]"#)
+ .build();
+
+ p.cargo("check")
+ .with_stderr_contains(
+ "\
+warning: path override for crate `bar` has altered the original list of
+dependencies; the dependency on `baz` was either added or\
+",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/pkgid.rs b/src/tools/cargo/tests/testsuite/pkgid.rs
new file mode 100644
index 000000000..3e3e4692a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/pkgid.rs
@@ -0,0 +1,128 @@
+//! Tests for the `cargo pkgid` command.
+
+use cargo_test_support::project;
+use cargo_test_support::registry::Package;
+
+#[cargo_test]
+fn simple() {
+ Package::new("bar", "0.1.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("generate-lockfile").run();
+
+ p.cargo("pkgid foo")
+ .with_stdout(format!("file://[..]{}#0.1.0", p.root().to_str().unwrap()))
+ .run();
+
+ p.cargo("pkgid bar")
+ .with_stdout("https://github.com/rust-lang/crates.io-index#bar@0.1.0")
+ .run();
+}
+
+#[cargo_test]
+fn suggestion_bad_pkgid() {
+ Package::new("crates-io", "0.1.0").publish();
+ Package::new("two-ver", "0.1.0").publish();
+ Package::new("two-ver", "0.2.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [dependencies]
+ crates-io = "0.1.0"
+ two-ver = "0.1.0"
+ two-ver2 = { package = "two-ver", version = "0.2.0" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("cratesio", "")
+ .build();
+
+ p.cargo("generate-lockfile").run();
+
+ // Bad URL.
+ p.cargo("pkgid https://example.com/crates-io")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: package ID specification `https://example.com/crates-io` did not match any packages
+Did you mean one of these?
+
+ crates-io@0.1.0
+",
+ )
+ .run();
+
+ // Bad name.
+ p.cargo("pkgid crates_io")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: package ID specification `crates_io` did not match any packages
+
+<tab>Did you mean `crates-io`?
+",
+ )
+ .run();
+
+ // Bad version.
+ p.cargo("pkgid two-ver:0.3.0")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: package ID specification `two-ver@0.3.0` did not match any packages
+Did you mean one of these?
+
+ two-ver@0.1.0
+ two-ver@0.2.0
+",
+ )
+ .run();
+
+ // Bad file URL.
+ p.cargo("pkgid ./Cargo.toml")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: invalid package ID specification: `./Cargo.toml`
+
+Caused by:
+ package ID specification `./Cargo.toml` looks like a file path, maybe try file://[..]/Cargo.toml
+",
+ )
+ .run();
+
+ // Bad file URL with similar name.
+ p.cargo("pkgid './cratesio'")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: invalid package ID specification: `./cratesio`
+
+<tab>Did you mean `crates-io`?
+
+Caused by:
+ package ID specification `./cratesio` looks like a file path, maybe try file://[..]/cratesio
+",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/plugins.rs b/src/tools/cargo/tests/testsuite/plugins.rs
new file mode 100644
index 000000000..331ba32e0
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/plugins.rs
@@ -0,0 +1,421 @@
+//! Tests for rustc plugins.
+
+use cargo_test_support::rustc_host;
+use cargo_test_support::{basic_manifest, project};
+
+#[cargo_test(nightly, reason = "plugins are unstable")]
+fn plugin_to_the_max() {
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ name = "foo_lib"
+
+ [dependencies.bar]
+ path = "../bar"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ #![feature(plugin)]
+ #![plugin(bar)]
+ extern crate foo_lib;
+
+ fn main() { foo_lib::foo(); }
+ "#,
+ )
+ .file(
+ "src/foo_lib.rs",
+ r#"
+ #![feature(plugin)]
+ #![plugin(bar)]
+
+ pub fn foo() {}
+ "#,
+ )
+ .build();
+ let _bar = project()
+ .at("bar")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ name = "bar"
+ plugin = true
+
+ [dependencies.baz]
+ path = "../baz"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ #![feature(rustc_private)]
+
+ extern crate baz;
+ extern crate rustc_driver;
+
+ use rustc_driver::plugin::Registry;
+
+ #[no_mangle]
+ pub fn __rustc_plugin_registrar(_reg: &mut Registry) {
+ println!("{}", baz::baz());
+ }
+ "#,
+ )
+ .build();
+ let _baz = project()
+ .at("baz")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "baz"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ name = "baz"
+ crate_type = ["dylib"]
+ "#,
+ )
+ .file("src/lib.rs", "pub fn baz() -> i32 { 1 }")
+ .build();
+
+ foo.cargo("build").run();
+ foo.cargo("doc").run();
+}
+
+#[cargo_test(nightly, reason = "plugins are unstable")]
+fn plugin_with_dynamic_native_dependency() {
+ let build = project()
+ .at("builder")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "builder"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ name = "builder"
+ crate-type = ["dylib"]
+ "#,
+ )
+ .file("src/lib.rs", "#[no_mangle] pub extern fn foo() {}")
+ .build();
+
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "bar"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ #![feature(plugin)]
+ #![plugin(bar)]
+
+ fn main() {}
+ "#,
+ )
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+ build = 'build.rs'
+
+ [lib]
+ name = "bar"
+ plugin = true
+ "#,
+ )
+ .file(
+ "bar/build.rs",
+ r#"
+ use std::env;
+ use std::fs;
+ use std::path::PathBuf;
+
+ fn main() {
+ let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
+ let root = PathBuf::from(env::var("BUILDER_ROOT").unwrap());
+ let file = format!("{}builder{}",
+ env::consts::DLL_PREFIX,
+ env::consts::DLL_SUFFIX);
+ let src = root.join(&file);
+ let dst = out_dir.join(&file);
+ fs::copy(src, dst).unwrap();
+ if cfg!(target_env = "msvc") {
+ fs::copy(root.join("builder.dll.lib"),
+ out_dir.join("builder.dll.lib")).unwrap();
+ }
+ println!("cargo:rustc-flags=-L {}", out_dir.display());
+ }
+ "#,
+ )
+ .file(
+ "bar/src/lib.rs",
+ r#"
+ #![feature(rustc_private)]
+
+ extern crate rustc_driver;
+ use rustc_driver::plugin::Registry;
+
+ #[cfg_attr(not(target_env = "msvc"), link(name = "builder"))]
+ #[cfg_attr(target_env = "msvc", link(name = "builder.dll"))]
+ extern { fn foo(); }
+
+ #[no_mangle]
+ pub fn __rustc_plugin_registrar(_reg: &mut Registry) {
+ unsafe { foo() }
+ }
+ "#,
+ )
+ .build();
+
+ build.cargo("build").run();
+
+ let root = build.root().join("target").join("debug");
+ foo.cargo("build -v").env("BUILDER_ROOT", root).run();
+}
+
+#[cargo_test]
+fn plugin_integration() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+
+ [lib]
+ name = "foo"
+ plugin = true
+ doctest = false
+ "#,
+ )
+ .file("build.rs", "fn main() {}")
+ .file("src/lib.rs", "")
+ .file("tests/it_works.rs", "")
+ .build();
+
+ p.cargo("test -v").run();
+}
+
+#[cargo_test]
+fn doctest_a_plugin() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = { path = "bar" }
+ "#,
+ )
+ .file("src/lib.rs", "#[macro_use] extern crate bar;")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ name = "bar"
+ plugin = true
+ "#,
+ )
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ p.cargo("test -v").run();
+}
+
+// See #1515
+#[cargo_test]
+fn native_plugin_dependency_with_custom_linker() {
+ let target = rustc_host();
+
+ let _foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ plugin = true
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ let bar = project()
+ .at("bar")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.foo]
+ path = "../foo"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [target.{}]
+ linker = "nonexistent-linker"
+ "#,
+ target
+ ),
+ )
+ .build();
+
+ bar.cargo("build --verbose")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[RUNNING] `rustc [..] -C linker=nonexistent-linker [..]`
+[ERROR] [..]linker[..]
+",
+ )
+ .run();
+}
+
+#[cargo_test(nightly, reason = "requires rustc_private")]
+fn panic_abort_plugins() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [profile.dev]
+ panic = 'abort'
+
+ [dependencies]
+ bar = { path = "bar" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ plugin = true
+ "#,
+ )
+ .file(
+ "bar/src/lib.rs",
+ r#"
+ #![feature(rustc_private)]
+ extern crate rustc_ast;
+ extern crate rustc_driver;
+ "#,
+ )
+ .build();
+
+ p.cargo("build").run();
+}
+
+#[cargo_test(nightly, reason = "requires rustc_private")]
+fn shared_panic_abort_plugins() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [profile.dev]
+ panic = 'abort'
+
+ [dependencies]
+ bar = { path = "bar" }
+ baz = { path = "baz" }
+ "#,
+ )
+ .file("src/lib.rs", "extern crate baz;")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ plugin = true
+
+ [dependencies]
+ baz = { path = "../baz" }
+ "#,
+ )
+ .file(
+ "bar/src/lib.rs",
+ r#"
+ #![feature(rustc_private)]
+ extern crate rustc_ast;
+ extern crate rustc_driver;
+ extern crate baz;
+ "#,
+ )
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.0.1"))
+ .file("baz/src/lib.rs", "")
+ .build();
+
+ p.cargo("build -v").run();
+}
diff --git a/src/tools/cargo/tests/testsuite/proc_macro.rs b/src/tools/cargo/tests/testsuite/proc_macro.rs
new file mode 100644
index 000000000..7d6f6ba86
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/proc_macro.rs
@@ -0,0 +1,560 @@
+//! Tests for proc-macros.
+
+use cargo_test_support::project;
+
+#[cargo_test]
+fn probe_cfg_before_crate_type_discovery() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [target.'cfg(not(stage300))'.dependencies.noop]
+ path = "../noop"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ #[macro_use]
+ extern crate noop;
+
+ #[derive(Noop)]
+ struct X;
+
+ fn main() {}
+ "#,
+ )
+ .build();
+ let _noop = project()
+ .at("noop")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "noop"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ proc-macro = true
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ extern crate proc_macro;
+ use proc_macro::TokenStream;
+
+ #[proc_macro_derive(Noop)]
+ pub fn noop(_input: TokenStream) -> TokenStream {
+ "".parse().unwrap()
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn noop() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.noop]
+ path = "../noop"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ #[macro_use]
+ extern crate noop;
+
+ #[derive(Noop)]
+ struct X;
+
+ fn main() {}
+ "#,
+ )
+ .build();
+ let _noop = project()
+ .at("noop")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "noop"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ proc-macro = true
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ extern crate proc_macro;
+ use proc_macro::TokenStream;
+
+ #[proc_macro_derive(Noop)]
+ pub fn noop(_input: TokenStream) -> TokenStream {
+ "".parse().unwrap()
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("check").run();
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn impl_and_derive() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.transmogrify]
+ path = "../transmogrify"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ #[macro_use]
+ extern crate transmogrify;
+
+ trait ImplByTransmogrify {
+ fn impl_by_transmogrify(&self) -> bool;
+ }
+
+ #[derive(Transmogrify, Debug)]
+ struct X { success: bool }
+
+ fn main() {
+ let x = X::new();
+ assert!(x.impl_by_transmogrify());
+ println!("{:?}", x);
+ }
+ "#,
+ )
+ .build();
+ let _transmogrify = project()
+ .at("transmogrify")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "transmogrify"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ proc-macro = true
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ extern crate proc_macro;
+ use proc_macro::TokenStream;
+
+ #[proc_macro_derive(Transmogrify)]
+ #[doc(hidden)]
+ pub fn transmogrify(input: TokenStream) -> TokenStream {
+ "
+ impl X {
+ fn new() -> Self {
+ X { success: true }
+ }
+ }
+
+ impl ImplByTransmogrify for X {
+ fn impl_by_transmogrify(&self) -> bool {
+ true
+ }
+ }
+ ".parse().unwrap()
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build").run();
+ p.cargo("run").with_stdout("X { success: true }").run();
+}
+
+#[cargo_test(nightly, reason = "plugins are unstable")]
+fn plugin_and_proc_macro() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ plugin = true
+ proc-macro = true
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ #![feature(rustc_private)]
+ #![feature(proc_macro, proc_macro_lib)]
+
+ extern crate rustc_driver;
+ use rustc_driver::plugin::Registry;
+
+ extern crate proc_macro;
+ use proc_macro::TokenStream;
+
+ #[no_mangle]
+ pub fn __rustc_plugin_registrar(reg: &mut Registry) {}
+
+ #[proc_macro_derive(Questionable)]
+ pub fn questionable(input: TokenStream) -> TokenStream {
+ input
+ }
+ "#,
+ )
+ .build();
+
+ let msg = " `lib.plugin` and `lib.proc-macro` cannot both be `true`";
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr_contains(msg)
+ .run();
+}
+
+#[cargo_test]
+fn proc_macro_doctest() {
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ [lib]
+ proc-macro = true
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ #![crate_type = "proc-macro"]
+
+ extern crate proc_macro;
+
+ use proc_macro::TokenStream;
+
+ /// ```
+ /// assert!(true);
+ /// ```
+ #[proc_macro_derive(Bar)]
+ pub fn derive(_input: TokenStream) -> TokenStream {
+ "".parse().unwrap()
+ }
+
+ #[test]
+ fn a() {
+ assert!(true);
+ }
+ "#,
+ )
+ .build();
+
+ foo.cargo("test")
+ .with_stdout_contains("test a ... ok")
+ .with_stdout_contains_n("test [..] ... ok", 2)
+ .run();
+}
+
+#[cargo_test]
+fn proc_macro_crate_type() {
+ // Verify that `crate-type = ["proc-macro"]` is the same as `proc-macro = true`
+ // and that everything, including rustdoc, works correctly.
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ [dependencies]
+ pm = { path = "pm" }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ //! ```
+ //! use foo::THING;
+ //! assert_eq!(THING, 123);
+ //! ```
+ #[macro_use]
+ extern crate pm;
+ #[derive(MkItem)]
+ pub struct S;
+ #[cfg(test)]
+ mod tests {
+ use super::THING;
+ #[test]
+ fn it_works() {
+ assert_eq!(THING, 123);
+ }
+ }
+ "#,
+ )
+ .file(
+ "pm/Cargo.toml",
+ r#"
+ [package]
+ name = "pm"
+ version = "0.1.0"
+ [lib]
+ crate-type = ["proc-macro"]
+ "#,
+ )
+ .file(
+ "pm/src/lib.rs",
+ r#"
+ extern crate proc_macro;
+ use proc_macro::TokenStream;
+
+ #[proc_macro_derive(MkItem)]
+ pub fn mk_item(_input: TokenStream) -> TokenStream {
+ "pub const THING: i32 = 123;".parse().unwrap()
+ }
+ "#,
+ )
+ .build();
+
+ foo.cargo("test")
+ .with_stdout_contains("test tests::it_works ... ok")
+ .with_stdout_contains_n("test [..] ... ok", 2)
+ .run();
+}
+
+#[cargo_test]
+fn proc_macro_crate_type_warning() {
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ [lib]
+ crate-type = ["proc-macro"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ foo.cargo("check")
+ .with_stderr_contains(
+ "[WARNING] library `foo` should only specify `proc-macro = true` instead of setting `crate-type`")
+ .run();
+}
+
+#[cargo_test]
+fn proc_macro_conflicting_warning() {
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ [lib]
+ proc-macro = false
+ proc_macro = true
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ foo.cargo("check")
+ .with_stderr_contains(
+"[WARNING] conflicting between `proc-macro` and `proc_macro` in the `foo` library target.\n
+ `proc_macro` is ignored and not recommended for use in the future",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn proc_macro_crate_type_warning_plugin() {
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ [lib]
+ crate-type = ["proc-macro"]
+ plugin = true
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ foo.cargo("check")
+ .with_stderr_contains(
+ "[WARNING] proc-macro library `foo` should not specify `plugin = true`")
+ .with_stderr_contains(
+ "[WARNING] library `foo` should only specify `proc-macro = true` instead of setting `crate-type`")
+ .run();
+}
+
+#[cargo_test]
+fn proc_macro_crate_type_multiple() {
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ [lib]
+ crate-type = ["proc-macro", "rlib"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ foo.cargo("check")
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]/foo/Cargo.toml`
+
+Caused by:
+ cannot mix `proc-macro` crate type with others
+",
+ )
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn proc_macro_extern_prelude() {
+ // Check that proc_macro is in the extern prelude.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+ [lib]
+ proc-macro = true
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ use proc_macro::TokenStream;
+ #[proc_macro]
+ pub fn foo(input: TokenStream) -> TokenStream {
+ "".parse().unwrap()
+ }
+ "#,
+ )
+ .build();
+ p.cargo("test").run();
+ p.cargo("doc").run();
+}
+
+#[cargo_test]
+fn proc_macro_built_once() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ['a', 'b']
+ resolver = "2"
+ "#,
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+
+ [build-dependencies]
+ the-macro = { path = '../the-macro' }
+ "#,
+ )
+ .file("a/build.rs", "fn main() {}")
+ .file("a/src/main.rs", "fn main() {}")
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.1.0"
+
+ [dependencies]
+ the-macro = { path = '../the-macro', features = ['a'] }
+ "#,
+ )
+ .file("b/src/main.rs", "fn main() {}")
+ .file(
+ "the-macro/Cargo.toml",
+ r#"
+ [package]
+ name = "the-macro"
+ version = "0.1.0"
+
+ [lib]
+ proc_macro = true
+
+ [features]
+ a = []
+ "#,
+ )
+ .file("the-macro/src/lib.rs", "")
+ .build();
+ p.cargo("build --verbose")
+ .with_stderr_unordered(
+ "\
+[COMPILING] the-macro [..]
+[RUNNING] `rustc --crate-name the_macro [..]`
+[COMPILING] b [..]
+[RUNNING] `rustc --crate-name b [..]`
+[COMPILING] a [..]
+[RUNNING] `rustc --crate-name build_script_build [..]`
+[RUNNING] `[..]build[..]script[..]build[..]`
+[RUNNING] `rustc --crate-name a [..]`
+[FINISHED] [..]
+",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/profile_config.rs b/src/tools/cargo/tests/testsuite/profile_config.rs
new file mode 100644
index 000000000..c59ed7a97
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/profile_config.rs
@@ -0,0 +1,519 @@
+//! Tests for profiles defined in config files.
+
+use cargo_test_support::paths::CargoPathExt;
+use cargo_test_support::registry::Package;
+use cargo_test_support::{basic_lib_manifest, paths, project};
+
+// TODO: this should be remove once -Zprofile-rustflags is stabilized
+#[cargo_test]
+fn rustflags_works_with_zflag() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ ".cargo/config.toml",
+ r#"
+ [profile.dev]
+ rustflags = ["-C", "link-dead-code=yes"]
+ "#,
+ )
+ .build();
+
+ p.cargo("check -v")
+ .masquerade_as_nightly_cargo(&["profile-rustflags"])
+ .with_status(101)
+ .with_stderr_contains("[..]feature `profile-rustflags` is required[..]")
+ .run();
+
+ p.cargo("check -v -Zprofile-rustflags")
+ .masquerade_as_nightly_cargo(&["profile-rustflags"])
+ .with_stderr(
+ "\
+[CHECKING] foo [..]
+[RUNNING] `rustc --crate-name foo [..] -C link-dead-code=yes [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ p.change_file(
+ ".cargo/config.toml",
+ r#"
+ [unstable]
+ profile-rustflags = true
+
+ [profile.dev]
+ rustflags = ["-C", "link-dead-code=yes"]
+ "#,
+ );
+
+ p.cargo("check -v")
+ .masquerade_as_nightly_cargo(&["profile-rustflags"])
+ .with_stderr(
+ "\
+[FRESH] foo [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn profile_config_validate_warnings() {
+ let p = project()
+ .file("Cargo.toml", &basic_lib_manifest("foo"))
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ r#"
+ [profile.test]
+ opt-level = 3
+
+ [profile.asdf]
+ opt-level = 3
+
+ [profile.dev]
+ bad-key = true
+
+ [profile.dev.build-override]
+ bad-key-bo = true
+
+ [profile.dev.package.bar]
+ bad-key-bar = true
+ "#,
+ )
+ .build();
+
+ p.cargo("build")
+ .with_stderr_unordered(
+ "\
+[WARNING] unused config key `profile.dev.bad-key` in `[..].cargo/config`
+[WARNING] unused config key `profile.dev.package.bar.bad-key-bar` in `[..].cargo/config`
+[WARNING] unused config key `profile.dev.build-override.bad-key-bo` in `[..].cargo/config`
+[COMPILING] foo [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn profile_config_error_paths() {
+ // Errors in config show where the error is located.
+ let p = project()
+ .file("Cargo.toml", &basic_lib_manifest("foo"))
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ r#"
+ [profile.dev]
+ opt-level = 3
+ "#,
+ )
+ .file(
+ paths::home().join(".cargo/config"),
+ r#"
+ [profile.dev]
+ rpath = "foo"
+ "#,
+ )
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] error in [..]/foo/.cargo/config: could not load config key `profile.dev`
+
+Caused by:
+ error in [..]/home/.cargo/config: `profile.dev.rpath` expected true/false, but found a string
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn profile_config_validate_errors() {
+ let p = project()
+ .file("Cargo.toml", &basic_lib_manifest("foo"))
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ r#"
+ [profile.dev.package.foo]
+ panic = "abort"
+ "#,
+ )
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] config profile `dev` is not valid (defined in `[..]/foo/.cargo/config`)
+
+Caused by:
+ `panic` may not be specified in a `package` profile
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn profile_config_syntax_errors() {
+ let p = project()
+ .file("Cargo.toml", &basic_lib_manifest("foo"))
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ r#"
+ [profile.dev]
+ codegen-units = "foo"
+ "#,
+ )
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] error in [..]/.cargo/config: could not load config key `profile.dev`
+
+Caused by:
+ error in [..]/foo/.cargo/config: `profile.dev.codegen-units` expected an integer, but found a string
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn profile_config_override_spec_multiple() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies]
+ bar = { path = "bar" }
+ "#,
+ )
+ .file(
+ ".cargo/config",
+ r#"
+ [profile.dev.package.bar]
+ opt-level = 3
+
+ [profile.dev.package."bar:0.5.0"]
+ opt-level = 3
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_lib_manifest("bar"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ // Unfortunately this doesn't tell you which file, hopefully it's not too
+ // much of a problem.
+ p.cargo("build -v")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] multiple package overrides in profile `dev` match package `bar v0.5.0 ([..])`
+found package specs: bar, bar@0.5.0",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn profile_config_all_options() {
+ // Ensure all profile options are supported.
+ let p = project()
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ ".cargo/config",
+ r#"
+ [profile.release]
+ opt-level = 1
+ debug = true
+ debug-assertions = true
+ overflow-checks = false
+ rpath = true
+ lto = true
+ codegen-units = 2
+ panic = "abort"
+ incremental = true
+ "#,
+ )
+ .build();
+
+ p.cargo("build --release -v")
+ .env_remove("CARGO_INCREMENTAL")
+ .with_stderr(
+ "\
+[COMPILING] foo [..]
+[RUNNING] `rustc --crate-name foo [..] \
+ -C opt-level=1 \
+ -C panic=abort \
+ -C lto[..]\
+ -C codegen-units=2 \
+ -C debuginfo=2 \
+ -C debug-assertions=on \
+ -C overflow-checks=off [..]\
+ -C rpath [..]\
+ -C incremental=[..]
+[FINISHED] release [optimized + debuginfo] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn profile_config_override_precedence() {
+ // Config values take precedence over manifest values.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies]
+ bar = {path = "bar"}
+
+ [profile.dev]
+ codegen-units = 2
+
+ [profile.dev.package.bar]
+ opt-level = 3
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_lib_manifest("bar"))
+ .file("bar/src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ r#"
+ [profile.dev.package.bar]
+ opt-level = 2
+ "#,
+ )
+ .build();
+
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[COMPILING] bar [..]
+[RUNNING] `rustc --crate-name bar [..] -C opt-level=2[..]-C codegen-units=2 [..]
+[COMPILING] foo [..]
+[RUNNING] `rustc --crate-name foo [..]-C codegen-units=2 [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn profile_config_no_warn_unknown_override() {
+ let p = project()
+ .file("Cargo.toml", &basic_lib_manifest("foo"))
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ r#"
+ [profile.dev.package.bar]
+ codegen-units = 4
+ "#,
+ )
+ .build();
+
+ p.cargo("build")
+ .with_stderr_does_not_contain("[..]warning[..]")
+ .run();
+}
+
+#[cargo_test]
+fn profile_config_mixed_types() {
+ let p = project()
+ .file("Cargo.toml", &basic_lib_manifest("foo"))
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ r#"
+ [profile.dev]
+ opt-level = 3
+ "#,
+ )
+ .file(
+ paths::home().join(".cargo/config"),
+ r#"
+ [profile.dev]
+ opt-level = 's'
+ "#,
+ )
+ .build();
+
+ p.cargo("build -v")
+ .with_stderr_contains("[..]-C opt-level=3 [..]")
+ .run();
+}
+
+#[cargo_test]
+fn named_config_profile() {
+ // Exercises config named profies.
+ // foo -> middle -> bar -> dev
+ // middle exists in Cargo.toml, the others in .cargo/config
+ use super::config::ConfigBuilder;
+ use cargo::core::compiler::CompileKind;
+ use cargo::core::profiles::{Profiles, UnitFor};
+ use cargo::core::{PackageId, Workspace};
+ use cargo::util::interning::InternedString;
+ use std::fs;
+ paths::root().join(".cargo").mkdir_p();
+ fs::write(
+ paths::root().join(".cargo/config"),
+ r#"
+ [profile.foo]
+ inherits = "middle"
+ codegen-units = 2
+ [profile.foo.build-override]
+ codegen-units = 6
+ [profile.foo.package.dep]
+ codegen-units = 7
+
+ [profile.middle]
+ inherits = "bar"
+ codegen-units = 3
+
+ [profile.bar]
+ inherits = "dev"
+ codegen-units = 4
+ debug = 1
+ "#,
+ )
+ .unwrap();
+ fs::write(
+ paths::root().join("Cargo.toml"),
+ r#"
+ [workspace]
+
+ [profile.middle]
+ inherits = "bar"
+ codegen-units = 1
+ opt-level = 1
+ [profile.middle.package.dep]
+ overflow-checks = false
+
+ [profile.foo.build-override]
+ codegen-units = 5
+ debug-assertions = false
+ [profile.foo.package.dep]
+ codegen-units = 8
+ "#,
+ )
+ .unwrap();
+ let config = ConfigBuilder::new().build();
+ let profile_name = InternedString::new("foo");
+ let ws = Workspace::new(&paths::root().join("Cargo.toml"), &config).unwrap();
+ let profiles = Profiles::new(&ws, profile_name).unwrap();
+
+ let crates_io = cargo::core::source::SourceId::crates_io(&config).unwrap();
+ let a_pkg = PackageId::new("a", "0.1.0", crates_io).unwrap();
+ let dep_pkg = PackageId::new("dep", "0.1.0", crates_io).unwrap();
+
+ // normal package
+ let kind = CompileKind::Host;
+ let p = profiles.get_profile(a_pkg, true, true, UnitFor::new_normal(kind), kind);
+ assert_eq!(p.name, "foo");
+ assert_eq!(p.codegen_units, Some(2)); // "foo" from config
+ assert_eq!(p.opt_level, "1"); // "middle" from manifest
+ assert_eq!(p.debuginfo.to_option(), Some(1)); // "bar" from config
+ assert_eq!(p.debug_assertions, true); // "dev" built-in (ignore build-override)
+ assert_eq!(p.overflow_checks, true); // "dev" built-in (ignore package override)
+
+ // build-override
+ let bo = profiles.get_profile(a_pkg, true, true, UnitFor::new_host(false, kind), kind);
+ assert_eq!(bo.name, "foo");
+ assert_eq!(bo.codegen_units, Some(6)); // "foo" build override from config
+ assert_eq!(bo.opt_level, "0"); // default to zero
+ assert_eq!(bo.debuginfo.to_option(), Some(1)); // SAME as normal
+ assert_eq!(bo.debug_assertions, false); // "foo" build override from manifest
+ assert_eq!(bo.overflow_checks, true); // SAME as normal
+
+ // package overrides
+ let po = profiles.get_profile(dep_pkg, false, true, UnitFor::new_normal(kind), kind);
+ assert_eq!(po.name, "foo");
+ assert_eq!(po.codegen_units, Some(7)); // "foo" package override from config
+ assert_eq!(po.opt_level, "1"); // SAME as normal
+ assert_eq!(po.debuginfo.to_option(), Some(1)); // SAME as normal
+ assert_eq!(po.debug_assertions, true); // SAME as normal
+ assert_eq!(po.overflow_checks, false); // "middle" package override from manifest
+}
+
+#[cargo_test]
+fn named_env_profile() {
+ // Environment variables used to define a named profile.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build -v --profile=other")
+ .env("CARGO_PROFILE_OTHER_CODEGEN_UNITS", "1")
+ .env("CARGO_PROFILE_OTHER_INHERITS", "dev")
+ .with_stderr_contains("[..]-C codegen-units=1 [..]")
+ .run();
+}
+
+#[cargo_test]
+fn test_with_dev_profile() {
+ // The `test` profile inherits from `dev` for both local crates and
+ // dependencies.
+ Package::new("somedep", "1.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ somedep = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("test --lib --no-run -v")
+ .env("CARGO_PROFILE_DEV_DEBUG", "0")
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[DOWNLOADING] [..]
+[DOWNLOADED] [..]
+[COMPILING] somedep v1.0.0
+[RUNNING] `rustc --crate-name somedep [..]-C debuginfo=0[..]
+[COMPILING] foo v0.1.0 [..]
+[RUNNING] `rustc --crate-name foo [..]-C debuginfo=0[..]
+[FINISHED] [..]
+[EXECUTABLE] `[..]/target/debug/deps/foo-[..][EXE]`
+",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/profile_custom.rs b/src/tools/cargo/tests/testsuite/profile_custom.rs
new file mode 100644
index 000000000..ea6b54c95
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/profile_custom.rs
@@ -0,0 +1,731 @@
+//! Tests for named profiles.
+
+use cargo_test_support::paths::CargoPathExt;
+use cargo_test_support::{basic_lib_manifest, project};
+
+#[cargo_test]
+fn inherits_on_release() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [profile.release]
+ inherits = "dev"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] `inherits` must not be specified in root profile `release`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn missing_inherits() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [profile.release-lto]
+ codegen-units = 7
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] profile `release-lto` is missing an `inherits` directive \
+ (`inherits` is required for all profiles except `dev` or `release`)
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn invalid_profile_name() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [profile.'.release-lto']
+ inherits = "release"
+ codegen-units = 7
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at [..]
+
+Caused by:
+ invalid character `.` in profile name `.release-lto`
+ Allowed characters are letters, numbers, underscore, and hyphen.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+// We are currently uncertain if dir-name will ever be exposed to the user.
+// The code for it still roughly exists, but only for the internal profiles.
+// This test was kept in case we ever want to enable support for it again.
+#[ignore = "dir-name is disabled"]
+fn invalid_dir_name() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [profile.'release-lto']
+ inherits = "release"
+ dir-name = ".subdir"
+ codegen-units = 7
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at [..]
+
+Caused by:
+ Invalid character `.` in dir-name: `.subdir`",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn dir_name_disabled() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [profile.release-lto]
+ inherits = "release"
+ dir-name = "lto"
+ lto = true
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[ROOT]/foo/Cargo.toml`
+
+Caused by:
+ dir-name=\"lto\" in profile `release-lto` is not currently allowed, \
+ directory names are tied to the profile name for custom profiles
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn invalid_inherits() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [profile.'release-lto']
+ inherits = ".release"
+ codegen-units = 7
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "error: profile `release-lto` inherits from `.release`, \
+ but that profile is not defined",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn non_existent_inherits() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [profile.release-lto]
+ codegen-units = 7
+ inherits = "non-existent"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] profile `release-lto` inherits from `non-existent`, but that profile is not defined
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn self_inherits() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [profile.release-lto]
+ codegen-units = 7
+ inherits = "release-lto"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] profile inheritance loop detected with profile `release-lto` inheriting `release-lto`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn inherits_loop() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [profile.release-lto]
+ codegen-units = 7
+ inherits = "release-lto2"
+
+ [profile.release-lto2]
+ codegen-units = 7
+ inherits = "release-lto"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] profile inheritance loop detected with profile `release-lto2` inheriting `release-lto`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn overrides_with_custom() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ xxx = {path = "xxx"}
+ yyy = {path = "yyy"}
+
+ [profile.dev]
+ codegen-units = 7
+
+ [profile.dev.package.xxx]
+ codegen-units = 5
+ [profile.dev.package.yyy]
+ codegen-units = 3
+
+ [profile.other]
+ inherits = "dev"
+ codegen-units = 2
+
+ [profile.other.package.yyy]
+ codegen-units = 6
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("xxx/Cargo.toml", &basic_lib_manifest("xxx"))
+ .file("xxx/src/lib.rs", "")
+ .file("yyy/Cargo.toml", &basic_lib_manifest("yyy"))
+ .file("yyy/src/lib.rs", "")
+ .build();
+
+ // profile overrides are inherited between profiles using inherits and have a
+ // higher priority than profile options provided by custom profiles
+ p.cargo("build -v")
+ .with_stderr_unordered(
+ "\
+[COMPILING] xxx [..]
+[COMPILING] yyy [..]
+[COMPILING] foo [..]
+[RUNNING] `rustc --crate-name xxx [..] -C codegen-units=5 [..]`
+[RUNNING] `rustc --crate-name yyy [..] -C codegen-units=3 [..]`
+[RUNNING] `rustc --crate-name foo [..] -C codegen-units=7 [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ // This also verifies that the custom profile names appears in the finished line.
+ p.cargo("build --profile=other -v")
+ .with_stderr_unordered(
+ "\
+[COMPILING] xxx [..]
+[COMPILING] yyy [..]
+[COMPILING] foo [..]
+[RUNNING] `rustc --crate-name xxx [..] -C codegen-units=5 [..]`
+[RUNNING] `rustc --crate-name yyy [..] -C codegen-units=6 [..]`
+[RUNNING] `rustc --crate-name foo [..] -C codegen-units=2 [..]`
+[FINISHED] other [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn conflicting_usage() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build --profile=dev --release")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: conflicting usage of --profile=dev and --release
+The `--release` flag is the same as `--profile=release`.
+Remove one flag or the other to continue.
+",
+ )
+ .run();
+
+ p.cargo("install --profile=release --debug")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: conflicting usage of --profile=release and --debug
+The `--debug` flag is the same as `--profile=dev`.
+Remove one flag or the other to continue.
+",
+ )
+ .run();
+
+ p.cargo("rustc --profile=dev --release")
+ .with_stderr(
+ "\
+warning: the `--release` flag should not be specified with the `--profile` flag
+The `--release` flag will be ignored.
+This was historically accepted, but will become an error in a future release.
+[COMPILING] foo [..]
+[FINISHED] dev [..]
+",
+ )
+ .run();
+
+ p.cargo("check --profile=dev --release")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: conflicting usage of --profile=dev and --release
+The `--release` flag is the same as `--profile=release`.
+Remove one flag or the other to continue.
+",
+ )
+ .run();
+
+ p.cargo("check --profile=test --release")
+ .with_stderr(
+ "\
+warning: the `--release` flag should not be specified with the `--profile` flag
+The `--release` flag will be ignored.
+This was historically accepted, but will become an error in a future release.
+[CHECKING] foo [..]
+[FINISHED] test [..]
+",
+ )
+ .run();
+
+ // This is OK since the two are the same.
+ p.cargo("rustc --profile=release --release")
+ .with_stderr(
+ "\
+[COMPILING] foo [..]
+[FINISHED] release [..]
+",
+ )
+ .run();
+
+ p.cargo("build --profile=release --release")
+ .with_stderr(
+ "\
+[FINISHED] release [..]
+",
+ )
+ .run();
+
+ p.cargo("install --path . --profile=dev --debug")
+ .with_stderr(
+ "\
+[INSTALLING] foo [..]
+[FINISHED] dev [..]
+[INSTALLING] [..]
+[INSTALLED] [..]
+[WARNING] be sure to add [..]
+",
+ )
+ .run();
+
+ p.cargo("install --path . --profile=release --debug")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: conflicting usage of --profile=release and --debug
+The `--debug` flag is the same as `--profile=dev`.
+Remove one flag or the other to continue.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn clean_custom_dirname() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [profile.other]
+ inherits = "release"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build --release")
+ .with_stdout("")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] release [optimized] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("clean -p foo").run();
+
+ p.cargo("build --release")
+ .with_stdout("")
+ .with_stderr(
+ "\
+[FINISHED] release [optimized] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("clean -p foo --release").run();
+
+ p.cargo("build --release")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] release [optimized] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("build")
+ .with_stdout("")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("build --profile=other")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] other [optimized] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("clean").arg("--release").run();
+
+ // Make sure that 'other' was not cleaned
+ assert!(p.build_dir().is_dir());
+ assert!(p.build_dir().join("debug").is_dir());
+ assert!(p.build_dir().join("other").is_dir());
+ assert!(!p.build_dir().join("release").is_dir());
+
+ // This should clean 'other'
+ p.cargo("clean --profile=other").with_stderr("").run();
+ assert!(p.build_dir().join("debug").is_dir());
+ assert!(!p.build_dir().join("other").is_dir());
+}
+
+#[cargo_test]
+fn unknown_profile() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build --profile alpha")
+ .with_stderr("[ERROR] profile `alpha` is not defined")
+ .with_status(101)
+ .run();
+ // Clean has a separate code path, need to check it too.
+ p.cargo("clean --profile alpha")
+ .with_stderr("[ERROR] profile `alpha` is not defined")
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn reserved_profile_names() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [profile.doc]
+ opt-level = 1
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build --profile=doc")
+ .with_status(101)
+ .with_stderr("error: profile `doc` is reserved and not allowed to be explicitly specified")
+ .run();
+ // Not an exhaustive list, just a sample.
+ for name in ["build", "cargo", "check", "rustc", "CaRgO_startswith"] {
+ p.cargo(&format!("build --profile={}", name))
+ .with_status(101)
+ .with_stderr(&format!(
+ "\
+error: profile name `{}` is reserved
+Please choose a different name.
+See https://doc.rust-lang.org/cargo/reference/profiles.html for more on configuring profiles.
+",
+ name
+ ))
+ .run();
+ }
+ for name in ["build", "check", "cargo", "rustc", "CaRgO_startswith"] {
+ p.change_file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [profile.{}]
+ opt-level = 1
+ "#,
+ name
+ ),
+ );
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(&format!(
+ "\
+error: failed to parse manifest at `[ROOT]/foo/Cargo.toml`
+
+Caused by:
+ profile name `{}` is reserved
+ Please choose a different name.
+ See https://doc.rust-lang.org/cargo/reference/profiles.html for more on configuring profiles.
+",
+ name
+ ))
+ .run();
+ }
+
+ p.change_file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [profile.debug]
+ debug = 1
+ inherits = "dev"
+ "#,
+ );
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[ROOT]/foo/Cargo.toml`
+
+Caused by:
+ profile name `debug` is reserved
+ To configure the default development profile, use the name `dev` as in [profile.dev]
+ See https://doc.rust-lang.org/cargo/reference/profiles.html for more on configuring profiles.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn legacy_commands_support_custom() {
+ // These commands have had `--profile` before custom named profiles.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [profile.super-dev]
+ codegen-units = 3
+ inherits = "dev"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ for command in ["rustc", "fix", "check"] {
+ let mut pb = p.cargo(command);
+ if command == "fix" {
+ pb.arg("--allow-no-vcs");
+ }
+ pb.arg("--profile=super-dev")
+ .arg("-v")
+ .with_stderr_contains("[RUNNING] [..]codegen-units=3[..]")
+ .run();
+ p.build_dir().rm_rf();
+ }
+}
+
+#[cargo_test]
+fn legacy_rustc() {
+ // `cargo rustc` historically has supported dev/test/bench/check
+ // other profiles are covered in check::rustc_check
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [profile.dev]
+ codegen-units = 3
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("rustc --profile dev -v")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.1.0 [..]
+[RUNNING] `rustc --crate-name foo [..]-C codegen-units=3[..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/profile_overrides.rs b/src/tools/cargo/tests/testsuite/profile_overrides.rs
new file mode 100644
index 000000000..dc9bafba1
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/profile_overrides.rs
@@ -0,0 +1,515 @@
+//! Tests for profile overrides (build-override and per-package overrides).
+
+use cargo_test_support::registry::Package;
+use cargo_test_support::{basic_lib_manifest, basic_manifest, project};
+
+#[cargo_test]
+fn profile_override_basic() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = {path = "bar"}
+
+ [profile.dev]
+ opt-level = 1
+
+ [profile.dev.package.bar]
+ opt-level = 3
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_lib_manifest("bar"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("check -v")
+ .with_stderr(
+ "[CHECKING] bar [..]
+[RUNNING] `rustc --crate-name bar [..] -C opt-level=3 [..]`
+[CHECKING] foo [..]
+[RUNNING] `rustc --crate-name foo [..] -C opt-level=1 [..]`
+[FINISHED] dev [optimized + debuginfo] target(s) in [..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn profile_override_warnings() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies]
+ bar = {path = "bar"}
+
+ [profile.dev.package.bart]
+ opt-level = 3
+
+ [profile.dev.package.no-suggestion]
+ opt-level = 3
+
+ [profile.dev.package."bar:1.2.3"]
+ opt-level = 3
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_lib_manifest("bar"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("build")
+ .with_stderr_contains(
+ "\
+[WARNING] profile package spec `bar@1.2.3` in profile `dev` \
+ has a version or URL that does not match any of the packages: \
+ bar v0.5.0 ([..]/foo/bar)
+[WARNING] profile package spec `bart` in profile `dev` did not match any packages
+
+<tab>Did you mean `bar`?
+[WARNING] profile package spec `no-suggestion` in profile `dev` did not match any packages
+[COMPILING] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn profile_override_bad_settings() {
+ let bad_values = [
+ (
+ "panic = \"abort\"",
+ "`panic` may not be specified in a `package` profile",
+ ),
+ (
+ "lto = true",
+ "`lto` may not be specified in a `package` profile",
+ ),
+ (
+ "rpath = true",
+ "`rpath` may not be specified in a `package` profile",
+ ),
+ ("package = {}", "package-specific profiles cannot be nested"),
+ ];
+ for &(snippet, expected) in bad_values.iter() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies]
+ bar = {{path = "bar"}}
+
+ [profile.dev.package.bar]
+ {}
+ "#,
+ snippet
+ ),
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_lib_manifest("bar"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr_contains(format!("Caused by:\n {}", expected))
+ .run();
+ }
+}
+
+#[cargo_test]
+fn profile_override_hierarchy() {
+ // Test that the precedence rules are correct for different types.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["m1", "m2", "m3"]
+
+ [profile.dev]
+ codegen-units = 1
+
+ [profile.dev.package.m2]
+ codegen-units = 2
+
+ [profile.dev.package."*"]
+ codegen-units = 3
+
+ [profile.dev.build-override]
+ codegen-units = 4
+ "#,
+ )
+ // m1
+ .file(
+ "m1/Cargo.toml",
+ r#"
+ [package]
+ name = "m1"
+ version = "0.0.1"
+
+ [dependencies]
+ m2 = { path = "../m2" }
+ dep = { path = "../../dep" }
+ "#,
+ )
+ .file("m1/src/lib.rs", "extern crate m2; extern crate dep;")
+ .file("m1/build.rs", "fn main() {}")
+ // m2
+ .file(
+ "m2/Cargo.toml",
+ r#"
+ [package]
+ name = "m2"
+ version = "0.0.1"
+
+ [dependencies]
+ m3 = { path = "../m3" }
+
+ [build-dependencies]
+ m3 = { path = "../m3" }
+ dep = { path = "../../dep" }
+ "#,
+ )
+ .file("m2/src/lib.rs", "extern crate m3;")
+ .file(
+ "m2/build.rs",
+ "extern crate m3; extern crate dep; fn main() {}",
+ )
+ // m3
+ .file("m3/Cargo.toml", &basic_lib_manifest("m3"))
+ .file("m3/src/lib.rs", "")
+ .build();
+
+ // dep (outside of workspace)
+ let _dep = project()
+ .at("dep")
+ .file("Cargo.toml", &basic_lib_manifest("dep"))
+ .file("src/lib.rs", "")
+ .build();
+
+ // Profiles should be:
+ // m3: 4 (as build.rs dependency)
+ // m3: 1 (as [profile.dev] as workspace member)
+ // dep: 3 (as [profile.dev.package."*"] as non-workspace member)
+ // m1 build.rs: 4 (as [profile.dev.build-override])
+ // m2 build.rs: 2 (as [profile.dev.package.m2])
+ // m2: 2 (as [profile.dev.package.m2])
+ // m1: 1 (as [profile.dev])
+
+ p.cargo("build -v").with_stderr_unordered("\
+[COMPILING] m3 [..]
+[COMPILING] dep [..]
+[RUNNING] `rustc --crate-name m3 m3/src/lib.rs [..] --crate-type lib --emit=[..]link[..]-C codegen-units=4 [..]
+[RUNNING] `rustc --crate-name dep [..]dep/src/lib.rs [..] --crate-type lib --emit=[..]link[..]-C codegen-units=3 [..]
+[RUNNING] `rustc --crate-name m3 m3/src/lib.rs [..] --crate-type lib --emit=[..]link[..]-C codegen-units=1 [..]
+[RUNNING] `rustc --crate-name build_script_build m1/build.rs [..] --crate-type bin --emit=[..]link[..]-C codegen-units=4 [..]
+[COMPILING] m2 [..]
+[RUNNING] `rustc --crate-name build_script_build m2/build.rs [..] --crate-type bin --emit=[..]link[..]-C codegen-units=2 [..]
+[RUNNING] `[..]/m1-[..]/build-script-build`
+[RUNNING] `[..]/m2-[..]/build-script-build`
+[RUNNING] `rustc --crate-name m2 m2/src/lib.rs [..] --crate-type lib --emit=[..]link[..]-C codegen-units=2 [..]
+[COMPILING] m1 [..]
+[RUNNING] `rustc --crate-name m1 m1/src/lib.rs [..] --crate-type lib --emit=[..]link[..]-C codegen-units=1 [..]
+[FINISHED] dev [unoptimized + debuginfo] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn profile_override_spec_multiple() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies]
+ bar = { path = "bar" }
+
+ [profile.dev.package.bar]
+ opt-level = 3
+
+ [profile.dev.package."bar:0.5.0"]
+ opt-level = 3
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_lib_manifest("bar"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("check -v")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+[ERROR] multiple package overrides in profile `dev` match package `bar v0.5.0 ([..])`
+found package specs: bar, bar@0.5.0",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn profile_override_spec() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["m1", "m2"]
+
+ [profile.dev.package."dep:1.0.0"]
+ codegen-units = 1
+
+ [profile.dev.package."dep:2.0.0"]
+ codegen-units = 2
+ "#,
+ )
+ // m1
+ .file(
+ "m1/Cargo.toml",
+ r#"
+ [package]
+ name = "m1"
+ version = "0.0.1"
+
+ [dependencies]
+ dep = { path = "../../dep1" }
+ "#,
+ )
+ .file("m1/src/lib.rs", "extern crate dep;")
+ // m2
+ .file(
+ "m2/Cargo.toml",
+ r#"
+ [package]
+ name = "m2"
+ version = "0.0.1"
+
+ [dependencies]
+ dep = {path = "../../dep2" }
+ "#,
+ )
+ .file("m2/src/lib.rs", "extern crate dep;")
+ .build();
+
+ project()
+ .at("dep1")
+ .file("Cargo.toml", &basic_manifest("dep", "1.0.0"))
+ .file("src/lib.rs", "")
+ .build();
+
+ project()
+ .at("dep2")
+ .file("Cargo.toml", &basic_manifest("dep", "2.0.0"))
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check -v")
+ .with_stderr_contains("[RUNNING] `rustc [..]dep1/src/lib.rs [..] -C codegen-units=1 [..]")
+ .with_stderr_contains("[RUNNING] `rustc [..]dep2/src/lib.rs [..] -C codegen-units=2 [..]")
+ .run();
+}
+
+#[cargo_test]
+fn override_proc_macro() {
+ Package::new("shared", "1.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [dependencies]
+ shared = "1.0"
+ pm = {path = "pm"}
+
+ [profile.dev.build-override]
+ codegen-units = 4
+ "#,
+ )
+ .file("src/lib.rs", r#"pm::eat!{}"#)
+ .file(
+ "pm/Cargo.toml",
+ r#"
+ [package]
+ name = "pm"
+ version = "0.1.0"
+
+ [lib]
+ proc-macro = true
+
+ [dependencies]
+ shared = "1.0"
+ "#,
+ )
+ .file(
+ "pm/src/lib.rs",
+ r#"
+ extern crate proc_macro;
+ use proc_macro::TokenStream;
+
+ #[proc_macro]
+ pub fn eat(_item: TokenStream) -> TokenStream {
+ "".parse().unwrap()
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("check -v")
+ // Shared built for the proc-macro.
+ .with_stderr_contains("[RUNNING] `rustc [..]--crate-name shared [..]-C codegen-units=4[..]")
+ // Shared built for the library.
+ .with_stderr_line_without(
+ &["[RUNNING] `rustc --crate-name shared"],
+ &["-C codegen-units"],
+ )
+ .with_stderr_contains("[RUNNING] `rustc [..]--crate-name pm [..]-C codegen-units=4[..]")
+ .with_stderr_line_without(
+ &["[RUNNING] `rustc [..]--crate-name foo"],
+ &["-C codegen-units"],
+ )
+ .run();
+}
+
+#[cargo_test]
+fn no_warning_ws() {
+ // https://github.com/rust-lang/cargo/issues/7378, avoid warnings in a workspace.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["a", "b"]
+
+ [profile.dev.package.a]
+ codegen-units = 3
+ "#,
+ )
+ .file("a/Cargo.toml", &basic_manifest("a", "0.1.0"))
+ .file("a/src/lib.rs", "")
+ .file("b/Cargo.toml", &basic_manifest("b", "0.1.0"))
+ .file("b/src/lib.rs", "")
+ .build();
+
+ p.cargo("check -p b")
+ .with_stderr(
+ "\
+[CHECKING] b [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_override_shared() {
+ // A dependency with a build script that is shared with a build
+ // dependency, using different profile settings. That is:
+ //
+ // foo DEBUG=2
+ // ├── common DEBUG=2
+ // │ └── common Run build.rs DEBUG=2
+ // │ └── common build.rs DEBUG=0 (build_override)
+ // └── foo Run build.rs DEBUG=2
+ // └── foo build.rs DEBUG=0 (build_override)
+ // └── common DEBUG=0 (build_override)
+ // └── common Run build.rs DEBUG=0 (build_override)
+ // └── common build.rs DEBUG=0 (build_override)
+ //
+ // The key part here is that `common` RunCustomBuild is run twice, once
+ // with DEBUG=2 (as a dependency of foo) and once with DEBUG=0 (as a
+ // build-dependency of foo's build script).
+ Package::new("common", "1.0.0")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ if std::env::var("DEBUG").unwrap() != "false" {
+ println!("cargo:rustc-cfg=foo_debug");
+ } else {
+ println!("cargo:rustc-cfg=foo_release");
+ }
+ }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn foo() -> u32 {
+ if cfg!(foo_debug) {
+ assert!(cfg!(debug_assertions));
+ 1
+ } else if cfg!(foo_release) {
+ assert!(!cfg!(debug_assertions));
+ 2
+ } else {
+ panic!("not set");
+ }
+ }
+ "#,
+ )
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [build-dependencies]
+ common = "1.0"
+
+ [dependencies]
+ common = "1.0"
+
+ [profile.dev.build-override]
+ debug = 0
+ debug-assertions = false
+ "#,
+ )
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ assert_eq!(common::foo(), 2);
+ }
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ assert_eq!(common::foo(), 1);
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("run").run();
+}
diff --git a/src/tools/cargo/tests/testsuite/profile_targets.rs b/src/tools/cargo/tests/testsuite/profile_targets.rs
new file mode 100644
index 000000000..b3235972c
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/profile_targets.rs
@@ -0,0 +1,674 @@
+//! Tests for checking exactly how profiles correspond with each unit. For
+//! example, the `test` profile applying to test targets, but not other
+//! targets, etc.
+
+use cargo_test_support::{basic_manifest, project, Project};
+
+fn all_target_project() -> Project {
+ // This abuses the `codegen-units` setting so that we can verify exactly
+ // which profile is used for each compiler invocation.
+ project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies]
+ bar = { path = "bar" }
+
+ [build-dependencies]
+ bdep = { path = "bdep" }
+
+ [profile.dev]
+ codegen-units = 1
+ panic = "abort"
+ [profile.release]
+ codegen-units = 2
+ panic = "abort"
+ [profile.test]
+ codegen-units = 3
+ [profile.bench]
+ codegen-units = 4
+ [profile.dev.build-override]
+ codegen-units = 5
+ [profile.release.build-override]
+ codegen-units = 6
+ "#,
+ )
+ .file("src/lib.rs", "extern crate bar;")
+ .file("src/main.rs", "extern crate foo; fn main() {}")
+ .file("examples/ex1.rs", "extern crate foo; fn main() {}")
+ .file("tests/test1.rs", "extern crate foo;")
+ .file("benches/bench1.rs", "extern crate foo;")
+ .file(
+ "build.rs",
+ r#"
+ extern crate bdep;
+ fn main() {
+ eprintln!("foo custom build PROFILE={} DEBUG={} OPT_LEVEL={}",
+ std::env::var("PROFILE").unwrap(),
+ std::env::var("DEBUG").unwrap(),
+ std::env::var("OPT_LEVEL").unwrap(),
+ );
+ }
+ "#,
+ )
+ // `bar` package.
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "")
+ // `bdep` package.
+ .file(
+ "bdep/Cargo.toml",
+ r#"
+ [package]
+ name = "bdep"
+ version = "0.0.1"
+
+ [dependencies]
+ bar = { path = "../bar" }
+ "#,
+ )
+ .file("bdep/src/lib.rs", "extern crate bar;")
+ .build()
+}
+
+#[cargo_test]
+fn profile_selection_build() {
+ let p = all_target_project();
+
+ // `build`
+ // NOTES:
+ // - bdep `panic` is not set because it thinks `build.rs` is a plugin.
+ // - build_script_build is built without panic because it thinks `build.rs` is a plugin.
+ // - We make sure that the build dependencies bar, bdep, and build.rs
+ // are built without debuginfo.
+ p.cargo("build -vv")
+ .with_stderr_unordered("\
+[COMPILING] bar [..]
+[RUNNING] `[..] rustc --crate-name bar bar/src/lib.rs [..]--crate-type lib --emit=[..]link -C panic=abort[..]-C codegen-units=1 -C debuginfo=2 [..]
+[RUNNING] `[..] rustc --crate-name bar bar/src/lib.rs [..]--crate-type lib --emit=[..]link[..]-C codegen-units=5 [..]
+[COMPILING] bdep [..]
+[RUNNING] `[..] rustc --crate-name bdep bdep/src/lib.rs [..]--crate-type lib --emit=[..]link[..]-C codegen-units=5 [..]
+[COMPILING] foo [..]
+[RUNNING] `[..] rustc --crate-name build_script_build build.rs [..]--crate-type bin --emit=[..]link[..]-C codegen-units=5 [..]
+[RUNNING] `[..]/target/debug/build/foo-[..]/build-script-build`
+[foo 0.0.1] foo custom build PROFILE=debug DEBUG=true OPT_LEVEL=0
+[RUNNING] `[..] rustc --crate-name foo src/lib.rs [..]--crate-type lib --emit=[..]link -C panic=abort[..]-C codegen-units=1 -C debuginfo=2 [..]
+[RUNNING] `[..] rustc --crate-name foo src/main.rs [..]--crate-type bin --emit=[..]link -C panic=abort[..]-C codegen-units=1 -C debuginfo=2 [..]
+[FINISHED] dev [unoptimized + debuginfo] [..]
+"
+ )
+ .with_stderr_line_without(&["[RUNNING] `[..] rustc --crate-name bar bar/src/lib.rs [..]-C codegen-units=5 [..]"], &["-C debuginfo"])
+ .with_stderr_line_without(&["[RUNNING] `[..] rustc --crate-name bdep bdep/src/lib.rs [..]-C codegen-units=5 [..]"], &["-C debuginfo"])
+ .with_stderr_line_without(&["[RUNNING] `[..] rustc --crate-name build_script_build build.rs [..]-C codegen-units=5 [..]"], &["-C debuginfo"])
+ .run();
+ p.cargo("build -vv")
+ .with_stderr_unordered(
+ "\
+[FRESH] bar [..]
+[FRESH] bdep [..]
+[FRESH] foo [..]
+[FINISHED] dev [unoptimized + debuginfo] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn profile_selection_build_release() {
+ let p = all_target_project();
+
+ // `build --release`
+ p.cargo("build --release -vv").with_stderr_unordered("\
+[COMPILING] bar [..]
+[RUNNING] `[..] rustc --crate-name bar bar/src/lib.rs [..]--crate-type lib --emit=[..]link -C opt-level=3 -C panic=abort[..]-C codegen-units=2 [..]
+[RUNNING] `[..] rustc --crate-name bar bar/src/lib.rs [..]--crate-type lib --emit=[..]link[..]-C codegen-units=6 [..]
+[COMPILING] bdep [..]
+[RUNNING] `[..] rustc --crate-name bdep bdep/src/lib.rs [..]--crate-type lib --emit=[..]link[..]-C codegen-units=6 [..]
+[COMPILING] foo [..]
+[RUNNING] `[..] rustc --crate-name build_script_build build.rs [..]--crate-type bin --emit=[..]link[..]-C codegen-units=6 [..]
+[RUNNING] `[..]/target/release/build/foo-[..]/build-script-build`
+[foo 0.0.1] foo custom build PROFILE=release DEBUG=false OPT_LEVEL=3
+[RUNNING] `[..] rustc --crate-name foo src/lib.rs [..]--crate-type lib --emit=[..]link -C opt-level=3 -C panic=abort[..]-C codegen-units=2 [..]
+[RUNNING] `[..] rustc --crate-name foo src/main.rs [..]--crate-type bin --emit=[..]link -C opt-level=3 -C panic=abort[..]-C codegen-units=2 [..]
+[FINISHED] release [optimized] [..]
+").run();
+ p.cargo("build --release -vv")
+ .with_stderr_unordered(
+ "\
+[FRESH] bar [..]
+[FRESH] bdep [..]
+[FRESH] foo [..]
+[FINISHED] release [optimized] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn profile_selection_build_all_targets() {
+ let p = all_target_project();
+ // `build`
+ // NOTES:
+ // - bdep `panic` is not set because it thinks `build.rs` is a plugin.
+ // - build_script_build is built without panic because it thinks
+ // `build.rs` is a plugin.
+ // - Benchmark dependencies are compiled in `dev` mode, which may be
+ // surprising. See issue rust-lang/cargo#4929.
+ // - We make sure that the build dependencies bar, bdep, and build.rs
+ // are built without debuginfo.
+ //
+ // - Dependency profiles:
+ // Pkg Target Profile Reason
+ // --- ------ ------- ------
+ // bar lib dev For foo-bin
+ // bar lib dev-panic For tests/benches and bdep
+ // bdep lib dev-panic For foo build.rs
+ // foo custom dev-panic
+ //
+ // - `foo` target list is:
+ // Target Profile Mode
+ // ------ ------- ----
+ // lib dev+panic build (a normal lib target)
+ // lib dev-panic build (used by tests/benches)
+ // lib dev dev
+ // test dev dev
+ // bench dev dev
+ // bin dev dev
+ // bin dev build
+ // example dev build
+ p.cargo("build --all-targets -vv")
+ .with_stderr_unordered("\
+[COMPILING] bar [..]
+[RUNNING] `[..] rustc --crate-name bar bar/src/lib.rs [..]--crate-type lib --emit=[..]link[..]-C codegen-units=1 -C debuginfo=2 [..]
+[RUNNING] `[..] rustc --crate-name bar bar/src/lib.rs [..]--crate-type lib --emit=[..]link -C panic=abort[..]-C codegen-units=1 -C debuginfo=2 [..]
+[RUNNING] `[..] rustc --crate-name bar bar/src/lib.rs [..]--crate-type lib --emit=[..]link[..]-C codegen-units=5 [..]
+[COMPILING] bdep [..]
+[RUNNING] `[..] rustc --crate-name bdep bdep/src/lib.rs [..]--crate-type lib --emit=[..]link[..]-C codegen-units=5 [..]
+[COMPILING] foo [..]
+[RUNNING] `[..] rustc --crate-name build_script_build build.rs [..]--crate-type bin --emit=[..]link[..]-C codegen-units=5 [..]
+[RUNNING] `[..]/target/debug/build/foo-[..]/build-script-build`
+[foo 0.0.1] foo custom build PROFILE=debug DEBUG=true OPT_LEVEL=0
+[RUNNING] `[..] rustc --crate-name foo src/lib.rs [..]--crate-type lib --emit=[..]link -C panic=abort[..]-C codegen-units=1 -C debuginfo=2 [..]`
+[RUNNING] `[..] rustc --crate-name foo src/lib.rs [..]--emit=[..]link[..]-C codegen-units=1 -C debuginfo=2 --test [..]`
+[RUNNING] `[..] rustc --crate-name foo src/lib.rs [..]--crate-type lib --emit=[..]link[..]-C codegen-units=1 -C debuginfo=2 [..]`
+[RUNNING] `[..] rustc --crate-name foo src/main.rs [..]--emit=[..]link[..]-C codegen-units=1 -C debuginfo=2 --test [..]`
+[RUNNING] `[..] rustc --crate-name test1 tests/test1.rs [..]--emit=[..]link[..]-C codegen-units=1 -C debuginfo=2 --test [..]`
+[RUNNING] `[..] rustc --crate-name bench1 benches/bench1.rs [..]--emit=[..]link[..]-C codegen-units=1 -C debuginfo=2 --test [..]`
+[RUNNING] `[..] rustc --crate-name foo src/main.rs [..]--crate-type bin --emit=[..]link -C panic=abort[..]-C codegen-units=1 -C debuginfo=2 [..]`
+[RUNNING] `[..] rustc --crate-name ex1 examples/ex1.rs [..]--crate-type bin --emit=[..]link -C panic=abort[..]-C codegen-units=1 -C debuginfo=2 [..]`
+[FINISHED] dev [unoptimized + debuginfo] [..]
+"
+ )
+ .with_stderr_line_without(&["[RUNNING] `[..] rustc --crate-name bar bar/src/lib.rs [..]-C codegen-units=5 [..]"], &["-C debuginfo"])
+ .with_stderr_line_without(&["[RUNNING] `[..] rustc --crate-name bdep bdep/src/lib.rs [..]-C codegen-units=5 [..]"], &["-C debuginfo"])
+ .with_stderr_line_without(&["[RUNNING] `[..] rustc --crate-name build_script_build build.rs [..]-C codegen-units=5 [..]"], &["-C debuginfo"])
+ .run();
+ p.cargo("build -vv")
+ .with_stderr_unordered(
+ "\
+[FRESH] bar [..]
+[FRESH] bdep [..]
+[FRESH] foo [..]
+[FINISHED] dev [unoptimized + debuginfo] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn profile_selection_build_all_targets_release() {
+ let p = all_target_project();
+ // `build --all-targets --release`
+ // NOTES:
+ // - bdep `panic` is not set because it thinks `build.rs` is a plugin.
+ // - bar compiled twice. It tries with and without panic, but the "is a
+ // plugin" logic is forcing it to be cleared.
+ // - build_script_build is built without panic because it thinks
+ // `build.rs` is a plugin.
+ // - build_script_build is being run two times. Once for the `dev` and
+ // `test` targets, once for the `bench` targets.
+ // TODO: "PROFILE" says debug both times, though!
+ //
+ // - Dependency profiles:
+ // Pkg Target Profile Reason
+ // --- ------ ------- ------
+ // bar lib release For foo-bin
+ // bar lib release-panic For tests/benches and bdep
+ // bdep lib release-panic For foo build.rs
+ // foo custom release-panic
+ //
+ // - `foo` target list is:
+ // Target Profile Mode
+ // ------ ------- ----
+ // lib release+panic build (a normal lib target)
+ // lib release-panic build (used by tests/benches)
+ // lib release test (bench/test de-duped)
+ // test release test
+ // bench release test
+ // bin release test (bench/test de-duped)
+ // bin release build
+ // example release build
+ p.cargo("build --all-targets --release -vv").with_stderr_unordered("\
+[COMPILING] bar [..]
+[RUNNING] `[..] rustc --crate-name bar bar/src/lib.rs [..]--crate-type lib --emit=[..]link -C opt-level=3[..]-C codegen-units=2 [..]
+[RUNNING] `[..] rustc --crate-name bar bar/src/lib.rs [..]--crate-type lib --emit=[..]link -C opt-level=3 -C panic=abort[..]-C codegen-units=2 [..]
+[RUNNING] `[..] rustc --crate-name bar bar/src/lib.rs [..]--crate-type lib --emit=[..]link[..]-C codegen-units=6 [..]
+[COMPILING] bdep [..]
+[RUNNING] `[..] rustc --crate-name bdep bdep/src/lib.rs [..]--crate-type lib --emit=[..]link[..]-C codegen-units=6 [..]
+[COMPILING] foo [..]
+[RUNNING] `[..] rustc --crate-name build_script_build build.rs [..]--crate-type bin --emit=[..]link[..]-C codegen-units=6 [..]
+[RUNNING] `[..]/target/release/build/foo-[..]/build-script-build`
+[foo 0.0.1] foo custom build PROFILE=release DEBUG=false OPT_LEVEL=3
+[RUNNING] `[..] rustc --crate-name foo src/lib.rs [..]--crate-type lib --emit=[..]link -C opt-level=3 -C panic=abort[..]-C codegen-units=2 [..]`
+[RUNNING] `[..] rustc --crate-name foo src/lib.rs [..]--emit=[..]link -C opt-level=3[..]-C codegen-units=2 --test [..]`
+[RUNNING] `[..] rustc --crate-name foo src/lib.rs [..]--crate-type lib --emit=[..]link -C opt-level=3[..]-C codegen-units=2 [..]`
+[RUNNING] `[..] rustc --crate-name test1 tests/test1.rs [..]--emit=[..]link -C opt-level=3[..]-C codegen-units=2 --test [..]`
+[RUNNING] `[..] rustc --crate-name bench1 benches/bench1.rs [..]--emit=[..]link -C opt-level=3[..]-C codegen-units=2 --test [..]`
+[RUNNING] `[..] rustc --crate-name foo src/main.rs [..]--emit=[..]link -C opt-level=3[..]-C codegen-units=2 --test [..]`
+[RUNNING] `[..] rustc --crate-name foo src/main.rs [..]--crate-type bin --emit=[..]link -C opt-level=3 -C panic=abort[..]-C codegen-units=2 [..]`
+[RUNNING] `[..] rustc --crate-name ex1 examples/ex1.rs [..]--crate-type bin --emit=[..]link -C opt-level=3 -C panic=abort[..]-C codegen-units=2 [..]`
+[FINISHED] release [optimized] [..]
+").run();
+ p.cargo("build --all-targets --release -vv")
+ .with_stderr_unordered(
+ "\
+[FRESH] bar [..]
+[FRESH] bdep [..]
+[FRESH] foo [..]
+[FINISHED] release [optimized] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn profile_selection_test() {
+ let p = all_target_project();
+ // `test`
+ // NOTES:
+ // - Dependency profiles:
+ // Pkg Target Profile Reason
+ // --- ------ ------- ------
+ // bar lib test For foo-bin
+ // bar lib test-panic For tests/benches and bdep
+ // bdep lib test-panic For foo build.rs
+ // foo custom test-panic
+ //
+ // - `foo` target list is:
+ // Target Profile Mode
+ // ------ ------- ----
+ // lib test-panic build (for tests)
+ // lib test build (for bins)
+ // lib test test
+ // test test test
+ // example test-panic build
+ // bin test test
+ // bin test build
+ //
+ p.cargo("test -vv").with_stderr_unordered("\
+[COMPILING] bar [..]
+[RUNNING] `[..] rustc --crate-name bar bar/src/lib.rs [..]--crate-type lib --emit=[..]link[..]-C codegen-units=3 -C debuginfo=2 [..]
+[RUNNING] `[..] rustc --crate-name bar bar/src/lib.rs [..]--crate-type lib --emit=[..]link[..]-C codegen-units=5 [..]
+[RUNNING] `[..] rustc --crate-name bar bar/src/lib.rs [..]--crate-type lib --emit=[..]link -C panic=abort[..]-C codegen-units=3 -C debuginfo=2 [..]
+[COMPILING] bdep [..]
+[RUNNING] `[..] rustc --crate-name bdep bdep/src/lib.rs [..]--crate-type lib --emit=[..]link[..]-C codegen-units=5 [..]
+[COMPILING] foo [..]
+[RUNNING] `[..] rustc --crate-name build_script_build build.rs [..]--crate-type bin --emit=[..]link[..]-C codegen-units=5 [..]
+[RUNNING] `[..]/target/debug/build/foo-[..]/build-script-build`
+[foo 0.0.1] foo custom build PROFILE=debug DEBUG=true OPT_LEVEL=0
+[RUNNING] `[..] rustc --crate-name foo src/lib.rs [..]--crate-type lib --emit=[..]link -C panic=abort[..]-C codegen-units=3 -C debuginfo=2 [..]
+[RUNNING] `[..] rustc --crate-name foo src/lib.rs [..]--crate-type lib --emit=[..]link[..]-C codegen-units=3 -C debuginfo=2 [..]
+[RUNNING] `[..] rustc --crate-name foo src/lib.rs [..]--emit=[..]link[..]-C codegen-units=3 -C debuginfo=2 --test [..]
+[RUNNING] `[..] rustc --crate-name test1 tests/test1.rs [..]--emit=[..]link[..]-C codegen-units=3 -C debuginfo=2 --test [..]
+[RUNNING] `[..] rustc --crate-name ex1 examples/ex1.rs [..]--crate-type bin --emit=[..]link[..]-C codegen-units=3 -C debuginfo=2 [..]
+[RUNNING] `[..] rustc --crate-name foo src/main.rs [..]--emit=[..]link[..]-C codegen-units=3 -C debuginfo=2 --test [..]
+[RUNNING] `[..] rustc --crate-name foo src/main.rs [..]--crate-type bin --emit=[..]link -C panic=abort[..]-C codegen-units=3 -C debuginfo=2 [..]
+[FINISHED] test [unoptimized + debuginfo] [..]
+[RUNNING] `[..]/deps/foo-[..]`
+[RUNNING] `[..]/deps/foo-[..]`
+[RUNNING] `[..]/deps/test1-[..]`
+[DOCTEST] foo
+[RUNNING] `rustdoc [..]--test [..]
+").run();
+ p.cargo("test -vv")
+ .with_stderr_unordered(
+ "\
+[FRESH] bar [..]
+[FRESH] bdep [..]
+[FRESH] foo [..]
+[FINISHED] test [unoptimized + debuginfo] [..]
+[RUNNING] `[..]/deps/foo-[..]`
+[RUNNING] `[..]/deps/foo-[..]`
+[RUNNING] `[..]/deps/test1-[..]`
+[DOCTEST] foo
+[RUNNING] `rustdoc [..]--test [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn profile_selection_test_release() {
+ let p = all_target_project();
+
+ // `test --release`
+ // NOTES:
+ // - Dependency profiles:
+ // Pkg Target Profile Reason
+ // --- ------ ------- ------
+ // bar lib release For foo-bin
+ // bar lib release-panic For tests/benches and bdep
+ // bdep lib release-panic For foo build.rs
+ // foo custom release-panic
+ //
+ // - `foo` target list is:
+ // Target Profile Mode
+ // ------ ------- ----
+ // lib release-panic build (for tests)
+ // lib release build (for bins)
+ // lib release test
+ // test release test
+ // example release-panic build
+ // bin release test
+ // bin release build
+ //
+ p.cargo("test --release -vv").with_stderr_unordered("\
+[COMPILING] bar [..]
+[RUNNING] `[..] rustc --crate-name bar bar/src/lib.rs [..]--crate-type lib --emit=[..]link[..]-C codegen-units=6 [..]
+[RUNNING] `[..] rustc --crate-name bar bar/src/lib.rs [..]--crate-type lib --emit=[..]link -C opt-level=3 -C panic=abort[..]-C codegen-units=2 [..]
+[RUNNING] `[..] rustc --crate-name bar bar/src/lib.rs [..]--crate-type lib --emit=[..]link[..]-C opt-level=3[..]-C codegen-units=2[..]
+[COMPILING] bdep [..]
+[RUNNING] `[..] rustc --crate-name bdep bdep/src/lib.rs [..]--crate-type lib --emit=[..]link[..]-C codegen-units=6 [..]
+[COMPILING] foo [..]
+[RUNNING] `[..] rustc --crate-name build_script_build build.rs [..]--crate-type bin --emit=[..]link[..]-C codegen-units=6 [..]
+[RUNNING] `[..]/target/release/build/foo-[..]/build-script-build`
+[foo 0.0.1] foo custom build PROFILE=release DEBUG=false OPT_LEVEL=3
+[RUNNING] `[..] rustc --crate-name foo src/lib.rs [..]--crate-type lib --emit=[..]link -C opt-level=3 -C panic=abort[..]-C codegen-units=2 [..]
+[RUNNING] `[..] rustc --crate-name foo src/lib.rs [..]--crate-type lib --emit=[..]link -C opt-level=3[..]-C codegen-units=2 [..]
+[RUNNING] `[..] rustc --crate-name foo src/lib.rs [..]--emit=[..]link -C opt-level=3[..]-C codegen-units=2 --test [..]
+[RUNNING] `[..] rustc --crate-name test1 tests/test1.rs [..]--emit=[..]link -C opt-level=3[..]-C codegen-units=2 --test [..]
+[RUNNING] `[..] rustc --crate-name foo src/main.rs [..]--emit=[..]link -C opt-level=3[..]-C codegen-units=2 --test [..]
+[RUNNING] `[..] rustc --crate-name ex1 examples/ex1.rs [..]--crate-type bin --emit=[..]link -C opt-level=3[..]-C codegen-units=2 [..]
+[RUNNING] `[..] rustc --crate-name foo src/main.rs [..]--crate-type bin --emit=[..]link -C opt-level=3 -C panic=abort[..]-C codegen-units=2 [..]
+[FINISHED] release [optimized] [..]
+[RUNNING] `[..]/deps/foo-[..]`
+[RUNNING] `[..]/deps/foo-[..]`
+[RUNNING] `[..]/deps/test1-[..]`
+[DOCTEST] foo
+[RUNNING] `rustdoc [..]--test [..]`
+").run();
+ p.cargo("test --release -vv")
+ .with_stderr_unordered(
+ "\
+[FRESH] bar [..]
+[FRESH] bdep [..]
+[FRESH] foo [..]
+[FINISHED] release [optimized] [..]
+[RUNNING] `[..]/deps/foo-[..]`
+[RUNNING] `[..]/deps/foo-[..]`
+[RUNNING] `[..]/deps/test1-[..]`
+[DOCTEST] foo
+[RUNNING] `rustdoc [..]--test [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn profile_selection_bench() {
+ let p = all_target_project();
+
+ // `bench`
+ // NOTES:
+ // - Dependency profiles:
+ // Pkg Target Profile Reason
+ // --- ------ ------- ------
+ // bar lib bench For foo-bin
+ // bar lib bench-panic For tests/benches and bdep
+ // bdep lib bench-panic For foo build.rs
+ // foo custom bench-panic
+ //
+ // - `foo` target list is:
+ // Target Profile Mode
+ // ------ ------- ----
+ // lib bench-panic build (for benches)
+ // lib bench build (for bins)
+ // lib bench test(bench)
+ // bench bench test(bench)
+ // bin bench test(bench)
+ // bin bench build
+ //
+ p.cargo("bench -vv").with_stderr_unordered("\
+[COMPILING] bar [..]
+[RUNNING] `[..] rustc --crate-name bar bar/src/lib.rs [..]--crate-type lib --emit=[..]link -C opt-level=3[..]-C codegen-units=4 [..]
+[RUNNING] `[..] rustc --crate-name bar bar/src/lib.rs [..]--crate-type lib --emit=[..]link -C opt-level=3 -C panic=abort[..]-C codegen-units=4 [..]
+[RUNNING] `[..] rustc --crate-name bar bar/src/lib.rs [..]--crate-type lib --emit=[..]link[..]-C codegen-units=6 [..]
+[COMPILING] bdep [..]
+[RUNNING] `[..] rustc --crate-name bdep bdep/src/lib.rs [..]--crate-type lib --emit=[..]link[..]-C codegen-units=6 [..]
+[COMPILING] foo [..]
+[RUNNING] `[..] rustc --crate-name build_script_build build.rs [..]--crate-type bin --emit=[..]link[..]-C codegen-units=6 [..]
+[RUNNING] `[..]target/release/build/foo-[..]/build-script-build`
+[foo 0.0.1] foo custom build PROFILE=release DEBUG=false OPT_LEVEL=3
+[RUNNING] `[..] rustc --crate-name foo src/lib.rs [..]--crate-type lib --emit=[..]link -C opt-level=3 -C panic=abort[..]-C codegen-units=4 [..]
+[RUNNING] `[..] rustc --crate-name foo src/lib.rs [..]--crate-type lib --emit=[..]link -C opt-level=3[..]-C codegen-units=4 [..]
+[RUNNING] `[..] rustc --crate-name foo src/lib.rs [..]--emit=[..]link -C opt-level=3[..]-C codegen-units=4 --test [..]
+[RUNNING] `[..] rustc --crate-name bench1 benches/bench1.rs [..]--emit=[..]link -C opt-level=3[..]-C codegen-units=4 --test [..]
+[RUNNING] `[..] rustc --crate-name foo src/main.rs [..]--emit=[..]link -C opt-level=3[..]-C codegen-units=4 --test [..]
+[RUNNING] `[..] rustc --crate-name foo src/main.rs [..]--crate-type bin --emit=[..]link -C opt-level=3 -C panic=abort[..]-C codegen-units=4 [..]
+[FINISHED] bench [optimized] [..]
+[RUNNING] `[..]/deps/foo-[..] --bench`
+[RUNNING] `[..]/deps/foo-[..] --bench`
+[RUNNING] `[..]/deps/bench1-[..] --bench`
+").run();
+ p.cargo("bench -vv")
+ .with_stderr_unordered(
+ "\
+[FRESH] bar [..]
+[FRESH] bdep [..]
+[FRESH] foo [..]
+[FINISHED] bench [optimized] [..]
+[RUNNING] `[..]/deps/foo-[..] --bench`
+[RUNNING] `[..]/deps/foo-[..] --bench`
+[RUNNING] `[..]/deps/bench1-[..] --bench`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn profile_selection_check_all_targets() {
+ let p = all_target_project();
+ // `check`
+ // NOTES:
+ // - Dependency profiles:
+ // Pkg Target Profile Action Reason
+ // --- ------ ------- ------ ------
+ // bar lib dev* link For bdep
+ // bar lib dev-panic metadata For tests/benches
+ // bar lib dev metadata For lib/bins
+ // bdep lib dev* link For foo build.rs
+ // foo custom dev* link For build.rs
+ //
+ // `*` = wants panic, but it is cleared when args are built.
+ //
+ // - foo target list is:
+ // Target Profile Mode
+ // ------ ------- ----
+ // lib dev check
+ // lib dev-panic check (for tests/benches)
+ // lib dev-panic check-test (checking lib as a unittest)
+ // example dev check
+ // test dev-panic check-test
+ // bench dev-panic check-test
+ // bin dev check
+ // bin dev-panic check-test (checking bin as a unittest)
+ //
+ p.cargo("check --all-targets -vv").with_stderr_unordered("\
+[COMPILING] bar [..]
+[RUNNING] `[..] rustc --crate-name bar bar/src/lib.rs [..]--crate-type lib --emit=[..]link[..]-C codegen-units=5 [..]
+[RUNNING] `[..] rustc --crate-name bar bar/src/lib.rs [..]--crate-type lib --emit=[..]metadata[..]-C codegen-units=1 -C debuginfo=2 [..]
+[RUNNING] `[..] rustc --crate-name bar bar/src/lib.rs [..]--crate-type lib --emit=[..]metadata -C panic=abort[..]-C codegen-units=1 -C debuginfo=2 [..]
+[COMPILING] bdep[..]
+[RUNNING] `[..] rustc --crate-name bdep bdep/src/lib.rs [..]--crate-type lib --emit=[..]link[..]-C codegen-units=5 [..]
+[COMPILING] foo [..]
+[RUNNING] `[..] rustc --crate-name build_script_build build.rs [..]--crate-type bin --emit=[..]link[..]-C codegen-units=5 [..]
+[RUNNING] `[..]target/debug/build/foo-[..]/build-script-build`
+[foo 0.0.1] foo custom build PROFILE=debug DEBUG=true OPT_LEVEL=0
+[RUNNING] `[..] rustc --crate-name foo src/lib.rs [..]--crate-type lib --emit=[..]metadata -C panic=abort[..]-C codegen-units=1 -C debuginfo=2 [..]
+[RUNNING] `[..] rustc --crate-name foo src/lib.rs [..]--crate-type lib --emit=[..]metadata[..]-C codegen-units=1 -C debuginfo=2 [..]
+[RUNNING] `[..] rustc --crate-name foo src/lib.rs [..]--emit=[..]metadata[..]-C codegen-units=1 -C debuginfo=2 --test [..]
+[RUNNING] `[..] rustc --crate-name test1 tests/test1.rs [..]--emit=[..]metadata[..]-C codegen-units=1 -C debuginfo=2 --test [..]
+[RUNNING] `[..] rustc --crate-name foo src/main.rs [..]--emit=[..]metadata[..]-C codegen-units=1 -C debuginfo=2 --test [..]
+[RUNNING] `[..] rustc --crate-name bench1 benches/bench1.rs [..]--emit=[..]metadata[..]-C codegen-units=1 -C debuginfo=2 --test [..]
+[RUNNING] `[..] rustc --crate-name ex1 examples/ex1.rs [..]--crate-type bin --emit=[..]metadata -C panic=abort[..]-C codegen-units=1 -C debuginfo=2 [..]
+[RUNNING] `[..] rustc --crate-name foo src/main.rs [..]--crate-type bin --emit=[..]metadata -C panic=abort[..]-C codegen-units=1 -C debuginfo=2 [..]
+[FINISHED] dev [unoptimized + debuginfo] [..]
+").run();
+ // Starting with Rust 1.27, rustc emits `rmeta` files for bins, so
+ // everything should be completely fresh. Previously, bins were being
+ // rechecked.
+ // See PR rust-lang/rust#49289 and issue rust-lang/cargo#3624.
+ p.cargo("check --all-targets -vv")
+ .with_stderr_unordered(
+ "\
+[FRESH] bar [..]
+[FRESH] bdep [..]
+[FRESH] foo [..]
+[FINISHED] dev [unoptimized + debuginfo] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn profile_selection_check_all_targets_release() {
+ let p = all_target_project();
+ // `check --release`
+ // See issue rust-lang/cargo#5218.
+ // This is a pretty straightforward variant of
+ // `profile_selection_check_all_targets` that uses `release` instead of
+ // `dev` for all targets.
+ p.cargo("check --all-targets --release -vv").with_stderr_unordered("\
+[COMPILING] bar [..]
+[RUNNING] `[..] rustc --crate-name bar bar/src/lib.rs [..]--crate-type lib --emit=[..]link[..]-C codegen-units=6 [..]
+[RUNNING] `[..] rustc --crate-name bar bar/src/lib.rs [..]--crate-type lib --emit=[..]metadata -C opt-level=3[..]-C codegen-units=2 [..]
+[RUNNING] `[..] rustc --crate-name bar bar/src/lib.rs [..]--crate-type lib --emit=[..]metadata -C opt-level=3 -C panic=abort[..]-C codegen-units=2 [..]
+[COMPILING] bdep[..]
+[RUNNING] `[..] rustc --crate-name bdep bdep/src/lib.rs [..]--crate-type lib --emit=[..]link [..]-C codegen-units=6 [..]
+[COMPILING] foo [..]
+[RUNNING] `[..] rustc --crate-name build_script_build build.rs [..]--crate-type bin --emit=[..]link[..]-C codegen-units=6 [..]
+[RUNNING] `[..]target/release/build/foo-[..]/build-script-build`
+[foo 0.0.1] foo custom build PROFILE=release DEBUG=false OPT_LEVEL=3
+[RUNNING] `[..] rustc --crate-name foo src/lib.rs [..]--crate-type lib --emit=[..]metadata -C opt-level=3 -C panic=abort[..]-C codegen-units=2 [..]
+[RUNNING] `[..] rustc --crate-name foo src/lib.rs [..]--crate-type lib --emit=[..]metadata -C opt-level=3[..]-C codegen-units=2 [..]
+[RUNNING] `[..] rustc --crate-name foo src/lib.rs [..]--emit=[..]metadata -C opt-level=3[..]-C codegen-units=2 --test [..]
+[RUNNING] `[..] rustc --crate-name test1 tests/test1.rs [..]--emit=[..]metadata -C opt-level=3[..]-C codegen-units=2 --test [..]
+[RUNNING] `[..] rustc --crate-name foo src/main.rs [..]--emit=[..]metadata -C opt-level=3[..]-C codegen-units=2 --test [..]
+[RUNNING] `[..] rustc --crate-name bench1 benches/bench1.rs [..]--emit=[..]metadata -C opt-level=3[..]-C codegen-units=2 --test [..]
+[RUNNING] `[..] rustc --crate-name ex1 examples/ex1.rs [..]--crate-type bin --emit=[..]metadata -C opt-level=3 -C panic=abort[..]-C codegen-units=2 [..]
+[RUNNING] `[..] rustc --crate-name foo src/main.rs [..]--crate-type bin --emit=[..]metadata -C opt-level=3 -C panic=abort[..]-C codegen-units=2 [..]
+[FINISHED] release [optimized] [..]
+").run();
+
+ p.cargo("check --all-targets --release -vv")
+ .with_stderr_unordered(
+ "\
+[FRESH] bar [..]
+[FRESH] bdep [..]
+[FRESH] foo [..]
+[FINISHED] release [optimized] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn profile_selection_check_all_targets_test() {
+ let p = all_target_project();
+
+ // `check --profile=test`
+ // - Dependency profiles:
+ // Pkg Target Profile Action Reason
+ // --- ------ ------- ------ ------
+ // bar lib test* link For bdep
+ // bar lib test-panic metadata For tests/benches
+ // bdep lib test* link For foo build.rs
+ // foo custom test* link For build.rs
+ //
+ // `*` = wants panic, but it is cleared when args are built.
+ //
+ // - foo target list is:
+ // Target Profile Mode
+ // ------ ------- ----
+ // lib test-panic check-test (for tests/benches)
+ // lib test-panic check-test (checking lib as a unittest)
+ // example test-panic check-test
+ // test test-panic check-test
+ // bench test-panic check-test
+ // bin test-panic check-test
+ //
+ p.cargo("check --all-targets --profile=test -vv").with_stderr_unordered("\
+[COMPILING] bar [..]
+[RUNNING] `[..] rustc --crate-name bar bar/src/lib.rs [..]--crate-type lib --emit=[..]link[..]-C codegen-units=5 [..]
+[RUNNING] `[..] rustc --crate-name bar bar/src/lib.rs [..]--crate-type lib --emit=[..]metadata[..]-C codegen-units=3 -C debuginfo=2 [..]
+[COMPILING] bdep[..]
+[RUNNING] `[..] rustc --crate-name bdep bdep/src/lib.rs [..]--crate-type lib --emit=[..]link[..]-C codegen-units=5 [..]
+[COMPILING] foo [..]
+[RUNNING] `[..] rustc --crate-name build_script_build build.rs [..]--crate-type bin --emit=[..]link[..]-C codegen-units=5 [..]
+[RUNNING] `[..]target/debug/build/foo-[..]/build-script-build`
+[foo 0.0.1] foo custom build PROFILE=debug DEBUG=true OPT_LEVEL=0
+[RUNNING] `[..] rustc --crate-name foo src/lib.rs [..]--crate-type lib --emit=[..]metadata[..]-C codegen-units=3 -C debuginfo=2 [..]
+[RUNNING] `[..] rustc --crate-name foo src/lib.rs [..]--emit=[..]metadata[..]-C codegen-units=3 -C debuginfo=2 --test [..]
+[RUNNING] `[..] rustc --crate-name test1 tests/test1.rs [..]--emit=[..]metadata[..]-C codegen-units=3 -C debuginfo=2 --test [..]
+[RUNNING] `[..] rustc --crate-name foo src/main.rs [..]--emit=[..]metadata[..]-C codegen-units=3 -C debuginfo=2 --test [..]
+[RUNNING] `[..] rustc --crate-name bench1 benches/bench1.rs [..]--emit=[..]metadata[..]-C codegen-units=3 -C debuginfo=2 --test [..]
+[RUNNING] `[..] rustc --crate-name ex1 examples/ex1.rs [..]--emit=[..]metadata[..]-C codegen-units=3 -C debuginfo=2 --test [..]
+[FINISHED] test [unoptimized + debuginfo] [..]
+").run();
+
+ p.cargo("check --all-targets --profile=test -vv")
+ .with_stderr_unordered(
+ "\
+[FRESH] bar [..]
+[FRESH] bdep [..]
+[FRESH] foo [..]
+[FINISHED] test [unoptimized + debuginfo] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn profile_selection_doc() {
+ let p = all_target_project();
+ // `doc`
+ // NOTES:
+ // - Dependency profiles:
+ // Pkg Target Profile Action Reason
+ // --- ------ ------- ------ ------
+ // bar lib dev* link For bdep
+ // bar lib dev metadata For rustdoc
+ // bdep lib dev* link For foo build.rs
+ // foo custom dev* link For build.rs
+ //
+ // `*` = wants panic, but it is cleared when args are built.
+ p.cargo("doc -vv").with_stderr_unordered("\
+[COMPILING] bar [..]
+[DOCUMENTING] bar [..]
+[RUNNING] `[..] rustc --crate-name bar bar/src/lib.rs [..]--crate-type lib --emit=[..]link[..]-C codegen-units=5 [..]
+[RUNNING] `rustdoc [..]--crate-name bar bar/src/lib.rs [..]
+[RUNNING] `[..] rustc --crate-name bar bar/src/lib.rs [..]--crate-type lib --emit=[..]metadata -C panic=abort[..]-C codegen-units=1 -C debuginfo=2 [..]
+[COMPILING] bdep [..]
+[RUNNING] `[..] rustc --crate-name bdep bdep/src/lib.rs [..]--crate-type lib --emit=[..]link[..]-C codegen-units=5 [..]
+[COMPILING] foo [..]
+[RUNNING] `[..] rustc --crate-name build_script_build build.rs [..]--crate-type bin --emit=[..]link[..]-C codegen-units=5 [..]
+[RUNNING] `[..]target/debug/build/foo-[..]/build-script-build`
+[foo 0.0.1] foo custom build PROFILE=debug DEBUG=true OPT_LEVEL=0
+[DOCUMENTING] foo [..]
+[RUNNING] `rustdoc [..]--crate-name foo src/lib.rs [..]
+[FINISHED] dev [unoptimized + debuginfo] [..]
+").run();
+}
diff --git a/src/tools/cargo/tests/testsuite/profiles.rs b/src/tools/cargo/tests/testsuite/profiles.rs
new file mode 100644
index 000000000..2d2646fe3
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/profiles.rs
@@ -0,0 +1,744 @@
+//! Tests for profiles.
+
+use cargo_test_support::project;
+use cargo_test_support::registry::Package;
+use std::env;
+
+#[cargo_test]
+fn profile_overrides() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "test"
+ version = "0.0.0"
+ authors = []
+
+ [profile.dev]
+ opt-level = 1
+ debug = false
+ rpath = true
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[COMPILING] test v0.0.0 ([CWD])
+[RUNNING] `rustc --crate-name test src/lib.rs [..]--crate-type lib \
+ --emit=[..]link[..]\
+ -C opt-level=1[..]\
+ -C debug-assertions=on \
+ -C metadata=[..] \
+ -C rpath \
+ --out-dir [..] \
+ -L dependency=[CWD]/target/debug/deps`
+[FINISHED] dev [optimized] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn opt_level_override_0() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "test"
+ version = "0.0.0"
+ authors = []
+
+ [profile.dev]
+ opt-level = 0
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[COMPILING] test v0.0.0 ([CWD])
+[RUNNING] `rustc --crate-name test src/lib.rs [..]--crate-type lib \
+ --emit=[..]link[..]\
+ -C debuginfo=2 \
+ -C metadata=[..] \
+ --out-dir [..] \
+ -L dependency=[CWD]/target/debug/deps`
+[FINISHED] [..] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn debug_override_1() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "test"
+ version = "0.0.0"
+ authors = []
+
+ [profile.dev]
+ debug = 1
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[COMPILING] test v0.0.0 ([CWD])
+[RUNNING] `rustc --crate-name test src/lib.rs [..]--crate-type lib \
+ --emit=[..]link[..]\
+ -C debuginfo=1 \
+ -C metadata=[..] \
+ --out-dir [..] \
+ -L dependency=[CWD]/target/debug/deps`
+[FINISHED] [..] target(s) in [..]
+",
+ )
+ .run();
+}
+
+fn check_opt_level_override(profile_level: &str, rustc_level: &str) {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+
+ name = "test"
+ version = "0.0.0"
+ authors = []
+
+ [profile.dev]
+ opt-level = {level}
+ "#,
+ level = profile_level
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("build -v")
+ .with_stderr(&format!(
+ "\
+[COMPILING] test v0.0.0 ([CWD])
+[RUNNING] `rustc --crate-name test src/lib.rs [..]--crate-type lib \
+ --emit=[..]link \
+ -C opt-level={level}[..]\
+ -C debuginfo=2 \
+ -C debug-assertions=on \
+ -C metadata=[..] \
+ --out-dir [..] \
+ -L dependency=[CWD]/target/debug/deps`
+[FINISHED] [..] target(s) in [..]
+",
+ level = rustc_level
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn opt_level_overrides() {
+ for &(profile_level, rustc_level) in &[
+ ("1", "1"),
+ ("2", "2"),
+ ("3", "3"),
+ ("\"s\"", "s"),
+ ("\"z\"", "z"),
+ ] {
+ check_opt_level_override(profile_level, rustc_level)
+ }
+}
+
+#[cargo_test]
+fn top_level_overrides_deps() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+
+ name = "test"
+ version = "0.0.0"
+ authors = []
+
+ [profile.release]
+ opt-level = 1
+ debug = true
+
+ [dependencies.foo]
+ path = "foo"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+
+ [profile.release]
+ opt-level = 0
+ debug = false
+
+ [lib]
+ name = "foo"
+ crate_type = ["dylib", "rlib"]
+ "#,
+ )
+ .file("foo/src/lib.rs", "")
+ .build();
+ p.cargo("build -v --release")
+ .with_stderr(&format!(
+ "\
+[COMPILING] foo v0.0.0 ([CWD]/foo)
+[RUNNING] `rustc --crate-name foo foo/src/lib.rs [..]\
+ --crate-type dylib --crate-type rlib \
+ --emit=[..]link \
+ -C prefer-dynamic \
+ -C opt-level=1[..]\
+ -C debuginfo=2 \
+ -C metadata=[..] \
+ --out-dir [CWD]/target/release/deps \
+ -L dependency=[CWD]/target/release/deps`
+[COMPILING] test v0.0.0 ([CWD])
+[RUNNING] `rustc --crate-name test src/lib.rs [..]--crate-type lib \
+ --emit=[..]link \
+ -C opt-level=1[..]\
+ -C debuginfo=2 \
+ -C metadata=[..] \
+ --out-dir [..] \
+ -L dependency=[CWD]/target/release/deps \
+ --extern foo=[CWD]/target/release/deps/\
+ {prefix}foo[..]{suffix} \
+ --extern foo=[CWD]/target/release/deps/libfoo.rlib`
+[FINISHED] release [optimized + debuginfo] target(s) in [..]
+",
+ prefix = env::consts::DLL_PREFIX,
+ suffix = env::consts::DLL_SUFFIX
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn profile_in_non_root_manifest_triggers_a_warning() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [workspace]
+ members = ["bar"]
+
+ [profile.dev]
+ debug = false
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+ workspace = ".."
+
+ [profile.dev]
+ opt-level = 1
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build -v")
+ .cwd("bar")
+ .with_stderr(
+ "\
+[WARNING] profiles for the non root package will be ignored, specify profiles at the workspace root:
+package: [..]
+workspace: [..]
+[COMPILING] bar v0.1.0 ([..])
+[RUNNING] `rustc [..]`
+[FINISHED] dev [unoptimized] target(s) in [..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn profile_in_virtual_manifest_works() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar"]
+
+ [profile.dev]
+ opt-level = 1
+ debug = false
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+ workspace = ".."
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build -v")
+ .cwd("bar")
+ .with_stderr(
+ "\
+[COMPILING] bar v0.1.0 ([..])
+[RUNNING] `rustc [..]`
+[FINISHED] dev [optimized] target(s) in [..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn profile_lto_string_bool_dev() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [profile.dev]
+ lto = "true"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[ROOT]/foo/Cargo.toml`
+
+Caused by:
+ `lto` setting of string `\"true\"` for `dev` profile is not a valid setting, \
+must be a boolean (`true`/`false`) or a string (`\"thin\"`/`\"fat\"`/`\"off\"`) or omitted.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn profile_panic_test_bench() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [profile.test]
+ panic = "abort"
+
+ [profile.bench]
+ panic = "abort"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build")
+ .with_stderr_contains(
+ "\
+[WARNING] `panic` setting is ignored for `bench` profile
+[WARNING] `panic` setting is ignored for `test` profile
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn profile_doc_deprecated() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [profile.doc]
+ opt-level = 0
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build")
+ .with_stderr_contains("[WARNING] profile `doc` is deprecated and has no effect")
+ .run();
+}
+
+#[cargo_test]
+fn panic_unwind_does_not_build_twice() {
+ // Check for a bug where `lib` was built twice, once with panic set and
+ // once without. Since "unwind" is the default, they are the same and
+ // should only be built once.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [profile.dev]
+ panic = "unwind"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("src/main.rs", "fn main() {}")
+ .file("tests/t1.rs", "")
+ .build();
+
+ p.cargo("test -v --tests --no-run")
+ .with_stderr_unordered(
+ "\
+[COMPILING] foo [..]
+[RUNNING] `rustc --crate-name foo src/lib.rs [..]--crate-type lib [..]
+[RUNNING] `rustc --crate-name foo src/lib.rs [..] --test [..]
+[RUNNING] `rustc --crate-name foo src/main.rs [..]--crate-type bin [..]
+[RUNNING] `rustc --crate-name foo src/main.rs [..] --test [..]
+[RUNNING] `rustc --crate-name t1 tests/t1.rs [..]
+[FINISHED] [..]
+[EXECUTABLE] `[..]/target/debug/deps/t1-[..][EXE]`
+[EXECUTABLE] `[..]/target/debug/deps/foo-[..][EXE]`
+[EXECUTABLE] `[..]/target/debug/deps/foo-[..][EXE]`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn debug_0_report() {
+ // The finished line handles 0 correctly.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [profile.dev]
+ debug = 0
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.1.0 [..]
+[RUNNING] `rustc --crate-name foo src/lib.rs [..]-C debuginfo=0 [..]
+[FINISHED] dev [unoptimized] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn thin_lto_works() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "top"
+ version = "0.5.0"
+ authors = []
+
+ [profile.release]
+ lto = 'thin'
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build --release -v")
+ .with_stderr(
+ "\
+[COMPILING] top [..]
+[RUNNING] `rustc [..] -C lto=thin [..]`
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn strip_works() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [profile.release]
+ strip = 'symbols'
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build --release -v")
+ .with_stderr(
+ "\
+[COMPILING] foo [..]
+[RUNNING] `rustc [..] -C strip=symbols [..]`
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn strip_passes_unknown_option_to_rustc() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [profile.release]
+ strip = 'unknown'
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build --release -v")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+[COMPILING] foo [..]
+[RUNNING] `rustc [..] -C strip=unknown [..]`
+error: incorrect value `unknown` for [..] `strip` [..] was expected
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn strip_accepts_true_to_strip_symbols() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [profile.release]
+ strip = true
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build --release -v")
+ .with_stderr(
+ "\
+[COMPILING] foo [..]
+[RUNNING] `rustc [..] -C strip=symbols [..]`
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn strip_accepts_false_to_disable_strip() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [profile.release]
+ strip = false
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build --release -v")
+ .with_stderr_does_not_contain("-C strip")
+ .run();
+}
+
+#[cargo_test]
+fn rustflags_works() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ cargo-features = ["profile-rustflags"]
+
+ [profile.dev]
+ rustflags = ["-C", "link-dead-code=yes"]
+
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build -v")
+ .masquerade_as_nightly_cargo(&["profile-rustflags"])
+ .with_stderr(
+ "\
+[COMPILING] foo [..]
+[RUNNING] `rustc --crate-name foo [..] -C link-dead-code=yes [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rustflags_works_with_env() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ cargo-features = ["profile-rustflags"]
+
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build -v")
+ .env("CARGO_PROFILE_DEV_RUSTFLAGS", "-C link-dead-code=yes")
+ .masquerade_as_nightly_cargo(&["profile-rustflags"])
+ .with_stderr(
+ "\
+[COMPILING] foo [..]
+[RUNNING] `rustc --crate-name foo [..] -C link-dead-code=yes [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rustflags_requires_cargo_feature() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [profile.dev]
+ rustflags = ["-C", "link-dead-code=yes"]
+
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build -v")
+ .masquerade_as_nightly_cargo(&["profile-rustflags"])
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[CWD]/Cargo.toml`
+
+Caused by:
+ feature `profile-rustflags` is required
+
+ The package requires the Cargo feature called `profile-rustflags`, but that feature is \
+ not stabilized in this version of Cargo (1.[..]).
+ Consider adding `cargo-features = [\"profile-rustflags\"]` to the top of Cargo.toml \
+ (above the [package] table) to tell Cargo you are opting in to use this unstable feature.
+ See https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#profile-rustflags-option \
+ for more information about the status of this feature.
+",
+ )
+ .run();
+
+ Package::new("bar", "1.0.0").publish();
+ p.change_file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies]
+ bar = "1.0"
+
+ [profile.dev.package.bar]
+ rustflags = ["-C", "link-dead-code=yes"]
+ "#,
+ );
+ p.cargo("check")
+ .masquerade_as_nightly_cargo(&["profile-rustflags"])
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[ROOT]/foo/Cargo.toml`
+
+Caused by:
+ feature `profile-rustflags` is required
+
+ The package requires the Cargo feature called `profile-rustflags`, but that feature is \
+ not stabilized in this version of Cargo (1.[..]).
+ Consider adding `cargo-features = [\"profile-rustflags\"]` to the top of Cargo.toml \
+ (above the [package] table) to tell Cargo you are opting in to use this unstable feature.
+ See https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#profile-rustflags-option \
+ for more information about the status of this feature.
+",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/progress.rs b/src/tools/cargo/tests/testsuite/progress.rs
new file mode 100644
index 000000000..20870a394
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/progress.rs
@@ -0,0 +1,159 @@
+//! Tests for progress bar.
+
+use cargo_test_support::project;
+use cargo_test_support::registry::Package;
+
+#[cargo_test]
+fn bad_progress_config_unknown_when() {
+ let p = project()
+ .file(
+ ".cargo/config",
+ r#"
+ [term]
+ progress = { when = 'unknown' }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] error in [..].cargo/config: \
+could not load config key `term.progress.when`
+
+Caused by:
+ unknown variant `unknown`, expected one of `auto`, `never`, `always`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bad_progress_config_missing_width() {
+ let p = project()
+ .file(
+ ".cargo/config",
+ r#"
+ [term]
+ progress = { when = 'always' }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] \"always\" progress requires a `width` key
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bad_progress_config_missing_when() {
+ let p = project()
+ .file(
+ ".cargo/config",
+ r#"
+ [term]
+ progress = { width = 1000 }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: missing field `when`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn always_shows_progress() {
+ const N: usize = 3;
+ let mut deps = String::new();
+ for i in 1..=N {
+ Package::new(&format!("dep{}", i), "1.0.0").publish();
+ deps.push_str(&format!("dep{} = \"1.0\"\n", i));
+ }
+
+ let p = project()
+ .file(
+ ".cargo/config",
+ r#"
+ [term]
+ progress = { when = 'always', width = 100 }
+ "#,
+ )
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ {}
+ "#,
+ deps
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_stderr_contains("[DOWNLOADING] [..] crates [..]")
+ .with_stderr_contains("[..][DOWNLOADED] 3 crates ([..]) in [..]")
+ .with_stderr_contains("[BUILDING] [..] [..]/4: [..]")
+ .run();
+}
+
+#[cargo_test]
+fn never_progress() {
+ const N: usize = 3;
+ let mut deps = String::new();
+ for i in 1..=N {
+ Package::new(&format!("dep{}", i), "1.0.0").publish();
+ deps.push_str(&format!("dep{} = \"1.0\"\n", i));
+ }
+
+ let p = project()
+ .file(
+ ".cargo/config",
+ r#"
+ [term]
+ progress = { when = 'never' }
+ "#,
+ )
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ {}
+ "#,
+ deps
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_stderr_does_not_contain("[DOWNLOADING] [..] crates [..]")
+ .with_stderr_does_not_contain("[..][DOWNLOADED] 3 crates ([..]) in [..]")
+ .with_stderr_does_not_contain("[BUILDING] [..] [..]/4: [..]")
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/pub_priv.rs b/src/tools/cargo/tests/testsuite/pub_priv.rs
new file mode 100644
index 000000000..83c6a49f8
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/pub_priv.rs
@@ -0,0 +1,199 @@
+//! Tests for public/private dependencies.
+
+use cargo_test_support::project;
+use cargo_test_support::registry::Package;
+
+#[cargo_test(nightly, reason = "exported_private_dependencies lint is unstable")]
+fn exported_priv_warning() {
+ Package::new("priv_dep", "0.1.0")
+ .file("src/lib.rs", "pub struct FromPriv;")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ cargo-features = ["public-dependency"]
+
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies]
+ priv_dep = "0.1.0"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "
+ extern crate priv_dep;
+ pub fn use_priv(_: priv_dep::FromPriv) {}
+ ",
+ )
+ .build();
+
+ p.cargo("check --message-format=short")
+ .masquerade_as_nightly_cargo(&["public-dependency"])
+ .with_stderr_contains(
+ "\
+src/lib.rs:3:13: warning: type `[..]FromPriv` from private dependency 'priv_dep' in public interface
+",
+ )
+ .run()
+}
+
+#[cargo_test(nightly, reason = "exported_private_dependencies lint is unstable")]
+fn exported_pub_dep() {
+ Package::new("pub_dep", "0.1.0")
+ .file("src/lib.rs", "pub struct FromPub;")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ cargo-features = ["public-dependency"]
+
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies]
+ pub_dep = {version = "0.1.0", public = true}
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "
+ extern crate pub_dep;
+ pub fn use_pub(_: pub_dep::FromPub) {}
+ ",
+ )
+ .build();
+
+ p.cargo("check --message-format=short")
+ .masquerade_as_nightly_cargo(&["public-dependency"])
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] pub_dep v0.1.0 ([..])
+[CHECKING] pub_dep v0.1.0
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run()
+}
+
+#[cargo_test]
+pub fn requires_nightly_cargo() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ cargo-features = ["public-dependency"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check --message-format=short")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[..]`
+
+Caused by:
+ the cargo feature `public-dependency` requires a nightly version of Cargo, but this is the `stable` channel
+ See https://doc.rust-lang.org/book/appendix-07-nightly-rust.html for more information about Rust release channels.
+ See https://doc.rust-lang.org/[..]cargo/reference/unstable.html#public-dependency for more information about using this feature.
+"
+ )
+ .run()
+}
+
+#[cargo_test]
+fn requires_feature() {
+ Package::new("pub_dep", "0.1.0")
+ .file("src/lib.rs", "")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies]
+ pub_dep = { version = "0.1.0", public = true }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check --message-format=short")
+ .masquerade_as_nightly_cargo(&["public-dependency"])
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[..]`
+
+Caused by:
+ feature `public-dependency` is required
+
+ The package requires the Cargo feature called `public-dependency`, \
+ but that feature is not stabilized in this version of Cargo (1.[..]).
+ Consider adding `cargo-features = [\"public-dependency\"]` to the top of Cargo.toml \
+ (above the [package] table) to tell Cargo you are opting in to use this unstable feature.
+ See https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#public-dependency \
+ for more information about the status of this feature.
+",
+ )
+ .run()
+}
+
+#[cargo_test]
+fn pub_dev_dependency() {
+ Package::new("pub_dep", "0.1.0")
+ .file("src/lib.rs", "pub struct FromPub;")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ cargo-features = ["public-dependency"]
+
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dev-dependencies]
+ pub_dep = {version = "0.1.0", public = true}
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "
+ extern crate pub_dep;
+ pub fn use_pub(_: pub_dep::FromPub) {}
+ ",
+ )
+ .build();
+
+ p.cargo("check --message-format=short")
+ .masquerade_as_nightly_cargo(&["public-dependency"])
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[..]`
+
+Caused by:
+ 'public' specifier can only be used on regular dependencies, not Development dependencies
+",
+ )
+ .run()
+}
diff --git a/src/tools/cargo/tests/testsuite/publish.rs b/src/tools/cargo/tests/testsuite/publish.rs
new file mode 100644
index 000000000..00a79fe73
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/publish.rs
@@ -0,0 +1,2951 @@
+//! Tests for the `cargo publish` command.
+
+use cargo_test_support::git::{self, repo};
+use cargo_test_support::paths;
+use cargo_test_support::registry::{self, Package, RegistryBuilder, Response};
+use cargo_test_support::{basic_manifest, no_such_file_err_msg, project, publish};
+use std::fs;
+use std::sync::{Arc, Mutex};
+
+const CLEAN_FOO_JSON: &str = r#"
+ {
+ "authors": [],
+ "badges": {},
+ "categories": [],
+ "deps": [],
+ "description": "foo",
+ "documentation": "foo",
+ "features": {},
+ "homepage": "foo",
+ "keywords": [],
+ "license": "MIT",
+ "license_file": null,
+ "links": null,
+ "name": "foo",
+ "readme": null,
+ "readme_file": null,
+ "repository": "foo",
+ "vers": "0.0.1"
+ }
+"#;
+
+fn validate_upload_foo() {
+ publish::validate_upload(
+ r#"
+ {
+ "authors": [],
+ "badges": {},
+ "categories": [],
+ "deps": [],
+ "description": "foo",
+ "documentation": null,
+ "features": {},
+ "homepage": null,
+ "keywords": [],
+ "license": "MIT",
+ "license_file": null,
+ "links": null,
+ "name": "foo",
+ "readme": null,
+ "readme_file": null,
+ "repository": null,
+ "vers": "0.0.1"
+ }
+ "#,
+ "foo-0.0.1.crate",
+ &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"],
+ );
+}
+
+fn validate_upload_li() {
+ publish::validate_upload(
+ r#"
+ {
+ "authors": [],
+ "badges": {},
+ "categories": [],
+ "deps": [],
+ "description": "li",
+ "documentation": null,
+ "features": {},
+ "homepage": null,
+ "keywords": [],
+ "license": "MIT",
+ "license_file": null,
+ "links": null,
+ "name": "li",
+ "readme": null,
+ "readme_file": null,
+ "repository": null,
+ "vers": "0.0.1"
+ }
+ "#,
+ "li-0.0.1.crate",
+ &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"],
+ );
+}
+
+#[cargo_test]
+fn simple() {
+ let registry = RegistryBuilder::new().http_api().http_index().build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("publish --no-verify")
+ .replace_crates_io(registry.index_url())
+ .with_stderr(
+ "\
+[UPDATING] crates.io index
+[WARNING] manifest has no documentation, [..]
+See [..]
+[PACKAGING] foo v0.0.1 ([CWD])
+[PACKAGED] [..] files, [..] ([..] compressed)
+[UPLOADING] foo v0.0.1 ([CWD])
+[UPLOADED] foo v0.0.1 to registry `crates-io`
+note: Waiting for `foo v0.0.1` to be available at registry `crates-io`.
+You may press ctrl-c to skip waiting; the crate should be available shortly.
+[PUBLISHED] foo v0.0.1 at registry `crates-io`
+",
+ )
+ .run();
+
+ validate_upload_foo();
+}
+
+// Check that the `token` key works at the root instead of under a
+// `[registry]` table.
+#[cargo_test]
+fn simple_publish_with_http() {
+ let _reg = registry::RegistryBuilder::new()
+ .http_api()
+ .token(registry::Token::Plaintext("sekrit".to_string()))
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("publish --no-verify --token sekrit --registry dummy-registry")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[WARNING] manifest has no documentation, [..]
+See [..]
+[PACKAGING] foo v0.0.1 ([CWD])
+[PACKAGED] [..] files, [..] ([..] compressed)
+[UPLOADING] foo v0.0.1 ([CWD])
+[UPLOADED] foo v0.0.1 to registry `dummy-registry`
+note: Waiting for `foo v0.0.1` to be available at registry `dummy-registry`.
+You may press ctrl-c to skip waiting; the crate should be available shortly.
+[PUBLISHED] foo v0.0.1 at registry `dummy-registry`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn simple_publish_with_asymmetric() {
+ let _reg = registry::RegistryBuilder::new()
+ .http_api()
+ .http_index()
+ .alternative_named("dummy-registry")
+ .token(registry::Token::rfc_key())
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("publish --no-verify -Zregistry-auth --registry dummy-registry")
+ .masquerade_as_nightly_cargo(&["registry-auth"])
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[WARNING] manifest has no documentation, [..]
+See [..]
+[PACKAGING] foo v0.0.1 ([CWD])
+[PACKAGED] [..] files, [..] ([..] compressed)
+[UPLOADING] foo v0.0.1 ([CWD])
+[UPLOADED] foo v0.0.1 to registry `dummy-registry`
+note: Waiting for `foo v0.0.1` to be available at registry `dummy-registry`.
+You may press ctrl-c to skip waiting; the crate should be available shortly.
+[PUBLISHED] foo v0.0.1 at registry `dummy-registry`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn old_token_location() {
+ // `publish` generally requires a remote registry
+ let registry = registry::RegistryBuilder::new().http_api().build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ let credentials = paths::home().join(".cargo/credentials.toml");
+ fs::remove_file(&credentials).unwrap();
+
+ // Verify can't publish without a token.
+ p.cargo("publish --no-verify")
+ .replace_crates_io(registry.index_url())
+ .with_status(101)
+ .with_stderr_contains(
+ "[ERROR] no token found, \
+ please run `cargo login`",
+ )
+ .run();
+
+ fs::write(&credentials, format!(r#"token = "{}""#, registry.token())).unwrap();
+
+ p.cargo("publish --no-verify")
+ .replace_crates_io(registry.index_url())
+ .with_stderr(
+ "\
+[UPDATING] crates.io index
+[WARNING] manifest has no documentation, [..]
+See [..]
+[PACKAGING] foo v0.0.1 ([CWD])
+[PACKAGED] [..] files, [..] ([..] compressed)
+[UPLOADING] foo v0.0.1 ([CWD])
+[UPLOADED] foo v0.0.1 to registry `crates-io`
+note: Waiting [..]
+You may press ctrl-c [..]
+[PUBLISHED] foo v0.0.1 [..]
+",
+ )
+ .run();
+
+ // Skip `validate_upload_foo` as we just cared we got far enough for verify the token behavior.
+ // Other tests will verify the endpoint gets the right payload.
+}
+
+#[cargo_test]
+fn simple_with_index() {
+ // `publish` generally requires a remote registry
+ let registry = registry::RegistryBuilder::new().http_api().build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("publish --no-verify")
+ .arg("--token")
+ .arg(registry.token())
+ .arg("--index")
+ .arg(registry.index_url().as_str())
+ .with_stderr(
+ "\
+[..]
+[..]
+[..]
+[..]
+[..]
+[UPLOADING] foo v0.0.1 ([CWD])
+[UPLOADED] foo v0.0.1 to registry `[ROOT]/registry`
+note: Waiting [..]
+You may press ctrl-c [..]
+[PUBLISHED] foo v0.0.1 [..]
+",
+ )
+ .run();
+
+ // Skip `validate_upload_foo` as we just cared we got far enough for verify the VCS behavior.
+ // Other tests will verify the endpoint gets the right payload.
+}
+
+#[cargo_test]
+fn git_deps() {
+ // Use local registry for faster test times since no publish will occur
+ let registry = registry::init();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+
+ [dependencies.foo]
+ git = "git://path/to/nowhere"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("publish -v --no-verify")
+ .replace_crates_io(registry.index_url())
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] [..] index
+[ERROR] all dependencies must have a version specified when publishing.
+dependency `foo` does not specify a version
+Note: The published dependency will use the version from crates.io,
+the `git` specification will be removed from the dependency declaration.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn path_dependency_no_version() {
+ // Use local registry for faster test times since no publish will occur
+ let registry = registry::init();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+
+ [dependencies.bar]
+ path = "bar"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("publish")
+ .replace_crates_io(registry.index_url())
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] [..] index
+[ERROR] all dependencies must have a version specified when publishing.
+dependency `bar` does not specify a version
+Note: The published dependency will use the version from crates.io,
+the `path` specification will be removed from the dependency declaration.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn unpublishable_crate() {
+ // Use local registry for faster test times since no publish will occur
+ let registry = registry::init();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ publish = false
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("publish --index")
+ .arg(registry.index_url().as_str())
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] `foo` cannot be published.
+`package.publish` is set to `false` or an empty list in Cargo.toml and prevents publishing.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn dont_publish_dirty() {
+ // Use local registry for faster test times since no publish will occur
+ let registry = registry::init();
+
+ let p = project().file("bar", "").build();
+
+ let _ = git::repo(&paths::root().join("foo"))
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ documentation = "foo"
+ homepage = "foo"
+ repository = "foo"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("publish")
+ .replace_crates_io(registry.index_url())
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] crates.io index
+error: 1 files in the working directory contain changes that were not yet \
+committed into git:
+
+bar
+
+to proceed despite this and include the uncommitted changes, pass the `--allow-dirty` flag
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn publish_clean() {
+ // `publish` generally requires a remote registry
+ let registry = registry::RegistryBuilder::new().http_api().build();
+
+ let p = project().build();
+
+ let _ = repo(&paths::root().join("foo"))
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ documentation = "foo"
+ homepage = "foo"
+ repository = "foo"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("publish")
+ .replace_crates_io(registry.index_url())
+ .with_stderr(
+ "\
+[..]
+[..]
+[VERIFYING] foo v0.0.1 ([CWD])
+[..]
+[..]
+[..]
+[UPLOADING] foo v0.0.1 ([CWD])
+[UPLOADED] foo v0.0.1 to registry `crates-io`
+note: Waiting [..]
+You may press ctrl-c to skip waiting; the crate should be available shortly.
+[PUBLISHED] foo v0.0.1 [..]
+",
+ )
+ .run();
+
+ // Skip `validate_upload_foo_clean` as we just cared we got far enough for verify the VCS behavior.
+ // Other tests will verify the endpoint gets the right payload.
+}
+
+#[cargo_test]
+fn publish_in_sub_repo() {
+ // `publish` generally requires a remote registry
+ let registry = registry::RegistryBuilder::new().http_api().build();
+
+ let p = project().no_manifest().file("baz", "").build();
+
+ let _ = repo(&paths::root().join("foo"))
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ documentation = "foo"
+ homepage = "foo"
+ repository = "foo"
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("publish")
+ .replace_crates_io(registry.index_url())
+ .cwd("bar")
+ .with_stderr(
+ "\
+[..]
+[..]
+[VERIFYING] foo v0.0.1 ([CWD])
+[..]
+[..]
+[..]
+[UPLOADING] foo v0.0.1 ([CWD])
+[UPLOADED] foo v0.0.1 to registry `crates-io`
+note: Waiting [..]
+You may press ctrl-c [..]
+[PUBLISHED] foo v0.0.1 [..]
+",
+ )
+ .run();
+
+ // Skip `validate_upload_foo_clean` as we just cared we got far enough for verify the VCS behavior.
+ // Other tests will verify the endpoint gets the right payload.
+}
+
+#[cargo_test]
+fn publish_when_ignored() {
+ // `publish` generally requires a remote registry
+ let registry = registry::RegistryBuilder::new().http_api().build();
+
+ let p = project().file("baz", "").build();
+
+ let _ = repo(&paths::root().join("foo"))
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ documentation = "foo"
+ homepage = "foo"
+ repository = "foo"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(".gitignore", "baz")
+ .build();
+
+ p.cargo("publish")
+ .replace_crates_io(registry.index_url())
+ .with_stderr(
+ "\
+[..]
+[..]
+[VERIFYING] foo v0.0.1 ([CWD])
+[..]
+[..]
+[..]
+[UPLOADING] foo v0.0.1 ([CWD])
+[UPLOADED] foo v0.0.1 to registry `crates-io`
+note: Waiting [..]
+You may press ctrl-c [..]
+[PUBLISHED] foo v0.0.1 [..]
+",
+ )
+ .run();
+
+ // Skip `validate_upload` as we just cared we got far enough for verify the VCS behavior.
+ // Other tests will verify the endpoint gets the right payload.
+}
+
+#[cargo_test]
+fn ignore_when_crate_ignored() {
+ // `publish` generally requires a remote registry
+ let registry = registry::RegistryBuilder::new().http_api().build();
+
+ let p = project().no_manifest().file("bar/baz", "").build();
+
+ let _ = repo(&paths::root().join("foo"))
+ .file(".gitignore", "bar")
+ .nocommit_file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ documentation = "foo"
+ homepage = "foo"
+ repository = "foo"
+ "#,
+ )
+ .nocommit_file("bar/src/main.rs", "fn main() {}");
+ p.cargo("publish")
+ .replace_crates_io(registry.index_url())
+ .cwd("bar")
+ .with_stderr(
+ "\
+[..]
+[..]
+[VERIFYING] foo v0.0.1 ([CWD])
+[..]
+[..]
+[..]
+[UPLOADING] foo v0.0.1 ([CWD])
+[UPLOADED] foo v0.0.1 to registry `crates-io`
+note: Waiting [..]
+You may press ctrl-c [..]
+[PUBLISHED] foo v0.0.1 [..]
+",
+ )
+ .run();
+
+ // Skip `validate_upload` as we just cared we got far enough for verify the VCS behavior.
+ // Other tests will verify the endpoint gets the right payload.
+}
+
+#[cargo_test]
+fn new_crate_rejected() {
+ // Use local registry for faster test times since no publish will occur
+ let registry = registry::init();
+
+ let p = project().file("baz", "").build();
+
+ let _ = repo(&paths::root().join("foo"))
+ .nocommit_file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ documentation = "foo"
+ homepage = "foo"
+ repository = "foo"
+ "#,
+ )
+ .nocommit_file("src/main.rs", "fn main() {}");
+ p.cargo("publish")
+ .replace_crates_io(registry.index_url())
+ .with_status(101)
+ .with_stderr_contains(
+ "[ERROR] 3 files in the working directory contain \
+ changes that were not yet committed into git:",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn dry_run() {
+ // Use local registry for faster test times since no publish will occur
+ let registry = registry::init();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("publish --dry-run --index")
+ .arg(registry.index_url().as_str())
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[WARNING] manifest has no documentation, [..]
+See [..]
+[PACKAGING] foo v0.0.1 ([CWD])
+[VERIFYING] foo v0.0.1 ([CWD])
+[COMPILING] foo v0.0.1 [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[PACKAGED] [..] files, [..] ([..] compressed)
+[UPLOADING] foo v0.0.1 ([CWD])
+[WARNING] aborting upload due to dry run
+",
+ )
+ .run();
+
+ // Ensure the API request wasn't actually made
+ assert!(registry::api_path().join("api/v1/crates").exists());
+ assert!(!registry::api_path().join("api/v1/crates/new").exists());
+}
+
+#[cargo_test]
+fn registry_not_in_publish_list() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ publish = [
+ "test"
+ ]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("publish")
+ .arg("--registry")
+ .arg("alternative")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] `foo` cannot be published.
+The registry `alternative` is not listed in the `package.publish` value in Cargo.toml.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn publish_empty_list() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ publish = []
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("publish --registry alternative")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] `foo` cannot be published.
+`package.publish` is set to `false` or an empty list in Cargo.toml and prevents publishing.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn publish_allowed_registry() {
+ let _registry = RegistryBuilder::new()
+ .http_api()
+ .http_index()
+ .alternative()
+ .build();
+
+ let p = project().build();
+
+ let _ = repo(&paths::root().join("foo"))
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ documentation = "foo"
+ homepage = "foo"
+ repository = "foo"
+ publish = ["alternative"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("publish --registry alternative")
+ .with_stderr(
+ "\
+[..]
+[..]
+[VERIFYING] foo v0.0.1 ([CWD])
+[..]
+[..]
+[..]
+[UPLOADING] foo v0.0.1 ([CWD])
+[UPLOADED] foo v0.0.1 to registry `alternative`
+note: Waiting for `foo v0.0.1` to be available at registry `alternative`.
+You may press ctrl-c [..]
+[PUBLISHED] foo v0.0.1 at registry `alternative`
+",
+ )
+ .run();
+
+ publish::validate_alt_upload(
+ CLEAN_FOO_JSON,
+ "foo-0.0.1.crate",
+ &[
+ "Cargo.lock",
+ "Cargo.toml",
+ "Cargo.toml.orig",
+ "src/main.rs",
+ ".cargo_vcs_info.json",
+ ],
+ );
+}
+
+#[cargo_test]
+fn publish_implicitly_to_only_allowed_registry() {
+ let _registry = RegistryBuilder::new()
+ .http_api()
+ .http_index()
+ .alternative()
+ .build();
+
+ let p = project().build();
+
+ let _ = repo(&paths::root().join("foo"))
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ documentation = "foo"
+ homepage = "foo"
+ repository = "foo"
+ publish = ["alternative"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("publish")
+ .with_stderr(
+ "\
+[NOTE] Found `alternative` as only allowed registry. Publishing to it automatically.
+[UPDATING] `alternative` index
+[..]
+[VERIFYING] foo v0.0.1 ([CWD])
+[..]
+[..]
+[..]
+[UPLOADING] foo v0.0.1 ([CWD])
+[UPLOADED] foo v0.0.1 to registry `alternative`
+note: Waiting [..]
+You may press ctrl-c [..]
+[PUBLISHED] foo v0.0.1 [..]
+",
+ )
+ .run();
+
+ publish::validate_alt_upload(
+ CLEAN_FOO_JSON,
+ "foo-0.0.1.crate",
+ &[
+ "Cargo.lock",
+ "Cargo.toml",
+ "Cargo.toml.orig",
+ "src/main.rs",
+ ".cargo_vcs_info.json",
+ ],
+ );
+}
+
+#[cargo_test]
+fn publish_fail_with_no_registry_specified() {
+ let p = project().build();
+
+ let _ = repo(&paths::root().join("foo"))
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ documentation = "foo"
+ homepage = "foo"
+ repository = "foo"
+ publish = ["alternative", "test"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("publish")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] `foo` cannot be published.
+The registry `crates-io` is not listed in the `package.publish` value in Cargo.toml.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn block_publish_no_registry() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ publish = []
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("publish --registry alternative")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] `foo` cannot be published.
+`package.publish` is set to `false` or an empty list in Cargo.toml and prevents publishing.
+",
+ )
+ .run();
+}
+
+// Explicitly setting `crates-io` in the publish list.
+#[cargo_test]
+fn publish_with_crates_io_explicit() {
+ // `publish` generally requires a remote registry
+ let registry = registry::RegistryBuilder::new().http_api().build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ publish = ["crates-io"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("publish --registry alternative")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] `foo` cannot be published.
+The registry `alternative` is not listed in the `package.publish` value in Cargo.toml.
+",
+ )
+ .run();
+
+ p.cargo("publish")
+ .replace_crates_io(registry.index_url())
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[WARNING] [..]
+[..]
+[PACKAGING] [..]
+[VERIFYING] foo v0.0.1 ([CWD])
+[..]
+[..]
+[..]
+[UPLOADING] foo v0.0.1 ([CWD])
+[UPLOADED] foo v0.0.1 to registry `crates-io`
+note: Waiting [..]
+You may press ctrl-c [..]
+[PUBLISHED] foo v0.0.1 [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn publish_with_select_features() {
+ // `publish` generally requires a remote registry
+ let registry = registry::RegistryBuilder::new().http_api().build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+
+ [features]
+ required = []
+ optional = []
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ "#[cfg(not(feature = \"required\"))]
+ compile_error!(\"This crate requires `required` feature!\");
+ fn main() {}",
+ )
+ .build();
+
+ p.cargo("publish --features required")
+ .replace_crates_io(registry.index_url())
+ .with_stderr(
+ "\
+[..]
+[..]
+[..]
+[..]
+[VERIFYING] foo v0.0.1 ([CWD])
+[..]
+[..]
+[..]
+[UPLOADING] foo v0.0.1 ([CWD])
+[UPLOADED] foo v0.0.1 to registry `crates-io`
+note: Waiting [..]
+You may press ctrl-c [..]
+[PUBLISHED] foo v0.0.1 [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn publish_with_all_features() {
+ // `publish` generally requires a remote registry
+ let registry = registry::RegistryBuilder::new().http_api().build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+
+ [features]
+ required = []
+ optional = []
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ "#[cfg(not(feature = \"required\"))]
+ compile_error!(\"This crate requires `required` feature!\");
+ fn main() {}",
+ )
+ .build();
+
+ p.cargo("publish --all-features")
+ .replace_crates_io(registry.index_url())
+ .with_stderr(
+ "\
+[..]
+[..]
+[..]
+[..]
+[VERIFYING] foo v0.0.1 ([CWD])
+[..]
+[..]
+[..]
+[UPLOADING] foo v0.0.1 ([CWD])
+[UPLOADED] foo v0.0.1 to registry `crates-io`
+note: Waiting [..]
+You may press ctrl-c [..]
+[PUBLISHED] foo v0.0.1 [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn publish_with_no_default_features() {
+ // Use local registry for faster test times since no publish will occur
+ let registry = registry::init();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+
+ [features]
+ default = ["required"]
+ required = []
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ "#[cfg(not(feature = \"required\"))]
+ compile_error!(\"This crate requires `required` feature!\");
+ fn main() {}",
+ )
+ .build();
+
+ p.cargo("publish --no-default-features")
+ .replace_crates_io(registry.index_url())
+ .with_status(101)
+ .with_stderr_contains("error: This crate requires `required` feature!")
+ .run();
+}
+
+#[cargo_test]
+fn publish_with_patch() {
+ let registry = RegistryBuilder::new().http_api().http_index().build();
+ Package::new("bar", "1.0.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ [dependencies]
+ bar = "1.0"
+ [patch.crates-io]
+ bar = { path = "bar" }
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ "extern crate bar;
+ fn main() {
+ bar::newfunc();
+ }",
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "1.0.0"))
+ .file("bar/src/lib.rs", "pub fn newfunc() {}")
+ .build();
+
+ // Check that it works with the patched crate.
+ p.cargo("build").run();
+
+ // Check that verify fails with patched crate which has new functionality.
+ p.cargo("publish")
+ .replace_crates_io(registry.index_url())
+ .with_status(101)
+ .with_stderr_contains("[..]newfunc[..]")
+ .run();
+
+ // Remove the usage of new functionality and try again.
+ p.change_file("src/main.rs", "extern crate bar; pub fn main() {}");
+
+ p.cargo("publish")
+ .replace_crates_io(registry.index_url())
+ .with_stderr(
+ "\
+[..]
+[..]
+[..]
+[..]
+[UPDATING] crates.io index
+[VERIFYING] foo v0.0.1 ([CWD])
+[..]
+[..]
+[..]
+[..]
+[UPLOADING] foo v0.0.1 ([CWD])
+[UPLOADED] foo v0.0.1 to registry `crates-io`
+note: Waiting [..]
+You may press ctrl-c [..]
+[PUBLISHED] foo v0.0.1 [..]
+",
+ )
+ .run();
+
+ publish::validate_upload(
+ r#"
+ {
+ "authors": [],
+ "badges": {},
+ "categories": [],
+ "deps": [
+ {
+ "default_features": true,
+ "features": [],
+ "kind": "normal",
+ "name": "bar",
+ "optional": false,
+ "target": null,
+ "version_req": "^1.0"
+ }
+ ],
+ "description": "foo",
+ "documentation": null,
+ "features": {},
+ "homepage": null,
+ "keywords": [],
+ "license": "MIT",
+ "license_file": null,
+ "links": null,
+ "name": "foo",
+ "readme": null,
+ "readme_file": null,
+ "repository": null,
+ "vers": "0.0.1"
+ }
+ "#,
+ "foo-0.0.1.crate",
+ &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"],
+ );
+}
+
+#[cargo_test]
+fn publish_checks_for_token_before_verify() {
+ let registry = registry::RegistryBuilder::new()
+ .no_configure_token()
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ // Assert upload token error before the package is verified
+ p.cargo("publish")
+ .replace_crates_io(registry.index_url())
+ .with_status(101)
+ .with_stderr_contains("[ERROR] no token found, please run `cargo login`")
+ .with_stderr_does_not_contain("[VERIFYING] foo v0.0.1 ([CWD])")
+ .run();
+
+ // Assert package verified successfully on dry run
+ p.cargo("publish --dry-run")
+ .replace_crates_io(registry.index_url())
+ .with_stderr(
+ "\
+[..]
+[..]
+[..]
+[..]
+[VERIFYING] foo v0.0.1 ([CWD])
+[..]
+[..]
+[..]
+[UPLOADING] foo v0.0.1 [..]
+[WARNING] aborting upload due to dry run
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn publish_with_bad_source() {
+ let p = project()
+ .file(
+ ".cargo/config",
+ r#"
+ [source.crates-io]
+ replace-with = 'local-registry'
+
+ [source.local-registry]
+ local-registry = 'registry'
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("publish")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] crates-io is replaced with non-remote-registry source registry `[..]/foo/registry`;
+include `--registry crates-io` to use crates.io
+",
+ )
+ .run();
+
+ p.change_file(
+ ".cargo/config",
+ r#"
+ [source.crates-io]
+ replace-with = "vendored-sources"
+
+ [source.vendored-sources]
+ directory = "vendor"
+ "#,
+ );
+
+ p.cargo("publish")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] crates-io is replaced with non-remote-registry source dir [..]/foo/vendor;
+include `--registry crates-io` to use crates.io
+",
+ )
+ .run();
+}
+
+// A dependency with both `git` and `version`.
+#[cargo_test]
+fn publish_git_with_version() {
+ let registry = RegistryBuilder::new().http_api().http_index().build();
+
+ Package::new("dep1", "1.0.1")
+ .file("src/lib.rs", "pub fn f() -> i32 {1}")
+ .publish();
+
+ let git_project = git::new("dep1", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("dep1", "1.0.0"))
+ .file("src/lib.rs", "pub fn f() -> i32 {2}")
+ });
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ edition = "2018"
+ license = "MIT"
+ description = "foo"
+
+ [dependencies]
+ dep1 = {{version = "1.0", git="{}"}}
+ "#,
+ git_project.url()
+ ),
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ pub fn main() {
+ println!("{}", dep1::f());
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("run").with_stdout("2").run();
+
+ p.cargo("publish --no-verify")
+ .replace_crates_io(registry.index_url())
+ .with_stderr(
+ "\
+[..]
+[..]
+[..]
+[..]
+[..]
+[..]
+[UPLOADING] foo v0.1.0 ([CWD])
+[UPLOADED] foo v0.1.0 to registry `crates-io`
+note: Waiting [..]
+You may press ctrl-c [..]
+[PUBLISHED] foo v0.1.0 [..]
+",
+ )
+ .run();
+
+ publish::validate_upload_with_contents(
+ r#"
+ {
+ "authors": [],
+ "badges": {},
+ "categories": [],
+ "deps": [
+ {
+ "default_features": true,
+ "features": [],
+ "kind": "normal",
+ "name": "dep1",
+ "optional": false,
+ "target": null,
+ "version_req": "^1.0"
+ }
+ ],
+ "description": "foo",
+ "documentation": null,
+ "features": {},
+ "homepage": null,
+ "keywords": [],
+ "license": "MIT",
+ "license_file": null,
+ "links": null,
+ "name": "foo",
+ "readme": null,
+ "readme_file": null,
+ "repository": null,
+ "vers": "0.1.0"
+ }
+ "#,
+ "foo-0.1.0.crate",
+ &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"],
+ &[
+ (
+ "Cargo.toml",
+ // Check that only `version` is included in Cargo.toml.
+ &format!(
+ "{}\n\
+ [package]\n\
+ edition = \"2018\"\n\
+ name = \"foo\"\n\
+ version = \"0.1.0\"\n\
+ authors = []\n\
+ description = \"foo\"\n\
+ license = \"MIT\"\n\
+ \n\
+ [dependencies.dep1]\n\
+ version = \"1.0\"\n\
+ ",
+ cargo::core::package::MANIFEST_PREAMBLE
+ ),
+ ),
+ (
+ "Cargo.lock",
+ // The important check here is that it is 1.0.1 in the registry.
+ "# This file is automatically @generated by Cargo.\n\
+ # It is not intended for manual editing.\n\
+ version = 3\n\
+ \n\
+ [[package]]\n\
+ name = \"dep1\"\n\
+ version = \"1.0.1\"\n\
+ source = \"registry+https://github.com/rust-lang/crates.io-index\"\n\
+ checksum = \"[..]\"\n\
+ \n\
+ [[package]]\n\
+ name = \"foo\"\n\
+ version = \"0.1.0\"\n\
+ dependencies = [\n\
+ \x20\"dep1\",\n\
+ ]\n\
+ ",
+ ),
+ ],
+ );
+}
+
+#[cargo_test]
+fn publish_dev_dep_no_version() {
+ let registry = RegistryBuilder::new().http_api().http_index().build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ documentation = "foo"
+ homepage = "foo"
+ repository = "foo"
+
+ [dev-dependencies]
+ bar = { path = "bar" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("publish --no-verify")
+ .replace_crates_io(registry.index_url())
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[PACKAGING] foo v0.1.0 [..]
+[PACKAGED] [..] files, [..] ([..] compressed)
+[UPLOADING] foo v0.1.0 [..]
+[UPLOADED] foo v0.1.0 [..]
+note: Waiting [..]
+You may press ctrl-c [..]
+[PUBLISHED] foo v0.1.0 [..]
+",
+ )
+ .run();
+
+ publish::validate_upload_with_contents(
+ r#"
+ {
+ "authors": [],
+ "badges": {},
+ "categories": [],
+ "deps": [],
+ "description": "foo",
+ "documentation": "foo",
+ "features": {},
+ "homepage": "foo",
+ "keywords": [],
+ "license": "MIT",
+ "license_file": null,
+ "links": null,
+ "name": "foo",
+ "readme": null,
+ "readme_file": null,
+ "repository": "foo",
+ "vers": "0.1.0"
+ }
+ "#,
+ "foo-0.1.0.crate",
+ &["Cargo.toml", "Cargo.toml.orig", "src/lib.rs"],
+ &[(
+ "Cargo.toml",
+ &format!(
+ r#"{}
+[package]
+name = "foo"
+version = "0.1.0"
+authors = []
+description = "foo"
+homepage = "foo"
+documentation = "foo"
+license = "MIT"
+repository = "foo"
+
+[dev-dependencies]
+"#,
+ cargo::core::package::MANIFEST_PREAMBLE
+ ),
+ )],
+ );
+}
+
+#[cargo_test]
+fn credentials_ambiguous_filename() {
+ // `publish` generally requires a remote registry
+ let registry = registry::RegistryBuilder::new().http_api().build();
+
+ // Make token in `credentials.toml` incorrect to ensure it is not read.
+ let credentials_toml = paths::home().join(".cargo/credentials.toml");
+ fs::write(credentials_toml, r#"token = "wrong-token""#).unwrap();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("publish --no-verify")
+ .replace_crates_io(registry.index_url())
+ .with_status(101)
+ .with_stderr_contains("[..]Unauthorized message from server[..]")
+ .run();
+
+ // Favor `credentials` if exists.
+ let credentials = paths::home().join(".cargo/credentials");
+ fs::write(credentials, r#"token = "sekrit""#).unwrap();
+
+ p.cargo("publish --no-verify")
+ .replace_crates_io(registry.index_url())
+ .with_stderr(
+ "\
+[..]
+[WARNING] Both `[..]/credentials` and `[..]/credentials.toml` exist. Using `[..]/credentials`
+[..]
+[..]
+[..]
+[..]
+[UPLOADING] foo v0.0.1 [..]
+[UPLOADED] foo v0.0.1 [..]
+note: Waiting [..]
+You may press ctrl-c [..]
+[PUBLISHED] foo v0.0.1 [..]
+",
+ )
+ .run();
+}
+
+// --index will not load registry.token to avoid possibly leaking
+// crates.io token to another server.
+#[cargo_test]
+fn index_requires_token() {
+ // Use local registry for faster test times since no publish will occur
+ let registry = registry::init();
+
+ let credentials = paths::home().join(".cargo/credentials.toml");
+ fs::remove_file(&credentials).unwrap();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("publish --no-verify --index")
+ .arg(registry.index_url().as_str())
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] command-line argument --index requires --token to be specified
+",
+ )
+ .run();
+}
+
+// publish with source replacement without --registry
+#[cargo_test]
+fn cratesio_source_replacement() {
+ registry::init();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("publish --no-verify")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] crates-io is replaced with remote registry dummy-registry;
+include `--registry dummy-registry` or `--registry crates-io`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn publish_with_missing_readme() {
+ // Use local registry for faster test times since no publish will occur
+ let registry = registry::init();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ homepage = "https://example.com/"
+ readme = "foo.md"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("publish --no-verify")
+ .replace_crates_io(registry.index_url())
+ .with_status(101)
+ .with_stderr(&format!(
+ "\
+[UPDATING] [..]
+[PACKAGING] foo v0.1.0 [..]
+[PACKAGED] [..] files, [..] ([..] compressed)
+[UPLOADING] foo v0.1.0 [..]
+[ERROR] failed to read `readme` file for package `foo v0.1.0 ([ROOT]/foo)`
+
+Caused by:
+ failed to read `[ROOT]/foo/foo.md`
+
+Caused by:
+ {}
+",
+ no_such_file_err_msg()
+ ))
+ .run();
+}
+
+// Registry returns an API error.
+#[cargo_test]
+fn api_error_json() {
+ let _registry = registry::RegistryBuilder::new()
+ .alternative()
+ .http_api()
+ .add_responder("/api/v1/crates/new", |_, _| Response {
+ body: br#"{"errors": [{"detail": "you must be logged in"}]}"#.to_vec(),
+ code: 403,
+ headers: vec![],
+ })
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ documentation = "foo"
+ homepage = "foo"
+ repository = "foo"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("publish --no-verify --registry alternative")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[PACKAGING] foo v0.0.1 [..]
+[PACKAGED] [..] files, [..] ([..] compressed)
+[UPLOADING] foo v0.0.1 [..]
+[ERROR] failed to publish to registry at http://127.0.0.1:[..]/
+
+Caused by:
+ the remote server responded with an error (status 403 Forbidden): you must be logged in
+",
+ )
+ .run();
+}
+
+// Registry returns an API error with a 200 status code.
+#[cargo_test]
+fn api_error_200() {
+ let _registry = registry::RegistryBuilder::new()
+ .alternative()
+ .http_api()
+ .add_responder("/api/v1/crates/new", |_, _| Response {
+ body: br#"{"errors": [{"detail": "max upload size is 123"}]}"#.to_vec(),
+ code: 200,
+ headers: vec![],
+ })
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ documentation = "foo"
+ homepage = "foo"
+ repository = "foo"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("publish --no-verify --registry alternative")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[PACKAGING] foo v0.0.1 [..]
+[PACKAGED] [..] files, [..] ([..] compressed)
+[UPLOADING] foo v0.0.1 [..]
+[ERROR] failed to publish to registry at http://127.0.0.1:[..]/
+
+Caused by:
+ the remote server responded with an error: max upload size is 123
+",
+ )
+ .run();
+}
+
+// Registry returns an error code without a JSON message.
+#[cargo_test]
+fn api_error_code() {
+ let _registry = registry::RegistryBuilder::new()
+ .alternative()
+ .http_api()
+ .add_responder("/api/v1/crates/new", |_, _| Response {
+ body: br#"go away"#.to_vec(),
+ code: 400,
+ headers: vec![],
+ })
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ documentation = "foo"
+ homepage = "foo"
+ repository = "foo"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("publish --no-verify --registry alternative")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[PACKAGING] foo v0.0.1 [..]
+[PACKAGED] [..] files, [..] ([..] compressed)
+[UPLOADING] foo v0.0.1 [..]
+[ERROR] failed to publish to registry at http://127.0.0.1:[..]/
+
+Caused by:
+ failed to get a 200 OK response, got 400
+ headers:
+ <tab>HTTP/1.1 400
+ <tab>Content-Length: 7
+ <tab>
+ body:
+ go away
+",
+ )
+ .run();
+}
+
+// Registry has a network error.
+#[cargo_test]
+fn api_curl_error() {
+ let _registry = registry::RegistryBuilder::new()
+ .alternative()
+ .http_api()
+ .add_responder("/api/v1/crates/new", |_, _| {
+ panic!("broke");
+ })
+ .build();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ documentation = "foo"
+ homepage = "foo"
+ repository = "foo"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ // This doesn't check for the exact text of the error in the remote
+ // possibility that cargo is linked with a weird version of libcurl, or
+ // curl changes the text of the message. Currently the message 52
+ // (CURLE_GOT_NOTHING) is:
+ // Server returned nothing (no headers, no data) (Empty reply from server)
+ p.cargo("publish --no-verify --registry alternative")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[PACKAGING] foo v0.0.1 [..]
+[PACKAGED] [..] files, [..] ([..] compressed)
+[UPLOADING] foo v0.0.1 [..]
+[ERROR] failed to publish to registry at http://127.0.0.1:[..]/
+
+Caused by:
+ [52] [..]
+",
+ )
+ .run();
+}
+
+// Registry returns an invalid response.
+#[cargo_test]
+fn api_other_error() {
+ let _registry = registry::RegistryBuilder::new()
+ .alternative()
+ .http_api()
+ .add_responder("/api/v1/crates/new", |_, _| Response {
+ body: b"\xff".to_vec(),
+ code: 200,
+ headers: vec![],
+ })
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ documentation = "foo"
+ homepage = "foo"
+ repository = "foo"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("publish --no-verify --registry alternative")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[PACKAGING] foo v0.0.1 [..]
+[PACKAGED] [..] files, [..] ([..] compressed)
+[UPLOADING] foo v0.0.1 [..]
+[ERROR] failed to publish to registry at http://127.0.0.1:[..]/
+
+Caused by:
+ invalid response from server
+
+Caused by:
+ response body was not valid utf-8
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn in_package_workspace() {
+ let registry = RegistryBuilder::new().http_api().http_index().build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2021"
+ [workspace]
+ members = ["li"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "li/Cargo.toml",
+ r#"
+ [package]
+ name = "li"
+ version = "0.0.1"
+ description = "li"
+ license = "MIT"
+ "#,
+ )
+ .file("li/src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("publish -p li --no-verify")
+ .replace_crates_io(registry.index_url())
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[WARNING] manifest has no documentation, homepage or repository.
+See [..]
+[PACKAGING] li v0.0.1 ([CWD]/li)
+[PACKAGED] [..] files, [..] ([..] compressed)
+[UPLOADING] li v0.0.1 ([CWD]/li)
+[UPLOADED] li v0.0.1 to registry `crates-io`
+note: Waiting [..]
+You may press ctrl-c [..]
+[PUBLISHED] li v0.0.1 [..]
+",
+ )
+ .run();
+
+ validate_upload_li();
+}
+
+#[cargo_test]
+fn with_duplicate_spec_in_members() {
+ // Use local registry for faster test times since no publish will occur
+ let registry = registry::init();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ [workspace]
+ resolver = "2"
+ members = ["li","bar"]
+ default-members = ["li","bar"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "li/Cargo.toml",
+ r#"
+ [package]
+ name = "li"
+ version = "0.0.1"
+ description = "li"
+ license = "MIT"
+ "#,
+ )
+ .file("li/src/main.rs", "fn main() {}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ description = "bar"
+ license = "MIT"
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("publish --no-verify")
+ .replace_crates_io(registry.index_url())
+ .with_status(101)
+ .with_stderr(
+ "error: the `-p` argument must be specified to select a single package to publish",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn in_package_workspace_with_members_with_features_old() {
+ let registry = RegistryBuilder::new().http_api().http_index().build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ [workspace]
+ members = ["li"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "li/Cargo.toml",
+ r#"
+ [package]
+ name = "li"
+ version = "0.0.1"
+ description = "li"
+ license = "MIT"
+ "#,
+ )
+ .file("li/src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("publish -p li --no-verify")
+ .replace_crates_io(registry.index_url())
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[WARNING] manifest has no documentation, homepage or repository.
+See [..]
+[PACKAGING] li v0.0.1 ([CWD]/li)
+[PACKAGED] [..] files, [..] ([..] compressed)
+[UPLOADING] li v0.0.1 ([CWD]/li)
+[UPLOADED] li v0.0.1 to registry `crates-io`
+note: Waiting [..]
+You may press ctrl-c [..]
+[PUBLISHED] li v0.0.1 [..]
+",
+ )
+ .run();
+
+ validate_upload_li();
+}
+
+#[cargo_test]
+fn in_virtual_workspace() {
+ // Use local registry for faster test times since no publish will occur
+ let registry = registry::init();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["foo"]
+ "#,
+ )
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ "#,
+ )
+ .file("foo/src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("publish --no-verify")
+ .replace_crates_io(registry.index_url())
+ .with_status(101)
+ .with_stderr(
+ "error: the `-p` argument must be specified in the root of a virtual workspace",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn in_virtual_workspace_with_p() {
+ // `publish` generally requires a remote registry
+ let registry = registry::RegistryBuilder::new().http_api().build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["foo","li"]
+ "#,
+ )
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ "#,
+ )
+ .file("foo/src/main.rs", "fn main() {}")
+ .file(
+ "li/Cargo.toml",
+ r#"
+ [package]
+ name = "li"
+ version = "0.0.1"
+ description = "li"
+ license = "MIT"
+ "#,
+ )
+ .file("li/src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("publish -p li --no-verify")
+ .replace_crates_io(registry.index_url())
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[WARNING] manifest has no documentation, homepage or repository.
+See [..]
+[PACKAGING] li v0.0.1 ([CWD]/li)
+[PACKAGED] [..] files, [..] ([..] compressed)
+[UPLOADING] li v0.0.1 ([CWD]/li)
+[UPLOADED] li v0.0.1 to registry `crates-io`
+note: Waiting [..]
+You may press ctrl-c [..]
+[PUBLISHED] li v0.0.1 [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn in_package_workspace_not_found() {
+ // Use local registry for faster test times since no publish will occur
+ let registry = registry::init();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2021"
+ [workspace]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "li/Cargo.toml",
+ r#"
+ [package]
+ name = "li"
+ version = "0.0.1"
+ edition = "2021"
+ authors = []
+ license = "MIT"
+ description = "li"
+ "#,
+ )
+ .file("li/src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("publish -p li --no-verify")
+ .replace_crates_io(registry.index_url())
+ .with_status(101)
+ .with_stderr(
+ "\
+error: package ID specification `li` did not match any packages
+
+<tab>Did you mean `foo`?
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn in_package_workspace_found_multiple() {
+ // Use local registry for faster test times since no publish will occur
+ let registry = registry::init();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2021"
+ [workspace]
+ members = ["li","lii"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "li/Cargo.toml",
+ r#"
+ [package]
+ name = "li"
+ version = "0.0.1"
+ edition = "2021"
+ authors = []
+ license = "MIT"
+ description = "li"
+ "#,
+ )
+ .file("li/src/main.rs", "fn main() {}")
+ .file(
+ "lii/Cargo.toml",
+ r#"
+ [package]
+ name = "lii"
+ version = "0.0.1"
+ edition = "2021"
+ authors = []
+ license = "MIT"
+ description = "lii"
+ "#,
+ )
+ .file("lii/src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("publish -p li* --no-verify")
+ .replace_crates_io(registry.index_url())
+ .with_status(101)
+ .with_stderr(
+ "\
+error: the `-p` argument must be specified to select a single package to publish
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+// https://github.com/rust-lang/cargo/issues/10536
+fn publish_path_dependency_without_workspace() {
+ // Use local registry for faster test times since no publish will occur
+ let registry = registry::init();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2021"
+ [dependencies.bar]
+ path = "bar"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ edition = "2021"
+ authors = []
+ license = "MIT"
+ description = "bar"
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("publish -p bar --no-verify")
+ .replace_crates_io(registry.index_url())
+ .with_status(101)
+ .with_stderr(
+ "\
+error: package ID specification `bar` did not match any packages
+
+<tab>Did you mean `foo`?
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn http_api_not_noop() {
+ let registry = registry::RegistryBuilder::new().http_api().build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("publish")
+ .replace_crates_io(registry.index_url())
+ .with_stderr(
+ "\
+[..]
+[..]
+[..]
+[..]
+[VERIFYING] foo v0.0.1 ([CWD])
+[..]
+[..]
+[..]
+[UPLOADING] foo v0.0.1 ([CWD])
+[UPLOADED] foo v0.0.1 to registry `crates-io`
+note: Waiting [..]
+You may press ctrl-c [..]
+[PUBLISHED] foo v0.0.1 [..]
+",
+ )
+ .run();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [project]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+
+ [dependencies]
+ foo = "0.0.1"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build").run();
+}
+
+#[cargo_test]
+fn wait_for_first_publish() {
+ // Counter for number of tries before the package is "published"
+ let arc: Arc<Mutex<u32>> = Arc::new(Mutex::new(0));
+ let arc2 = arc.clone();
+
+ // Registry returns an invalid response.
+ let registry = registry::RegistryBuilder::new()
+ .http_index()
+ .http_api()
+ .add_responder("/index/de/la/delay", move |req, server| {
+ let mut lock = arc.lock().unwrap();
+ *lock += 1;
+ if *lock <= 1 {
+ server.not_found(req)
+ } else {
+ server.index(req)
+ }
+ })
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "delay"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("publish --no-verify")
+ .replace_crates_io(registry.index_url())
+ .with_status(0)
+ .with_stderr(
+ "\
+[UPDATING] crates.io index
+[WARNING] manifest has no documentation, [..]
+See [..]
+[PACKAGING] delay v0.0.1 ([CWD])
+[PACKAGED] [..] files, [..] ([..] compressed)
+[UPLOADING] delay v0.0.1 ([CWD])
+[UPLOADED] delay v0.0.1 to registry `crates-io`
+note: Waiting for `delay v0.0.1` to be available at registry `crates-io`.
+You may press ctrl-c to skip waiting; the crate should be available shortly.
+[PUBLISHED] delay v0.0.1 at registry `crates-io`
+",
+ )
+ .run();
+
+ // Verify the responder has been pinged
+ let lock = arc2.lock().unwrap();
+ assert_eq!(*lock, 2);
+ drop(lock);
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ [dependencies]
+ delay = "0.0.1"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build").with_status(0).run();
+}
+
+/// A separate test is needed for package names with - or _ as they hit
+/// the responder twice per cargo invocation. If that ever gets changed
+/// this test will need to be changed accordingly.
+#[cargo_test]
+fn wait_for_first_publish_underscore() {
+ // Counter for number of tries before the package is "published"
+ let arc: Arc<Mutex<u32>> = Arc::new(Mutex::new(0));
+ let arc2 = arc.clone();
+ let misses = Arc::new(Mutex::new(Vec::new()));
+ let misses2 = misses.clone();
+
+ // Registry returns an invalid response.
+ let registry = registry::RegistryBuilder::new()
+ .http_index()
+ .http_api()
+ .add_responder("/index/de/la/delay_with_underscore", move |req, server| {
+ let mut lock = arc.lock().unwrap();
+ *lock += 1;
+ if *lock <= 1 {
+ server.not_found(req)
+ } else {
+ server.index(req)
+ }
+ })
+ .not_found_handler(move |req, _| {
+ misses.lock().unwrap().push(req.url.to_string());
+ Response {
+ body: b"not found".to_vec(),
+ code: 404,
+ headers: vec![],
+ }
+ })
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "delay_with_underscore"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("publish --no-verify")
+ .replace_crates_io(registry.index_url())
+ .with_status(0)
+ .with_stderr(
+ "\
+[UPDATING] crates.io index
+[WARNING] manifest has no documentation, [..]
+See [..]
+[PACKAGING] delay_with_underscore v0.0.1 ([CWD])
+[PACKAGED] [..] files, [..] ([..] compressed)
+[UPLOADING] delay_with_underscore v0.0.1 ([CWD])
+[UPLOADED] delay_with_underscore v0.0.1 to registry `crates-io`
+note: Waiting for `delay_with_underscore v0.0.1` to be available at registry `crates-io`.
+You may press ctrl-c to skip waiting; the crate should be available shortly.
+[PUBLISHED] delay_with_underscore v0.0.1 at registry `crates-io`
+",
+ )
+ .run();
+
+ // Verify the repsponder has been pinged
+ let lock = arc2.lock().unwrap();
+ assert_eq!(*lock, 2);
+ drop(lock);
+ {
+ let misses = misses2.lock().unwrap();
+ assert!(
+ misses.len() == 1,
+ "should only have 1 not found URL; instead found {misses:?}"
+ );
+ }
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ [dependencies]
+ delay_with_underscore = "0.0.1"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build").with_status(0).run();
+}
+
+#[cargo_test]
+fn wait_for_subsequent_publish() {
+ // Counter for number of tries before the package is "published"
+ let arc: Arc<Mutex<u32>> = Arc::new(Mutex::new(0));
+ let arc2 = arc.clone();
+ let publish_req = Arc::new(Mutex::new(None));
+ let publish_req2 = publish_req.clone();
+
+ let registry = registry::RegistryBuilder::new()
+ .http_index()
+ .http_api()
+ .add_responder("/api/v1/crates/new", move |req, server| {
+ // Capture the publish request, but defer publishing
+ *publish_req.lock().unwrap() = Some(req.clone());
+ server.ok(req)
+ })
+ .add_responder("/index/de/la/delay", move |req, server| {
+ let mut lock = arc.lock().unwrap();
+ *lock += 1;
+ if *lock == 3 {
+ // Run the publish on the 3rd attempt
+ let rep = server
+ .check_authorized_publish(&publish_req2.lock().unwrap().as_ref().unwrap());
+ assert_eq!(rep.code, 200);
+ }
+ server.index(req)
+ })
+ .build();
+
+ // Publish an earlier version
+ Package::new("delay", "0.0.1")
+ .file("src/lib.rs", "")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "delay"
+ version = "0.0.2"
+ authors = []
+ license = "MIT"
+ description = "foo"
+
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("publish --no-verify")
+ .replace_crates_io(registry.index_url())
+ .with_status(0)
+ .with_stderr(
+ "\
+[UPDATING] crates.io index
+[WARNING] manifest has no documentation, [..]
+See [..]
+[PACKAGING] delay v0.0.2 ([CWD])
+[PACKAGED] [..] files, [..] ([..] compressed)
+[UPLOADING] delay v0.0.2 ([CWD])
+[UPLOADED] delay v0.0.2 to registry `crates-io`
+note: Waiting for `delay v0.0.2` to be available at registry `crates-io`.
+You may press ctrl-c to skip waiting; the crate should be available shortly.
+[PUBLISHED] delay v0.0.2 at registry `crates-io`
+",
+ )
+ .run();
+
+ // Verify the responder has been pinged
+ let lock = arc2.lock().unwrap();
+ assert_eq!(*lock, 3);
+ drop(lock);
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ [dependencies]
+ delay = "0.0.2"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check").with_status(0).run();
+}
+
+#[cargo_test]
+fn skip_wait_for_publish() {
+ // Intentionally using local registry so the crate never makes it to the index
+ let registry = registry::init();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ ".cargo/config",
+ "
+ [publish]
+ timeout = 0
+ ",
+ )
+ .build();
+
+ p.cargo("publish --no-verify -Zpublish-timeout")
+ .replace_crates_io(registry.index_url())
+ .masquerade_as_nightly_cargo(&["publish-timeout"])
+ .with_stderr(
+ "\
+[UPDATING] crates.io index
+[WARNING] manifest has no documentation, [..]
+See [..]
+[PACKAGING] foo v0.0.1 ([CWD])
+[PACKAGED] [..] files, [..] ([..] compressed)
+[UPLOADING] foo v0.0.1 ([CWD])
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn timeout_waiting_for_publish() {
+ // Publish doesn't happen within the timeout window.
+ let registry = registry::RegistryBuilder::new()
+ .http_api()
+ .delayed_index_update(20)
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "delay"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config.toml",
+ r#"
+ [publish]
+ timeout = 2
+ "#,
+ )
+ .build();
+
+ p.cargo("publish --no-verify -Zpublish-timeout")
+ .replace_crates_io(registry.index_url())
+ .masquerade_as_nightly_cargo(&["publish-timeout"])
+ .with_status(0)
+ .with_stderr(
+ "\
+[UPDATING] crates.io index
+[WARNING] manifest has no documentation, [..]
+See [..]
+[PACKAGING] delay v0.0.1 ([CWD])
+[PACKAGED] [..] files, [..] ([..] compressed)
+[UPLOADING] delay v0.0.1 ([CWD])
+[UPLOADED] delay v0.0.1 to registry `crates-io`
+note: Waiting for `delay v0.0.1` to be available at registry `crates-io`.
+You may press ctrl-c to skip waiting; the crate should be available shortly.
+warning: timed out waiting for `delay v0.0.1` to be available in registry `crates-io`
+note: The registry may have a backlog that is delaying making the crate available. The crate should be available soon.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn wait_for_git_publish() {
+ // Slow publish to an index with a git index.
+ let registry = registry::RegistryBuilder::new()
+ .http_api()
+ .delayed_index_update(5)
+ .build();
+
+ // Publish an earlier version
+ Package::new("delay", "0.0.1")
+ .file("src/lib.rs", "")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "delay"
+ version = "0.0.2"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("publish --no-verify")
+ .replace_crates_io(registry.index_url())
+ .with_status(0)
+ .with_stderr(
+ "\
+[UPDATING] crates.io index
+[WARNING] manifest has no documentation, [..]
+See [..]
+[PACKAGING] delay v0.0.2 ([CWD])
+[PACKAGED] [..] files, [..] ([..] compressed)
+[UPLOADING] delay v0.0.2 ([CWD])
+[UPLOADED] delay v0.0.2 to registry `crates-io`
+note: Waiting for `delay v0.0.2` to be available at registry `crates-io`.
+You may press ctrl-c to skip waiting; the crate should be available shortly.
+[PUBLISHED] delay v0.0.2 at registry `crates-io`
+",
+ )
+ .run();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ [dependencies]
+ delay = "0.0.2"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check").with_status(0).run();
+}
+
+#[cargo_test]
+fn invalid_token() {
+ // Checks publish behavior with an invalid token.
+ let registry = RegistryBuilder::new().http_api().http_index().build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ documentation = "foo"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("publish --no-verify")
+ .replace_crates_io(registry.index_url())
+ .env("CARGO_REGISTRY_TOKEN", "\x16")
+ .with_stderr(
+ "\
+[UPDATING] crates.io index
+[PACKAGING] foo v0.0.1 ([ROOT]/foo)
+[PACKAGED] 4 files, [..]
+[UPLOADING] foo v0.0.1 ([ROOT]/foo)
+error: failed to publish to registry at http://127.0.0.1:[..]/
+
+Caused by:
+ token contains invalid characters.
+ Only printable ISO-8859-1 characters are allowed as it is sent in a HTTPS header.
+",
+ )
+ .with_status(101)
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/publish_lockfile.rs b/src/tools/cargo/tests/testsuite/publish_lockfile.rs
new file mode 100644
index 000000000..35da5131f
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/publish_lockfile.rs
@@ -0,0 +1,592 @@
+//! Tests for including `Cargo.lock` when publishing/packaging.
+
+use std::fs::File;
+
+use cargo_test_support::registry::Package;
+use cargo_test_support::{
+ basic_manifest, cargo_process, git, paths, project, publish::validate_crate_contents,
+};
+
+fn pl_manifest(name: &str, version: &str, extra: &str) -> String {
+ format!(
+ r#"
+ [package]
+ name = "{}"
+ version = "{}"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ documentation = "foo"
+ homepage = "foo"
+ repository = "foo"
+
+ {}
+ "#,
+ name, version, extra
+ )
+}
+
+#[cargo_test]
+fn removed() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ cargo-features = ["publish-lockfile"]
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ publish-lockfile = true
+ license = "MIT"
+ description = "foo"
+ documentation = "foo"
+ homepage = "foo"
+ repository = "foo"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("package")
+ .masquerade_as_nightly_cargo(&["publish-lockfile"])
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at [..]
+
+Caused by:
+ the cargo feature `publish-lockfile` has been removed in the 1.37 release
+
+ Remove the feature from Cargo.toml to remove this error.
+ See https://doc.rust-lang.org/[..]cargo/reference/unstable.html#publish-lockfile [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn package_lockfile() {
+ let p = project()
+ .file("Cargo.toml", &pl_manifest("foo", "0.0.1", ""))
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("package")
+ .with_stderr(
+ "\
+[PACKAGING] foo v0.0.1 ([CWD])
+[VERIFYING] foo v0.0.1 ([CWD])
+[COMPILING] foo v0.0.1 ([CWD][..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[PACKAGED] [..] files, [..] ([..] compressed)
+",
+ )
+ .run();
+ assert!(p.root().join("target/package/foo-0.0.1.crate").is_file());
+ p.cargo("package -l")
+ .with_stdout(
+ "\
+Cargo.lock
+Cargo.toml
+Cargo.toml.orig
+src/main.rs
+",
+ )
+ .run();
+ p.cargo("package").with_stdout("").run();
+
+ let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap();
+ validate_crate_contents(
+ f,
+ "foo-0.0.1.crate",
+ &["Cargo.toml", "Cargo.toml.orig", "Cargo.lock", "src/main.rs"],
+ &[],
+ );
+}
+
+#[cargo_test]
+fn package_lockfile_git_repo() {
+ // Create a Git repository containing a minimal Rust project.
+ let g = git::repo(&paths::root().join("foo"))
+ .file("Cargo.toml", &pl_manifest("foo", "0.0.1", ""))
+ .file("src/main.rs", "fn main() {}")
+ .build();
+ cargo_process("package -l")
+ .cwd(g.root())
+ .with_stdout(
+ "\
+.cargo_vcs_info.json
+Cargo.lock
+Cargo.toml
+Cargo.toml.orig
+src/main.rs
+",
+ )
+ .run();
+ cargo_process("package -v")
+ .cwd(g.root())
+ .with_stderr(
+ "\
+[PACKAGING] foo v0.0.1 ([..])
+[ARCHIVING] .cargo_vcs_info.json
+[ARCHIVING] Cargo.lock
+[ARCHIVING] Cargo.toml
+[ARCHIVING] Cargo.toml.orig
+[ARCHIVING] src/main.rs
+[VERIFYING] foo v0.0.1 ([..])
+[COMPILING] foo v0.0.1 ([..])
+[RUNNING] `rustc --crate-name foo src/main.rs [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[PACKAGED] 5 files, [..] ([..] compressed)
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn no_lock_file_with_library() {
+ let p = project()
+ .file("Cargo.toml", &pl_manifest("foo", "0.0.1", ""))
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("package").run();
+
+ let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap();
+ validate_crate_contents(
+ f,
+ "foo-0.0.1.crate",
+ &["Cargo.toml", "Cargo.toml.orig", "src/lib.rs"],
+ &[],
+ );
+}
+
+#[cargo_test]
+fn lock_file_and_workspace() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["foo"]
+ "#,
+ )
+ .file("foo/Cargo.toml", &pl_manifest("foo", "0.0.1", ""))
+ .file("foo/src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("package").cwd("foo").run();
+
+ let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap();
+ validate_crate_contents(
+ f,
+ "foo-0.0.1.crate",
+ &["Cargo.toml", "Cargo.toml.orig", "src/main.rs", "Cargo.lock"],
+ &[],
+ );
+}
+
+#[cargo_test]
+fn note_resolve_changes() {
+ // `multi` has multiple sources (path and registry).
+ Package::new("multi", "0.1.0").publish();
+ // `updated` is always from registry, but should not change.
+ Package::new("updated", "1.0.0").publish();
+ // `patched` is [patch]ed.
+ Package::new("patched", "1.0.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &pl_manifest(
+ "foo",
+ "0.0.1",
+ r#"
+ [dependencies]
+ multi = { path = "multi", version = "0.1" }
+ updated = "1.0"
+ patched = "1.0"
+
+ [patch.crates-io]
+ patched = { path = "patched" }
+ "#,
+ ),
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("multi/Cargo.toml", &basic_manifest("multi", "0.1.0"))
+ .file("multi/src/lib.rs", "")
+ .file("patched/Cargo.toml", &basic_manifest("patched", "1.0.0"))
+ .file("patched/src/lib.rs", "")
+ .build();
+
+ p.cargo("generate-lockfile").run();
+
+ // Make sure this does not change or warn.
+ Package::new("updated", "1.0.1").publish();
+
+ p.cargo("package --no-verify -v --allow-dirty")
+ .with_stderr_unordered(
+ "\
+[PACKAGING] foo v0.0.1 ([..])
+[ARCHIVING] Cargo.lock
+[ARCHIVING] Cargo.toml
+[ARCHIVING] Cargo.toml.orig
+[ARCHIVING] src/main.rs
+[UPDATING] `[..]` index
+[NOTE] package `multi v0.1.0` added to the packaged Cargo.lock file, was originally sourced from `[..]/foo/multi`
+[NOTE] package `patched v1.0.0` added to the packaged Cargo.lock file, was originally sourced from `[..]/foo/patched`
+[PACKAGED] [..] files, [..] ([..] compressed)
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn outdated_lock_version_change_does_not_warn() {
+ // If the version of the package being packaged changes, but Cargo.lock is
+ // not updated, don't bother warning about it.
+ let p = project()
+ .file("Cargo.toml", &pl_manifest("foo", "0.1.0", ""))
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("generate-lockfile").run();
+
+ p.change_file("Cargo.toml", &pl_manifest("foo", "0.2.0", ""));
+
+ p.cargo("package --no-verify")
+ .with_stderr(
+ "\
+[PACKAGING] foo v0.2.0 ([..])
+[PACKAGED] [..] files, [..] ([..] compressed)
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn no_warn_workspace_extras() {
+ // Other entries in workspace lock file should be ignored.
+ Package::new("dep1", "1.0.0").publish();
+ Package::new("dep2", "1.0.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["a", "b"]
+ "#,
+ )
+ .file(
+ "a/Cargo.toml",
+ &pl_manifest(
+ "a",
+ "0.1.0",
+ r#"
+ [dependencies]
+ dep1 = "1.0"
+ "#,
+ ),
+ )
+ .file("a/src/main.rs", "fn main() {}")
+ .file(
+ "b/Cargo.toml",
+ &pl_manifest(
+ "b",
+ "0.1.0",
+ r#"
+ [dependencies]
+ dep2 = "1.0"
+ "#,
+ ),
+ )
+ .file("b/src/main.rs", "fn main() {}")
+ .build();
+ p.cargo("generate-lockfile").run();
+ p.cargo("package --no-verify")
+ .cwd("a")
+ .with_stderr(
+ "\
+[PACKAGING] a v0.1.0 ([..])
+[UPDATING] `[..]` index
+[PACKAGED] [..] files, [..] ([..] compressed)
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn warn_package_with_yanked() {
+ Package::new("bar", "0.1.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &pl_manifest(
+ "foo",
+ "0.0.1",
+ r#"
+ [dependencies]
+ bar = "0.1"
+ "#,
+ ),
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+ p.cargo("generate-lockfile").run();
+ Package::new("bar", "0.1.0").yanked(true).publish();
+ // Make sure it sticks with the locked (yanked) version.
+ Package::new("bar", "0.1.1").publish();
+ p.cargo("package --no-verify")
+ .with_stderr(
+ "\
+[PACKAGING] foo v0.0.1 ([..])
+[UPDATING] `[..]` index
+[WARNING] package `bar v0.1.0` in Cargo.lock is yanked in registry \
+ `crates-io`, consider updating to a version that is not yanked
+[PACKAGED] [..] files, [..] ([..] compressed)
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn warn_install_with_yanked() {
+ Package::new("bar", "0.1.0").yanked(true).publish();
+ Package::new("bar", "0.1.1").publish();
+ Package::new("foo", "0.1.0")
+ .dep("bar", "0.1")
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "Cargo.lock",
+ r#"
+[[package]]
+name = "bar"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "foo"
+version = "0.1.0"
+dependencies = [
+ "bar 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+ "#,
+ )
+ .publish();
+
+ cargo_process("install --locked foo")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] foo v0.1.0 (registry `[..]`)
+[INSTALLING] foo v0.1.0
+[WARNING] package `bar v0.1.0` in Cargo.lock is yanked in registry \
+ `crates-io`, consider running without --locked
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.1.0 (registry `[..]`)
+[COMPILING] bar v0.1.0
+[COMPILING] foo v0.1.0
+[FINISHED] release [optimized] target(s) in [..]
+[INSTALLING] [..]/.cargo/bin/foo[EXE]
+[INSTALLED] package `foo v0.1.0` (executable `foo[EXE]`)
+[WARNING] be sure to add [..]
+",
+ )
+ .run();
+
+ // Try again without --locked, make sure it uses 0.1.1 and does not warn.
+ cargo_process("install --force foo")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[INSTALLING] foo v0.1.0
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.1.1 (registry `[..]`)
+[COMPILING] bar v0.1.1
+[COMPILING] foo v0.1.0
+[FINISHED] release [optimized] target(s) in [..]
+[REPLACING] [..]/.cargo/bin/foo[EXE]
+[REPLACED] package `foo v0.1.0` with `foo v0.1.0` (executable `foo[EXE]`)
+[WARNING] be sure to add [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn ignore_lockfile() {
+ // With an explicit `include` list, but Cargo.lock in .gitignore, don't
+ // complain about `Cargo.lock` being ignored. Note that it is still
+ // included in the packaged regardless.
+ let p = git::new("foo", |p| {
+ p.file(
+ "Cargo.toml",
+ &pl_manifest(
+ "foo",
+ "0.0.1",
+ r#"
+ include = [
+ "src/main.rs"
+ ]
+ "#,
+ ),
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(".gitignore", "Cargo.lock")
+ });
+ p.cargo("package -l")
+ .with_stdout(
+ "\
+.cargo_vcs_info.json
+Cargo.lock
+Cargo.toml
+Cargo.toml.orig
+src/main.rs
+",
+ )
+ .run();
+ p.cargo("generate-lockfile").run();
+ p.cargo("package -v")
+ .with_stderr(
+ "\
+[PACKAGING] foo v0.0.1 ([..])
+[ARCHIVING] .cargo_vcs_info.json
+[ARCHIVING] Cargo.lock
+[ARCHIVING] Cargo.toml
+[ARCHIVING] Cargo.toml.orig
+[ARCHIVING] src/main.rs
+[VERIFYING] foo v0.0.1 ([..])
+[COMPILING] foo v0.0.1 ([..])
+[RUNNING] `rustc --crate-name foo src/main.rs [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[PACKAGED] 5 files, [..] ([..] compressed)
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn ignore_lockfile_inner() {
+ // Ignore `Cargo.lock` if in .gitignore in a git subdirectory.
+ let p = git::new("foo", |p| {
+ p.no_manifest()
+ .file("bar/Cargo.toml", &pl_manifest("bar", "0.0.1", ""))
+ .file("bar/src/main.rs", "fn main() {}")
+ .file("bar/.gitignore", "Cargo.lock")
+ });
+ p.cargo("generate-lockfile").cwd("bar").run();
+ p.cargo("package -v --no-verify")
+ .cwd("bar")
+ .with_stderr(
+ "\
+[PACKAGING] bar v0.0.1 ([..])
+[ARCHIVING] .cargo_vcs_info.json
+[ARCHIVING] .gitignore
+[ARCHIVING] Cargo.lock
+[ARCHIVING] Cargo.toml
+[ARCHIVING] Cargo.toml.orig
+[ARCHIVING] src/main.rs
+[PACKAGED] 6 files, [..] ([..] compressed)
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn use_workspace_root_lockfile() {
+ // Issue #11148
+ // Workspace members should use `Cargo.lock` at workspace root
+
+ Package::new("serde", "0.2.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+
+ [dependencies]
+ serde = "0.2"
+
+ [workspace]
+ members = ["bar"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "bar"
+ workspace = ".."
+
+ [dependencies]
+ serde = "0.2"
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}")
+ .build();
+
+ // Create `Cargo.lock` in the workspace root.
+ p.cargo("generate-lockfile").run();
+
+ // Now, add a newer version of `serde`.
+ Package::new("serde", "0.2.1").publish();
+
+ // Expect: package `bar` uses `serde v0.2.0` as required by workspace `Cargo.lock`.
+ p.cargo("package --workspace")
+ .with_stderr(
+ "\
+[WARNING] manifest has no documentation, [..]
+See [..]
+[PACKAGING] bar v0.0.1 ([CWD]/bar)
+[UPDATING] `dummy-registry` index
+[VERIFYING] bar v0.0.1 ([CWD]/bar)
+[DOWNLOADING] crates ...
+[DOWNLOADED] serde v0.2.0 ([..])
+[COMPILING] serde v0.2.0
+[COMPILING] bar v0.0.1 ([CWD][..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[PACKAGED] 4 files, [..]
+[WARNING] manifest has no documentation, [..]
+See [..]
+[PACKAGING] foo v0.0.1 ([CWD])
+[VERIFYING] foo v0.0.1 ([CWD])
+[COMPILING] serde v0.2.0
+[COMPILING] foo v0.0.1 ([CWD][..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[PACKAGED] 4 files, [..]
+",
+ )
+ .run();
+
+ let package_path = p.root().join("target/package/foo-0.0.1.crate");
+ assert!(package_path.is_file());
+ let f = File::open(&package_path).unwrap();
+ validate_crate_contents(
+ f,
+ "foo-0.0.1.crate",
+ &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"],
+ &[],
+ );
+
+ let package_path = p.root().join("target/package/bar-0.0.1.crate");
+ assert!(package_path.is_file());
+ let f = File::open(&package_path).unwrap();
+ validate_crate_contents(
+ f,
+ "bar-0.0.1.crate",
+ &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"],
+ &[],
+ );
+}
diff --git a/src/tools/cargo/tests/testsuite/read_manifest.rs b/src/tools/cargo/tests/testsuite/read_manifest.rs
new file mode 100644
index 000000000..b5e9f05a3
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/read_manifest.rs
@@ -0,0 +1,206 @@
+//! Tests for the `cargo read-manifest` command.
+
+use cargo_test_support::{basic_bin_manifest, main_file, project};
+
+fn manifest_output(readme_value: &str) -> String {
+ format!(
+ r#"
+{{
+ "authors": [
+ "wycats@example.com"
+ ],
+ "categories": [],
+ "default_run": null,
+ "name":"foo",
+ "readme": {},
+ "homepage": null,
+ "documentation": null,
+ "repository": null,
+ "rust_version": null,
+ "version":"0.5.0",
+ "id":"foo[..]0.5.0[..](path+file://[..]/foo)",
+ "keywords": [],
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "description": null,
+ "edition": "2015",
+ "source":null,
+ "dependencies":[],
+ "targets":[{{
+ "kind":["bin"],
+ "crate_types":["bin"],
+ "doc": true,
+ "doctest": false,
+ "test": true,
+ "edition": "2015",
+ "name":"foo",
+ "src_path":"[..]/foo/src/foo.rs"
+ }}],
+ "features":{{}},
+ "manifest_path":"[..]Cargo.toml",
+ "metadata": null,
+ "publish": null
+}}"#,
+ readme_value
+ )
+}
+
+fn manifest_output_no_readme() -> String {
+ manifest_output("null")
+}
+
+pub fn basic_bin_manifest_with_readme(name: &str, readme_filename: &str) -> String {
+ format!(
+ r#"
+ [package]
+
+ name = "{}"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+ readme = {}
+
+ [[bin]]
+
+ name = "{}"
+ "#,
+ name, readme_filename, name
+ )
+}
+
+#[cargo_test]
+fn cargo_read_manifest_path_to_cargo_toml_relative() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .build();
+
+ p.cargo("read-manifest --manifest-path foo/Cargo.toml")
+ .cwd(p.root().parent().unwrap())
+ .with_json(&manifest_output_no_readme())
+ .run();
+}
+
+#[cargo_test]
+fn cargo_read_manifest_path_to_cargo_toml_absolute() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .build();
+
+ p.cargo("read-manifest --manifest-path")
+ .arg(p.root().join("Cargo.toml"))
+ .cwd(p.root().parent().unwrap())
+ .with_json(&manifest_output_no_readme())
+ .run();
+}
+
+#[cargo_test]
+fn cargo_read_manifest_path_to_cargo_toml_parent_relative() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .build();
+
+ p.cargo("read-manifest --manifest-path foo")
+ .cwd(p.root().parent().unwrap())
+ .with_status(101)
+ .with_stderr(
+ "[ERROR] the manifest-path must be \
+ a path to a Cargo.toml file",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_read_manifest_path_to_cargo_toml_parent_absolute() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .build();
+
+ p.cargo("read-manifest --manifest-path")
+ .arg(p.root())
+ .cwd(p.root().parent().unwrap())
+ .with_status(101)
+ .with_stderr(
+ "[ERROR] the manifest-path must be \
+ a path to a Cargo.toml file",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cargo_read_manifest_cwd() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .build();
+
+ p.cargo("read-manifest")
+ .with_json(&manifest_output_no_readme())
+ .run();
+}
+
+#[cargo_test]
+fn cargo_read_manifest_with_specified_readme() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &basic_bin_manifest_with_readme("foo", r#""SomeReadme.txt""#),
+ )
+ .file("SomeReadme.txt", "Sample Project")
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .build();
+
+ p.cargo("read-manifest")
+ .with_json(&manifest_output(&format!(r#""{}""#, "SomeReadme.txt")))
+ .run();
+}
+
+#[cargo_test]
+fn cargo_read_manifest_default_readme() {
+ let readme_filenames = ["README.md", "README.txt", "README"];
+
+ for readme in readme_filenames.iter() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file(readme, "Sample project")
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .build();
+
+ p.cargo("read-manifest")
+ .with_json(&manifest_output(&format!(r#""{}""#, readme)))
+ .run();
+ }
+}
+
+#[cargo_test]
+fn cargo_read_manifest_suppress_default_readme() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &basic_bin_manifest_with_readme("foo", "false"),
+ )
+ .file("README.txt", "Sample project")
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .build();
+
+ p.cargo("read-manifest")
+ .with_json(&manifest_output_no_readme())
+ .run();
+}
+
+// If a file named README.md exists, and `readme = true`, the value `README.md` should be defaulted in.
+#[cargo_test]
+fn cargo_read_manifest_defaults_readme_if_true() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest_with_readme("foo", "true"))
+ .file("README.md", "Sample project")
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .build();
+
+ p.cargo("read-manifest")
+ .with_json(&manifest_output(r#""README.md""#))
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/registry.rs b/src/tools/cargo/tests/testsuite/registry.rs
new file mode 100644
index 000000000..05ec9b158
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/registry.rs
@@ -0,0 +1,3406 @@
+//! Tests for normal registry dependencies.
+
+use cargo::core::SourceId;
+use cargo_test_support::cargo_process;
+use cargo_test_support::paths::{self, CargoPathExt};
+use cargo_test_support::registry::{
+ self, registry_path, Dependency, Package, RegistryBuilder, Response, TestRegistry,
+};
+use cargo_test_support::{basic_manifest, project};
+use cargo_test_support::{git, install::cargo_home, t};
+use cargo_util::paths::remove_dir_all;
+use std::fmt::Write;
+use std::fs::{self, File};
+use std::path::Path;
+use std::sync::Arc;
+use std::sync::Mutex;
+
+fn setup_http() -> TestRegistry {
+ RegistryBuilder::new().http_index().build()
+}
+
+#[cargo_test]
+fn test_server_stops() {
+ let server = setup_http();
+ server.join(); // ensure the server fully shuts down
+}
+
+#[cargo_test]
+fn simple_http() {
+ let _server = setup_http();
+ simple();
+}
+
+#[cargo_test]
+fn simple_git() {
+ simple();
+}
+
+fn simple() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = ">= 0.0.0"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("bar", "0.0.1").publish();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.0.1 (registry `dummy-registry`)
+[CHECKING] bar v0.0.1
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s
+",
+ )
+ .run();
+
+ p.cargo("clean").run();
+
+ assert!(paths::home().join(".cargo/registry/CACHEDIR.TAG").is_file());
+
+ // Don't download a second time
+ p.cargo("check")
+ .with_stderr(
+ "\
+[CHECKING] bar v0.0.1
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn deps_http() {
+ let _server = setup_http();
+ deps();
+}
+
+#[cargo_test]
+fn deps_git() {
+ deps();
+}
+
+fn deps() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = ">= 0.0.0"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("baz", "0.0.1").publish();
+ Package::new("bar", "0.0.1").dep("baz", "*").publish();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] [..] v0.0.1 (registry `dummy-registry`)
+[DOWNLOADED] [..] v0.0.1 (registry `dummy-registry`)
+[CHECKING] baz v0.0.1
+[CHECKING] bar v0.0.1
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s
+",
+ )
+ .run();
+
+ assert!(paths::home().join(".cargo/registry/CACHEDIR.TAG").is_file());
+}
+
+#[cargo_test]
+fn nonexistent_http() {
+ let _server = setup_http();
+ nonexistent();
+}
+
+#[cargo_test]
+fn nonexistent_git() {
+ nonexistent();
+}
+
+fn nonexistent() {
+ Package::new("init", "0.0.1").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ nonexistent = ">= 0.0.0"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] [..] index
+error: no matching package named `nonexistent` found
+location searched: registry [..]
+required by package `foo v0.0.1 ([..])`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn wrong_case_http() {
+ let _server = setup_http();
+ wrong_case();
+}
+
+#[cargo_test]
+fn wrong_case_git() {
+ wrong_case();
+}
+
+fn wrong_case() {
+ Package::new("init", "0.0.1").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ Init = ">= 0.0.0"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ // #5678 to make this work
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] [..] index
+error: no matching package found
+searched package name: `Init`
+perhaps you meant: init
+location searched: registry [..]
+required by package `foo v0.0.1 ([..])`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn mis_hyphenated_http() {
+ let _server = setup_http();
+ mis_hyphenated();
+}
+
+#[cargo_test]
+fn mis_hyphenated_git() {
+ mis_hyphenated();
+}
+
+fn mis_hyphenated() {
+ Package::new("mis-hyphenated", "0.0.1").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ mis_hyphenated = ">= 0.0.0"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ // #2775 to make this work
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] [..] index
+error: no matching package found
+searched package name: `mis_hyphenated`
+perhaps you meant: mis-hyphenated
+location searched: registry [..]
+required by package `foo v0.0.1 ([..])`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn wrong_version_http() {
+ let _server = setup_http();
+ wrong_version();
+}
+
+#[cargo_test]
+fn wrong_version_git() {
+ wrong_version();
+}
+
+fn wrong_version() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ foo = ">= 1.0.0"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("foo", "0.0.1").publish();
+ Package::new("foo", "0.0.2").publish();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+error: failed to select a version for the requirement `foo = \">=1.0.0\"`
+candidate versions found which didn't match: 0.0.2, 0.0.1
+location searched: `[..]` index (which is replacing registry `[..]`)
+required by package `foo v0.0.1 ([..])`
+",
+ )
+ .run();
+
+ Package::new("foo", "0.0.3").publish();
+ Package::new("foo", "0.0.4").publish();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+error: failed to select a version for the requirement `foo = \">=1.0.0\"`
+candidate versions found which didn't match: 0.0.4, 0.0.3, 0.0.2, ...
+location searched: `[..]` index (which is replacing registry `[..]`)
+required by package `foo v0.0.1 ([..])`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bad_cksum_http() {
+ let _server = setup_http();
+ bad_cksum();
+}
+
+#[cargo_test]
+fn bad_cksum_git() {
+ bad_cksum();
+}
+
+fn bad_cksum() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bad-cksum = ">= 0.0.0"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ let pkg = Package::new("bad-cksum", "0.0.1");
+ pkg.publish();
+ t!(File::create(&pkg.archive_dst()));
+
+ p.cargo("check -v")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] [..] index
+[DOWNLOADING] crates ...
+[DOWNLOADED] bad-cksum [..]
+[ERROR] failed to download replaced source registry `crates-io`
+
+Caused by:
+ failed to verify the checksum of `bad-cksum v0.0.1 (registry `dummy-registry`)`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn update_registry_http() {
+ let _server = setup_http();
+ update_registry();
+}
+
+#[cargo_test]
+fn update_registry_git() {
+ update_registry();
+}
+
+fn update_registry() {
+ Package::new("init", "0.0.1").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ notyet = ">= 0.0.0"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+error: no matching package named `notyet` found
+location searched: registry `[..]`
+required by package `foo v0.0.1 ([..])`
+",
+ )
+ .run();
+
+ Package::new("notyet", "0.0.1").publish();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] notyet v0.0.1 (registry `dummy-registry`)
+[CHECKING] notyet v0.0.1
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn package_with_path_deps_http() {
+ let _server = setup_http();
+ package_with_path_deps();
+}
+
+#[cargo_test]
+fn package_with_path_deps_git() {
+ package_with_path_deps();
+}
+
+fn package_with_path_deps() {
+ Package::new("init", "0.0.1").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ repository = "bar"
+
+ [dependencies.notyet]
+ version = "0.0.1"
+ path = "notyet"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("notyet/Cargo.toml", &basic_manifest("notyet", "0.0.1"))
+ .file("notyet/src/lib.rs", "")
+ .build();
+
+ p.cargo("package")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+[PACKAGING] foo [..]
+[UPDATING] [..]
+[ERROR] failed to prepare local package for uploading
+
+Caused by:
+ no matching package named `notyet` found
+ location searched: registry `crates-io`
+ required by package `foo v0.0.1 [..]`
+",
+ )
+ .run();
+
+ Package::new("notyet", "0.0.1").publish();
+
+ p.cargo("package")
+ .with_stderr(
+ "\
+[PACKAGING] foo v0.0.1 ([CWD])
+[UPDATING] `[..]` index
+[VERIFYING] foo v0.0.1 ([CWD])
+[DOWNLOADING] crates ...
+[DOWNLOADED] notyet v0.0.1 (registry `dummy-registry`)
+[COMPILING] notyet v0.0.1
+[COMPILING] foo v0.0.1 ([CWD][..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s
+[PACKAGED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn lockfile_locks_http() {
+ let _server = setup_http();
+ lockfile_locks();
+}
+
+#[cargo_test]
+fn lockfile_locks_git() {
+ lockfile_locks();
+}
+
+fn lockfile_locks() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "*"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("bar", "0.0.1").publish();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.0.1 (registry `dummy-registry`)
+[CHECKING] bar v0.0.1
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s
+",
+ )
+ .run();
+
+ p.root().move_into_the_past();
+ Package::new("bar", "0.0.2").publish();
+
+ p.cargo("check").with_stdout("").run();
+}
+
+#[cargo_test]
+fn lockfile_locks_transitively_http() {
+ let _server = setup_http();
+ lockfile_locks_transitively();
+}
+
+#[cargo_test]
+fn lockfile_locks_transitively_git() {
+ lockfile_locks_transitively();
+}
+
+fn lockfile_locks_transitively() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "*"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("baz", "0.0.1").publish();
+ Package::new("bar", "0.0.1").dep("baz", "*").publish();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] [..] v0.0.1 (registry `dummy-registry`)
+[DOWNLOADED] [..] v0.0.1 (registry `dummy-registry`)
+[CHECKING] baz v0.0.1
+[CHECKING] bar v0.0.1
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s
+",
+ )
+ .run();
+
+ p.root().move_into_the_past();
+ Package::new("baz", "0.0.2").publish();
+ Package::new("bar", "0.0.2").dep("baz", "*").publish();
+
+ p.cargo("check").with_stdout("").run();
+}
+
+#[cargo_test]
+fn yanks_are_not_used_http() {
+ let _server = setup_http();
+ yanks_are_not_used();
+}
+
+#[cargo_test]
+fn yanks_are_not_used_git() {
+ yanks_are_not_used();
+}
+
+fn yanks_are_not_used() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "*"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("baz", "0.0.1").publish();
+ Package::new("baz", "0.0.2").yanked(true).publish();
+ Package::new("bar", "0.0.1").dep("baz", "*").publish();
+ Package::new("bar", "0.0.2")
+ .dep("baz", "*")
+ .yanked(true)
+ .publish();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] [..] v0.0.1 (registry `dummy-registry`)
+[DOWNLOADED] [..] v0.0.1 (registry `dummy-registry`)
+[CHECKING] baz v0.0.1
+[CHECKING] bar v0.0.1
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn relying_on_a_yank_is_bad_http() {
+ let _server = setup_http();
+ relying_on_a_yank_is_bad();
+}
+
+#[cargo_test]
+fn relying_on_a_yank_is_bad_git() {
+ relying_on_a_yank_is_bad();
+}
+
+fn relying_on_a_yank_is_bad() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "*"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("baz", "0.0.1").publish();
+ Package::new("baz", "0.0.2").yanked(true).publish();
+ Package::new("bar", "0.0.1").dep("baz", "=0.0.2").publish();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+error: failed to select a version for the requirement `baz = \"=0.0.2\"`
+candidate versions found which didn't match: 0.0.1
+location searched: `[..]` index (which is replacing registry `[..]`)
+required by package `bar v0.0.1`
+ ... which satisfies dependency `bar = \"*\"` of package `foo [..]`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn yanks_in_lockfiles_are_ok_http() {
+ let _server = setup_http();
+ yanks_in_lockfiles_are_ok();
+}
+
+#[cargo_test]
+fn yanks_in_lockfiles_are_ok_git() {
+ yanks_in_lockfiles_are_ok();
+}
+
+fn yanks_in_lockfiles_are_ok() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "*"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("bar", "0.0.1").publish();
+
+ p.cargo("check").run();
+
+ registry_path().join("3").rm_rf();
+
+ Package::new("bar", "0.0.1").yanked(true).publish();
+
+ p.cargo("check").with_stdout("").run();
+
+ p.cargo("update")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+error: no matching package named `bar` found
+location searched: registry [..]
+required by package `foo v0.0.1 ([..])`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn yanks_in_lockfiles_are_ok_for_other_update_http() {
+ let _server = setup_http();
+ yanks_in_lockfiles_are_ok_for_other_update();
+}
+
+#[cargo_test]
+fn yanks_in_lockfiles_are_ok_for_other_update_git() {
+ yanks_in_lockfiles_are_ok_for_other_update();
+}
+
+fn yanks_in_lockfiles_are_ok_for_other_update() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "*"
+ baz = "*"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("bar", "0.0.1").publish();
+ Package::new("baz", "0.0.1").publish();
+
+ p.cargo("check").run();
+
+ registry_path().join("3").rm_rf();
+
+ Package::new("bar", "0.0.1").yanked(true).publish();
+ Package::new("baz", "0.0.1").publish();
+
+ p.cargo("check").with_stdout("").run();
+
+ Package::new("baz", "0.0.2").publish();
+
+ p.cargo("update")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+error: no matching package named `bar` found
+location searched: registry [..]
+required by package `foo v0.0.1 ([..])`
+",
+ )
+ .run();
+
+ p.cargo("update -p baz")
+ .with_stderr_contains(
+ "\
+[UPDATING] `[..]` index
+[UPDATING] baz v0.0.1 -> v0.0.2
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn yanks_in_lockfiles_are_ok_with_new_dep_http() {
+ let _server = setup_http();
+ yanks_in_lockfiles_are_ok_with_new_dep();
+}
+
+#[cargo_test]
+fn yanks_in_lockfiles_are_ok_with_new_dep_git() {
+ yanks_in_lockfiles_are_ok_with_new_dep();
+}
+
+fn yanks_in_lockfiles_are_ok_with_new_dep() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "*"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("bar", "0.0.1").publish();
+
+ p.cargo("check").run();
+
+ registry_path().join("3").rm_rf();
+
+ Package::new("bar", "0.0.1").yanked(true).publish();
+ Package::new("baz", "0.0.1").publish();
+
+ p.change_file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "*"
+ baz = "*"
+ "#,
+ );
+
+ p.cargo("check").with_stdout("").run();
+}
+
+#[cargo_test]
+fn update_with_lockfile_if_packages_missing_http() {
+ let _server = setup_http();
+ update_with_lockfile_if_packages_missing();
+}
+
+#[cargo_test]
+fn update_with_lockfile_if_packages_missing_git() {
+ update_with_lockfile_if_packages_missing();
+}
+
+fn update_with_lockfile_if_packages_missing() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "*"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("bar", "0.0.1").publish();
+ p.cargo("check").run();
+ p.root().move_into_the_past();
+
+ paths::home().join(".cargo/registry").rm_rf();
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.0.1 (registry `dummy-registry`)
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn update_lockfile_http() {
+ let _server = setup_http();
+ update_lockfile();
+}
+
+#[cargo_test]
+fn update_lockfile_git() {
+ update_lockfile();
+}
+
+fn update_lockfile() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "*"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ println!("0.0.1");
+ Package::new("bar", "0.0.1").publish();
+ p.cargo("check").run();
+
+ Package::new("bar", "0.0.2").publish();
+ Package::new("bar", "0.0.3").publish();
+ paths::home().join(".cargo/registry").rm_rf();
+ println!("0.0.2 update");
+ p.cargo("update -p bar --precise 0.0.2")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[UPDATING] bar v0.0.1 -> v0.0.2
+",
+ )
+ .run();
+
+ println!("0.0.2 build");
+ p.cargo("check")
+ .with_stderr(
+ "\
+[DOWNLOADING] crates ...
+[DOWNLOADED] [..] v0.0.2 (registry `dummy-registry`)
+[CHECKING] bar v0.0.2
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s
+",
+ )
+ .run();
+
+ println!("0.0.3 update");
+ p.cargo("update -p bar")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[UPDATING] bar v0.0.2 -> v0.0.3
+",
+ )
+ .run();
+
+ println!("0.0.3 build");
+ p.cargo("check")
+ .with_stderr(
+ "\
+[DOWNLOADING] crates ...
+[DOWNLOADED] [..] v0.0.3 (registry `dummy-registry`)
+[CHECKING] bar v0.0.3
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s
+",
+ )
+ .run();
+
+ println!("new dependencies update");
+ Package::new("bar", "0.0.4").dep("spam", "0.2.5").publish();
+ Package::new("spam", "0.2.5").publish();
+ p.cargo("update -p bar")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[UPDATING] bar v0.0.3 -> v0.0.4
+[ADDING] spam v0.2.5
+",
+ )
+ .run();
+
+ println!("new dependencies update");
+ Package::new("bar", "0.0.5").publish();
+ p.cargo("update -p bar")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[UPDATING] bar v0.0.4 -> v0.0.5
+[REMOVING] spam v0.2.5
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn dev_dependency_not_used_http() {
+ let _server = setup_http();
+ dev_dependency_not_used();
+}
+
+#[cargo_test]
+fn dev_dependency_not_used_git() {
+ dev_dependency_not_used();
+}
+
+fn dev_dependency_not_used() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "*"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("baz", "0.0.1").publish();
+ Package::new("bar", "0.0.1").dev_dep("baz", "*").publish();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] [..] v0.0.1 (registry `dummy-registry`)
+[CHECKING] bar v0.0.1
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bad_license_file_http() {
+ let registry = setup_http();
+ bad_license_file(&registry);
+}
+
+#[cargo_test]
+fn bad_license_file_git() {
+ let registry = registry::init();
+ bad_license_file(&registry);
+}
+
+fn bad_license_file(registry: &TestRegistry) {
+ Package::new("foo", "1.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license-file = "foo"
+ description = "bar"
+ repository = "baz"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+ p.cargo("publish -v")
+ .replace_crates_io(registry.index_url())
+ .with_status(101)
+ .with_stderr_contains("[ERROR] the license file `foo` does not exist")
+ .run();
+}
+
+#[cargo_test]
+fn updating_a_dep_http() {
+ let _server = setup_http();
+ updating_a_dep();
+}
+
+#[cargo_test]
+fn updating_a_dep_git() {
+ updating_a_dep();
+}
+
+fn updating_a_dep() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.a]
+ path = "a"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "*"
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .build();
+
+ Package::new("bar", "0.0.1").publish();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.0.1 (registry `dummy-registry`)
+[CHECKING] bar v0.0.1
+[CHECKING] a v0.0.1 ([CWD]/a)
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s
+",
+ )
+ .run();
+ assert!(paths::home().join(".cargo/registry/CACHEDIR.TAG").is_file());
+
+ // Now delete the CACHEDIR.TAG file: this is the situation we'll be in after
+ // upgrading from a version of Cargo that doesn't mark this directory, to one that
+ // does. It should be recreated.
+ fs::remove_file(paths::home().join(".cargo/registry/CACHEDIR.TAG"))
+ .expect("remove CACHEDIR.TAG");
+
+ p.change_file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ );
+ Package::new("bar", "0.1.0").publish();
+
+ println!("second");
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.1.0 (registry `dummy-registry`)
+[CHECKING] bar v0.1.0
+[CHECKING] a v0.0.1 ([CWD]/a)
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s
+",
+ )
+ .run();
+
+ assert!(
+ paths::home().join(".cargo/registry/CACHEDIR.TAG").is_file(),
+ "CACHEDIR.TAG recreated in existing registry"
+ );
+}
+
+#[cargo_test]
+fn git_and_registry_dep_http() {
+ let _server = setup_http();
+ git_and_registry_dep();
+}
+
+#[cargo_test]
+fn git_and_registry_dep_git() {
+ git_and_registry_dep();
+}
+
+fn git_and_registry_dep() {
+ let b = git::repo(&paths::root().join("b"))
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ a = "0.0.1"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ a = "0.0.1"
+
+ [dependencies.b]
+ git = '{}'
+ "#,
+ b.url()
+ ),
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("a", "0.0.1").publish();
+
+ p.root().move_into_the_past();
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[UPDATING] [..]
+[DOWNLOADING] crates ...
+[DOWNLOADED] a v0.0.1 (registry `dummy-registry`)
+[CHECKING] a v0.0.1
+[CHECKING] b v0.0.1 ([..])
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s
+",
+ )
+ .run();
+ p.root().move_into_the_past();
+
+ println!("second");
+ p.cargo("check").with_stdout("").run();
+}
+
+#[cargo_test]
+fn update_publish_then_update_http() {
+ let _server = setup_http();
+ update_publish_then_update();
+}
+
+#[cargo_test]
+fn update_publish_then_update_git() {
+ update_publish_then_update();
+}
+
+fn update_publish_then_update() {
+ // First generate a Cargo.lock and a clone of the registry index at the
+ // "head" of the current registry.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ a = "0.1.0"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+ Package::new("a", "0.1.0").publish();
+ p.cargo("build").run();
+
+ // Next, publish a new package and back up the copy of the registry we just
+ // created.
+ Package::new("a", "0.1.1").publish();
+ let registry = paths::home().join(".cargo/registry");
+ let backup = paths::root().join("registry-backup");
+ t!(fs::rename(&registry, &backup));
+
+ // Generate a Cargo.lock with the newer version, and then move the old copy
+ // of the registry back into place.
+ let p2 = project()
+ .at("foo2")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ a = "0.1.1"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+ p2.cargo("build").run();
+ registry.rm_rf();
+ t!(fs::rename(&backup, &registry));
+ t!(fs::rename(
+ p2.root().join("Cargo.lock"),
+ p.root().join("Cargo.lock")
+ ));
+
+ // Finally, build the first project again (with our newer Cargo.lock) which
+ // should force an update of the old registry, download the new crate, and
+ // then build everything again.
+ p.cargo("build")
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[DOWNLOADING] crates ...
+[DOWNLOADED] a v0.1.1 (registry `dummy-registry`)
+[COMPILING] a v0.1.1
+[COMPILING] foo v0.5.0 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn fetch_downloads_http() {
+ let _server = setup_http();
+ fetch_downloads();
+}
+
+#[cargo_test]
+fn fetch_downloads_git() {
+ fetch_downloads();
+}
+
+fn fetch_downloads() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ a = "0.1.0"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("a", "0.1.0").publish();
+
+ p.cargo("fetch")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] a v0.1.0 (registry [..])
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn update_transitive_dependency_http() {
+ let _server = setup_http();
+ update_transitive_dependency();
+}
+
+#[cargo_test]
+fn update_transitive_dependency_git() {
+ update_transitive_dependency();
+}
+
+fn update_transitive_dependency() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ a = "0.1.0"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("a", "0.1.0").dep("b", "*").publish();
+ Package::new("b", "0.1.0").publish();
+
+ p.cargo("fetch").run();
+
+ Package::new("b", "0.1.1").publish();
+
+ p.cargo("update -pb")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[UPDATING] b v0.1.0 -> v0.1.1
+",
+ )
+ .run();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[DOWNLOADING] crates ...
+[DOWNLOADED] b v0.1.1 (registry `dummy-registry`)
+[CHECKING] b v0.1.1
+[CHECKING] a v0.1.0
+[CHECKING] foo v0.5.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn update_backtracking_ok_http() {
+ let _server = setup_http();
+ update_backtracking_ok();
+}
+
+#[cargo_test]
+fn update_backtracking_ok_git() {
+ update_backtracking_ok();
+}
+
+fn update_backtracking_ok() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ webdriver = "0.1"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("webdriver", "0.1.0")
+ .dep("hyper", "0.6")
+ .publish();
+ Package::new("hyper", "0.6.5")
+ .dep("openssl", "0.1")
+ .dep("cookie", "0.1")
+ .publish();
+ Package::new("cookie", "0.1.0")
+ .dep("openssl", "0.1")
+ .publish();
+ Package::new("openssl", "0.1.0").publish();
+
+ p.cargo("generate-lockfile").run();
+
+ Package::new("openssl", "0.1.1").publish();
+ Package::new("hyper", "0.6.6")
+ .dep("openssl", "0.1.1")
+ .dep("cookie", "0.1.0")
+ .publish();
+
+ p.cargo("update -p hyper")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[UPDATING] hyper v0.6.5 -> v0.6.6
+[UPDATING] openssl v0.1.0 -> v0.1.1
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn update_multiple_packages_http() {
+ let _server = setup_http();
+ update_multiple_packages();
+}
+
+#[cargo_test]
+fn update_multiple_packages_git() {
+ update_multiple_packages();
+}
+
+fn update_multiple_packages() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ a = "*"
+ b = "*"
+ c = "*"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("a", "0.1.0").publish();
+ Package::new("b", "0.1.0").publish();
+ Package::new("c", "0.1.0").publish();
+
+ p.cargo("fetch").run();
+
+ Package::new("a", "0.1.1").publish();
+ Package::new("b", "0.1.1").publish();
+ Package::new("c", "0.1.1").publish();
+
+ p.cargo("update -pa -pb")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[UPDATING] a v0.1.0 -> v0.1.1
+[UPDATING] b v0.1.0 -> v0.1.1
+",
+ )
+ .run();
+
+ p.cargo("update -pb -pc")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[UPDATING] c v0.1.0 -> v0.1.1
+",
+ )
+ .run();
+
+ p.cargo("check")
+ .with_stderr_contains("[DOWNLOADED] a v0.1.1 (registry `dummy-registry`)")
+ .with_stderr_contains("[DOWNLOADED] b v0.1.1 (registry `dummy-registry`)")
+ .with_stderr_contains("[DOWNLOADED] c v0.1.1 (registry `dummy-registry`)")
+ .with_stderr_contains("[CHECKING] a v0.1.1")
+ .with_stderr_contains("[CHECKING] b v0.1.1")
+ .with_stderr_contains("[CHECKING] c v0.1.1")
+ .with_stderr_contains("[CHECKING] foo v0.5.0 ([..])")
+ .run();
+}
+
+#[cargo_test]
+fn bundled_crate_in_registry_http() {
+ let _server = setup_http();
+ bundled_crate_in_registry();
+}
+
+#[cargo_test]
+fn bundled_crate_in_registry_git() {
+ bundled_crate_in_registry();
+}
+
+fn bundled_crate_in_registry() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ bar = "0.1"
+ baz = "0.1"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("bar", "0.1.0").publish();
+ Package::new("baz", "0.1.0")
+ .dep("bar", "0.1.0")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "baz"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ bar = { path = "bar", version = "0.1.0" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "")
+ .publish();
+
+ p.cargo("run").run();
+}
+
+#[cargo_test]
+fn update_same_prefix_oh_my_how_was_this_a_bug_http() {
+ let _server = setup_http();
+ update_same_prefix_oh_my_how_was_this_a_bug();
+}
+
+#[cargo_test]
+fn update_same_prefix_oh_my_how_was_this_a_bug_git() {
+ update_same_prefix_oh_my_how_was_this_a_bug();
+}
+
+fn update_same_prefix_oh_my_how_was_this_a_bug() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "ugh"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ foo = "0.1"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("foobar", "0.2.0").publish();
+ Package::new("foo", "0.1.0")
+ .dep("foobar", "0.2.0")
+ .publish();
+
+ p.cargo("generate-lockfile").run();
+ p.cargo("update -pfoobar --precise=0.2.0").run();
+}
+
+#[cargo_test]
+fn use_semver_http() {
+ let _server = setup_http();
+ use_semver();
+}
+
+#[cargo_test]
+fn use_semver_git() {
+ use_semver();
+}
+
+fn use_semver() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ foo = "1.2.3-alpha.0"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("foo", "1.2.3-alpha.0").publish();
+
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn use_semver_package_incorrectly_http() {
+ let _server = setup_http();
+ use_semver_package_incorrectly();
+}
+
+#[cargo_test]
+fn use_semver_package_incorrectly_git() {
+ use_semver_package_incorrectly();
+}
+
+fn use_semver_package_incorrectly() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["a", "b"]
+ "#,
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.1-alpha.0"
+ authors = []
+ "#,
+ )
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ a = { version = "^0.1", path = "../a" }
+ "#,
+ )
+ .file("a/src/main.rs", "fn main() {}")
+ .file("b/src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: no matching package found
+searched package name: `a`
+prerelease package needs to be specified explicitly
+a = { version = \"0.1.1-alpha.0\" }
+location searched: [..]
+required by package `b v0.1.0 ([..])`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn only_download_relevant_http() {
+ let _server = setup_http();
+ only_download_relevant();
+}
+
+#[cargo_test]
+fn only_download_relevant_git() {
+ only_download_relevant();
+}
+
+fn only_download_relevant() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.5.0"
+ authors = []
+
+ [target.foo.dependencies]
+ foo = "*"
+ [dev-dependencies]
+ bar = "*"
+ [dependencies]
+ baz = "*"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("foo", "0.1.0").publish();
+ Package::new("bar", "0.1.0").publish();
+ Package::new("baz", "0.1.0").publish();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] baz v0.1.0 ([..])
+[CHECKING] baz v0.1.0
+[CHECKING] bar v0.5.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn resolve_and_backtracking_http() {
+ let _server = setup_http();
+ resolve_and_backtracking();
+}
+
+#[cargo_test]
+fn resolve_and_backtracking_git() {
+ resolve_and_backtracking();
+}
+
+fn resolve_and_backtracking() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ foo = "*"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("foo", "0.1.1")
+ .feature_dep("bar", "0.1", &["a", "b"])
+ .publish();
+ Package::new("foo", "0.1.0").publish();
+
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn upstream_warnings_on_extra_verbose_http() {
+ let _server = setup_http();
+ upstream_warnings_on_extra_verbose();
+}
+
+#[cargo_test]
+fn upstream_warnings_on_extra_verbose_git() {
+ upstream_warnings_on_extra_verbose();
+}
+
+fn upstream_warnings_on_extra_verbose() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ foo = "*"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("foo", "0.1.0")
+ .file("src/lib.rs", "fn unused() {}")
+ .publish();
+
+ p.cargo("check -vv")
+ .with_stderr_contains("[WARNING] [..]unused[..]")
+ .run();
+}
+
+#[cargo_test]
+fn disallow_network_http() {
+ let _server = setup_http();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ foo = "*"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check --frozen")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[ERROR] failed to get `foo` as a dependency of package `bar v0.5.0 ([..])`
+
+Caused by:
+ failed to query replaced source registry `crates-io`
+
+Caused by:
+ attempting to make an HTTP request, but --frozen was specified
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn disallow_network_git() {
+ let _server = RegistryBuilder::new().build();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ foo = "*"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check --frozen")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to get `foo` as a dependency of package `bar v0.5.0 [..]`
+
+Caused by:
+ failed to load source for dependency `foo`
+
+Caused by:
+ Unable to update registry `crates-io`
+
+Caused by:
+ failed to update replaced source registry `crates-io`
+
+Caused by:
+ attempting to make an HTTP request, but --frozen was specified
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn add_dep_dont_update_registry_http() {
+ let _server = setup_http();
+ add_dep_dont_update_registry();
+}
+
+#[cargo_test]
+fn add_dep_dont_update_registry_git() {
+ add_dep_dont_update_registry();
+}
+
+fn add_dep_dont_update_registry() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ baz = { path = "baz" }
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "baz/Cargo.toml",
+ r#"
+ [package]
+ name = "baz"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ remote = "0.3"
+ "#,
+ )
+ .file("baz/src/lib.rs", "")
+ .build();
+
+ Package::new("remote", "0.3.4").publish();
+
+ p.cargo("check").run();
+
+ p.change_file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ baz = { path = "baz" }
+ remote = "0.3"
+ "#,
+ );
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[CHECKING] bar v0.5.0 ([..])
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bump_version_dont_update_registry_http() {
+ let _server = setup_http();
+ bump_version_dont_update_registry();
+}
+
+#[cargo_test]
+fn bump_version_dont_update_registry_git() {
+ bump_version_dont_update_registry();
+}
+
+fn bump_version_dont_update_registry() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ baz = { path = "baz" }
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "baz/Cargo.toml",
+ r#"
+ [package]
+ name = "baz"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ remote = "0.3"
+ "#,
+ )
+ .file("baz/src/lib.rs", "")
+ .build();
+
+ Package::new("remote", "0.3.4").publish();
+
+ p.cargo("check").run();
+
+ p.change_file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.6.0"
+ authors = []
+
+ [dependencies]
+ baz = { path = "baz" }
+ "#,
+ );
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[CHECKING] bar v0.6.0 ([..])
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn toml_lies_but_index_is_truth_http() {
+ let _server = setup_http();
+ toml_lies_but_index_is_truth();
+}
+
+#[cargo_test]
+fn toml_lies_but_index_is_truth_git() {
+ toml_lies_but_index_is_truth();
+}
+
+fn toml_lies_but_index_is_truth() {
+ Package::new("foo", "0.2.0").publish();
+ Package::new("bar", "0.3.0")
+ .dep("foo", "0.2.0")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.3.0"
+ authors = []
+
+ [dependencies]
+ foo = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "extern crate foo;")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ bar = "0.3"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check -v").run();
+}
+
+#[cargo_test]
+fn vv_prints_warnings_http() {
+ let _server = setup_http();
+ vv_prints_warnings();
+}
+
+#[cargo_test]
+fn vv_prints_warnings_git() {
+ vv_prints_warnings();
+}
+
+fn vv_prints_warnings() {
+ Package::new("foo", "0.2.0")
+ .file(
+ "src/lib.rs",
+ "#![deny(warnings)] fn foo() {} // unused function",
+ )
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "fo"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ foo = "0.2"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check -vv").run();
+}
+
+#[cargo_test]
+fn bad_and_or_malicious_packages_rejected_http() {
+ let _server = setup_http();
+ bad_and_or_malicious_packages_rejected();
+}
+
+#[cargo_test]
+fn bad_and_or_malicious_packages_rejected_git() {
+ bad_and_or_malicious_packages_rejected();
+}
+
+fn bad_and_or_malicious_packages_rejected() {
+ Package::new("foo", "0.2.0")
+ .extra_file("foo-0.1.0/src/lib.rs", "")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "fo"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ foo = "0.2"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check -vv")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[DOWNLOADING] crates ...
+[DOWNLOADED] [..]
+error: failed to download [..]
+
+Caused by:
+ failed to unpack [..]
+
+Caused by:
+ [..] contains a file at \"foo-0.1.0/src/lib.rs\" which isn't under \"foo-0.2.0\"
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn git_init_templatedir_missing_http() {
+ let _server = setup_http();
+ git_init_templatedir_missing();
+}
+
+#[cargo_test]
+fn git_init_templatedir_missing_git() {
+ git_init_templatedir_missing();
+}
+
+fn git_init_templatedir_missing() {
+ Package::new("foo", "0.2.0").dep("bar", "*").publish();
+ Package::new("bar", "0.2.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "fo"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ foo = "0.2"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check").run();
+
+ remove_dir_all(paths::home().join(".cargo/registry")).unwrap();
+ fs::write(
+ paths::home().join(".gitconfig"),
+ r#"
+ [init]
+ templatedir = nowhere
+ "#,
+ )
+ .unwrap();
+
+ p.cargo("check").run();
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn rename_deps_and_features_http() {
+ let _server = setup_http();
+ rename_deps_and_features();
+}
+
+#[cargo_test]
+fn rename_deps_and_features_git() {
+ rename_deps_and_features();
+}
+
+fn rename_deps_and_features() {
+ Package::new("foo", "0.1.0")
+ .file("src/lib.rs", "pub fn f1() {}")
+ .publish();
+ Package::new("foo", "0.2.0")
+ .file("src/lib.rs", "pub fn f2() {}")
+ .publish();
+ Package::new("bar", "0.2.0")
+ .add_dep(
+ Dependency::new("foo01", "0.1.0")
+ .package("foo")
+ .optional(true),
+ )
+ .add_dep(Dependency::new("foo02", "0.2.0").package("foo"))
+ .feature("another", &["foo01"])
+ .file(
+ "src/lib.rs",
+ r#"
+ extern crate foo02;
+ #[cfg(feature = "foo01")]
+ extern crate foo01;
+
+ pub fn foo() {
+ foo02::f2();
+ #[cfg(feature = "foo01")]
+ foo01::f1();
+ }
+ "#,
+ )
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ bar = "0.2"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ "
+ extern crate bar;
+ fn main() { bar::foo(); }
+ ",
+ )
+ .build();
+
+ p.cargo("check").run();
+ p.cargo("check --features bar/foo01").run();
+ p.cargo("check --features bar/another").run();
+}
+
+#[cargo_test]
+fn ignore_invalid_json_lines_http() {
+ let _server = setup_http();
+ ignore_invalid_json_lines();
+}
+
+#[cargo_test]
+fn ignore_invalid_json_lines_git() {
+ ignore_invalid_json_lines();
+}
+
+fn ignore_invalid_json_lines() {
+ Package::new("foo", "0.1.0").publish();
+ Package::new("foo", "0.1.1").invalid_json(true).publish();
+ Package::new("foo", "0.2.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ foo = '0.1.0'
+ foo02 = { version = '0.2.0', package = 'foo' }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn readonly_registry_still_works_http() {
+ let _server = setup_http();
+ readonly_registry_still_works();
+}
+
+#[cargo_test]
+fn readonly_registry_still_works_git() {
+ readonly_registry_still_works();
+}
+
+fn readonly_registry_still_works() {
+ Package::new("foo", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ foo = '0.1.0'
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("generate-lockfile").run();
+ p.cargo("fetch --locked").run();
+ chmod_readonly(&paths::home(), true);
+ p.cargo("check").run();
+ // make sure we un-readonly the files afterwards so "cargo clean" can remove them (#6934)
+ chmod_readonly(&paths::home(), false);
+
+ fn chmod_readonly(path: &Path, readonly: bool) {
+ for entry in t!(path.read_dir()) {
+ let entry = t!(entry);
+ let path = entry.path();
+ if t!(entry.file_type()).is_dir() {
+ chmod_readonly(&path, readonly);
+ } else {
+ set_readonly(&path, readonly);
+ }
+ }
+ set_readonly(path, readonly);
+ }
+
+ fn set_readonly(path: &Path, readonly: bool) {
+ let mut perms = t!(path.metadata()).permissions();
+ perms.set_readonly(readonly);
+ t!(fs::set_permissions(path, perms));
+ }
+}
+
+#[cargo_test]
+fn registry_index_rejected_http() {
+ let _server = setup_http();
+ registry_index_rejected();
+}
+
+#[cargo_test]
+fn registry_index_rejected_git() {
+ registry_index_rejected();
+}
+
+fn registry_index_rejected() {
+ Package::new("dep", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ ".cargo/config",
+ r#"
+ [registry]
+ index = "https://example.com/"
+ "#,
+ )
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ dep = "0.1"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]/foo/Cargo.toml`
+
+Caused by:
+ the `registry.index` config value is no longer supported
+ Use `[source]` replacement to alter the default index for crates.io.
+",
+ )
+ .run();
+
+ p.cargo("login")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] the `registry.index` config value is no longer supported
+Use `[source]` replacement to alter the default index for crates.io.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn package_lock_inside_package_is_overwritten() {
+ let registry = registry::init();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = ">= 0.0.0"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("bar", "0.0.1")
+ .file("src/lib.rs", "")
+ .file(".cargo-ok", "")
+ .publish();
+
+ p.cargo("check").run();
+
+ let id = SourceId::for_registry(registry.index_url()).unwrap();
+ let hash = cargo::util::hex::short_hash(&id);
+ let ok = cargo_home()
+ .join("registry")
+ .join("src")
+ .join(format!("-{}", hash))
+ .join("bar-0.0.1")
+ .join(".cargo-ok");
+
+ assert_eq!(ok.metadata().unwrap().len(), 2);
+}
+
+#[cargo_test]
+fn package_lock_as_a_symlink_inside_package_is_overwritten() {
+ let registry = registry::init();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = ">= 0.0.0"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("bar", "0.0.1")
+ .file("src/lib.rs", "pub fn f() {}")
+ .symlink(".cargo-ok", "src/lib.rs")
+ .publish();
+
+ p.cargo("check").run();
+
+ let id = SourceId::for_registry(registry.index_url()).unwrap();
+ let hash = cargo::util::hex::short_hash(&id);
+ let pkg_root = cargo_home()
+ .join("registry")
+ .join("src")
+ .join(format!("-{}", hash))
+ .join("bar-0.0.1");
+ let ok = pkg_root.join(".cargo-ok");
+ let librs = pkg_root.join("src/lib.rs");
+
+ // Is correctly overwritten and doesn't affect the file linked to
+ assert_eq!(ok.metadata().unwrap().len(), 2);
+ assert_eq!(fs::read_to_string(librs).unwrap(), "pub fn f() {}");
+}
+
+#[cargo_test]
+fn ignores_unknown_index_version_http() {
+ let _server = setup_http();
+ ignores_unknown_index_version();
+}
+
+#[cargo_test]
+fn ignores_unknown_index_version_git() {
+ ignores_unknown_index_version();
+}
+
+fn ignores_unknown_index_version() {
+ // If the version field is not understood, it is ignored.
+ Package::new("bar", "1.0.0").publish();
+ Package::new("bar", "1.0.1").schema_version(9999).publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("tree")
+ .with_stdout(
+ "foo v0.1.0 [..]\n\
+ └── bar v1.0.0\n\
+ ",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn protocol() {
+ cargo_process("install bar")
+ .with_status(101)
+ .env("CARGO_REGISTRIES_CRATES_IO_PROTOCOL", "invalid")
+ .with_stderr("[ERROR] unsupported registry protocol `invalid` (defined in environment variable `CARGO_REGISTRIES_CRATES_IO_PROTOCOL`)")
+ .run()
+}
+
+#[cargo_test]
+fn http_requires_trailing_slash() {
+ cargo_process("install bar --index sparse+https://invalid.crates.io/test")
+ .with_status(101)
+ .with_stderr("[ERROR] sparse registry url must end in a slash `/`: sparse+https://invalid.crates.io/test")
+ .run()
+}
+
+// Limit the test to debug builds so that `__CARGO_TEST_MAX_UNPACK_SIZE` will take affect.
+#[cfg(debug_assertions)]
+#[cargo_test]
+fn reach_max_unpack_size() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies]
+ bar = ">= 0.0.0"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ // Size of bar.crate is around 180 bytes.
+ Package::new("bar", "0.0.1").publish();
+
+ p.cargo("check")
+ .env("__CARGO_TEST_MAX_UNPACK_SIZE", "8") // hit 8 bytes limit and boom!
+ .env("__CARGO_TEST_MAX_UNPACK_RATIO", "0")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.0.1 (registry `dummy-registry`)
+[ERROR] failed to download replaced source registry `crates-io`
+
+Caused by:
+ failed to unpack package `bar v0.0.1 (registry `dummy-registry`)`
+
+Caused by:
+ failed to iterate over archive
+
+Caused by:
+ maximum limit reached when reading
+",
+ )
+ .run();
+
+ // Restore to the default ratio and it should compile.
+ p.cargo("check")
+ .env("__CARGO_TEST_MAX_UNPACK_SIZE", "8")
+ .with_stderr(
+ "\
+[CHECKING] bar v0.0.1
+[CHECKING] foo v0.0.1 ([..])
+[FINISHED] dev [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn sparse_retry_single() {
+ let fail_count = Mutex::new(0);
+ let _registry = RegistryBuilder::new()
+ .http_index()
+ .add_responder("/index/3/b/bar", move |req, server| {
+ let mut fail_count = fail_count.lock().unwrap();
+ if *fail_count < 2 {
+ *fail_count += 1;
+ server.internal_server_error(req)
+ } else {
+ server.index(req)
+ }
+ })
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = ">= 0.0.0"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ Package::new("bar", "0.0.1").publish();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+warning: spurious network error (3 tries remaining): failed to get successful HTTP response from `[..]` (127.0.0.1), got 500
+body:
+internal server error
+warning: spurious network error (2 tries remaining): failed to get successful HTTP response from `[..]` (127.0.0.1), got 500
+body:
+internal server error
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.0.1 (registry `dummy-registry`)
+[CHECKING] bar v0.0.1
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn sparse_retry_multiple() {
+ // Tests retry behavior of downloading lots of packages with various
+ // failure rates accessing the sparse index.
+
+ // The index is the number of retries, the value is the number of packages
+ // that retry that number of times. Thus 50 packages succeed on first try,
+ // 25 on second, etc.
+ const RETRIES: &[u32] = &[50, 25, 12, 6];
+
+ let pkgs: Vec<_> = RETRIES
+ .iter()
+ .enumerate()
+ .flat_map(|(retries, num)| {
+ (0..*num)
+ .into_iter()
+ .map(move |n| (retries as u32, format!("{}-{n}-{retries}", rand_prefix())))
+ })
+ .collect();
+
+ let mut builder = RegistryBuilder::new().http_index();
+ let fail_counts: Arc<Mutex<Vec<u32>>> = Arc::new(Mutex::new(vec![0; pkgs.len()]));
+ let mut cargo_toml = r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ "#
+ .to_string();
+ // The expected stderr output.
+ let mut expected = "\
+[UPDATING] `dummy-registry` index
+[DOWNLOADING] crates ...
+"
+ .to_string();
+ for (n, (retries, name)) in pkgs.iter().enumerate() {
+ let count_clone = fail_counts.clone();
+ let retries = *retries;
+ let ab = &name[..2];
+ let cd = &name[2..4];
+ builder = builder.add_responder(format!("/index/{ab}/{cd}/{name}"), move |req, server| {
+ let mut fail_counts = count_clone.lock().unwrap();
+ if fail_counts[n] < retries {
+ fail_counts[n] += 1;
+ server.internal_server_error(req)
+ } else {
+ server.index(req)
+ }
+ });
+ write!(&mut cargo_toml, "{name} = \"1.0.0\"\n").unwrap();
+ for retry in 0..retries {
+ let remain = 3 - retry;
+ write!(
+ &mut expected,
+ "warning: spurious network error ({remain} tries remaining): \
+ failed to get successful HTTP response from \
+ `http://127.0.0.1:[..]/{ab}/{cd}/{name}` (127.0.0.1), got 500\n\
+ body:\n\
+ internal server error\n"
+ )
+ .unwrap();
+ }
+ write!(
+ &mut expected,
+ "[DOWNLOADED] {name} v1.0.0 (registry `dummy-registry`)\n"
+ )
+ .unwrap();
+ }
+ let _server = builder.build();
+ for (_, name) in &pkgs {
+ Package::new(name, "1.0.0").publish();
+ }
+ let p = project()
+ .file("Cargo.toml", &cargo_toml)
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("fetch").with_stderr_unordered(expected).run();
+}
+
+#[cargo_test]
+fn dl_retry_single() {
+ // Tests retry behavior of downloading a package.
+ // This tests a single package which exercises the code path that causes
+ // it to block.
+ let fail_count = Mutex::new(0);
+ let _server = RegistryBuilder::new()
+ .http_index()
+ .add_responder("/dl/bar/1.0.0/download", move |req, server| {
+ let mut fail_count = fail_count.lock().unwrap();
+ if *fail_count < 2 {
+ *fail_count += 1;
+ server.internal_server_error(req)
+ } else {
+ server.dl(req)
+ }
+ })
+ .build();
+ Package::new("bar", "1.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("fetch")
+ .with_stderr("\
+[UPDATING] `dummy-registry` index
+[DOWNLOADING] crates ...
+warning: spurious network error (3 tries remaining): \
+ failed to get successful HTTP response from `http://127.0.0.1:[..]/dl/bar/1.0.0/download` (127.0.0.1), got 500
+body:
+internal server error
+warning: spurious network error (2 tries remaining): \
+ failed to get successful HTTP response from `http://127.0.0.1:[..]/dl/bar/1.0.0/download` (127.0.0.1), got 500
+body:
+internal server error
+[DOWNLOADED] bar v1.0.0 (registry `dummy-registry`)
+").run();
+}
+
+/// Creates a random prefix to randomly spread out the package names
+/// to somewhat evenly distribute the different failures at different
+/// points.
+fn rand_prefix() -> String {
+ use rand::Rng;
+ const CHARS: &[u8] = b"abcdefghijklmnopqrstuvwxyz";
+ let mut rng = rand::thread_rng();
+ (0..5)
+ .map(|_| CHARS[rng.gen_range(0..CHARS.len())] as char)
+ .collect()
+}
+
+#[cargo_test]
+fn dl_retry_multiple() {
+ // Tests retry behavior of downloading lots of packages with various
+ // failure rates.
+
+ // The index is the number of retries, the value is the number of packages
+ // that retry that number of times. Thus 50 packages succeed on first try,
+ // 25 on second, etc.
+ const RETRIES: &[u32] = &[50, 25, 12, 6];
+
+ let pkgs: Vec<_> = RETRIES
+ .iter()
+ .enumerate()
+ .flat_map(|(retries, num)| {
+ (0..*num)
+ .into_iter()
+ .map(move |n| (retries as u32, format!("{}-{n}-{retries}", rand_prefix())))
+ })
+ .collect();
+
+ let mut builder = RegistryBuilder::new().http_index();
+ let fail_counts: Arc<Mutex<Vec<u32>>> = Arc::new(Mutex::new(vec![0; pkgs.len()]));
+ let mut cargo_toml = r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ "#
+ .to_string();
+ // The expected stderr output.
+ let mut expected = "\
+[UPDATING] `dummy-registry` index
+[DOWNLOADING] crates ...
+"
+ .to_string();
+ for (n, (retries, name)) in pkgs.iter().enumerate() {
+ let count_clone = fail_counts.clone();
+ let retries = *retries;
+ builder =
+ builder.add_responder(format!("/dl/{name}/1.0.0/download"), move |req, server| {
+ let mut fail_counts = count_clone.lock().unwrap();
+ if fail_counts[n] < retries {
+ fail_counts[n] += 1;
+ server.internal_server_error(req)
+ } else {
+ server.dl(req)
+ }
+ });
+ write!(&mut cargo_toml, "{name} = \"1.0.0\"\n").unwrap();
+ for retry in 0..retries {
+ let remain = 3 - retry;
+ write!(
+ &mut expected,
+ "warning: spurious network error ({remain} tries remaining): \
+ failed to get successful HTTP response from \
+ `http://127.0.0.1:[..]/dl/{name}/1.0.0/download` (127.0.0.1), got 500\n\
+ body:\n\
+ internal server error\n"
+ )
+ .unwrap();
+ }
+ write!(
+ &mut expected,
+ "[DOWNLOADED] {name} v1.0.0 (registry `dummy-registry`)\n"
+ )
+ .unwrap();
+ }
+ let _server = builder.build();
+ for (_, name) in &pkgs {
+ Package::new(name, "1.0.0").publish();
+ }
+ let p = project()
+ .file("Cargo.toml", &cargo_toml)
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("fetch").with_stderr_unordered(expected).run();
+}
+
+#[cargo_test]
+fn deleted_entry() {
+ // Checks the behavior when a package is removed from the index.
+ // This is done occasionally on crates.io to handle things like
+ // copyright takedowns.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "0.1"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ // First, test removing a single version, but leaving an older version.
+ Package::new("bar", "0.1.0").publish();
+ let bar_path = Path::new("3/b/bar");
+ let bar_reg_path = registry_path().join(&bar_path);
+ let old_index = fs::read_to_string(&bar_reg_path).unwrap();
+ Package::new("bar", "0.1.1").publish();
+ p.cargo("tree")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.1.1 (registry `dummy-registry`)
+",
+ )
+ .with_stdout(
+ "\
+foo v0.1.0 ([ROOT]/foo)
+└── bar v0.1.1
+",
+ )
+ .run();
+
+ // Remove 0.1.1
+ fs::remove_file(paths::root().join("dl/bar/0.1.1/download")).unwrap();
+ let repo = git2::Repository::open(registry_path()).unwrap();
+ let mut index = repo.index().unwrap();
+ fs::write(&bar_reg_path, &old_index).unwrap();
+ index.add_path(&bar_path).unwrap();
+ index.write().unwrap();
+ git::commit(&repo);
+
+ // With `Cargo.lock` unchanged, it shouldn't have an impact.
+ p.cargo("tree")
+ .with_stderr("")
+ .with_stdout(
+ "\
+foo v0.1.0 ([ROOT]/foo)
+└── bar v0.1.1
+",
+ )
+ .run();
+
+ // Regenerating Cargo.lock should switch to old version.
+ fs::remove_file(p.root().join("Cargo.lock")).unwrap();
+ p.cargo("tree")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.1.0 (registry `dummy-registry`)
+",
+ )
+ .with_stdout(
+ "\
+foo v0.1.0 ([ROOT]/foo)
+└── bar v0.1.0
+",
+ )
+ .run();
+
+ // Remove the package entirely.
+ fs::remove_file(paths::root().join("dl/bar/0.1.0/download")).unwrap();
+ let mut index = repo.index().unwrap();
+ index.remove(&bar_path, 0).unwrap();
+ index.write().unwrap();
+ git::commit(&repo);
+ fs::remove_file(&bar_reg_path).unwrap();
+
+ // With `Cargo.lock` unchanged, it shouldn't have an impact.
+ p.cargo("tree")
+ .with_stderr("")
+ .with_stdout(
+ "\
+foo v0.1.0 ([ROOT]/foo)
+└── bar v0.1.0
+",
+ )
+ .run();
+
+ // Regenerating Cargo.lock should fail.
+ fs::remove_file(p.root().join("Cargo.lock")).unwrap();
+ p.cargo("tree")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+error: no matching package named `bar` found
+location searched: registry `crates-io`
+required by package `foo v0.1.0 ([ROOT]/foo)`
+",
+ )
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn corrupted_ok_overwritten() {
+ // Checks what happens if .cargo-ok gets truncated, such as if the file is
+ // created, but the flush/close is interrupted.
+ Package::new("bar", "1.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "1"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("fetch")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v1.0.0 (registry `dummy-registry`)
+",
+ )
+ .run();
+ let ok = glob::glob(
+ paths::home()
+ .join(".cargo/registry/src/*/bar-1.0.0/.cargo-ok")
+ .to_str()
+ .unwrap(),
+ )
+ .unwrap()
+ .next()
+ .unwrap()
+ .unwrap();
+ // Simulate cargo being interrupted, or filesystem corruption.
+ fs::write(&ok, "").unwrap();
+ assert_eq!(fs::read_to_string(&ok).unwrap(), "");
+ p.cargo("fetch").with_stderr("").run();
+ assert_eq!(fs::read_to_string(&ok).unwrap(), "ok");
+}
+
+#[cargo_test]
+fn not_found_permutations() {
+ // Test for querying permutations for a missing dependency.
+ let misses = Arc::new(Mutex::new(Vec::new()));
+ let misses2 = misses.clone();
+ let _registry = RegistryBuilder::new()
+ .http_index()
+ .not_found_handler(move |req, _server| {
+ let mut misses = misses2.lock().unwrap();
+ misses.push(req.url.path().to_string());
+ Response {
+ code: 404,
+ headers: vec![],
+ body: b"not found".to_vec(),
+ }
+ })
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ a-b-c = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+error: no matching package named `a-b-c` found
+location searched: registry `crates-io`
+required by package `foo v0.0.1 ([ROOT]/foo)`
+",
+ )
+ .run();
+ let mut misses = misses.lock().unwrap();
+ misses.sort();
+ assert_eq!(
+ &*misses,
+ &[
+ "/index/a-/b-/a-b-c",
+ "/index/a-/b_/a-b_c",
+ "/index/a_/b-/a_b-c",
+ "/index/a_/b_/a_b_c"
+ ]
+ );
+}
+
+#[cargo_test]
+fn default_auth_error() {
+ // Check for the error message for an authentication error when default is set.
+ let crates_io = RegistryBuilder::new().http_api().build();
+ let _alternative = RegistryBuilder::new().http_api().alternative().build();
+
+ paths::home().join(".cargo/credentials.toml").rm_rf();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ license = "MIT"
+ description = "foo"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ // Test output before setting the default.
+ p.cargo("publish --no-verify")
+ .replace_crates_io(crates_io.index_url())
+ .with_stderr(
+ "\
+[UPDATING] crates.io index
+error: no token found, please run `cargo login`
+or use environment variable CARGO_REGISTRY_TOKEN
+",
+ )
+ .with_status(101)
+ .run();
+
+ p.cargo("publish --no-verify --registry alternative")
+ .replace_crates_io(crates_io.index_url())
+ .with_stderr(
+ "\
+[UPDATING] `alternative` index
+error: no token found for `alternative`, please run `cargo login --registry alternative`
+or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN
+",
+ )
+ .with_status(101)
+ .run();
+
+ // Test the output with the default.
+ cargo_util::paths::append(
+ &cargo_home().join("config"),
+ br#"
+ [registry]
+ default = "alternative"
+ "#,
+ )
+ .unwrap();
+
+ p.cargo("publish --no-verify")
+ .replace_crates_io(crates_io.index_url())
+ .with_stderr(
+ "\
+[UPDATING] `alternative` index
+error: no token found for `alternative`, please run `cargo login --registry alternative`
+or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN
+",
+ )
+ .with_status(101)
+ .run();
+
+ p.cargo("publish --no-verify --registry crates-io")
+ .replace_crates_io(crates_io.index_url())
+ .with_stderr(
+ "\
+[UPDATING] crates.io index
+error: no token found, please run `cargo login --registry crates-io`
+or use environment variable CARGO_REGISTRY_TOKEN
+",
+ )
+ .with_status(101)
+ .run();
+}
+
+const SAMPLE_HEADERS: &[&str] = &[
+ "x-amz-cf-pop: SFO53-P2",
+ "x-amz-cf-id: vEc3osJrCAXVaciNnF4Vev-hZFgnYwmNZtxMKRJ5bF6h9FTOtbTMnA==",
+ "x-cache: Hit from cloudfront",
+ "server: AmazonS3",
+ "x-amz-version-id: pvsJYY_JGsWiSETZvLJKb7DeEW5wWq1W",
+ "x-amz-server-side-encryption: AES256",
+ "content-type: text/plain",
+ "via: 1.1 bcbc5b46216015493e082cfbcf77ef10.cloudfront.net (CloudFront)",
+];
+
+#[cargo_test]
+fn debug_header_message_index() {
+ // The error message should include some headers for debugging purposes.
+ let _server = RegistryBuilder::new()
+ .http_index()
+ .add_responder("/index/3/b/bar", |_, _| Response {
+ code: 503,
+ headers: SAMPLE_HEADERS.iter().map(|s| s.to_string()).collect(),
+ body: b"Please slow down".to_vec(),
+ })
+ .build();
+ Package::new("bar", "1.0.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("fetch").with_status(101).with_stderr("\
+[UPDATING] `dummy-registry` index
+warning: spurious network error (3 tries remaining): \
+ failed to get successful HTTP response from `http://127.0.0.1:[..]/index/3/b/bar` (127.0.0.1), got 503
+body:
+Please slow down
+warning: spurious network error (2 tries remaining): \
+ failed to get successful HTTP response from `http://127.0.0.1:[..]/index/3/b/bar` (127.0.0.1), got 503
+body:
+Please slow down
+warning: spurious network error (1 tries remaining): \
+ failed to get successful HTTP response from `http://127.0.0.1:[..]/index/3/b/bar` (127.0.0.1), got 503
+body:
+Please slow down
+error: failed to get `bar` as a dependency of package `foo v0.1.0 ([ROOT]/foo)`
+
+Caused by:
+ failed to query replaced source registry `crates-io`
+
+Caused by:
+ download of 3/b/bar failed
+
+Caused by:
+ failed to get successful HTTP response from `http://127.0.0.1:[..]/index/3/b/bar` (127.0.0.1), got 503
+ debug headers:
+ x-amz-cf-pop: SFO53-P2
+ x-amz-cf-id: vEc3osJrCAXVaciNnF4Vev-hZFgnYwmNZtxMKRJ5bF6h9FTOtbTMnA==
+ x-cache: Hit from cloudfront
+ body:
+ Please slow down
+").run();
+}
+
+#[cargo_test]
+fn debug_header_message_dl() {
+ // Same as debug_header_message_index, but for the dl endpoint which goes
+ // through a completely different code path.
+ let _server = RegistryBuilder::new()
+ .http_index()
+ .add_responder("/dl/bar/1.0.0/download", |_, _| Response {
+ code: 503,
+ headers: SAMPLE_HEADERS.iter().map(|s| s.to_string()).collect(),
+ body: b"Please slow down".to_vec(),
+ })
+ .build();
+ Package::new("bar", "1.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("fetch").with_status(101).with_stderr("\
+[UPDATING] `dummy-registry` index
+[DOWNLOADING] crates ...
+warning: spurious network error (3 tries remaining): \
+ failed to get successful HTTP response from `http://127.0.0.1:[..]/dl/bar/1.0.0/download` (127.0.0.1), got 503
+body:
+Please slow down
+warning: spurious network error (2 tries remaining): \
+ failed to get successful HTTP response from `http://127.0.0.1:[..]/dl/bar/1.0.0/download` (127.0.0.1), got 503
+body:
+Please slow down
+warning: spurious network error (1 tries remaining): \
+ failed to get successful HTTP response from `http://127.0.0.1:[..]/dl/bar/1.0.0/download` (127.0.0.1), got 503
+body:
+Please slow down
+error: failed to download from `http://127.0.0.1:[..]/dl/bar/1.0.0/download`
+
+Caused by:
+ failed to get successful HTTP response from `http://127.0.0.1:[..]/dl/bar/1.0.0/download` (127.0.0.1), got 503
+ debug headers:
+ x-amz-cf-pop: SFO53-P2
+ x-amz-cf-id: vEc3osJrCAXVaciNnF4Vev-hZFgnYwmNZtxMKRJ5bF6h9FTOtbTMnA==
+ x-cache: Hit from cloudfront
+ body:
+ Please slow down
+").run();
+}
diff --git a/src/tools/cargo/tests/testsuite/registry_auth.rs b/src/tools/cargo/tests/testsuite/registry_auth.rs
new file mode 100644
index 000000000..7779e285a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/registry_auth.rs
@@ -0,0 +1,519 @@
+//! Tests for registry authentication.
+
+use cargo_test_support::registry::{Package, RegistryBuilder};
+use cargo_test_support::{project, Execs, Project};
+
+fn cargo(p: &Project, s: &str) -> Execs {
+ let mut e = p.cargo(s);
+ e.masquerade_as_nightly_cargo(&["registry-auth"])
+ .arg("-Zregistry-auth");
+ e
+}
+
+fn make_project() -> Project {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ version = "0.0.1"
+ registry = "alternative"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+ Package::new("bar", "0.0.1").alternative(true).publish();
+ p
+}
+
+static SUCCESS_OUTPUT: &'static str = "\
+[UPDATING] `alternative` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.0.1 (registry `alternative`)
+[COMPILING] bar v0.0.1 (registry `alternative`)
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s
+";
+
+#[cargo_test]
+fn requires_nightly() {
+ let _registry = RegistryBuilder::new()
+ .alternative()
+ .auth_required()
+ .http_api()
+ .build();
+
+ let p = make_project();
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ r#"[UPDATING] `alternative` index
+[DOWNLOADING] crates ...
+error: failed to download from `[..]/dl/bar/0.0.1/download`
+
+Caused by:
+ failed to get successful HTTP response from `[..]` (127.0.0.1), got 401
+ body:
+ Unauthorized message from server.
+"#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn simple() {
+ let _registry = RegistryBuilder::new()
+ .alternative()
+ .auth_required()
+ .http_index()
+ .build();
+
+ let p = make_project();
+ cargo(&p, "build").with_stderr(SUCCESS_OUTPUT).run();
+}
+
+#[cargo_test]
+fn simple_with_asymmetric() {
+ let _registry = RegistryBuilder::new()
+ .alternative()
+ .auth_required()
+ .http_index()
+ .token(cargo_test_support::registry::Token::rfc_key())
+ .build();
+
+ let p = make_project();
+ cargo(&p, "build").with_stderr(SUCCESS_OUTPUT).run();
+}
+
+#[cargo_test]
+fn environment_config() {
+ let registry = RegistryBuilder::new()
+ .alternative()
+ .auth_required()
+ .no_configure_registry()
+ .no_configure_token()
+ .http_index()
+ .build();
+ let p = make_project();
+ cargo(&p, "build")
+ .env(
+ "CARGO_REGISTRIES_ALTERNATIVE_INDEX",
+ registry.index_url().as_str(),
+ )
+ .env("CARGO_REGISTRIES_ALTERNATIVE_TOKEN", registry.token())
+ .with_stderr(SUCCESS_OUTPUT)
+ .run();
+}
+
+#[cargo_test]
+fn environment_token() {
+ let registry = RegistryBuilder::new()
+ .alternative()
+ .auth_required()
+ .no_configure_token()
+ .http_index()
+ .build();
+
+ let p = make_project();
+ cargo(&p, "build")
+ .env("CARGO_REGISTRIES_ALTERNATIVE_TOKEN", registry.token())
+ .with_stderr(SUCCESS_OUTPUT)
+ .run();
+}
+
+#[cargo_test]
+fn environment_token_with_asymmetric() {
+ let registry = RegistryBuilder::new()
+ .alternative()
+ .auth_required()
+ .no_configure_token()
+ .http_index()
+ .token(cargo_test_support::registry::Token::Keys(
+ "k3.secret.fNYVuMvBgOlljt9TDohnaYLblghqaHoQquVZwgR6X12cBFHZLFsaU3q7X3k1Zn36"
+ .to_string(),
+ None,
+ ))
+ .build();
+
+ let p = make_project();
+ cargo(&p, "build")
+ .env("CARGO_REGISTRIES_ALTERNATIVE_SECRET_KEY", registry.key())
+ .with_stderr(SUCCESS_OUTPUT)
+ .run();
+}
+
+#[cargo_test]
+fn warn_both_asymmetric_and_token() {
+ let _server = RegistryBuilder::new()
+ .alternative()
+ .no_configure_token()
+ .build();
+ let p = project()
+ .file(
+ ".cargo/config",
+ r#"
+ [registries.alternative]
+ token = "sekrit"
+ secret-key = "k3.secret.fNYVuMvBgOlljt9TDohnaYLblghqaHoQquVZwgR6X12cBFHZLFsaU3q7X3k1Zn36"
+ "#,
+ )
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ description = "foo"
+ authors = []
+ license = "MIT"
+ homepage = "https://example.com/"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("publish --no-verify --registry alternative")
+ .masquerade_as_nightly_cargo(&["credential-process", "registry-auth"])
+ .arg("-Zregistry-auth")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[ERROR] both `token` and `secret-key` were specified in the config for registry `alternative`.
+Only one of these values may be set, remove one or the other to proceed.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn warn_both_asymmetric_and_credential_process() {
+ let _server = RegistryBuilder::new()
+ .alternative()
+ .no_configure_token()
+ .build();
+ let p = project()
+ .file(
+ ".cargo/config",
+ r#"
+ [registries.alternative]
+ credential-process = "false"
+ secret-key = "k3.secret.fNYVuMvBgOlljt9TDohnaYLblghqaHoQquVZwgR6X12cBFHZLFsaU3q7X3k1Zn36"
+ "#,
+ )
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ description = "foo"
+ authors = []
+ license = "MIT"
+ homepage = "https://example.com/"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("publish --no-verify --registry alternative")
+ .masquerade_as_nightly_cargo(&["credential-process", "registry-auth"])
+ .arg("-Zcredential-process")
+ .arg("-Zregistry-auth")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[ERROR] both `credential-process` and `secret-key` were specified in the config for registry `alternative`.
+Only one of these values may be set, remove one or the other to proceed.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bad_environment_token_with_asymmetric_subject() {
+ let registry = RegistryBuilder::new()
+ .alternative()
+ .auth_required()
+ .no_configure_token()
+ .http_index()
+ .token(cargo_test_support::registry::Token::Keys(
+ "k3.secret.fNYVuMvBgOlljt9TDohnaYLblghqaHoQquVZwgR6X12cBFHZLFsaU3q7X3k1Zn36"
+ .to_string(),
+ None,
+ ))
+ .build();
+
+ let p = make_project();
+ cargo(&p, "build")
+ .env("CARGO_REGISTRIES_ALTERNATIVE_SECRET_KEY", registry.key())
+ .env(
+ "CARGO_REGISTRIES_ALTERNATIVE_SECRET_KEY_SUBJECT",
+ "incorrect",
+ )
+ .with_stderr_contains(
+ " token rejected for `alternative`, please run `cargo login --registry alternative`",
+ )
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn bad_environment_token_with_asymmetric_incorrect_subject() {
+ let registry = RegistryBuilder::new()
+ .alternative()
+ .auth_required()
+ .no_configure_token()
+ .http_index()
+ .token(cargo_test_support::registry::Token::rfc_key())
+ .build();
+
+ let p = make_project();
+ cargo(&p, "build")
+ .env("CARGO_REGISTRIES_ALTERNATIVE_SECRET_KEY", registry.key())
+ .env(
+ "CARGO_REGISTRIES_ALTERNATIVE_SECRET_KEY_SUBJECT",
+ "incorrect",
+ )
+ .with_stderr_contains(
+ " token rejected for `alternative`, please run `cargo login --registry alternative`",
+ )
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn bad_environment_token_with_incorrect_asymmetric() {
+ let _registry = RegistryBuilder::new()
+ .alternative()
+ .auth_required()
+ .no_configure_token()
+ .http_index()
+ .token(cargo_test_support::registry::Token::Keys(
+ "k3.secret.fNYVuMvBgOlljt9TDohnaYLblghqaHoQquVZwgR6X12cBFHZLFsaU3q7X3k1Zn36"
+ .to_string(),
+ None,
+ ))
+ .build();
+
+ let p = make_project();
+ cargo(&p, "build")
+ .env(
+ "CARGO_REGISTRIES_ALTERNATIVE_SECRET_KEY",
+ "k3.secret.9Vxr5hVlI_g_orBZN54vPz20bmB4O76wB_MVqUSuJJJqHFLwP8kdn_RY5g6J6pQG",
+ )
+ .with_stderr_contains(
+ " token rejected for `alternative`, please run `cargo login --registry alternative`",
+ )
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn missing_token() {
+ let _registry = RegistryBuilder::new()
+ .alternative()
+ .auth_required()
+ .no_configure_token()
+ .http_index()
+ .build();
+
+ let p = make_project();
+ cargo(&p, "build")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] `alternative` index
+[ERROR] failed to get `bar` as a dependency of package `foo v0.0.1 ([..])`
+
+Caused by:
+ no token found for `alternative`, please run `cargo login --registry alternative`
+ or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn missing_token_git() {
+ let _registry = RegistryBuilder::new()
+ .alternative()
+ .auth_required()
+ .no_configure_token()
+ .build();
+
+ let p = make_project();
+ cargo(&p, "build")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] `alternative` index
+[ERROR] failed to download `bar v0.0.1 (registry `alternative`)`
+
+Caused by:
+ unable to get packages from source
+
+Caused by:
+ no token found for `alternative`, please run `cargo login --registry alternative`
+ or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn incorrect_token() {
+ let _registry = RegistryBuilder::new()
+ .alternative()
+ .auth_required()
+ .no_configure_token()
+ .http_index()
+ .build();
+
+ let p = make_project();
+ cargo(&p, "build")
+ .env("CARGO_REGISTRIES_ALTERNATIVE_TOKEN", "incorrect")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] `alternative` index
+[ERROR] failed to get `bar` as a dependency of package `foo v0.0.1 ([..])`
+
+Caused by:
+ token rejected for `alternative`, please run `cargo login --registry alternative`
+ or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN
+
+Caused by:
+ failed to get successful HTTP response from `http://[..]/index/config.json`, got 401
+ body:
+ Unauthorized message from server.",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn incorrect_token_git() {
+ let _registry = RegistryBuilder::new()
+ .alternative()
+ .auth_required()
+ .no_configure_token()
+ .http_api()
+ .build();
+
+ let p = make_project();
+ cargo(&p, "build")
+ .env("CARGO_REGISTRIES_ALTERNATIVE_TOKEN", "incorrect")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] `alternative` index
+[DOWNLOADING] crates ...
+[ERROR] failed to download from `http://[..]/dl/bar/0.0.1/download`
+
+Caused by:
+ failed to get successful HTTP response from `http://[..]/dl/bar/0.0.1/download` (127.0.0.1), got 401
+ body:
+ Unauthorized message from server.",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn anonymous_alt_registry() {
+ // An alternative registry that requires auth, but is not in the config.
+ let registry = RegistryBuilder::new()
+ .alternative()
+ .auth_required()
+ .no_configure_token()
+ .no_configure_registry()
+ .http_index()
+ .build();
+
+ let p = make_project();
+ cargo(&p, &format!("install --index {} bar", registry.index_url()))
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[ERROR] no token found for `[..]`
+consider setting up an alternate registry in Cargo's configuration
+as described by https://doc.rust-lang.org/cargo/reference/registries.html
+
+[registries]
+my-registry = { index = \"[..]\" }
+
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn login() {
+ let _registry = RegistryBuilder::new()
+ .alternative()
+ .no_configure_token()
+ .auth_required()
+ .http_index()
+ .build();
+
+ let p = make_project();
+ cargo(&p, "login --registry alternative")
+ .with_stdout("please paste the token found on https://test-registry-login/me below")
+ .with_stdin("sekrit")
+ .run();
+}
+
+#[cargo_test]
+fn login_existing_token() {
+ let _registry = RegistryBuilder::new()
+ .alternative()
+ .auth_required()
+ .http_index()
+ .build();
+
+ let p = make_project();
+ cargo(&p, "login --registry alternative")
+ .with_stdout("please paste the token found on file://[..]/me below")
+ .with_stdin("sekrit")
+ .run();
+}
+
+#[cargo_test]
+fn duplicate_index() {
+ let server = RegistryBuilder::new()
+ .alternative()
+ .no_configure_token()
+ .auth_required()
+ .build();
+ let p = make_project();
+
+ // Two alternative registries with the same index.
+ cargo(&p, "build")
+ .env(
+ "CARGO_REGISTRIES_ALTERNATIVE1_INDEX",
+ server.index_url().as_str(),
+ )
+ .env(
+ "CARGO_REGISTRIES_ALTERNATIVE2_INDEX",
+ server.index_url().as_str(),
+ )
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] `alternative` index
+[ERROR] failed to download `bar v0.0.1 (registry `alternative`)`
+
+Caused by:
+ unable to get packages from source
+
+Caused by:
+ multiple registries are configured with the same index url \
+ 'registry+file://[..]/alternative-registry': alternative1, alternative2
+",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/rename_deps.rs b/src/tools/cargo/tests/testsuite/rename_deps.rs
new file mode 100644
index 000000000..f2744049b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/rename_deps.rs
@@ -0,0 +1,391 @@
+//! Tests for renaming dependencies.
+
+use cargo_test_support::git;
+use cargo_test_support::paths;
+use cargo_test_support::registry::{self, Package};
+use cargo_test_support::{basic_manifest, project};
+
+#[cargo_test]
+fn rename_dependency() {
+ Package::new("bar", "0.1.0").publish();
+ Package::new("bar", "0.2.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = { version = "0.1.0" }
+ baz = { version = "0.2.0", package = "bar" }
+ "#,
+ )
+ .file("src/lib.rs", "extern crate bar; extern crate baz;")
+ .build();
+
+ p.cargo("build").run();
+}
+
+#[cargo_test]
+fn rename_with_different_names() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ baz = { path = "bar", package = "bar" }
+ "#,
+ )
+ .file("src/lib.rs", "extern crate baz;")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ name = "random_name"
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+}
+
+#[cargo_test]
+fn lots_of_names() {
+ registry::alt_init();
+ Package::new("foo", "0.1.0")
+ .file("src/lib.rs", "pub fn foo1() {}")
+ .publish();
+ Package::new("foo", "0.2.0")
+ .file("src/lib.rs", "pub fn foo() {}")
+ .publish();
+ Package::new("foo", "0.1.0")
+ .file("src/lib.rs", "pub fn foo2() {}")
+ .alternative(true)
+ .publish();
+
+ let g = git::repo(&paths::root().join("another"))
+ .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("src/lib.rs", "pub fn foo3() {}")
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "test"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ foo = "0.2"
+ foo1 = {{ version = "0.1", package = "foo" }}
+ foo2 = {{ version = "0.1", registry = "alternative", package = "foo" }}
+ foo3 = {{ git = '{}', package = "foo" }}
+ foo4 = {{ path = "foo", package = "foo" }}
+ "#,
+ g.url()
+ ),
+ )
+ .file(
+ "src/lib.rs",
+ "
+ extern crate foo;
+ extern crate foo1;
+ extern crate foo2;
+ extern crate foo3;
+ extern crate foo4;
+
+ pub fn foo() {
+ foo::foo();
+ foo1::foo1();
+ foo2::foo2();
+ foo3::foo3();
+ foo4::foo4();
+ }
+ ",
+ )
+ .file("foo/Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("foo/src/lib.rs", "pub fn foo4() {}")
+ .build();
+
+ p.cargo("build -v").run();
+}
+
+#[cargo_test]
+fn rename_and_patch() {
+ Package::new("foo", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "test"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ bar = { version = "0.1", package = "foo" }
+
+ [patch.crates-io]
+ foo = { path = "foo" }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "extern crate bar; pub fn foo() { bar::foo(); }",
+ )
+ .file("foo/Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("foo/src/lib.rs", "pub fn foo() {}")
+ .build();
+
+ p.cargo("build -v").run();
+}
+
+#[cargo_test]
+fn rename_twice() {
+ Package::new("foo", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "test"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ bar = { version = "0.1", package = "foo" }
+ [build-dependencies]
+ foo = { version = "0.1" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build -v")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] foo v0.1.0 (registry [..])
+error: the crate `test v0.1.0 ([CWD])` depends on crate `foo v0.1.0` multiple times with different names
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rename_affects_fingerprint() {
+ Package::new("foo", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "test"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ foo = { version = "0.1", package = "foo" }
+ "#,
+ )
+ .file("src/lib.rs", "extern crate foo;")
+ .build();
+
+ p.cargo("build -v").run();
+
+ p.change_file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "test"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ bar = { version = "0.1", package = "foo" }
+ "#,
+ );
+
+ p.cargo("build -v")
+ .with_status(101)
+ .with_stderr_contains("[..]can't find crate for `foo`")
+ .run();
+}
+
+#[cargo_test]
+fn can_run_doc_tests() {
+ Package::new("bar", "0.1.0").publish();
+ Package::new("bar", "0.2.0").publish();
+
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies]
+ bar = { version = "0.1.0" }
+ baz = { version = "0.2.0", package = "bar" }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "
+ extern crate bar;
+ extern crate baz;
+ ",
+ )
+ .build();
+
+ foo.cargo("test -v")
+ .with_stderr_contains(
+ "\
+[DOCTEST] foo
+[RUNNING] `rustdoc [..]--test [..]src/lib.rs \
+ [..] \
+ --extern bar=[CWD]/target/debug/deps/libbar-[..].rlib \
+ --extern baz=[CWD]/target/debug/deps/libbar-[..].rlib \
+ [..]`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn features_still_work() {
+ Package::new("foo", "0.1.0").publish();
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "test"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ p1 = { path = 'a', features = ['b'] }
+ p2 = { path = 'b' }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "p1"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ b = { version = "0.1", package = "foo", optional = true }
+ "#,
+ )
+ .file("a/src/lib.rs", "extern crate b;")
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "p2"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ b = { version = "0.1", package = "bar", optional = true }
+
+ [features]
+ default = ['b']
+ "#,
+ )
+ .file("b/src/lib.rs", "extern crate b;")
+ .build();
+
+ p.cargo("build -v").run();
+}
+
+#[cargo_test]
+fn features_not_working() {
+ Package::new("foo", "0.1.0").publish();
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "test"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ a = { path = 'a', package = 'p1', optional = true }
+
+ [features]
+ default = ['p1']
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("a/Cargo.toml", &basic_manifest("p1", "0.1.0"))
+ .build();
+
+ p.cargo("build -v")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[..]`
+
+Caused by:
+ feature `default` includes `p1` which is neither a dependency nor another feature
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rename_with_dash() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "qwerty"
+ version = "0.1.0"
+
+ [dependencies]
+ foo-bar = { path = 'a', package = 'a' }
+ "#,
+ )
+ .file("src/lib.rs", "extern crate foo_bar;")
+ .file("a/Cargo.toml", &basic_manifest("a", "0.1.0"))
+ .file("a/src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+}
diff --git a/src/tools/cargo/tests/testsuite/replace.rs b/src/tools/cargo/tests/testsuite/replace.rs
new file mode 100644
index 000000000..c11c49330
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/replace.rs
@@ -0,0 +1,1300 @@
+//! Tests for `[replace]` table source replacement.
+
+use cargo_test_support::git;
+use cargo_test_support::paths;
+use cargo_test_support::registry::Package;
+use cargo_test_support::{basic_manifest, project};
+
+#[cargo_test]
+fn override_simple() {
+ Package::new("bar", "0.1.0").publish();
+
+ let bar = git::repo(&paths::root().join("override"))
+ .file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+
+ [replace]
+ "bar:0.1.0" = {{ git = '{}' }}
+ "#,
+ bar.url()
+ ),
+ )
+ .file(
+ "src/lib.rs",
+ "extern crate bar; pub fn foo() { bar::bar(); }",
+ )
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[UPDATING] git repository `[..]`
+[CHECKING] bar v0.1.0 (file://[..])
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn override_with_features() {
+ Package::new("bar", "0.1.0").publish();
+
+ let bar = git::repo(&paths::root().join("override"))
+ .file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+
+ [replace]
+ "bar:0.1.0" = {{ git = '{}', features = ["some_feature"] }}
+ "#,
+ bar.url()
+ ),
+ )
+ .file(
+ "src/lib.rs",
+ "extern crate bar; pub fn foo() { bar::bar(); }",
+ )
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] [..] index
+[UPDATING] git repository `[..]`
+[WARNING] replacement for `bar` uses the features mechanism. default-features and features \
+will not take effect because the replacement dependency does not support this mechanism
+[CHECKING] bar v0.1.0 (file://[..])
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn override_with_setting_default_features() {
+ Package::new("bar", "0.1.0").publish();
+
+ let bar = git::repo(&paths::root().join("override"))
+ .file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+
+ [replace]
+ "bar:0.1.0" = {{ git = '{}', default-features = false, features = ["none_default_feature"] }}
+ "#,
+ bar.url()
+ ),
+ )
+ .file(
+ "src/lib.rs",
+ "extern crate bar; pub fn foo() { bar::bar(); }",
+ )
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] [..] index
+[UPDATING] git repository `[..]`
+[WARNING] replacement for `bar` uses the features mechanism. default-features and features \
+will not take effect because the replacement dependency does not support this mechanism
+[CHECKING] bar v0.1.0 (file://[..])
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn missing_version() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+
+ [replace]
+ bar = { git = 'https://example.com' }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[..]`
+
+Caused by:
+ replacements must specify a version to replace, but `[..]bar` does not
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn invalid_semver_version() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "*"
+
+ [replace]
+ "bar:*" = { git = 'https://example.com' }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+error: failed to parse manifest at `[..]`
+
+Caused by:
+ replacements must specify a valid semver version to replace, but `bar:*` does not
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn different_version() {
+ Package::new("bar", "0.2.0").publish();
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+
+ [replace]
+ "bar:0.1.0" = "0.2.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[..]`
+
+Caused by:
+ replacements cannot specify a version requirement, but found one for [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn transitive() {
+ Package::new("bar", "0.1.0").publish();
+ Package::new("baz", "0.2.0")
+ .dep("bar", "0.1.0")
+ .file("src/lib.rs", "extern crate bar; fn baz() { bar::bar(); }")
+ .publish();
+
+ let foo = git::repo(&paths::root().join("override"))
+ .file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ baz = "0.2.0"
+
+ [replace]
+ "bar:0.1.0" = {{ git = '{}' }}
+ "#,
+ foo.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[UPDATING] git repository `[..]`
+[DOWNLOADING] crates ...
+[DOWNLOADED] baz v0.2.0 (registry [..])
+[CHECKING] bar v0.1.0 (file://[..])
+[CHECKING] baz v0.2.0
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("check").with_stdout("").run();
+}
+
+#[cargo_test]
+fn persists_across_rebuilds() {
+ Package::new("bar", "0.1.0").publish();
+
+ let foo = git::repo(&paths::root().join("override"))
+ .file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+
+ [replace]
+ "bar:0.1.0" = {{ git = '{}' }}
+ "#,
+ foo.url()
+ ),
+ )
+ .file(
+ "src/lib.rs",
+ "extern crate bar; pub fn foo() { bar::bar(); }",
+ )
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[UPDATING] git repository `file://[..]`
+[CHECKING] bar v0.1.0 (file://[..])
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("check").with_stdout("").run();
+}
+
+#[cargo_test]
+fn replace_registry_with_path() {
+ Package::new("bar", "0.1.0").publish();
+
+ let _ = project()
+ .at("bar")
+ .file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+
+ [replace]
+ "bar:0.1.0" = { path = "../bar" }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "extern crate bar; pub fn foo() { bar::bar(); }",
+ )
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[CHECKING] bar v0.1.0 ([ROOT][..]/bar)
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn use_a_spec_to_select() {
+ Package::new("baz", "0.1.1")
+ .file("src/lib.rs", "pub fn baz1() {}")
+ .publish();
+ Package::new("baz", "0.2.0").publish();
+ Package::new("bar", "0.1.1")
+ .dep("baz", "0.2")
+ .file(
+ "src/lib.rs",
+ "extern crate baz; pub fn bar() { baz::baz3(); }",
+ )
+ .publish();
+
+ let foo = git::repo(&paths::root().join("override"))
+ .file("Cargo.toml", &basic_manifest("baz", "0.2.0"))
+ .file("src/lib.rs", "pub fn baz3() {}")
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1"
+ baz = "0.1"
+
+ [replace]
+ "baz:0.2.0" = {{ git = '{}' }}
+ "#,
+ foo.url()
+ ),
+ )
+ .file(
+ "src/lib.rs",
+ "
+ extern crate bar;
+ extern crate baz;
+
+ pub fn local() {
+ baz::baz1();
+ bar::bar();
+ }
+ ",
+ )
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[UPDATING] git repository `[..]`
+[DOWNLOADING] crates ...
+[DOWNLOADED] [..]
+[DOWNLOADED] [..]
+[CHECKING] [..]
+[CHECKING] [..]
+[CHECKING] [..]
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn override_adds_some_deps() {
+ Package::new("baz", "0.1.1").publish();
+ Package::new("bar", "0.1.0").publish();
+
+ let foo = git::repo(&paths::root().join("override"))
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ baz = "0.1"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1"
+
+ [replace]
+ "bar:0.1.0" = {{ git = '{}' }}
+ "#,
+ foo.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+[UPDATING] git repository `[..]`
+[DOWNLOADING] crates ...
+[DOWNLOADED] baz v0.1.1 (registry [..])
+[CHECKING] baz v0.1.1
+[CHECKING] bar v0.1.0 ([..])
+[CHECKING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("check").with_stdout("").run();
+
+ Package::new("baz", "0.1.2").publish();
+ p.cargo("update -p")
+ .arg(&format!("{}#bar", foo.url()))
+ .with_stderr(
+ "\
+[UPDATING] git repository `file://[..]`
+[UPDATING] `dummy-registry` index
+",
+ )
+ .run();
+ p.cargo("update -p https://github.com/rust-lang/crates.io-index#bar")
+ .with_stderr(
+ "\
+[UPDATING] `dummy-registry` index
+",
+ )
+ .run();
+
+ p.cargo("check").with_stdout("").run();
+}
+
+#[cargo_test]
+fn locked_means_locked_yes_no_seriously_i_mean_locked() {
+ // this in theory exercises #2041
+ Package::new("baz", "0.1.0").publish();
+ Package::new("baz", "0.2.0").publish();
+ Package::new("bar", "0.1.0").publish();
+
+ let foo = git::repo(&paths::root().join("override"))
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ baz = "*"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1"
+ baz = "0.1"
+
+ [replace]
+ "bar:0.1.0" = {{ git = '{}' }}
+ "#,
+ foo.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check").run();
+
+ p.cargo("check").with_stdout("").run();
+ p.cargo("check").with_stdout("").run();
+}
+
+#[cargo_test]
+fn override_wrong_name() {
+ Package::new("baz", "0.1.0").publish();
+
+ let foo = git::repo(&paths::root().join("override"))
+ .file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("src/lib.rs", "")
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ baz = "0.1"
+
+ [replace]
+ "baz:0.1.0" = {{ git = '{}' }}
+ "#,
+ foo.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] [..] index
+[UPDATING] git repository [..]
+[ERROR] failed to get `baz` as a dependency of package `foo v0.0.1 ([..])`
+
+Caused by:
+ no matching package for override `[..]baz@0.1.0` found
+ location searched: file://[..]
+ version required: =0.1.0
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn override_with_nothing() {
+ Package::new("bar", "0.1.0").publish();
+
+ let foo = git::repo(&paths::root().join("override"))
+ .file("src/lib.rs", "")
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1"
+
+ [replace]
+ "bar:0.1.0" = {{ git = '{}' }}
+ "#,
+ foo.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] [..] index
+[UPDATING] git repository [..]
+[ERROR] failed to get `bar` as a dependency of package `foo v0.0.1 ([..])`
+
+Caused by:
+ failed to load source for dependency `bar`
+
+Caused by:
+ Unable to update file://[..]
+
+Caused by:
+ Could not find Cargo.toml in `[..]`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn override_wrong_version() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [replace]
+ "bar:0.1.0" = { git = 'https://example.com', version = '0.2.0' }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to parse manifest at `[..]`
+
+Caused by:
+ replacements cannot specify a version requirement, but found one for `[..]bar@0.1.0`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn multiple_specs() {
+ Package::new("bar", "0.1.0").publish();
+
+ let bar = git::repo(&paths::root().join("override"))
+ .file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+
+ [replace]
+ "bar:0.1.0" = {{ git = '{0}' }}
+
+ [replace."https://github.com/rust-lang/crates.io-index#bar:0.1.0"]
+ git = '{0}'
+ "#,
+ bar.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] [..] index
+[UPDATING] git repository [..]
+[ERROR] failed to get `bar` as a dependency of package `foo v0.0.1 ([..])`
+
+Caused by:
+ overlapping replacement specifications found:
+
+ * [..]
+ * [..]
+
+ both specifications match: bar v0.1.0
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn test_override_dep() {
+ Package::new("bar", "0.1.0").publish();
+
+ let bar = git::repo(&paths::root().join("override"))
+ .file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+
+ [replace]
+ "bar:0.1.0" = {{ git = '{0}' }}
+ "#,
+ bar.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("test -p bar")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+error: There are multiple `bar` packages in your project, and the [..]
+Please re-run this command with [..]
+ [..]#bar@0.1.0
+ [..]#bar@0.1.0
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn update() {
+ Package::new("bar", "0.1.0").publish();
+
+ let bar = git::repo(&paths::root().join("override"))
+ .file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+
+ [replace]
+ "bar:0.1.0" = {{ git = '{0}' }}
+ "#,
+ bar.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("generate-lockfile").run();
+ p.cargo("update")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[UPDATING] git repository `[..]`
+",
+ )
+ .run();
+}
+
+// foo -> near -> far
+// near is overridden with itself
+#[cargo_test]
+fn no_override_self() {
+ let deps = git::repo(&paths::root().join("override"))
+ .file("far/Cargo.toml", &basic_manifest("far", "0.1.0"))
+ .file("far/src/lib.rs", "")
+ .file(
+ "near/Cargo.toml",
+ r#"
+ [package]
+ name = "near"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ far = { path = "../far" }
+ "#,
+ )
+ .file("near/src/lib.rs", "#![no_std] pub extern crate far;")
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ near = {{ git = '{0}' }}
+
+ [replace]
+ "near:0.1.0" = {{ git = '{0}' }}
+ "#,
+ deps.url()
+ ),
+ )
+ .file("src/lib.rs", "#![no_std] pub extern crate near;")
+ .build();
+
+ p.cargo("check --verbose").run();
+}
+
+#[cargo_test]
+fn override_an_override() {
+ Package::new("chrono", "0.2.0")
+ .dep("serde", "< 0.9")
+ .publish();
+ Package::new("serde", "0.7.0")
+ .file("src/lib.rs", "pub fn serde07() {}")
+ .publish();
+ Package::new("serde", "0.8.0")
+ .file("src/lib.rs", "pub fn serde08() {}")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ chrono = "0.2"
+ serde = "0.8"
+
+ [replace]
+ "chrono:0.2.0" = { path = "chrono" }
+ "serde:0.8.0" = { path = "serde" }
+ "#,
+ )
+ .file(
+ "Cargo.lock",
+ r#"
+ [[package]]
+ name = "foo"
+ version = "0.0.1"
+ dependencies = [
+ "chrono 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ ]
+
+ [[package]]
+ name = "chrono"
+ version = "0.2.0"
+ source = "registry+https://github.com/rust-lang/crates.io-index"
+ replace = "chrono 0.2.0"
+
+ [[package]]
+ name = "chrono"
+ version = "0.2.0"
+ dependencies = [
+ "serde 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ ]
+
+ [[package]]
+ name = "serde"
+ version = "0.7.0"
+ source = "registry+https://github.com/rust-lang/crates.io-index"
+
+ [[package]]
+ name = "serde"
+ version = "0.8.0"
+ source = "registry+https://github.com/rust-lang/crates.io-index"
+ replace = "serde 0.8.0"
+
+ [[package]]
+ name = "serde"
+ version = "0.8.0"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "
+ extern crate chrono;
+ extern crate serde;
+
+ pub fn foo() {
+ chrono::chrono();
+ serde::serde08_override();
+ }
+ ",
+ )
+ .file(
+ "chrono/Cargo.toml",
+ r#"
+ [package]
+ name = "chrono"
+ version = "0.2.0"
+ authors = []
+
+ [dependencies]
+ serde = "< 0.9"
+ "#,
+ )
+ .file(
+ "chrono/src/lib.rs",
+ "
+ extern crate serde;
+ pub fn chrono() {
+ serde::serde07();
+ }
+ ",
+ )
+ .file("serde/Cargo.toml", &basic_manifest("serde", "0.8.0"))
+ .file("serde/src/lib.rs", "pub fn serde08_override() {}")
+ .build();
+
+ p.cargo("check -v").run();
+}
+
+#[cargo_test]
+fn overriding_nonexistent_no_spurious() {
+ Package::new("bar", "0.1.0").dep("baz", "0.1").publish();
+ Package::new("baz", "0.1.0").publish();
+
+ let bar = git::repo(&paths::root().join("override"))
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ baz = { path = "baz" }
+ "#,
+ )
+ .file("src/lib.rs", "pub fn bar() {}")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("baz/src/lib.rs", "pub fn baz() {}")
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+
+ [replace]
+ "bar:0.1.0" = {{ git = '{url}' }}
+ "baz:0.1.0" = {{ git = '{url}' }}
+ "#,
+ url = bar.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check").run();
+ p.cargo("check")
+ .with_stderr(
+ "\
+[WARNING] package replacement is not used: [..]baz@0.1.0
+[FINISHED] [..]
+",
+ )
+ .with_stdout("")
+ .run();
+}
+
+#[cargo_test]
+fn no_warnings_when_replace_is_used_in_another_workspace_member() {
+ Package::new("bar", "0.1.0").publish();
+ Package::new("baz", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = [ "first_crate", "second_crate"]
+
+ [replace]
+ "bar:0.1.0" = { path = "local_bar" }
+ "#,
+ )
+ .file(
+ "first_crate/Cargo.toml",
+ r#"
+ [package]
+ name = "first_crate"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file("first_crate/src/lib.rs", "")
+ .file(
+ "second_crate/Cargo.toml",
+ &basic_manifest("second_crate", "0.1.0"),
+ )
+ .file("second_crate/src/lib.rs", "")
+ .file("local_bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("local_bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .cwd("first_crate")
+ .with_stdout("")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[CHECKING] bar v0.1.0 ([..])
+[CHECKING] first_crate v0.1.0 ([..])
+[FINISHED] [..]",
+ )
+ .run();
+
+ p.cargo("check")
+ .cwd("second_crate")
+ .with_stdout("")
+ .with_stderr(
+ "\
+[CHECKING] second_crate v0.1.0 ([..])
+[FINISHED] [..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn replace_to_path_dep() {
+ Package::new("bar", "0.1.0").dep("baz", "0.1").publish();
+ Package::new("baz", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1.0"
+
+ [replace]
+ "bar:0.1.0" = { path = "bar" }
+ "#,
+ )
+ .file("src/lib.rs", "extern crate bar;")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ baz = { path = "baz" }
+ "#,
+ )
+ .file(
+ "bar/src/lib.rs",
+ "extern crate baz; pub fn bar() { baz::baz(); }",
+ )
+ .file("bar/baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("bar/baz/src/lib.rs", "pub fn baz() {}")
+ .build();
+
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn override_with_default_feature() {
+ Package::new("another", "0.1.0").publish();
+ Package::new("another", "0.1.1").dep("bar", "0.1").publish();
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = { path = "bar", default-features = false }
+ another = "0.1"
+ another2 = { path = "another2" }
+
+ [replace]
+ 'bar:0.1.0' = { path = "bar" }
+ "#,
+ )
+ .file("src/main.rs", "extern crate bar; fn main() { bar::bar(); }")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+
+ [features]
+ default = []
+ "#,
+ )
+ .file(
+ "bar/src/lib.rs",
+ r#"
+ #[cfg(feature = "default")]
+ pub fn bar() {}
+ "#,
+ )
+ .file(
+ "another2/Cargo.toml",
+ r#"
+ [package]
+ name = "another2"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ bar = { version = "0.1", default-features = false }
+ "#,
+ )
+ .file("another2/src/lib.rs", "")
+ .build();
+
+ p.cargo("run").run();
+}
+
+#[cargo_test]
+fn override_plus_dep() {
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "0.1"
+
+ [replace]
+ 'bar:0.1.0' = { path = "bar" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ foo = { path = ".." }
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr_contains("error: cyclic package dependency: [..]")
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/required_features.rs b/src/tools/cargo/tests/testsuite/required_features.rs
new file mode 100644
index 000000000..ac6c9d233
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/required_features.rs
@@ -0,0 +1,1452 @@
+//! Tests for targets with `required-features`.
+
+use cargo_test_support::install::{
+ assert_has_installed_exe, assert_has_not_installed_exe, cargo_home,
+};
+use cargo_test_support::is_nightly;
+use cargo_test_support::paths::CargoPathExt;
+use cargo_test_support::project;
+
+#[cargo_test]
+fn build_bin_default_features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ default = ["a"]
+ a = []
+
+ [[bin]]
+ name = "foo"
+ required-features = ["a"]
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ extern crate foo;
+
+ #[cfg(feature = "a")]
+ fn test() {
+ foo::foo();
+ }
+
+ fn main() {}
+ "#,
+ )
+ .file("src/lib.rs", r#"#[cfg(feature = "a")] pub fn foo() {}"#)
+ .build();
+
+ p.cargo("build").run();
+ assert!(p.bin("foo").is_file());
+
+ p.cargo("build --no-default-features").run();
+
+ p.cargo("build --bin=foo").run();
+ assert!(p.bin("foo").is_file());
+
+ p.cargo("build --bin=foo --no-default-features")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: target `foo` in package `foo` requires the features: `a`
+Consider enabling them by passing, e.g., `--features=\"a\"`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_bin_arg_features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ a = []
+
+ [[bin]]
+ name = "foo"
+ required-features = ["a"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build --features a").run();
+ assert!(p.bin("foo").is_file());
+}
+
+#[cargo_test]
+fn build_bin_multiple_required_features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ default = ["a", "b"]
+ a = []
+ b = ["a"]
+ c = []
+
+ [[bin]]
+ name = "foo_1"
+ path = "src/foo_1.rs"
+ required-features = ["b", "c"]
+
+ [[bin]]
+ name = "foo_2"
+ path = "src/foo_2.rs"
+ required-features = ["a"]
+ "#,
+ )
+ .file("src/foo_1.rs", "fn main() {}")
+ .file("src/foo_2.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build").run();
+
+ assert!(!p.bin("foo_1").is_file());
+ assert!(p.bin("foo_2").is_file());
+
+ p.cargo("build --features c").run();
+
+ assert!(p.bin("foo_1").is_file());
+ assert!(p.bin("foo_2").is_file());
+
+ p.cargo("build --no-default-features").run();
+}
+
+#[cargo_test]
+fn build_example_default_features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ default = ["a"]
+ a = []
+
+ [[example]]
+ name = "foo"
+ required-features = ["a"]
+ "#,
+ )
+ .file("examples/foo.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build --example=foo").run();
+ assert!(p.bin("examples/foo").is_file());
+
+ p.cargo("build --example=foo --no-default-features")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: target `foo` in package `foo` requires the features: `a`
+Consider enabling them by passing, e.g., `--features=\"a\"`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_example_arg_features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ a = []
+
+ [[example]]
+ name = "foo"
+ required-features = ["a"]
+ "#,
+ )
+ .file("examples/foo.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build --example=foo --features a").run();
+ assert!(p.bin("examples/foo").is_file());
+}
+
+#[cargo_test]
+fn build_example_multiple_required_features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ default = ["a", "b"]
+ a = []
+ b = ["a"]
+ c = []
+
+ [[example]]
+ name = "foo_1"
+ required-features = ["b", "c"]
+
+ [[example]]
+ name = "foo_2"
+ required-features = ["a"]
+ "#,
+ )
+ .file("examples/foo_1.rs", "fn main() {}")
+ .file("examples/foo_2.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build --example=foo_1")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: target `foo_1` in package `foo` requires the features: `b`, `c`
+Consider enabling them by passing, e.g., `--features=\"b c\"`
+",
+ )
+ .run();
+ p.cargo("build --example=foo_2").run();
+
+ assert!(!p.bin("examples/foo_1").is_file());
+ assert!(p.bin("examples/foo_2").is_file());
+
+ p.cargo("build --example=foo_1 --features c").run();
+ p.cargo("build --example=foo_2 --features c").run();
+
+ assert!(p.bin("examples/foo_1").is_file());
+ assert!(p.bin("examples/foo_2").is_file());
+
+ p.cargo("build --example=foo_1 --no-default-features")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: target `foo_1` in package `foo` requires the features: `b`, `c`
+Consider enabling them by passing, e.g., `--features=\"b c\"`
+",
+ )
+ .run();
+ p.cargo("build --example=foo_2 --no-default-features")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: target `foo_2` in package `foo` requires the features: `a`
+Consider enabling them by passing, e.g., `--features=\"a\"`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn test_default_features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ default = ["a"]
+ a = []
+
+ [[test]]
+ name = "foo"
+ required-features = ["a"]
+ "#,
+ )
+ .file("tests/foo.rs", "#[test]\nfn test() {}")
+ .build();
+
+ p.cargo("test")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo-[..][EXE])",
+ )
+ .with_stdout_contains("test test ... ok")
+ .run();
+
+ p.cargo("test --no-default-features")
+ .with_stderr("[FINISHED] test [unoptimized + debuginfo] target(s) in [..]")
+ .with_stdout("")
+ .run();
+
+ p.cargo("test --test=foo")
+ .with_stderr(
+ "\
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo-[..][EXE])",
+ )
+ .with_stdout_contains("test test ... ok")
+ .run();
+
+ p.cargo("test --test=foo --no-default-features")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: target `foo` in package `foo` requires the features: `a`
+Consider enabling them by passing, e.g., `--features=\"a\"`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn test_arg_features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ a = []
+
+ [[test]]
+ name = "foo"
+ required-features = ["a"]
+ "#,
+ )
+ .file("tests/foo.rs", "#[test]\nfn test() {}")
+ .build();
+
+ p.cargo("test --features a")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo-[..][EXE])",
+ )
+ .with_stdout_contains("test test ... ok")
+ .run();
+}
+
+#[cargo_test]
+fn test_multiple_required_features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ default = ["a", "b"]
+ a = []
+ b = ["a"]
+ c = []
+
+ [[test]]
+ name = "foo_1"
+ required-features = ["b", "c"]
+
+ [[test]]
+ name = "foo_2"
+ required-features = ["a"]
+ "#,
+ )
+ .file("tests/foo_1.rs", "#[test]\nfn test() {}")
+ .file("tests/foo_2.rs", "#[test]\nfn test() {}")
+ .build();
+
+ p.cargo("test")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo_2-[..][EXE])",
+ )
+ .with_stdout_contains("test test ... ok")
+ .run();
+
+ p.cargo("test --features c")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo_1-[..][EXE])
+[RUNNING] [..] (target/debug/deps/foo_2-[..][EXE])",
+ )
+ .with_stdout_contains_n("test test ... ok", 2)
+ .run();
+
+ p.cargo("test --no-default-features")
+ .with_stderr("[FINISHED] test [unoptimized + debuginfo] target(s) in [..]")
+ .with_stdout("")
+ .run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn bench_default_features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ default = ["a"]
+ a = []
+
+ [[bench]]
+ name = "foo"
+ required-features = ["a"]
+ "#,
+ )
+ .file(
+ "benches/foo.rs",
+ r#"
+ #![feature(test)]
+ extern crate test;
+
+ #[bench]
+ fn bench(_: &mut test::Bencher) {
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("bench")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] bench [optimized] target(s) in [..]
+[RUNNING] [..] (target/release/deps/foo-[..][EXE])",
+ )
+ .with_stdout_contains("test bench ... bench: [..]")
+ .run();
+
+ p.cargo("bench --no-default-features")
+ .with_stderr("[FINISHED] bench [optimized] target(s) in [..]".to_string())
+ .with_stdout("")
+ .run();
+
+ p.cargo("bench --bench=foo")
+ .with_stderr(
+ "\
+[FINISHED] bench [optimized] target(s) in [..]
+[RUNNING] [..] (target/release/deps/foo-[..][EXE])",
+ )
+ .with_stdout_contains("test bench ... bench: [..]")
+ .run();
+
+ p.cargo("bench --bench=foo --no-default-features")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: target `foo` in package `foo` requires the features: `a`
+Consider enabling them by passing, e.g., `--features=\"a\"`
+",
+ )
+ .run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn bench_arg_features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ a = []
+
+ [[bench]]
+ name = "foo"
+ required-features = ["a"]
+ "#,
+ )
+ .file(
+ "benches/foo.rs",
+ r#"
+ #![feature(test)]
+ extern crate test;
+
+ #[bench]
+ fn bench(_: &mut test::Bencher) {
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("bench --features a")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] bench [optimized] target(s) in [..]
+[RUNNING] [..] (target/release/deps/foo-[..][EXE])",
+ )
+ .with_stdout_contains("test bench ... bench: [..]")
+ .run();
+}
+
+#[cargo_test(nightly, reason = "bench")]
+fn bench_multiple_required_features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ default = ["a", "b"]
+ a = []
+ b = ["a"]
+ c = []
+
+ [[bench]]
+ name = "foo_1"
+ required-features = ["b", "c"]
+
+ [[bench]]
+ name = "foo_2"
+ required-features = ["a"]
+ "#,
+ )
+ .file(
+ "benches/foo_1.rs",
+ r#"
+ #![feature(test)]
+ extern crate test;
+
+ #[bench]
+ fn bench(_: &mut test::Bencher) {
+ }
+ "#,
+ )
+ .file(
+ "benches/foo_2.rs",
+ r#"
+ #![feature(test)]
+ extern crate test;
+
+ #[bench]
+ fn bench(_: &mut test::Bencher) {
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("bench")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] bench [optimized] target(s) in [..]
+[RUNNING] [..] (target/release/deps/foo_2-[..][EXE])",
+ )
+ .with_stdout_contains("test bench ... bench: [..]")
+ .run();
+
+ p.cargo("bench --features c")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] bench [optimized] target(s) in [..]
+[RUNNING] [..] (target/release/deps/foo_1-[..][EXE])
+[RUNNING] [..] (target/release/deps/foo_2-[..][EXE])",
+ )
+ .with_stdout_contains_n("test bench ... bench: [..]", 2)
+ .run();
+
+ p.cargo("bench --no-default-features")
+ .with_stderr("[FINISHED] bench [optimized] target(s) in [..]")
+ .with_stdout("")
+ .run();
+}
+
+#[cargo_test]
+fn install_default_features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ default = ["a"]
+ a = []
+
+ [[bin]]
+ name = "foo"
+ required-features = ["a"]
+
+ [[example]]
+ name = "foo"
+ required-features = ["a"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("examples/foo.rs", "fn main() {}")
+ .build();
+
+ p.cargo("install --path .").run();
+ assert_has_installed_exe(cargo_home(), "foo");
+ p.cargo("uninstall foo").run();
+
+ p.cargo("install --path . --no-default-features")
+ .with_stderr(
+ "\
+[INSTALLING] foo v0.0.1 ([..])
+[FINISHED] release [optimized] target(s) in [..]
+[WARNING] none of the package's binaries are available for install using the selected features
+ bin \"foo\" requires the features: `a`
+ example \"foo\" requires the features: `a`
+Consider enabling some of the needed features by passing, e.g., `--features=\"a\"`
+",
+ )
+ .run();
+ assert_has_not_installed_exe(cargo_home(), "foo");
+
+ p.cargo("install --path . --bin=foo").run();
+ assert_has_installed_exe(cargo_home(), "foo");
+ p.cargo("uninstall foo").run();
+
+ p.cargo("install --path . --bin=foo --no-default-features")
+ .with_status(101)
+ .with_stderr(
+ "\
+[INSTALLING] foo v0.0.1 ([..])
+[ERROR] failed to compile `foo v0.0.1 ([..])`, intermediate artifacts can be found at \
+ `[..]target`
+
+Caused by:
+ target `foo` in package `foo` requires the features: `a`
+ Consider enabling them by passing, e.g., `--features=\"a\"`
+",
+ )
+ .run();
+ assert_has_not_installed_exe(cargo_home(), "foo");
+
+ p.cargo("install --path . --example=foo").run();
+ assert_has_installed_exe(cargo_home(), "foo");
+ p.cargo("uninstall foo").run();
+
+ p.cargo("install --path . --example=foo --no-default-features")
+ .with_status(101)
+ .with_stderr(
+ "\
+[INSTALLING] foo v0.0.1 ([..])
+[ERROR] failed to compile `foo v0.0.1 ([..])`, intermediate artifacts can be found at \
+ `[..]target`
+
+Caused by:
+ target `foo` in package `foo` requires the features: `a`
+ Consider enabling them by passing, e.g., `--features=\"a\"`
+",
+ )
+ .run();
+ assert_has_not_installed_exe(cargo_home(), "foo");
+}
+
+#[cargo_test]
+fn install_arg_features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ a = []
+
+ [[bin]]
+ name = "foo"
+ required-features = ["a"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("install --features a").run();
+ assert_has_installed_exe(cargo_home(), "foo");
+ p.cargo("uninstall foo").run();
+}
+
+#[cargo_test]
+fn install_multiple_required_features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ default = ["a", "b"]
+ a = []
+ b = ["a"]
+ c = []
+
+ [[bin]]
+ name = "foo_1"
+ path = "src/foo_1.rs"
+ required-features = ["b", "c"]
+
+ [[bin]]
+ name = "foo_2"
+ path = "src/foo_2.rs"
+ required-features = ["a"]
+
+ [[example]]
+ name = "foo_3"
+ path = "src/foo_3.rs"
+ required-features = ["b", "c"]
+
+ [[example]]
+ name = "foo_4"
+ path = "src/foo_4.rs"
+ required-features = ["a"]
+ "#,
+ )
+ .file("src/foo_1.rs", "fn main() {}")
+ .file("src/foo_2.rs", "fn main() {}")
+ .file("src/foo_3.rs", "fn main() {}")
+ .file("src/foo_4.rs", "fn main() {}")
+ .build();
+
+ p.cargo("install --path .").run();
+ assert_has_not_installed_exe(cargo_home(), "foo_1");
+ assert_has_installed_exe(cargo_home(), "foo_2");
+ assert_has_not_installed_exe(cargo_home(), "foo_3");
+ assert_has_not_installed_exe(cargo_home(), "foo_4");
+ p.cargo("uninstall foo").run();
+
+ p.cargo("install --path . --bins --examples").run();
+ assert_has_not_installed_exe(cargo_home(), "foo_1");
+ assert_has_installed_exe(cargo_home(), "foo_2");
+ assert_has_not_installed_exe(cargo_home(), "foo_3");
+ assert_has_installed_exe(cargo_home(), "foo_4");
+ p.cargo("uninstall foo").run();
+
+ p.cargo("install --path . --features c").run();
+ assert_has_installed_exe(cargo_home(), "foo_1");
+ assert_has_installed_exe(cargo_home(), "foo_2");
+ assert_has_not_installed_exe(cargo_home(), "foo_3");
+ assert_has_not_installed_exe(cargo_home(), "foo_4");
+ p.cargo("uninstall foo").run();
+
+ p.cargo("install --path . --features c --bins --examples")
+ .run();
+ assert_has_installed_exe(cargo_home(), "foo_1");
+ assert_has_installed_exe(cargo_home(), "foo_2");
+ assert_has_installed_exe(cargo_home(), "foo_3");
+ assert_has_installed_exe(cargo_home(), "foo_4");
+ p.cargo("uninstall foo").run();
+
+ p.cargo("install --path . --no-default-features")
+ .with_stderr(
+ "\
+[INSTALLING] foo v0.0.1 ([..])
+[FINISHED] release [optimized] target(s) in [..]
+[WARNING] none of the package's binaries are available for install using the selected features
+ bin \"foo_1\" requires the features: `b`, `c`
+ bin \"foo_2\" requires the features: `a`
+ example \"foo_3\" requires the features: `b`, `c`
+ example \"foo_4\" requires the features: `a`
+Consider enabling some of the needed features by passing, e.g., `--features=\"b c\"`
+",
+ )
+ .run();
+ p.cargo("install --path . --no-default-features --bins")
+ .with_stderr(
+ "\
+[INSTALLING] foo v0.0.1 ([..])
+[WARNING] Target filter `bins` specified, but no targets matched. This is a no-op
+[FINISHED] release [optimized] target(s) in [..]
+[WARNING] none of the package's binaries are available for install using the selected features
+ bin \"foo_1\" requires the features: `b`, `c`
+ bin \"foo_2\" requires the features: `a`
+ example \"foo_3\" requires the features: `b`, `c`
+ example \"foo_4\" requires the features: `a`
+Consider enabling some of the needed features by passing, e.g., `--features=\"b c\"`
+",
+ )
+ .run();
+ p.cargo("install --path . --no-default-features --examples")
+ .with_stderr(
+ "\
+[INSTALLING] foo v0.0.1 ([..])
+[WARNING] Target filter `examples` specified, but no targets matched. This is a no-op
+[FINISHED] release [optimized] target(s) in [..]
+[WARNING] none of the package's binaries are available for install using the selected features
+ bin \"foo_1\" requires the features: `b`, `c`
+ bin \"foo_2\" requires the features: `a`
+ example \"foo_3\" requires the features: `b`, `c`
+ example \"foo_4\" requires the features: `a`
+Consider enabling some of the needed features by passing, e.g., `--features=\"b c\"`
+",
+ )
+ .run();
+ p.cargo("install --path . --no-default-features --bins --examples")
+ .with_stderr(
+ "\
+[INSTALLING] foo v0.0.1 ([..])
+[WARNING] Target filters `bins`, `examples` specified, but no targets matched. This is a no-op
+[FINISHED] release [optimized] target(s) in [..]
+[WARNING] none of the package's binaries are available for install using the selected features
+ bin \"foo_1\" requires the features: `b`, `c`
+ bin \"foo_2\" requires the features: `a`
+ example \"foo_3\" requires the features: `b`, `c`
+ example \"foo_4\" requires the features: `a`
+Consider enabling some of the needed features by passing, e.g., `--features=\"b c\"`
+",
+ )
+ .run();
+ assert_has_not_installed_exe(cargo_home(), "foo_1");
+ assert_has_not_installed_exe(cargo_home(), "foo_2");
+ assert_has_not_installed_exe(cargo_home(), "foo_3");
+ assert_has_not_installed_exe(cargo_home(), "foo_4");
+}
+
+#[cargo_test]
+fn dep_feature_in_toml() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = { path = "bar", features = ["a"] }
+
+ [[bin]]
+ name = "foo"
+ required-features = ["bar/a"]
+
+ [[example]]
+ name = "foo"
+ required-features = ["bar/a"]
+
+ [[test]]
+ name = "foo"
+ required-features = ["bar/a"]
+
+ [[bench]]
+ name = "foo"
+ required-features = ["bar/a"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("examples/foo.rs", "fn main() {}")
+ .file("tests/foo.rs", "#[test]\nfn test() {}")
+ .file(
+ "benches/foo.rs",
+ r#"
+ #![feature(test)]
+ extern crate test;
+
+ #[bench]
+ fn bench(_: &mut test::Bencher) {
+ }
+ "#,
+ )
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ a = []
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+
+ // bin
+ p.cargo("build --bin=foo").run();
+ assert!(p.bin("foo").is_file());
+
+ // example
+ p.cargo("build --example=foo").run();
+ assert!(p.bin("examples/foo").is_file());
+
+ // test
+ p.cargo("test --test=foo")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo-[..][EXE])",
+ )
+ .with_stdout_contains("test test ... ok")
+ .run();
+
+ // bench
+ if is_nightly() {
+ p.cargo("bench --bench=foo")
+ .with_stderr(
+ "\
+[COMPILING] bar v0.0.1 ([CWD]/bar)
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] bench [optimized] target(s) in [..]
+[RUNNING] [..] (target/release/deps/foo-[..][EXE])",
+ )
+ .with_stdout_contains("test bench ... bench: [..]")
+ .run();
+ }
+
+ // install
+ p.cargo("install").run();
+ assert_has_installed_exe(cargo_home(), "foo");
+ p.cargo("uninstall foo").run();
+}
+
+#[cargo_test]
+fn dep_feature_in_cmd_line() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = { path = "bar" }
+
+ [[bin]]
+ name = "foo"
+ required-features = ["bar/a"]
+
+ [[example]]
+ name = "foo"
+ required-features = ["bar/a"]
+
+ [[test]]
+ name = "foo"
+ required-features = ["bar/a"]
+
+ [[bench]]
+ name = "foo"
+ required-features = ["bar/a"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("examples/foo.rs", "fn main() {}")
+ .file(
+ "tests/foo.rs",
+ r#"
+ #[test]
+ fn bin_is_built() {
+ let s = format!("target/debug/foo{}", std::env::consts::EXE_SUFFIX);
+ let p = std::path::Path::new(&s);
+ assert!(p.exists(), "foo does not exist");
+ }
+ "#,
+ )
+ .file(
+ "benches/foo.rs",
+ r#"
+ #![feature(test)]
+ extern crate test;
+
+ #[bench]
+ fn bench(_: &mut test::Bencher) {
+ }
+ "#,
+ )
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ a = []
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ // This is a no-op
+ p.cargo("build").with_stderr("[FINISHED] dev [..]").run();
+ assert!(!p.bin("foo").is_file());
+
+ // bin
+ p.cargo("build --bin=foo")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: target `foo` in package `foo` requires the features: `bar/a`
+Consider enabling them by passing, e.g., `--features=\"bar/a\"`
+",
+ )
+ .run();
+
+ p.cargo("build --bin=foo --features bar/a").run();
+ assert!(p.bin("foo").is_file());
+
+ // example
+ p.cargo("build --example=foo")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: target `foo` in package `foo` requires the features: `bar/a`
+Consider enabling them by passing, e.g., `--features=\"bar/a\"`
+",
+ )
+ .run();
+
+ p.cargo("build --example=foo --features bar/a").run();
+ assert!(p.bin("examples/foo").is_file());
+
+ // test
+ // This is a no-op, since no tests are enabled
+ p.cargo("test")
+ .with_stderr("[FINISHED] test [unoptimized + debuginfo] target(s) in [..]")
+ .with_stdout("")
+ .run();
+
+ // Delete the target directory so this can check if the main.rs gets built.
+ p.build_dir().rm_rf();
+ p.cargo("test --test=foo --features bar/a")
+ .with_stderr(
+ "\
+[COMPILING] bar v0.0.1 ([CWD]/bar)
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo-[..][EXE])",
+ )
+ .with_stdout_contains("test bin_is_built ... ok")
+ .run();
+
+ // bench
+ if is_nightly() {
+ p.cargo("bench")
+ .with_stderr("[FINISHED] bench [optimized] target(s) in [..]")
+ .with_stdout("")
+ .run();
+
+ p.cargo("bench --bench=foo --features bar/a")
+ .with_stderr(
+ "\
+[COMPILING] bar v0.0.1 ([CWD]/bar)
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] bench [optimized] target(s) in [..]
+[RUNNING] [..] (target/release/deps/foo-[..][EXE])",
+ )
+ .with_stdout_contains("test bench ... bench: [..]")
+ .run();
+ }
+
+ // install
+ p.cargo("install --path .")
+ .with_stderr(
+ "\
+[INSTALLING] foo v0.0.1 ([..])
+[FINISHED] release [optimized] target(s) in [..]
+[WARNING] none of the package's binaries are available for install using the selected features
+ bin \"foo\" requires the features: `bar/a`
+ example \"foo\" requires the features: `bar/a`
+Consider enabling some of the needed features by passing, e.g., `--features=\"bar/a\"`
+",
+ )
+ .run();
+ assert_has_not_installed_exe(cargo_home(), "foo");
+
+ p.cargo("install --features bar/a").run();
+ assert_has_installed_exe(cargo_home(), "foo");
+ p.cargo("uninstall foo").run();
+}
+
+#[cargo_test]
+fn test_skips_compiling_bin_with_missing_required_features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ a = []
+
+ [[bin]]
+ name = "bin_foo"
+ path = "src/bin/foo.rs"
+ required-features = ["a"]
+ "#,
+ )
+ .file("src/bin/foo.rs", "extern crate bar; fn main() {}")
+ .file("tests/foo.rs", "")
+ .file("benches/foo.rs", "")
+ .build();
+
+ p.cargo("test")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo-[..][EXE])",
+ )
+ .with_stdout_contains("running 0 tests")
+ .run();
+
+ p.cargo("test --features a -j 1")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+error[E0463]: can't find crate for `bar`",
+ )
+ .run();
+
+ if is_nightly() {
+ p.cargo("bench")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] bench [optimized] target(s) in [..]
+[RUNNING] [..] (target/release/deps/foo-[..][EXE])",
+ )
+ .with_stdout_contains("running 0 tests")
+ .run();
+
+ p.cargo("bench --features a -j 1")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+error[E0463]: can't find crate for `bar`",
+ )
+ .run();
+ }
+}
+
+#[cargo_test]
+fn run_default() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ default = []
+ a = []
+
+ [[bin]]
+ name = "foo"
+ required-features = ["a"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("src/main.rs", "extern crate foo; fn main() {}")
+ .build();
+
+ p.cargo("run")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: target `foo` in package `foo` requires the features: `a`
+Consider enabling them by passing, e.g., `--features=\"a\"`
+",
+ )
+ .run();
+
+ p.cargo("run --features a").run();
+}
+
+#[cargo_test]
+fn run_default_multiple_required_features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ default = ["a"]
+ a = []
+ b = []
+
+ [[bin]]
+ name = "foo1"
+ path = "src/foo1.rs"
+ required-features = ["a"]
+
+ [[bin]]
+ name = "foo3"
+ path = "src/foo3.rs"
+ required-features = ["b"]
+
+ [[bin]]
+ name = "foo2"
+ path = "src/foo2.rs"
+ required-features = ["b"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("src/foo1.rs", "extern crate foo; fn main() {}")
+ .file("src/foo3.rs", "extern crate foo; fn main() {}")
+ .file("src/foo2.rs", "extern crate foo; fn main() {}")
+ .build();
+
+ p.cargo("run")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: `cargo run` could not determine which binary to run[..]
+available binaries: foo1, foo2, foo3",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn renamed_required_features() {
+ // Test that required-features uses renamed package feature names.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [[bin]]
+ name = "x"
+ required-features = ["a1/f1"]
+
+ [dependencies]
+ a1 = {path="a1", package="a"}
+ a2 = {path="a2", package="a"}
+ "#,
+ )
+ .file(
+ "src/bin/x.rs",
+ r#"
+ fn main() {
+ a1::f();
+ a2::f();
+ }
+ "#,
+ )
+ .file(
+ "a1/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+
+ [features]
+ f1 = []
+ "#,
+ )
+ .file(
+ "a1/src/lib.rs",
+ r#"
+ pub fn f() {
+ if cfg!(feature="f1") {
+ println!("a1 f1");
+ }
+ }
+ "#,
+ )
+ .file(
+ "a2/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.2.0"
+
+ [features]
+ f2 = []
+ "#,
+ )
+ .file(
+ "a2/src/lib.rs",
+ r#"
+ pub fn f() {
+ if cfg!(feature="f2") {
+ println!("a2 f2");
+ }
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("run")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] target `x` in package `foo` requires the features: `a1/f1`
+Consider enabling them by passing, e.g., `--features=\"a1/f1\"`
+",
+ )
+ .run();
+
+ p.cargo("build --features a1/f1").run();
+ p.rename_run("x", "x_with_f1").with_stdout("a1 f1").run();
+
+ p.cargo("build --features a1/f1,a2/f2").run();
+ p.rename_run("x", "x_with_f1_f2")
+ .with_stdout("a1 f1\na2 f2")
+ .run();
+}
+
+#[cargo_test]
+fn truncated_install_warning_message() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2021"
+
+ [features]
+ feature1 = []
+ feature2 = []
+ feature3 = []
+ feature4 = []
+ feature5 = []
+
+ [[bin]]
+ name = "foo1"
+ required-features = ["feature1", "feature2", "feature3"]
+
+ [[bin]]
+ name = "foo2"
+ required-features = ["feature2"]
+
+ [[bin]]
+ name = "foo3"
+ required-features = ["feature3"]
+
+ [[bin]]
+ name = "foo4"
+ required-features = ["feature4", "feature1"]
+
+ [[bin]]
+ name = "foo5"
+ required-features = ["feature1", "feature2", "feature3", "feature4", "feature5"]
+
+ [[bin]]
+ name = "foo6"
+ required-features = ["feature1", "feature2", "feature3", "feature4", "feature5"]
+
+ [[bin]]
+ name = "foo7"
+ required-features = ["feature1", "feature2", "feature3", "feature4", "feature5"]
+
+ [[bin]]
+ name = "foo8"
+ required-features = ["feature1", "feature2", "feature3", "feature4", "feature5"]
+
+ [[bin]]
+ name = "foo9"
+ required-features = ["feature1", "feature2", "feature3", "feature4", "feature5"]
+
+ [[bin]]
+ name = "foo10"
+ required-features = ["feature1", "feature2", "feature3", "feature4", "feature5"]
+
+ [[example]]
+ name = "example1"
+ required-features = ["feature1", "feature2"]
+ "#,
+ )
+ .file("src/bin/foo1.rs", "fn main() {}")
+ .file("src/bin/foo2.rs", "fn main() {}")
+ .file("src/bin/foo3.rs", "fn main() {}")
+ .file("src/bin/foo4.rs", "fn main() {}")
+ .file("src/bin/foo5.rs", "fn main() {}")
+ .file("src/bin/foo6.rs", "fn main() {}")
+ .file("src/bin/foo7.rs", "fn main() {}")
+ .file("src/bin/foo8.rs", "fn main() {}")
+ .file("src/bin/foo9.rs", "fn main() {}")
+ .file("src/bin/foo10.rs", "fn main() {}")
+ .file("examples/example1.rs", "fn main() {}")
+ .build();
+
+ p.cargo("install --path .").with_stderr("\
+[INSTALLING] foo v0.1.0 ([..])
+[FINISHED] release [optimized] target(s) in [..]
+[WARNING] none of the package's binaries are available for install using the selected features
+ bin \"foo1\" requires the features: `feature1`, `feature2`, `feature3`
+ bin \"foo2\" requires the features: `feature2`
+ bin \"foo3\" requires the features: `feature3`
+ bin \"foo4\" requires the features: `feature4`, `feature1`
+ bin \"foo5\" requires the features: `feature1`, `feature2`, `feature3`, `feature4`, `feature5`
+ bin \"foo6\" requires the features: `feature1`, `feature2`, `feature3`, `feature4`, `feature5`
+ bin \"foo7\" requires the features: `feature1`, `feature2`, `feature3`, `feature4`, `feature5`
+4 more targets also requires features not enabled. See them in the Cargo.toml file.
+Consider enabling some of the needed features by passing, e.g., `--features=\"feature1 feature2 feature3\"`").run();
+}
diff --git a/src/tools/cargo/tests/testsuite/run.rs b/src/tools/cargo/tests/testsuite/run.rs
new file mode 100644
index 000000000..aa210d6ae
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/run.rs
@@ -0,0 +1,1509 @@
+//! Tests for the `cargo run` command.
+
+use cargo_test_support::{basic_bin_manifest, basic_lib_manifest, project, Project};
+use cargo_util::paths::dylib_path_envvar;
+
+#[cargo_test]
+fn simple() {
+ let p = project()
+ .file("src/main.rs", r#"fn main() { println!("hello"); }"#)
+ .build();
+
+ p.cargo("run")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `target/debug/foo[EXE]`",
+ )
+ .with_stdout("hello")
+ .run();
+ assert!(p.bin("foo").is_file());
+}
+
+#[cargo_test]
+fn quiet_arg() {
+ let p = project()
+ .file("src/main.rs", r#"fn main() { println!("hello"); }"#)
+ .build();
+
+ p.cargo("run -q").with_stderr("").with_stdout("hello").run();
+
+ p.cargo("run --quiet")
+ .with_stderr("")
+ .with_stdout("hello")
+ .run();
+}
+
+#[cargo_test]
+fn quiet_arg_and_verbose_arg() {
+ let p = project()
+ .file("src/main.rs", r#"fn main() { println!("hello"); }"#)
+ .build();
+
+ p.cargo("run -q -v")
+ .with_status(101)
+ .with_stderr("[ERROR] cannot set both --verbose and --quiet")
+ .run();
+}
+
+#[cargo_test]
+fn quiet_arg_and_verbose_config() {
+ let p = project()
+ .file(
+ ".cargo/config",
+ r#"
+ [term]
+ verbose = true
+ "#,
+ )
+ .file("src/main.rs", r#"fn main() { println!("hello"); }"#)
+ .build();
+
+ p.cargo("run -q").with_stderr("").with_stdout("hello").run();
+}
+
+#[cargo_test]
+fn verbose_arg_and_quiet_config() {
+ let p = project()
+ .file(
+ ".cargo/config",
+ r#"
+ [term]
+ quiet = true
+ "#,
+ )
+ .file("src/main.rs", r#"fn main() { println!("hello"); }"#)
+ .build();
+
+ p.cargo("run -v")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `target/debug/foo[EXE]`",
+ )
+ .with_stdout("hello")
+ .run();
+}
+
+#[cargo_test]
+fn quiet_config_alone() {
+ let p = project()
+ .file(
+ ".cargo/config",
+ r#"
+ [term]
+ quiet = true
+ "#,
+ )
+ .file("src/main.rs", r#"fn main() { println!("hello"); }"#)
+ .build();
+
+ p.cargo("run").with_stderr("").with_stdout("hello").run();
+}
+
+#[cargo_test]
+fn verbose_config_alone() {
+ let p = project()
+ .file(
+ ".cargo/config",
+ r#"
+ [term]
+ verbose = true
+ "#,
+ )
+ .file("src/main.rs", r#"fn main() { println!("hello"); }"#)
+ .build();
+
+ p.cargo("run")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `target/debug/foo[EXE]`",
+ )
+ .with_stdout("hello")
+ .run();
+}
+
+#[cargo_test]
+fn quiet_config_and_verbose_config() {
+ let p = project()
+ .file(
+ ".cargo/config",
+ r#"
+ [term]
+ verbose = true
+ quiet = true
+ "#,
+ )
+ .file("src/main.rs", r#"fn main() { println!("hello"); }"#)
+ .build();
+
+ p.cargo("run")
+ .with_status(101)
+ .with_stderr("[ERROR] cannot set both `term.verbose` and `term.quiet`")
+ .run();
+}
+
+#[cargo_test]
+fn simple_with_args() {
+ let p = project()
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ assert_eq!(std::env::args().nth(1).unwrap(), "hello");
+ assert_eq!(std::env::args().nth(2).unwrap(), "world");
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("run hello world").run();
+}
+
+#[cfg(unix)]
+#[cargo_test]
+fn simple_with_non_utf8_args() {
+ use std::os::unix::ffi::OsStrExt;
+
+ let p = project()
+ .file(
+ "src/main.rs",
+ r#"
+ use std::ffi::OsStr;
+ use std::os::unix::ffi::OsStrExt;
+
+ fn main() {
+ assert_eq!(std::env::args_os().nth(1).unwrap(), OsStr::from_bytes(b"hello"));
+ assert_eq!(std::env::args_os().nth(2).unwrap(), OsStr::from_bytes(b"ab\xffcd"));
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("run")
+ .arg("hello")
+ .arg(std::ffi::OsStr::from_bytes(b"ab\xFFcd"))
+ .run();
+}
+
+#[cargo_test]
+fn exit_code() {
+ let p = project()
+ .file("src/main.rs", "fn main() { std::process::exit(2); }")
+ .build();
+
+ let mut output = String::from(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `target[..]`
+",
+ );
+ if !cfg!(unix) {
+ output.push_str(
+ "[ERROR] process didn't exit successfully: `target[..]foo[..]` (exit [..]: 2)",
+ );
+ }
+ p.cargo("run").with_status(2).with_stderr(output).run();
+}
+
+#[cargo_test]
+fn exit_code_verbose() {
+ let p = project()
+ .file("src/main.rs", "fn main() { std::process::exit(2); }")
+ .build();
+
+ let mut output = String::from(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `target[..]`
+",
+ );
+ if !cfg!(unix) {
+ output.push_str(
+ "[ERROR] process didn't exit successfully: `target[..]foo[..]` (exit [..]: 2)",
+ );
+ }
+
+ p.cargo("run -v").with_status(2).with_stderr(output).run();
+}
+
+#[cargo_test]
+fn no_main_file() {
+ let p = project().file("src/lib.rs", "").build();
+
+ p.cargo("run")
+ .with_status(101)
+ .with_stderr(
+ "[ERROR] a bin target must be available \
+ for `cargo run`\n",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn too_many_bins() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file("src/bin/a.rs", "")
+ .file("src/bin/b.rs", "")
+ .build();
+
+ // Using [..] here because the order is not stable
+ p.cargo("run")
+ .with_status(101)
+ .with_stderr(
+ "[ERROR] `cargo run` could not determine which binary to run. \
+ Use the `--bin` option to specify a binary, or the \
+ `default-run` manifest key.\
+ \navailable binaries: [..]\n",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn specify_name() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(
+ "src/bin/a.rs",
+ r#"
+ #[allow(unused_extern_crates)]
+ extern crate foo;
+ fn main() { println!("hello a.rs"); }
+ "#,
+ )
+ .file(
+ "src/bin/b.rs",
+ r#"
+ #[allow(unused_extern_crates)]
+ extern crate foo;
+ fn main() { println!("hello b.rs"); }
+ "#,
+ )
+ .build();
+
+ p.cargo("run --bin a -v")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc [..] src/lib.rs [..]`
+[RUNNING] `rustc [..] src/bin/a.rs [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `target/debug/a[EXE]`",
+ )
+ .with_stdout("hello a.rs")
+ .run();
+
+ p.cargo("run --bin b -v")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[RUNNING] `rustc [..] src/bin/b.rs [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `target/debug/b[EXE]`",
+ )
+ .with_stdout("hello b.rs")
+ .run();
+}
+
+#[cargo_test]
+fn specify_default_run() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ default-run = "a"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("src/bin/a.rs", r#"fn main() { println!("hello A"); }"#)
+ .file("src/bin/b.rs", r#"fn main() { println!("hello B"); }"#)
+ .build();
+
+ p.cargo("run").with_stdout("hello A").run();
+ p.cargo("run --bin a").with_stdout("hello A").run();
+ p.cargo("run --bin b").with_stdout("hello B").run();
+}
+
+#[cargo_test]
+fn bogus_default_run() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ default-run = "b"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("src/bin/a.rs", r#"fn main() { println!("hello A"); }"#)
+ .build();
+
+ p.cargo("run")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]/foo/Cargo.toml`
+
+Caused by:
+ default-run target `b` not found
+
+ <tab>Did you mean `a`?
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn run_example() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file("examples/a.rs", r#"fn main() { println!("example"); }"#)
+ .file("src/bin/a.rs", r#"fn main() { println!("bin"); }"#)
+ .build();
+
+ p.cargo("run --example a")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `target/debug/examples/a[EXE]`",
+ )
+ .with_stdout("example")
+ .run();
+}
+
+#[cargo_test]
+fn run_library_example() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ [[example]]
+ name = "bar"
+ crate_type = ["lib"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("examples/bar.rs", "fn foo() {}")
+ .build();
+
+ p.cargo("run --example bar")
+ .with_status(101)
+ .with_stderr("[ERROR] example target `bar` is a library and cannot be executed")
+ .run();
+}
+
+#[cargo_test]
+fn run_bin_example() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ [[example]]
+ name = "bar"
+ crate_type = ["bin"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("examples/bar.rs", r#"fn main() { println!("example"); }"#)
+ .build();
+
+ p.cargo("run --example bar")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `target/debug/examples/bar[EXE]`",
+ )
+ .with_stdout("example")
+ .run();
+}
+
+fn autodiscover_examples_project(rust_edition: &str, autoexamples: Option<bool>) -> Project {
+ let autoexamples = match autoexamples {
+ None => "".to_string(),
+ Some(bool) => format!("autoexamples = {}", bool),
+ };
+ project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ edition = "{rust_edition}"
+ {autoexamples}
+
+ [features]
+ magic = []
+
+ [[example]]
+ name = "do_magic"
+ required-features = ["magic"]
+ "#,
+ rust_edition = rust_edition,
+ autoexamples = autoexamples
+ ),
+ )
+ .file("examples/a.rs", r#"fn main() { println!("example"); }"#)
+ .file(
+ "examples/do_magic.rs",
+ r#"
+ fn main() { println!("magic example"); }
+ "#,
+ )
+ .build()
+}
+
+#[cargo_test]
+fn run_example_autodiscover_2015() {
+ let p = autodiscover_examples_project("2015", None);
+ p.cargo("run --example a")
+ .with_status(101)
+ .with_stderr(
+ "warning: \
+An explicit [[example]] section is specified in Cargo.toml which currently
+disables Cargo from automatically inferring other example targets.
+This inference behavior will change in the Rust 2018 edition and the following
+files will be included as a example target:
+
+* [..]a.rs
+
+This is likely to break cargo build or cargo test as these files may not be
+ready to be compiled as a example target today. You can future-proof yourself
+and disable this warning by adding `autoexamples = false` to your [package]
+section. You may also move the files to a location where Cargo would not
+automatically infer them to be a target, such as in subfolders.
+
+For more information on this warning you can consult
+https://github.com/rust-lang/cargo/issues/5330
+error: no example target named `a`.
+Available example targets:
+ do_magic
+
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn run_example_autodiscover_2015_with_autoexamples_enabled() {
+ let p = autodiscover_examples_project("2015", Some(true));
+ p.cargo("run --example a")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `target/debug/examples/a[EXE]`",
+ )
+ .with_stdout("example")
+ .run();
+}
+
+#[cargo_test]
+fn run_example_autodiscover_2015_with_autoexamples_disabled() {
+ let p = autodiscover_examples_project("2015", Some(false));
+ p.cargo("run --example a")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: no example target named `a`.
+Available example targets:
+ do_magic
+
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn run_example_autodiscover_2018() {
+ let p = autodiscover_examples_project("2018", None);
+ p.cargo("run --example a")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `target/debug/examples/a[EXE]`",
+ )
+ .with_stdout("example")
+ .run();
+}
+
+#[cargo_test]
+fn autobins_disables() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ autobins = false
+ "#,
+ )
+ .file("src/lib.rs", "pub mod bin;")
+ .file("src/bin/mod.rs", "// empty")
+ .build();
+
+ p.cargo("run")
+ .with_status(101)
+ .with_stderr("[ERROR] a bin target must be available for `cargo run`")
+ .run();
+}
+
+#[cargo_test]
+fn run_bins() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file("examples/a.rs", r#"fn main() { println!("example"); }"#)
+ .file("src/bin/a.rs", r#"fn main() { println!("bin"); }"#)
+ .build();
+
+ p.cargo("run --bins")
+ .with_status(1)
+ .with_stderr_contains(
+ "\
+error: unexpected argument '--bins' found
+
+ tip: a similar argument exists: '--bin'",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn run_with_filename() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(
+ "src/bin/a.rs",
+ r#"
+ extern crate foo;
+ fn main() { println!("hello a.rs"); }
+ "#,
+ )
+ .file("examples/a.rs", r#"fn main() { println!("example"); }"#)
+ .build();
+
+ p.cargo("run --bin bin.rs")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] no bin target named `bin.rs`.
+Available bin targets:
+ a
+
+",
+ )
+ .run();
+
+ p.cargo("run --bin a.rs")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] no bin target named `a.rs`
+
+<tab>Did you mean `a`?",
+ )
+ .run();
+
+ p.cargo("run --example example.rs")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] no example target named `example.rs`.
+Available example targets:
+ a
+
+",
+ )
+ .run();
+
+ p.cargo("run --example a.rs")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] no example target named `a.rs`
+
+<tab>Did you mean `a`?",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn either_name_or_example() {
+ let p = project()
+ .file("src/bin/a.rs", r#"fn main() { println!("hello a.rs"); }"#)
+ .file("examples/b.rs", r#"fn main() { println!("hello b.rs"); }"#)
+ .build();
+
+ p.cargo("run --bin a --example b")
+ .with_status(101)
+ .with_stderr(
+ "[ERROR] `cargo run` can run at most one \
+ executable, but multiple were \
+ specified",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn one_bin_multiple_examples() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(
+ "src/bin/main.rs",
+ r#"fn main() { println!("hello main.rs"); }"#,
+ )
+ .file("examples/a.rs", r#"fn main() { println!("hello a.rs"); }"#)
+ .file("examples/b.rs", r#"fn main() { println!("hello b.rs"); }"#)
+ .build();
+
+ p.cargo("run")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `target/debug/main[EXE]`",
+ )
+ .with_stdout("hello main.rs")
+ .run();
+}
+
+#[cargo_test]
+fn example_with_release_flag() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ version = "*"
+ path = "bar"
+ "#,
+ )
+ .file(
+ "examples/a.rs",
+ r#"
+ extern crate bar;
+
+ fn main() {
+ if cfg!(debug_assertions) {
+ println!("slow1")
+ } else {
+ println!("fast1")
+ }
+ bar::baz();
+ }
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_lib_manifest("bar"))
+ .file(
+ "bar/src/bar.rs",
+ r#"
+ pub fn baz() {
+ if cfg!(debug_assertions) {
+ println!("slow2")
+ } else {
+ println!("fast2")
+ }
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("run -v --release --example a")
+ .with_stderr(
+ "\
+[COMPILING] bar v0.5.0 ([CWD]/bar)
+[RUNNING] `rustc --crate-name bar bar/src/bar.rs [..]--crate-type lib \
+ --emit=[..]link \
+ -C opt-level=3[..]\
+ -C metadata=[..] \
+ --out-dir [CWD]/target/release/deps \
+ -L dependency=[CWD]/target/release/deps`
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name a examples/a.rs [..]--crate-type bin \
+ --emit=[..]link \
+ -C opt-level=3[..]\
+ -C metadata=[..] \
+ --out-dir [CWD]/target/release/examples \
+ -L dependency=[CWD]/target/release/deps \
+ --extern bar=[CWD]/target/release/deps/libbar-[..].rlib`
+[FINISHED] release [optimized] target(s) in [..]
+[RUNNING] `target/release/examples/a[EXE]`
+",
+ )
+ .with_stdout(
+ "\
+fast1
+fast2",
+ )
+ .run();
+
+ p.cargo("run -v --example a")
+ .with_stderr(
+ "\
+[COMPILING] bar v0.5.0 ([CWD]/bar)
+[RUNNING] `rustc --crate-name bar bar/src/bar.rs [..]--crate-type lib \
+ --emit=[..]link[..]\
+ -C debuginfo=2 \
+ -C metadata=[..] \
+ --out-dir [CWD]/target/debug/deps \
+ -L dependency=[CWD]/target/debug/deps`
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name a examples/a.rs [..]--crate-type bin \
+ --emit=[..]link[..]\
+ -C debuginfo=2 \
+ -C metadata=[..] \
+ --out-dir [CWD]/target/debug/examples \
+ -L dependency=[CWD]/target/debug/deps \
+ --extern bar=[CWD]/target/debug/deps/libbar-[..].rlib`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `target/debug/examples/a[EXE]`
+",
+ )
+ .with_stdout(
+ "\
+slow1
+slow2",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn run_dylib_dep() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "bar"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"extern crate bar; fn main() { bar::bar(); }"#,
+ )
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ name = "bar"
+ crate-type = ["dylib"]
+ "#,
+ )
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ p.cargo("run hello world").run();
+}
+
+#[cargo_test]
+fn run_with_bin_dep() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies.bar]
+ path = "bar"
+ "#,
+ )
+ .file("src/main.rs", r#"fn main() { println!("hello"); }"#)
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [[bin]]
+ name = "bar"
+ "#,
+ )
+ .file("bar/src/main.rs", r#"fn main() { println!("bar"); }"#)
+ .build();
+
+ p.cargo("run")
+ .with_stderr(
+ "\
+[WARNING] foo v0.0.1 ([CWD]) ignoring invalid dependency `bar` which is missing a lib target
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `target/debug/foo[EXE]`",
+ )
+ .with_stdout("hello")
+ .run();
+}
+
+#[cargo_test]
+fn run_with_bin_deps() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies.bar1]
+ path = "bar1"
+ [dependencies.bar2]
+ path = "bar2"
+ "#,
+ )
+ .file("src/main.rs", r#"fn main() { println!("hello"); }"#)
+ .file(
+ "bar1/Cargo.toml",
+ r#"
+ [package]
+ name = "bar1"
+ version = "0.0.1"
+ authors = []
+
+ [[bin]]
+ name = "bar1"
+ "#,
+ )
+ .file("bar1/src/main.rs", r#"fn main() { println!("bar1"); }"#)
+ .file(
+ "bar2/Cargo.toml",
+ r#"
+ [package]
+ name = "bar2"
+ version = "0.0.1"
+ authors = []
+
+ [[bin]]
+ name = "bar2"
+ "#,
+ )
+ .file("bar2/src/main.rs", r#"fn main() { println!("bar2"); }"#)
+ .build();
+
+ p.cargo("run")
+ .with_stderr(
+ "\
+[WARNING] foo v0.0.1 ([CWD]) ignoring invalid dependency `bar1` which is missing a lib target
+[WARNING] foo v0.0.1 ([CWD]) ignoring invalid dependency `bar2` which is missing a lib target
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `target/debug/foo[EXE]`",
+ )
+ .with_stdout("hello")
+ .run();
+}
+
+#[cargo_test]
+fn run_with_bin_dep_in_workspace() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["foo1", "foo2"]
+ "#,
+ )
+ .file(
+ "foo1/Cargo.toml",
+ r#"
+ [package]
+ name = "foo1"
+ version = "0.0.1"
+
+ [dependencies.bar1]
+ path = "bar1"
+ "#,
+ )
+ .file("foo1/src/main.rs", r#"fn main() { println!("hello"); }"#)
+ .file(
+ "foo1/bar1/Cargo.toml",
+ r#"
+ [package]
+ name = "bar1"
+ version = "0.0.1"
+ authors = []
+
+ [[bin]]
+ name = "bar1"
+ "#,
+ )
+ .file(
+ "foo1/bar1/src/main.rs",
+ r#"fn main() { println!("bar1"); }"#,
+ )
+ .file(
+ "foo2/Cargo.toml",
+ r#"
+ [package]
+ name = "foo2"
+ version = "0.0.1"
+
+ [dependencies.bar2]
+ path = "bar2"
+ "#,
+ )
+ .file("foo2/src/main.rs", r#"fn main() { println!("hello"); }"#)
+ .file(
+ "foo2/bar2/Cargo.toml",
+ r#"
+ [package]
+ name = "bar2"
+ version = "0.0.1"
+ authors = []
+
+ [[bin]]
+ name = "bar2"
+ "#,
+ )
+ .file(
+ "foo2/bar2/src/main.rs",
+ r#"fn main() { println!("bar2"); }"#,
+ )
+ .build();
+
+ p.cargo("run")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] `cargo run` could not determine which binary to run[..]
+available binaries: bar1, bar2, foo1, foo2",
+ )
+ .run();
+
+ p.cargo("run --bin foo1")
+ .with_stderr(
+ "\
+[WARNING] foo1 v0.0.1 ([CWD]/foo1) ignoring invalid dependency `bar1` which is missing a lib target
+[WARNING] foo2 v0.0.1 ([CWD]/foo2) ignoring invalid dependency `bar2` which is missing a lib target
+[COMPILING] foo1 v0.0.1 ([CWD]/foo1)
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `target/debug/foo1[EXE]`",
+ )
+ .with_stdout("hello")
+ .run();
+}
+
+#[cargo_test]
+fn release_works() {
+ let p = project()
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() { if cfg!(debug_assertions) { panic!() } }
+ "#,
+ )
+ .build();
+
+ p.cargo("run --release")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] release [optimized] target(s) in [..]
+[RUNNING] `target/release/foo[EXE]`
+",
+ )
+ .run();
+ assert!(p.release_bin("foo").is_file());
+}
+
+#[cargo_test]
+fn release_short_works() {
+ let p = project()
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() { if cfg!(debug_assertions) { panic!() } }
+ "#,
+ )
+ .build();
+
+ p.cargo("run -r")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] release [optimized] target(s) in [..]
+[RUNNING] `target/release/foo[EXE]`
+",
+ )
+ .run();
+ assert!(p.release_bin("foo").is_file());
+}
+
+#[cargo_test]
+fn run_bin_different_name() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [[bin]]
+ name = "bar"
+ "#,
+ )
+ .file("src/bar.rs", "fn main() {}")
+ .build();
+
+ p.cargo("run").run();
+}
+
+#[cargo_test]
+fn dashes_are_forwarded() {
+ let p = project()
+ .file(
+ "src/bin/bar.rs",
+ r#"
+ fn main() {
+ let s: Vec<String> = std::env::args().collect();
+ assert_eq!(s[1], "--");
+ assert_eq!(s[2], "a");
+ assert_eq!(s[3], "--");
+ assert_eq!(s[4], "b");
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("run -- -- a -- b").run();
+}
+
+#[cargo_test]
+fn run_from_executable_folder() {
+ let p = project()
+ .file("src/main.rs", r#"fn main() { println!("hello"); }"#)
+ .build();
+
+ let cwd = p.root().join("target").join("debug");
+ p.cargo("build").run();
+
+ p.cargo("run")
+ .cwd(cwd)
+ .with_stderr(
+ "[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]\n\
+ [RUNNING] `./foo[EXE]`",
+ )
+ .with_stdout("hello")
+ .run();
+}
+
+#[cargo_test]
+fn run_with_library_paths() {
+ let p = project();
+
+ // Only link search directories within the target output directory are
+ // propagated through to dylib_path_envvar() (see #3366).
+ let mut dir1 = p.target_debug_dir();
+ dir1.push("foo\\backslash");
+
+ let mut dir2 = p.target_debug_dir();
+ dir2.push("dir=containing=equal=signs");
+
+ let p = p
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file(
+ "build.rs",
+ &format!(
+ r##"
+ fn main() {{
+ println!(r#"cargo:rustc-link-search=native={}"#);
+ println!(r#"cargo:rustc-link-search={}"#);
+ }}
+ "##,
+ dir1.display(),
+ dir2.display()
+ ),
+ )
+ .file(
+ "src/main.rs",
+ &format!(
+ r##"
+ fn main() {{
+ let search_path = std::env::var_os("{}").unwrap();
+ let paths = std::env::split_paths(&search_path).collect::<Vec<_>>();
+ println!("{{:#?}}", paths);
+ assert!(paths.contains(&r#"{}"#.into()));
+ assert!(paths.contains(&r#"{}"#.into()));
+ }}
+ "##,
+ dylib_path_envvar(),
+ dir1.display(),
+ dir2.display()
+ ),
+ )
+ .build();
+
+ p.cargo("run").run();
+}
+
+#[cargo_test]
+fn library_paths_sorted_alphabetically() {
+ let p = project();
+
+ let mut dir1 = p.target_debug_dir();
+ dir1.push("zzzzzzz");
+
+ let mut dir2 = p.target_debug_dir();
+ dir2.push("BBBBBBB");
+
+ let mut dir3 = p.target_debug_dir();
+ dir3.push("aaaaaaa");
+
+ let p = p
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file(
+ "build.rs",
+ &format!(
+ r##"
+ fn main() {{
+ println!(r#"cargo:rustc-link-search=native={}"#);
+ println!(r#"cargo:rustc-link-search=native={}"#);
+ println!(r#"cargo:rustc-link-search=native={}"#);
+ }}
+ "##,
+ dir1.display(),
+ dir2.display(),
+ dir3.display()
+ ),
+ )
+ .file(
+ "src/main.rs",
+ &format!(
+ r##"
+ fn main() {{
+ let search_path = std::env::var_os("{}").unwrap();
+ let paths = std::env::split_paths(&search_path).collect::<Vec<_>>();
+ // ASCII case-sensitive sort
+ assert_eq!("BBBBBBB", paths[0].file_name().unwrap().to_string_lossy());
+ assert_eq!("aaaaaaa", paths[1].file_name().unwrap().to_string_lossy());
+ assert_eq!("zzzzzzz", paths[2].file_name().unwrap().to_string_lossy());
+ }}
+ "##,
+ dylib_path_envvar()
+ ),
+ )
+ .build();
+
+ p.cargo("run").run();
+}
+
+#[cargo_test]
+fn fail_no_extra_verbose() {
+ let p = project()
+ .file("src/main.rs", "fn main() { std::process::exit(1); }")
+ .build();
+
+ p.cargo("run -q")
+ .with_status(1)
+ .with_stdout("")
+ .with_stderr("")
+ .run();
+}
+
+#[cargo_test]
+fn run_multiple_packages() {
+ let p = project()
+ .no_manifest()
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [workspace]
+
+ [dependencies]
+ d1 = { path = "d1" }
+ d2 = { path = "d2" }
+ d3 = { path = "../d3" } # outside of the workspace
+
+ [[bin]]
+ name = "foo"
+ "#,
+ )
+ .file("foo/src/foo.rs", "fn main() { println!(\"foo\"); }")
+ .file("foo/d1/Cargo.toml", &basic_bin_manifest("d1"))
+ .file("foo/d1/src/lib.rs", "")
+ .file("foo/d1/src/main.rs", "fn main() { println!(\"d1\"); }")
+ .file("foo/d2/Cargo.toml", &basic_bin_manifest("d2"))
+ .file("foo/d2/src/main.rs", "fn main() { println!(\"d2\"); }")
+ .file("d3/Cargo.toml", &basic_bin_manifest("d3"))
+ .file("d3/src/main.rs", "fn main() { println!(\"d2\"); }")
+ .build();
+
+ let cargo = || {
+ let mut process_builder = p.cargo("run");
+ process_builder.cwd("foo");
+ process_builder
+ };
+
+ cargo().arg("-p").arg("d1").with_stdout("d1").run();
+
+ cargo()
+ .arg("-p")
+ .arg("d2")
+ .arg("--bin")
+ .arg("d2")
+ .with_stdout("d2")
+ .run();
+
+ cargo().with_stdout("foo").run();
+
+ cargo()
+ .arg("-p")
+ .arg("d1")
+ .arg("-p")
+ .arg("d2")
+ .with_status(1)
+ .with_stderr_contains(
+ "error: the argument '--package [<SPEC>]' cannot be used multiple times",
+ )
+ .run();
+
+ cargo()
+ .arg("-p")
+ .arg("d3")
+ .with_status(101)
+ .with_stderr_contains("[ERROR] package(s) `d3` not found in workspace [..]")
+ .run();
+
+ cargo()
+ .arg("-p")
+ .arg("d*")
+ .with_status(101)
+ .with_stderr_contains(
+ "[ERROR] `cargo run` does not support glob pattern `d*` on package selection",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn explicit_bin_with_args() {
+ let p = project()
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ assert_eq!(std::env::args().nth(1).unwrap(), "hello");
+ assert_eq!(std::env::args().nth(2).unwrap(), "world");
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("run --bin foo hello world").run();
+}
+
+#[cargo_test]
+fn run_workspace() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["a", "b"]
+ "#,
+ )
+ .file("a/Cargo.toml", &basic_bin_manifest("a"))
+ .file("a/src/main.rs", r#"fn main() {println!("run-a");}"#)
+ .file("b/Cargo.toml", &basic_bin_manifest("b"))
+ .file("b/src/main.rs", r#"fn main() {println!("run-b");}"#)
+ .build();
+
+ p.cargo("run")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] `cargo run` could not determine which binary to run[..]
+available binaries: a, b",
+ )
+ .run();
+ p.cargo("run --bin a").with_stdout("run-a").run();
+}
+
+#[cargo_test]
+fn default_run_workspace() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["a", "b"]
+ "#,
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+ default-run = "a"
+ "#,
+ )
+ .file("a/src/main.rs", r#"fn main() {println!("run-a");}"#)
+ .file("b/Cargo.toml", &basic_bin_manifest("b"))
+ .file("b/src/main.rs", r#"fn main() {println!("run-b");}"#)
+ .build();
+
+ p.cargo("run").with_stdout("run-a").run();
+}
+
+#[cargo_test]
+#[cfg(target_os = "macos")]
+fn run_link_system_path_macos() {
+ use cargo_test_support::paths::{self, CargoPathExt};
+ use std::fs;
+ // Check that the default system library path is honored.
+ // First, build a shared library that will be accessed from
+ // DYLD_FALLBACK_LIBRARY_PATH.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ [lib]
+ crate-type = ["cdylib"]
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "#[no_mangle] pub extern fn something_shared() {}",
+ )
+ .build();
+ p.cargo("build").run();
+
+ // This is convoluted. Since this test can't modify things in /usr,
+ // this needs to dance around to check that things work.
+ //
+ // The default DYLD_FALLBACK_LIBRARY_PATH is:
+ // $(HOME)/lib:/usr/local/lib:/lib:/usr/lib
+ //
+ // This will make use of ~/lib in the path, but the default cc link
+ // path is /usr/lib:/usr/local/lib. So first need to build in one
+ // location, and then move it to ~/lib.
+ //
+ // 1. Build with rustc-link-search pointing to libfoo so the initial
+ // binary can be linked.
+ // 2. Move the library to ~/lib
+ // 3. Run `cargo run` to make sure it can still find the library in
+ // ~/lib.
+ //
+ // This should be equivalent to having the library in /usr/local/lib.
+ let p2 = project()
+ .at("bar")
+ .file("Cargo.toml", &basic_bin_manifest("bar"))
+ .file(
+ "src/main.rs",
+ r#"
+ extern {
+ fn something_shared();
+ }
+ fn main() {
+ unsafe { something_shared(); }
+ }
+ "#,
+ )
+ .file(
+ "build.rs",
+ &format!(
+ r#"
+ fn main() {{
+ println!("cargo:rustc-link-lib=foo");
+ println!("cargo:rustc-link-search={}");
+ }}
+ "#,
+ p.target_debug_dir().display()
+ ),
+ )
+ .build();
+ p2.cargo("build").run();
+ p2.cargo("test").run();
+
+ let libdir = paths::home().join("lib");
+ fs::create_dir(&libdir).unwrap();
+ fs::rename(
+ p.target_debug_dir().join("libfoo.dylib"),
+ libdir.join("libfoo.dylib"),
+ )
+ .unwrap();
+ p.root().rm_rf();
+ const VAR: &str = "DYLD_FALLBACK_LIBRARY_PATH";
+ // Reset DYLD_FALLBACK_LIBRARY_PATH so that we don't inherit anything that
+ // was set by the cargo that invoked the test.
+ p2.cargo("run").env_remove(VAR).run();
+ p2.cargo("test").env_remove(VAR).run();
+ // Ensure this still works when DYLD_FALLBACK_LIBRARY_PATH has
+ // a value set.
+ p2.cargo("run").env(VAR, &libdir).run();
+ p2.cargo("test").env(VAR, &libdir).run();
+}
diff --git a/src/tools/cargo/tests/testsuite/rust_version.rs b/src/tools/cargo/tests/testsuite/rust_version.rs
new file mode 100644
index 000000000..91711cf1a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/rust_version.rs
@@ -0,0 +1,194 @@
+//! Tests for targets with `rust-version`.
+
+use cargo_test_support::{project, registry::Package};
+
+#[cargo_test]
+fn rust_version_satisfied() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ rust-version = "1.1.1"
+ [[bin]]
+ name = "foo"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check").run();
+ p.cargo("check --ignore-rust-version").run();
+}
+
+#[cargo_test]
+fn rust_version_bad_caret() {
+ project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ rust-version = "^1.43"
+ [[bin]]
+ name = "foo"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build()
+ .cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "error: failed to parse manifest at `[..]`\n\n\
+ Caused by:\n `rust-version` must be a value like \"1.32\"",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rust_version_bad_pre_release() {
+ project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ rust-version = "1.43-beta.1"
+ [[bin]]
+ name = "foo"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build()
+ .cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "error: failed to parse manifest at `[..]`\n\n\
+ Caused by:\n `rust-version` must be a value like \"1.32\"",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rust_version_bad_nonsense() {
+ project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ rust-version = "foodaddle"
+ [[bin]]
+ name = "foo"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build()
+ .cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "error: failed to parse manifest at `[..]`\n\n\
+ Caused by:\n `rust-version` must be a value like \"1.32\"",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rust_version_too_high() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ rust-version = "1.9876.0"
+ [[bin]]
+ name = "foo"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "error: package `foo v0.0.1 ([..])` cannot be built because it requires \
+ rustc 1.9876.0 or newer, while the currently active rustc version is [..]\n\n",
+ )
+ .run();
+ p.cargo("check --ignore-rust-version").run();
+}
+
+#[cargo_test]
+fn rust_version_dependency_fails() {
+ Package::new("bar", "0.0.1")
+ .rust_version("1.2345.0")
+ .file("src/lib.rs", "fn other_stuff() {}")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ [dependencies]
+ bar = "0.0.1"
+ "#,
+ )
+ .file("src/main.rs", "fn main(){}")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ " Updating `[..]` index\n \
+ Downloading crates ...\n \
+ Downloaded bar v0.0.1 (registry `[..]`)\n\
+ error: package `bar v0.0.1` cannot be built because it requires \
+ rustc 1.2345.0 or newer, while the currently active rustc version is [..]\n\
+ Either upgrade to rustc 1.2345.0 or newer, or use\n\
+ cargo update -p bar@0.0.1 --precise ver\n\
+ where `ver` is the latest version of `bar` supporting rustc [..]",
+ )
+ .run();
+ p.cargo("check --ignore-rust-version").run();
+}
+
+#[cargo_test]
+fn rust_version_older_than_edition() {
+ project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ rust-version = "1.1"
+ edition = "2018"
+ [[bin]]
+ name = "foo"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build()
+ .cargo("check")
+ .with_status(101)
+ .with_stderr_contains(" rust-version 1.1 is older than first version (1.31.0) required by the specified edition (2018)",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/rustc.rs b/src/tools/cargo/tests/testsuite/rustc.rs
new file mode 100644
index 000000000..65e0740f8
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/rustc.rs
@@ -0,0 +1,794 @@
+//! Tests for the `cargo rustc` command.
+
+use cargo_test_support::{basic_bin_manifest, basic_lib_manifest, basic_manifest, project};
+
+const CARGO_RUSTC_ERROR: &str =
+ "[ERROR] extra arguments to `rustc` can only be passed to one target, consider filtering
+the package by passing, e.g., `--lib` or `--bin NAME` to specify a single target";
+
+#[cargo_test]
+fn build_lib_for_foo() {
+ let p = project()
+ .file("src/main.rs", "fn main() {}")
+ .file("src/lib.rs", r#" "#)
+ .build();
+
+ p.cargo("rustc --lib -v")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name foo src/lib.rs [..]--crate-type lib \
+ --emit=[..]link[..]-C debuginfo=2 \
+ -C metadata=[..] \
+ --out-dir [..] \
+ -L dependency=[CWD]/target/debug/deps`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn lib() {
+ let p = project()
+ .file("src/main.rs", "fn main() {}")
+ .file("src/lib.rs", r#" "#)
+ .build();
+
+ p.cargo("rustc --lib -v -- -C debug-assertions=off")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name foo src/lib.rs [..]--crate-type lib \
+ --emit=[..]link[..]-C debuginfo=2 \
+ -C debug-assertions=off \
+ -C metadata=[..] \
+ --out-dir [..] \
+ -L dependency=[CWD]/target/debug/deps`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_main_and_allow_unstable_options() {
+ let p = project()
+ .file("src/main.rs", "fn main() {}")
+ .file("src/lib.rs", r#" "#)
+ .build();
+
+ p.cargo("rustc -v --bin foo -- -C debug-assertions")
+ .with_stderr(format!(
+ "\
+[COMPILING] {name} v{version} ([CWD])
+[RUNNING] `rustc --crate-name {name} src/lib.rs [..]--crate-type lib \
+ --emit=[..]link[..]-C debuginfo=2 \
+ -C metadata=[..] \
+ --out-dir [..] \
+ -L dependency=[CWD]/target/debug/deps`
+[RUNNING] `rustc --crate-name {name} src/main.rs [..]--crate-type bin \
+ --emit=[..]link[..]-C debuginfo=2 \
+ -C debug-assertions \
+ -C metadata=[..] \
+ --out-dir [..] \
+ -L dependency=[CWD]/target/debug/deps \
+ --extern {name}=[CWD]/target/debug/deps/lib{name}-[..].rlib`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ name = "foo",
+ version = "0.0.1"
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn fails_when_trying_to_build_main_and_lib_with_args() {
+ let p = project()
+ .file("src/main.rs", "fn main() {}")
+ .file("src/lib.rs", r#" "#)
+ .build();
+
+ p.cargo("rustc -v -- -C debug-assertions")
+ .with_status(101)
+ .with_stderr(CARGO_RUSTC_ERROR)
+ .run();
+}
+
+#[cargo_test]
+fn build_with_args_to_one_of_multiple_binaries() {
+ let p = project()
+ .file("src/bin/foo.rs", "fn main() {}")
+ .file("src/bin/bar.rs", "fn main() {}")
+ .file("src/bin/baz.rs", "fn main() {}")
+ .file("src/lib.rs", r#" "#)
+ .build();
+
+ p.cargo("rustc -v --bin bar -- -C debug-assertions")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name foo src/lib.rs [..]--crate-type lib --emit=[..]link[..]\
+ -C debuginfo=2 -C metadata=[..] \
+ --out-dir [..]`
+[RUNNING] `rustc --crate-name bar src/bin/bar.rs [..]--crate-type bin --emit=[..]link[..]\
+ -C debuginfo=2 -C debug-assertions [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn fails_with_args_to_all_binaries() {
+ let p = project()
+ .file("src/bin/foo.rs", "fn main() {}")
+ .file("src/bin/bar.rs", "fn main() {}")
+ .file("src/bin/baz.rs", "fn main() {}")
+ .file("src/lib.rs", r#" "#)
+ .build();
+
+ p.cargo("rustc -v -- -C debug-assertions")
+ .with_status(101)
+ .with_stderr(CARGO_RUSTC_ERROR)
+ .run();
+}
+
+#[cargo_test]
+fn fails_with_crate_type_to_multi_binaries() {
+ let p = project()
+ .file("src/bin/foo.rs", "fn main() {}")
+ .file("src/bin/bar.rs", "fn main() {}")
+ .file("src/bin/baz.rs", "fn main() {}")
+ .file("src/lib.rs", r#" "#)
+ .build();
+
+ p.cargo("rustc --crate-type lib")
+ .with_status(101)
+ .with_stderr(
+ "[ERROR] crate types to rustc can only be passed to one target, consider filtering
+the package by passing, e.g., `--lib` or `--example` to specify a single target",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn fails_with_crate_type_to_multi_examples() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [[example]]
+ name = "ex1"
+ crate-type = ["rlib"]
+ [[example]]
+ name = "ex2"
+ crate-type = ["rlib"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("examples/ex1.rs", "")
+ .file("examples/ex2.rs", "")
+ .build();
+
+ p.cargo("rustc -v --example ex1 --example ex2 --crate-type lib,cdylib")
+ .with_status(101)
+ .with_stderr(
+ "[ERROR] crate types to rustc can only be passed to one target, consider filtering
+the package by passing, e.g., `--lib` or `--example` to specify a single target",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn fails_with_crate_type_to_binary() {
+ let p = project().file("src/bin/foo.rs", "fn main() {}").build();
+
+ p.cargo("rustc --crate-type lib")
+ .with_status(101)
+ .with_stderr(
+ "[ERROR] crate types can only be specified for libraries and example libraries.
+Binaries, tests, and benchmarks are always the `bin` crate type",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_with_crate_type_for_foo() {
+ let p = project().file("src/lib.rs", "").build();
+
+ p.cargo("rustc -v --crate-type cdylib")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name foo src/lib.rs [..]--crate-type cdylib [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_with_crate_type_for_foo_with_deps() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ extern crate a;
+ pub fn foo() { a::hello(); }
+ "#,
+ )
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ a = { path = "a" }
+ "#,
+ )
+ .file("a/Cargo.toml", &basic_manifest("a", "0.1.0"))
+ .file("a/src/lib.rs", "pub fn hello() {}")
+ .build();
+
+ p.cargo("rustc -v --crate-type cdylib")
+ .with_stderr(
+ "\
+[COMPILING] a v0.1.0 ([CWD]/a)
+[RUNNING] `rustc --crate-name a a/src/lib.rs [..]--crate-type lib [..]
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name foo src/lib.rs [..]--crate-type cdylib [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_with_crate_types_for_foo() {
+ let p = project().file("src/lib.rs", "").build();
+
+ p.cargo("rustc -v --crate-type lib,cdylib")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name foo src/lib.rs [..]--crate-type lib,cdylib [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_with_crate_type_to_example() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [[example]]
+ name = "ex"
+ crate-type = ["rlib"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("examples/ex.rs", "")
+ .build();
+
+ p.cargo("rustc -v --example ex --crate-type cdylib")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name foo src/lib.rs [..]--crate-type lib [..]
+[RUNNING] `rustc --crate-name ex examples/ex.rs [..]--crate-type cdylib [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_with_crate_types_to_example() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [[example]]
+ name = "ex"
+ crate-type = ["rlib"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("examples/ex.rs", "")
+ .build();
+
+ p.cargo("rustc -v --example ex --crate-type lib,cdylib")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name foo src/lib.rs [..]--crate-type lib [..]
+[RUNNING] `rustc --crate-name ex examples/ex.rs [..]--crate-type lib,cdylib [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_with_crate_types_to_one_of_multi_examples() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [[example]]
+ name = "ex1"
+ crate-type = ["rlib"]
+ [[example]]
+ name = "ex2"
+ crate-type = ["rlib"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("examples/ex1.rs", "")
+ .file("examples/ex2.rs", "")
+ .build();
+
+ p.cargo("rustc -v --example ex1 --crate-type lib,cdylib")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name foo src/lib.rs [..]--crate-type lib [..]
+[RUNNING] `rustc --crate-name ex1 examples/ex1.rs [..]--crate-type lib,cdylib [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_with_args_to_one_of_multiple_tests() {
+ let p = project()
+ .file("tests/foo.rs", r#" "#)
+ .file("tests/bar.rs", r#" "#)
+ .file("tests/baz.rs", r#" "#)
+ .file("src/lib.rs", r#" "#)
+ .build();
+
+ p.cargo("rustc -v --test bar -- -C debug-assertions")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name foo src/lib.rs [..]--crate-type lib --emit=[..]link[..]\
+ -C debuginfo=2 -C metadata=[..] \
+ --out-dir [..]`
+[RUNNING] `rustc --crate-name bar tests/bar.rs [..]--emit=[..]link[..]-C debuginfo=2 \
+ -C debug-assertions [..]--test[..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_foo_with_bar_dependency() {
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "../bar"
+ "#,
+ )
+ .file("src/main.rs", "extern crate bar; fn main() { bar::baz() }")
+ .build();
+ let _bar = project()
+ .at("bar")
+ .file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("src/lib.rs", "pub fn baz() {}")
+ .build();
+
+ foo.cargo("rustc -v -- -C debug-assertions")
+ .with_stderr(
+ "\
+[COMPILING] bar v0.1.0 ([..])
+[RUNNING] `[..] -C debuginfo=2 [..]`
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `[..] -C debuginfo=2 -C debug-assertions [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn build_only_bar_dependency() {
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "../bar"
+ "#,
+ )
+ .file("src/main.rs", "extern crate bar; fn main() { bar::baz() }")
+ .build();
+ let _bar = project()
+ .at("bar")
+ .file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("src/lib.rs", "pub fn baz() {}")
+ .build();
+
+ foo.cargo("rustc -v -p bar -- -C debug-assertions")
+ .with_stderr(
+ "\
+[COMPILING] bar v0.1.0 ([..])
+[RUNNING] `rustc --crate-name bar [..]--crate-type lib [..] -C debug-assertions [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn targets_selected_default() {
+ let p = project().file("src/main.rs", "fn main() {}").build();
+ p.cargo("rustc -v")
+ // bin
+ .with_stderr_contains(
+ "[RUNNING] `rustc --crate-name foo src/main.rs [..]--crate-type bin \
+ --emit=[..]link[..]",
+ )
+ // bench
+ .with_stderr_does_not_contain(
+ "[RUNNING] `rustc --crate-name foo src/main.rs [..]--emit=[..]link \
+ -C opt-level=3 --test [..]",
+ )
+ // unit test
+ .with_stderr_does_not_contain(
+ "[RUNNING] `rustc --crate-name foo src/main.rs [..]--emit=[..]link \
+ -C debuginfo=2 --test [..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn targets_selected_all() {
+ let p = project().file("src/main.rs", "fn main() {}").build();
+ p.cargo("rustc -v --all-targets")
+ // bin
+ .with_stderr_contains(
+ "[RUNNING] `rustc --crate-name foo src/main.rs [..]--crate-type bin \
+ --emit=[..]link[..]",
+ )
+ // unit test
+ .with_stderr_contains(
+ "[RUNNING] `rustc --crate-name foo src/main.rs [..]--emit=[..]link[..]\
+ -C debuginfo=2 --test [..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn fail_with_multiple_packages() {
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "../bar"
+
+ [dependencies.baz]
+ path = "../baz"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ let _bar = project()
+ .at("bar")
+ .file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ if cfg!(flag = "1") { println!("Yeah from bar!"); }
+ }
+ "#,
+ )
+ .build();
+
+ let _baz = project()
+ .at("baz")
+ .file("Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ if cfg!(flag = "1") { println!("Yeah from baz!"); }
+ }
+ "#,
+ )
+ .build();
+
+ foo.cargo("rustc -v -p bar -p baz")
+ .with_status(1)
+ .with_stderr_contains(
+ "\
+error: the argument '--package [<SPEC>]' cannot be used multiple times
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn fail_with_glob() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() { break_the_build(); }")
+ .build();
+
+ p.cargo("rustc -p '*z'")
+ .with_status(101)
+ .with_stderr("[ERROR] Glob patterns on package selection are not supported.")
+ .run();
+}
+
+#[cargo_test]
+fn rustc_with_other_profile() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dev-dependencies]
+ a = { path = "a" }
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ #[cfg(test)] extern crate a;
+
+ #[test]
+ fn foo() {}
+ "#,
+ )
+ .file("a/Cargo.toml", &basic_manifest("a", "0.1.0"))
+ .file("a/src/lib.rs", "")
+ .build();
+
+ p.cargo("rustc --profile test").run();
+}
+
+#[cargo_test]
+fn rustc_fingerprint() {
+ // Verify that the fingerprint includes the rustc args.
+ let p = project()
+ .file("Cargo.toml", &basic_lib_manifest("foo"))
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("rustc -v -- -C debug-assertions")
+ .with_stderr(
+ "\
+[COMPILING] foo [..]
+[RUNNING] `rustc [..]-C debug-assertions [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ p.cargo("rustc -v -- -C debug-assertions")
+ .with_stderr(
+ "\
+[FRESH] foo [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ p.cargo("rustc -v")
+ .with_stderr_does_not_contain("-C debug-assertions")
+ .with_stderr(
+ "\
+[DIRTY] foo [..]: the profile configuration changed
+[COMPILING] foo [..]
+[RUNNING] `rustc [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ p.cargo("rustc -v")
+ .with_stderr(
+ "\
+[FRESH] foo [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rustc_test_with_implicit_bin() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file(
+ "src/main.rs",
+ r#"
+ #[cfg(foo)]
+ fn f() { compile_fail!("Foo shouldn't be set."); }
+ fn main() {}
+ "#,
+ )
+ .file(
+ "tests/test1.rs",
+ r#"
+ #[cfg(not(foo))]
+ fn f() { compile_fail!("Foo should be set."); }
+ "#,
+ )
+ .build();
+
+ p.cargo("rustc --test test1 -v -- --cfg foo")
+ .with_stderr_contains(
+ "\
+[RUNNING] `rustc --crate-name test1 tests/test1.rs [..] --cfg foo [..]
+",
+ )
+ .with_stderr_contains(
+ "\
+[RUNNING] `rustc --crate-name foo src/main.rs [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rustc_with_print_cfg_single_target() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/main.rs", r#"fn main() {} "#)
+ .build();
+
+ p.cargo("rustc -Z unstable-options --target x86_64-pc-windows-msvc --print cfg")
+ .masquerade_as_nightly_cargo(&["print"])
+ .with_stdout_contains("debug_assertions")
+ .with_stdout_contains("target_arch=\"x86_64\"")
+ .with_stdout_contains("target_endian=\"little\"")
+ .with_stdout_contains("target_env=\"msvc\"")
+ .with_stdout_contains("target_family=\"windows\"")
+ .with_stdout_contains("target_os=\"windows\"")
+ .with_stdout_contains("target_pointer_width=\"64\"")
+ .with_stdout_contains("target_vendor=\"pc\"")
+ .with_stdout_contains("windows")
+ .run();
+}
+
+#[cargo_test]
+fn rustc_with_print_cfg_multiple_targets() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/main.rs", r#"fn main() {} "#)
+ .build();
+
+ p.cargo("rustc -Z unstable-options --target x86_64-pc-windows-msvc --target i686-unknown-linux-gnu --print cfg")
+ .masquerade_as_nightly_cargo(&["print"])
+ .with_stdout_contains("debug_assertions")
+ .with_stdout_contains("target_arch=\"x86_64\"")
+ .with_stdout_contains("target_endian=\"little\"")
+ .with_stdout_contains("target_env=\"msvc\"")
+ .with_stdout_contains("target_family=\"windows\"")
+ .with_stdout_contains("target_os=\"windows\"")
+ .with_stdout_contains("target_pointer_width=\"64\"")
+ .with_stdout_contains("target_vendor=\"pc\"")
+ .with_stdout_contains("windows")
+ .with_stdout_contains("target_env=\"gnu\"")
+ .with_stdout_contains("target_family=\"unix\"")
+ .with_stdout_contains("target_pointer_width=\"32\"")
+ .with_stdout_contains("target_vendor=\"unknown\"")
+ .with_stdout_contains("target_os=\"linux\"")
+ .with_stdout_contains("unix")
+ .run();
+}
+
+#[cargo_test]
+fn rustc_with_print_cfg_rustflags_env_var() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/main.rs", r#"fn main() {} "#)
+ .build();
+
+ p.cargo("rustc -Z unstable-options --target x86_64-pc-windows-msvc --print cfg")
+ .masquerade_as_nightly_cargo(&["print"])
+ .env("RUSTFLAGS", "-C target-feature=+crt-static")
+ .with_stdout_contains("debug_assertions")
+ .with_stdout_contains("target_arch=\"x86_64\"")
+ .with_stdout_contains("target_endian=\"little\"")
+ .with_stdout_contains("target_env=\"msvc\"")
+ .with_stdout_contains("target_family=\"windows\"")
+ .with_stdout_contains("target_feature=\"crt-static\"")
+ .with_stdout_contains("target_os=\"windows\"")
+ .with_stdout_contains("target_pointer_width=\"64\"")
+ .with_stdout_contains("target_vendor=\"pc\"")
+ .with_stdout_contains("windows")
+ .run();
+}
+
+#[cargo_test]
+fn rustc_with_print_cfg_config_toml() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file(
+ ".cargo/config.toml",
+ r#"
+[target.x86_64-pc-windows-msvc]
+rustflags = ["-C", "target-feature=+crt-static"]
+"#,
+ )
+ .file("src/main.rs", r#"fn main() {} "#)
+ .build();
+
+ p.cargo("rustc -Z unstable-options --target x86_64-pc-windows-msvc --print cfg")
+ .masquerade_as_nightly_cargo(&["print"])
+ .env("RUSTFLAGS", "-C target-feature=+crt-static")
+ .with_stdout_contains("debug_assertions")
+ .with_stdout_contains("target_arch=\"x86_64\"")
+ .with_stdout_contains("target_endian=\"little\"")
+ .with_stdout_contains("target_env=\"msvc\"")
+ .with_stdout_contains("target_family=\"windows\"")
+ .with_stdout_contains("target_feature=\"crt-static\"")
+ .with_stdout_contains("target_os=\"windows\"")
+ .with_stdout_contains("target_pointer_width=\"64\"")
+ .with_stdout_contains("target_vendor=\"pc\"")
+ .with_stdout_contains("windows")
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/rustc_info_cache.rs b/src/tools/cargo/tests/testsuite/rustc_info_cache.rs
new file mode 100644
index 000000000..9747fa357
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/rustc_info_cache.rs
@@ -0,0 +1,186 @@
+//! Tests for the cache file for the rustc version info.
+
+use cargo_test_support::{basic_bin_manifest, paths::CargoPathExt};
+use cargo_test_support::{basic_manifest, project};
+use std::env;
+
+const MISS: &str = "[..] rustc info cache miss[..]";
+const HIT: &str = "[..]rustc info cache hit[..]";
+const UPDATE: &str = "[..]updated rustc info cache[..]";
+
+#[cargo_test]
+fn rustc_info_cache() {
+ let p = project()
+ .file("src/main.rs", r#"fn main() { println!("hello"); }"#)
+ .build();
+
+ p.cargo("build")
+ .env("CARGO_LOG", "cargo::util::rustc=debug")
+ .with_stderr_contains("[..]failed to read rustc info cache[..]")
+ .with_stderr_contains(MISS)
+ .with_stderr_does_not_contain(HIT)
+ .with_stderr_contains(UPDATE)
+ .run();
+
+ p.cargo("build")
+ .env("CARGO_LOG", "cargo::util::rustc=debug")
+ .with_stderr_contains("[..]reusing existing rustc info cache[..]")
+ .with_stderr_contains(HIT)
+ .with_stderr_does_not_contain(MISS)
+ .with_stderr_does_not_contain(UPDATE)
+ .run();
+
+ p.cargo("build")
+ .env("CARGO_LOG", "cargo::util::rustc=debug")
+ .env("CARGO_CACHE_RUSTC_INFO", "0")
+ .with_stderr_contains("[..]rustc info cache disabled[..]")
+ .with_stderr_does_not_contain(UPDATE)
+ .run();
+
+ let other_rustc = {
+ let p = project()
+ .at("compiler")
+ .file("Cargo.toml", &basic_manifest("compiler", "0.1.0"))
+ .file(
+ "src/main.rs",
+ r#"
+ use std::process::Command;
+ use std::env;
+
+ fn main() {
+ let mut cmd = Command::new("rustc");
+ for arg in env::args_os().skip(1) {
+ cmd.arg(arg);
+ }
+ std::process::exit(cmd.status().unwrap().code().unwrap());
+ }
+ "#,
+ )
+ .build();
+ p.cargo("build").run();
+
+ p.root()
+ .join("target/debug/compiler")
+ .with_extension(env::consts::EXE_EXTENSION)
+ };
+
+ p.cargo("build")
+ .env("CARGO_LOG", "cargo::util::rustc=debug")
+ .env("RUSTC", other_rustc.display().to_string())
+ .with_stderr_contains("[..]different compiler, creating new rustc info cache[..]")
+ .with_stderr_contains(MISS)
+ .with_stderr_does_not_contain(HIT)
+ .with_stderr_contains(UPDATE)
+ .run();
+
+ p.cargo("build")
+ .env("CARGO_LOG", "cargo::util::rustc=debug")
+ .env("RUSTC", other_rustc.display().to_string())
+ .with_stderr_contains("[..]reusing existing rustc info cache[..]")
+ .with_stderr_contains(HIT)
+ .with_stderr_does_not_contain(MISS)
+ .with_stderr_does_not_contain(UPDATE)
+ .run();
+
+ other_rustc.move_into_the_future();
+
+ p.cargo("build")
+ .env("CARGO_LOG", "cargo::util::rustc=debug")
+ .env("RUSTC", other_rustc.display().to_string())
+ .with_stderr_contains("[..]different compiler, creating new rustc info cache[..]")
+ .with_stderr_contains(MISS)
+ .with_stderr_does_not_contain(HIT)
+ .with_stderr_contains(UPDATE)
+ .run();
+
+ p.cargo("build")
+ .env("CARGO_LOG", "cargo::util::rustc=debug")
+ .env("RUSTC", other_rustc.display().to_string())
+ .with_stderr_contains("[..]reusing existing rustc info cache[..]")
+ .with_stderr_contains(HIT)
+ .with_stderr_does_not_contain(MISS)
+ .with_stderr_does_not_contain(UPDATE)
+ .run();
+}
+
+#[cargo_test]
+fn rustc_info_cache_with_wrappers() {
+ let wrapper_project = project()
+ .at("wrapper")
+ .file("Cargo.toml", &basic_bin_manifest("wrapper"))
+ .file("src/main.rs", r#"fn main() { }"#)
+ .build();
+ let wrapper = wrapper_project.bin("wrapper");
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "test"
+ version = "0.0.0"
+ authors = []
+ [workspace]
+ "#,
+ )
+ .file("src/main.rs", r#"fn main() { println!("hello"); }"#)
+ .build();
+
+ for &wrapper_env in ["RUSTC_WRAPPER", "RUSTC_WORKSPACE_WRAPPER"].iter() {
+ p.cargo("clean").with_status(0).run();
+ wrapper_project.change_file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ let mut args = std::env::args_os();
+ let _me = args.next().unwrap();
+ let rustc = args.next().unwrap();
+ let status = std::process::Command::new(rustc).args(args).status().unwrap();
+ std::process::exit(if status.success() { 0 } else { 1 })
+ }
+ "#,
+ );
+ wrapper_project.cargo("build").with_status(0).run();
+
+ p.cargo("build")
+ .env("CARGO_LOG", "cargo::util::rustc=debug")
+ .env(wrapper_env, &wrapper)
+ .with_stderr_contains("[..]failed to read rustc info cache[..]")
+ .with_stderr_contains(MISS)
+ .with_stderr_contains(UPDATE)
+ .with_stderr_does_not_contain(HIT)
+ .with_status(0)
+ .run();
+ p.cargo("build")
+ .env("CARGO_LOG", "cargo::util::rustc=debug")
+ .env(wrapper_env, &wrapper)
+ .with_stderr_contains("[..]reusing existing rustc info cache[..]")
+ .with_stderr_contains(HIT)
+ .with_stderr_does_not_contain(UPDATE)
+ .with_stderr_does_not_contain(MISS)
+ .with_status(0)
+ .run();
+
+ wrapper_project.change_file("src/main.rs", r#"fn main() { panic!() }"#);
+ wrapper_project.cargo("build").with_status(0).run();
+
+ p.cargo("build")
+ .env("CARGO_LOG", "cargo::util::rustc=debug")
+ .env(wrapper_env, &wrapper)
+ .with_stderr_contains("[..]different compiler, creating new rustc info cache[..]")
+ .with_stderr_contains(MISS)
+ .with_stderr_contains(UPDATE)
+ .with_stderr_does_not_contain(HIT)
+ .with_status(101)
+ .run();
+ p.cargo("build")
+ .env("CARGO_LOG", "cargo::util::rustc=debug")
+ .env(wrapper_env, &wrapper)
+ .with_stderr_contains("[..]reusing existing rustc info cache[..]")
+ .with_stderr_contains(HIT)
+ .with_stderr_does_not_contain(UPDATE)
+ .with_stderr_does_not_contain(MISS)
+ .with_status(101)
+ .run();
+ }
+}
diff --git a/src/tools/cargo/tests/testsuite/rustdoc.rs b/src/tools/cargo/tests/testsuite/rustdoc.rs
new file mode 100644
index 000000000..5650f3e0a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/rustdoc.rs
@@ -0,0 +1,252 @@
+//! Tests for the `cargo rustdoc` command.
+
+use cargo_test_support::{basic_manifest, cross_compile, project};
+
+#[cargo_test]
+fn rustdoc_simple() {
+ let p = project().file("src/lib.rs", "").build();
+
+ p.cargo("rustdoc -v")
+ .with_stderr(
+ "\
+[DOCUMENTING] foo v0.0.1 ([CWD])
+[RUNNING] `rustdoc [..]--crate-name foo src/lib.rs [..]\
+ -o [CWD]/target/doc \
+ [..] \
+ -L dependency=[CWD]/target/debug/deps [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rustdoc_args() {
+ let p = project().file("src/lib.rs", "").build();
+
+ p.cargo("rustdoc -v -- --cfg=foo")
+ .with_stderr(
+ "\
+[DOCUMENTING] foo v0.0.1 ([CWD])
+[RUNNING] `rustdoc [..]--crate-name foo src/lib.rs [..]\
+ -o [CWD]/target/doc \
+ [..] \
+ --cfg=foo \
+ -C metadata=[..] \
+ -L dependency=[CWD]/target/debug/deps [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rustdoc_binary_args_passed() {
+ let p = project().file("src/main.rs", "").build();
+
+ p.cargo("rustdoc -v")
+ .arg("--")
+ .arg("--markdown-no-toc")
+ .with_stderr_contains("[RUNNING] `rustdoc [..] --markdown-no-toc[..]`")
+ .run();
+}
+
+#[cargo_test]
+fn rustdoc_foo_with_bar_dependency() {
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "../bar"
+ "#,
+ )
+ .file("src/lib.rs", "extern crate bar; pub fn foo() {}")
+ .build();
+ let _bar = project()
+ .at("bar")
+ .file("Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("src/lib.rs", "pub fn baz() {}")
+ .build();
+
+ foo.cargo("rustdoc -v -- --cfg=foo")
+ .with_stderr(
+ "\
+[CHECKING] bar v0.0.1 ([..])
+[RUNNING] `rustc [..]bar/src/lib.rs [..]`
+[DOCUMENTING] foo v0.0.1 ([CWD])
+[RUNNING] `rustdoc [..]--crate-name foo src/lib.rs [..]\
+ -o [CWD]/target/doc \
+ [..] \
+ --cfg=foo \
+ -C metadata=[..] \
+ -L dependency=[CWD]/target/debug/deps \
+ --extern [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rustdoc_only_bar_dependency() {
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "../bar"
+ "#,
+ )
+ .file("src/main.rs", "extern crate bar; fn main() { bar::baz() }")
+ .build();
+ let _bar = project()
+ .at("bar")
+ .file("Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("src/lib.rs", "pub fn baz() {}")
+ .build();
+
+ foo.cargo("rustdoc -v -p bar -- --cfg=foo")
+ .with_stderr(
+ "\
+[DOCUMENTING] bar v0.0.1 ([..])
+[RUNNING] `rustdoc [..]--crate-name bar [..]bar/src/lib.rs [..]\
+ -o [CWD]/target/doc \
+ [..] \
+ --cfg=foo \
+ -C metadata=[..] \
+ -L dependency=[CWD]/target/debug/deps [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rustdoc_same_name_documents_lib() {
+ let p = project()
+ .file("src/main.rs", "fn main() {}")
+ .file("src/lib.rs", r#" "#)
+ .build();
+
+ p.cargo("rustdoc -v -- --cfg=foo")
+ .with_stderr(
+ "\
+[DOCUMENTING] foo v0.0.1 ([..])
+[RUNNING] `rustdoc [..]--crate-name foo src/lib.rs [..]\
+ -o [CWD]/target/doc \
+ [..] \
+ --cfg=foo \
+ -C metadata=[..] \
+ -L dependency=[CWD]/target/debug/deps [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ quux = []
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("rustdoc --verbose --features quux")
+ .with_stderr_contains("[..]feature=[..]quux[..]")
+ .run();
+}
+
+#[cargo_test]
+fn proc_macro_crate_type() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ proc-macro = true
+
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("rustdoc --verbose")
+ .with_stderr_contains(
+ "\
+[RUNNING] `rustdoc --crate-type proc-macro [..]`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rustdoc_target() {
+ if cross_compile::disabled() {
+ return;
+ }
+
+ let p = project().file("src/lib.rs", "").build();
+
+ p.cargo("rustdoc --verbose --target")
+ .arg(cross_compile::alternate())
+ .with_stderr(format!(
+ "\
+[DOCUMENTING] foo v0.0.1 ([..])
+[RUNNING] `rustdoc [..]--crate-name foo src/lib.rs [..]\
+ --target {target} \
+ -o [CWD]/target/{target}/doc \
+ [..] \
+ -L dependency=[CWD]/target/{target}/debug/deps \
+ -L dependency=[CWD]/target/debug/deps[..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ target = cross_compile::alternate()
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn fail_with_glob() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() { break_the_build(); }")
+ .build();
+
+ p.cargo("rustdoc -p '*z'")
+ .with_status(101)
+ .with_stderr("[ERROR] Glob patterns on package selection are not supported.")
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/rustdoc_extern_html.rs b/src/tools/cargo/tests/testsuite/rustdoc_extern_html.rs
new file mode 100644
index 000000000..b18358d1c
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/rustdoc_extern_html.rs
@@ -0,0 +1,426 @@
+//! Tests for the -Zrustdoc-map feature.
+
+use cargo_test_support::registry::{self, Package};
+use cargo_test_support::{paths, project, Project};
+
+fn basic_project() -> Project {
+ Package::new("bar", "1.0.0")
+ .file("src/lib.rs", "pub struct Straw;")
+ .publish();
+
+ project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [dependencies]
+ bar = "1.0"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn myfun() -> Option<bar::Straw> {
+ None
+ }
+ "#,
+ )
+ .build()
+}
+
+#[cargo_test]
+fn ignores_on_stable() {
+ // Requires -Zrustdoc-map to use.
+ let p = basic_project();
+ p.cargo("doc -v --no-deps")
+ .with_stderr_does_not_contain("[..]--extern-html-root-url[..]")
+ .run();
+}
+
+#[cargo_test(nightly, reason = "--extern-html-root-url is unstable")]
+fn simple() {
+ // Basic test that it works with crates.io.
+ let p = basic_project();
+ p.cargo("doc -v --no-deps -Zrustdoc-map")
+ .masquerade_as_nightly_cargo(&["rustdoc-map"])
+ .with_stderr_contains(
+ "[RUNNING] `rustdoc [..]--crate-name foo [..]bar=https://docs.rs/bar/1.0.0/[..]",
+ )
+ .run();
+ let myfun = p.read_file("target/doc/foo/fn.myfun.html");
+ assert!(myfun.contains(r#"href="https://docs.rs/bar/1.0.0/bar/struct.Straw.html""#));
+}
+
+#[ignore = "Broken, temporarily disabled until https://github.com/rust-lang/rust/pull/82776 is resolved."]
+#[cargo_test]
+// #[cargo_test(nightly, reason = "--extern-html-root-url is unstable")]
+fn std_docs() {
+ // Mapping std docs somewhere else.
+ // For local developers, skip this test if docs aren't installed.
+ let docs = std::path::Path::new(&paths::sysroot()).join("share/doc/rust/html");
+ if !docs.exists() {
+ if cargo_util::is_ci() {
+ panic!("std docs are not installed, check that the rust-docs component is installed");
+ } else {
+ eprintln!(
+ "documentation not found at {}, \
+ skipping test (run `rustdoc component add rust-docs` to install",
+ docs.display()
+ );
+ return;
+ }
+ }
+ let p = basic_project();
+ p.change_file(
+ ".cargo/config",
+ r#"
+ [doc.extern-map]
+ std = "local"
+ "#,
+ );
+ p.cargo("doc -v --no-deps -Zrustdoc-map")
+ .masquerade_as_nightly_cargo(&["rustdoc-map"])
+ .with_stderr_contains("[RUNNING] `rustdoc [..]--crate-name foo [..]std=file://[..]")
+ .run();
+ let myfun = p.read_file("target/doc/foo/fn.myfun.html");
+ assert!(myfun.contains(r#"share/doc/rust/html/core/option/enum.Option.html""#));
+
+ p.change_file(
+ ".cargo/config",
+ r#"
+ [doc.extern-map]
+ std = "https://example.com/rust/"
+ "#,
+ );
+ p.cargo("doc -v --no-deps -Zrustdoc-map")
+ .masquerade_as_nightly_cargo(&["rustdoc-map"])
+ .with_stderr_contains(
+ "[RUNNING] `rustdoc [..]--crate-name foo [..]std=https://example.com/rust/[..]",
+ )
+ .run();
+ let myfun = p.read_file("target/doc/foo/fn.myfun.html");
+ assert!(myfun.contains(r#"href="https://example.com/rust/core/option/enum.Option.html""#));
+}
+
+#[cargo_test(nightly, reason = "--extern-html-root-url is unstable")]
+fn renamed_dep() {
+ // Handles renamed dependencies.
+ Package::new("bar", "1.0.0")
+ .file("src/lib.rs", "pub struct Straw;")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [dependencies]
+ groovy = { version = "1.0", package = "bar" }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn myfun() -> Option<groovy::Straw> {
+ None
+ }
+ "#,
+ )
+ .build();
+ p.cargo("doc -v --no-deps -Zrustdoc-map")
+ .masquerade_as_nightly_cargo(&["rustdoc-map"])
+ .with_stderr_contains(
+ "[RUNNING] `rustdoc [..]--crate-name foo [..]bar=https://docs.rs/bar/1.0.0/[..]",
+ )
+ .run();
+ let myfun = p.read_file("target/doc/foo/fn.myfun.html");
+ assert!(myfun.contains(r#"href="https://docs.rs/bar/1.0.0/bar/struct.Straw.html""#));
+}
+
+#[cargo_test(nightly, reason = "--extern-html-root-url is unstable")]
+fn lib_name() {
+ // Handles lib name != package name.
+ Package::new("bar", "1.0.0")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "1.0.0"
+
+ [lib]
+ name = "rumpelstiltskin"
+ "#,
+ )
+ .file("src/lib.rs", "pub struct Straw;")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "1.0"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn myfun() -> Option<rumpelstiltskin::Straw> {
+ None
+ }
+ "#,
+ )
+ .build();
+ p.cargo("doc -v --no-deps -Zrustdoc-map")
+ .masquerade_as_nightly_cargo(&["rustdoc-map"])
+ .with_stderr_contains(
+ "[RUNNING] `rustdoc [..]--crate-name foo [..]rumpelstiltskin=https://docs.rs/bar/1.0.0/[..]",
+ )
+ .run();
+ let myfun = p.read_file("target/doc/foo/fn.myfun.html");
+ assert!(myfun.contains(r#"href="https://docs.rs/bar/1.0.0/rumpelstiltskin/struct.Straw.html""#));
+}
+
+#[cargo_test(nightly, reason = "--extern-html-root-url is unstable")]
+fn alt_registry() {
+ // Supports other registry names.
+ registry::alt_init();
+ Package::new("bar", "1.0.0")
+ .alternative(true)
+ .file(
+ "src/lib.rs",
+ r#"
+ extern crate baz;
+ pub struct Queen;
+ pub use baz::King;
+ "#,
+ )
+ .registry_dep("baz", "1.0")
+ .publish();
+ Package::new("baz", "1.0.0")
+ .alternative(true)
+ .file("src/lib.rs", "pub struct King;")
+ .publish();
+ Package::new("grimm", "1.0.0")
+ .file("src/lib.rs", "pub struct Gold;")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [dependencies]
+ bar = { version = "1.0", registry="alternative" }
+ grimm = "1.0"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn queen() -> bar::Queen { bar::Queen }
+ pub fn king() -> bar::King { bar::King }
+ pub fn gold() -> grimm::Gold { grimm::Gold }
+ "#,
+ )
+ .file(
+ ".cargo/config",
+ r#"
+ [doc.extern-map.registries]
+ alternative = "https://example.com/{pkg_name}/{version}/"
+ crates-io = "https://docs.rs/"
+ "#,
+ )
+ .build();
+ p.cargo("doc -v --no-deps -Zrustdoc-map")
+ .masquerade_as_nightly_cargo(&["rustdoc-map"])
+ .with_stderr_contains(
+ "[RUNNING] `rustdoc [..]--crate-name foo \
+ [..]bar=https://example.com/bar/1.0.0/[..]grimm=https://docs.rs/grimm/1.0.0/[..]",
+ )
+ .run();
+ let queen = p.read_file("target/doc/foo/fn.queen.html");
+ assert!(queen.contains(r#"href="https://example.com/bar/1.0.0/bar/struct.Queen.html""#));
+ // The king example fails to link. Rustdoc seems to want the origin crate
+ // name (baz) for re-exports. There are many issues in the issue tracker
+ // for rustdoc re-exports, so I'm not sure, but I think this is maybe a
+ // rustdoc issue. Alternatively, Cargo could provide mappings for all
+ // transitive dependencies to fix this.
+ let king = p.read_file("target/doc/foo/fn.king.html");
+ assert!(king.contains(r#"-&gt; King"#));
+
+ let gold = p.read_file("target/doc/foo/fn.gold.html");
+ assert!(gold.contains(r#"href="https://docs.rs/grimm/1.0.0/grimm/struct.Gold.html""#));
+}
+
+#[cargo_test(nightly, reason = "--extern-html-root-url is unstable")]
+fn multiple_versions() {
+ // What happens when there are multiple versions.
+ // NOTE: This is currently broken behavior. Rustdoc does not provide a way
+ // to match renamed dependencies.
+ Package::new("bar", "1.0.0")
+ .file("src/lib.rs", "pub struct Spin;")
+ .publish();
+ Package::new("bar", "2.0.0")
+ .file("src/lib.rs", "pub struct Straw;")
+ .publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [dependencies]
+ bar = "1.0"
+ bar2 = {version="2.0", package="bar"}
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "
+ pub fn fn1() -> bar::Spin {bar::Spin}
+ pub fn fn2() -> bar2::Straw {bar2::Straw}
+ ",
+ )
+ .build();
+ p.cargo("doc -v --no-deps -Zrustdoc-map")
+ .masquerade_as_nightly_cargo(&["rustdoc-map"])
+ .with_stderr_contains(
+ "[RUNNING] `rustdoc [..]--crate-name foo \
+ [..]bar=https://docs.rs/bar/1.0.0/[..]bar=https://docs.rs/bar/2.0.0/[..]",
+ )
+ .run();
+ let fn1 = p.read_file("target/doc/foo/fn.fn1.html");
+ // This should be 1.0.0, rustdoc seems to use the last entry when there
+ // are duplicates.
+ assert!(fn1.contains(r#"href="https://docs.rs/bar/2.0.0/bar/struct.Spin.html""#));
+ let fn2 = p.read_file("target/doc/foo/fn.fn2.html");
+ assert!(fn2.contains(r#"href="https://docs.rs/bar/2.0.0/bar/struct.Straw.html""#));
+}
+
+#[cargo_test(nightly, reason = "--extern-html-root-url is unstable")]
+fn rebuilds_when_changing() {
+ // Make sure it rebuilds if the map changes.
+ let p = basic_project();
+ p.cargo("doc -v --no-deps -Zrustdoc-map")
+ .masquerade_as_nightly_cargo(&["rustdoc-map"])
+ .with_stderr_contains("[..]--extern-html-root-url[..]")
+ .run();
+
+ // This also tests that the map for docs.rs can be overridden.
+ p.change_file(
+ ".cargo/config",
+ r#"
+ [doc.extern-map.registries]
+ crates-io = "https://example.com/"
+ "#,
+ );
+ p.cargo("doc -v --no-deps -Zrustdoc-map")
+ .masquerade_as_nightly_cargo(&["rustdoc-map"])
+ .with_stderr_contains(
+ "[RUNNING] `rustdoc [..]--extern-html-root-url [..]bar=https://example.com/bar/1.0.0/[..]",
+ )
+ .run();
+}
+
+#[cargo_test(nightly, reason = "--extern-html-root-url is unstable")]
+fn alt_sparse_registry() {
+ // Supports other registry names.
+
+ registry::init();
+ let _registry = registry::RegistryBuilder::new()
+ .http_index()
+ .alternative()
+ .build();
+
+ Package::new("bar", "1.0.0")
+ .alternative(true)
+ .file(
+ "src/lib.rs",
+ r#"
+ extern crate baz;
+ pub struct Queen;
+ pub use baz::King;
+ "#,
+ )
+ .registry_dep("baz", "1.0")
+ .publish();
+ Package::new("baz", "1.0.0")
+ .alternative(true)
+ .file("src/lib.rs", "pub struct King;")
+ .publish();
+ Package::new("grimm", "1.0.0")
+ .file("src/lib.rs", "pub struct Gold;")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [dependencies]
+ bar = { version = "1.0", registry="alternative" }
+ grimm = "1.0"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn queen() -> bar::Queen { bar::Queen }
+ pub fn king() -> bar::King { bar::King }
+ pub fn gold() -> grimm::Gold { grimm::Gold }
+ "#,
+ )
+ .file(
+ ".cargo/config",
+ r#"
+ [doc.extern-map.registries]
+ alternative = "https://example.com/{pkg_name}/{version}/"
+ crates-io = "https://docs.rs/"
+ "#,
+ )
+ .build();
+ p.cargo("doc -v --no-deps -Zrustdoc-map")
+ .masquerade_as_nightly_cargo(&["rustdoc-map"])
+ .with_stderr_contains(
+ "[RUNNING] `rustdoc [..]--crate-name foo \
+ [..]bar=https://example.com/bar/1.0.0/[..]grimm=https://docs.rs/grimm/1.0.0/[..]",
+ )
+ .run();
+ let queen = p.read_file("target/doc/foo/fn.queen.html");
+ assert!(queen.contains(r#"href="https://example.com/bar/1.0.0/bar/struct.Queen.html""#));
+ // The king example fails to link. Rustdoc seems to want the origin crate
+ // name (baz) for re-exports. There are many issues in the issue tracker
+ // for rustdoc re-exports, so I'm not sure, but I think this is maybe a
+ // rustdoc issue. Alternatively, Cargo could provide mappings for all
+ // transitive dependencies to fix this.
+ let king = p.read_file("target/doc/foo/fn.king.html");
+ assert!(king.contains(r#"-&gt; King"#));
+
+ let gold = p.read_file("target/doc/foo/fn.gold.html");
+ assert!(gold.contains(r#"href="https://docs.rs/grimm/1.0.0/grimm/struct.Gold.html""#));
+}
diff --git a/src/tools/cargo/tests/testsuite/rustdocflags.rs b/src/tools/cargo/tests/testsuite/rustdocflags.rs
new file mode 100644
index 000000000..6992961ce
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/rustdocflags.rs
@@ -0,0 +1,155 @@
+//! Tests for setting custom rustdoc flags.
+
+use cargo_test_support::project;
+
+#[cargo_test]
+fn parses_env() {
+ let p = project().file("src/lib.rs", "").build();
+
+ p.cargo("doc -v")
+ .env("RUSTDOCFLAGS", "--cfg=foo")
+ .with_stderr_contains("[RUNNING] `rustdoc [..] --cfg=foo[..]`")
+ .run();
+}
+
+#[cargo_test]
+fn parses_config() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ r#"
+ [build]
+ rustdocflags = ["--cfg", "foo"]
+ "#,
+ )
+ .build();
+
+ p.cargo("doc -v")
+ .with_stderr_contains("[RUNNING] `rustdoc [..] --cfg foo[..]`")
+ .run();
+}
+
+#[cargo_test]
+fn bad_flags() {
+ let p = project().file("src/lib.rs", "").build();
+
+ p.cargo("doc")
+ .env("RUSTDOCFLAGS", "--bogus")
+ .with_status(101)
+ .with_stderr_contains("[..]bogus[..]")
+ .run();
+}
+
+#[cargo_test]
+fn rerun() {
+ let p = project().file("src/lib.rs", "").build();
+
+ p.cargo("doc").env("RUSTDOCFLAGS", "--cfg=foo").run();
+ p.cargo("doc")
+ .env("RUSTDOCFLAGS", "--cfg=foo")
+ .with_stderr("[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]")
+ .run();
+ p.cargo("doc")
+ .env("RUSTDOCFLAGS", "--cfg=bar")
+ .with_stderr(
+ "\
+[DOCUMENTING] foo v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rustdocflags_passed_to_rustdoc_through_cargo_test() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ //! ```
+ //! assert!(cfg!(do_not_choke));
+ //! ```
+ "#,
+ )
+ .build();
+
+ p.cargo("test --doc")
+ .env("RUSTDOCFLAGS", "--cfg do_not_choke")
+ .run();
+}
+
+#[cargo_test]
+fn rustdocflags_passed_to_rustdoc_through_cargo_test_only_once() {
+ let p = project().file("src/lib.rs", "").build();
+
+ p.cargo("test --doc")
+ .env("RUSTDOCFLAGS", "--markdown-no-toc")
+ .run();
+}
+
+#[cargo_test]
+fn rustdocflags_misspelled() {
+ let p = project().file("src/main.rs", "fn main() { }").build();
+
+ p.cargo("doc")
+ .env("RUSTDOC_FLAGS", "foo")
+ .with_stderr_contains("[WARNING] Cargo does not read `RUSTDOC_FLAGS` environment variable. Did you mean `RUSTDOCFLAGS`?")
+ .run();
+}
+
+#[cargo_test]
+fn whitespace() {
+ // Checks behavior of different whitespace characters.
+ let p = project().file("src/lib.rs", "").build();
+
+ // "too many operands"
+ p.cargo("doc")
+ .env("RUSTDOCFLAGS", "--crate-version this has spaces")
+ .with_stderr_contains("[ERROR] could not document `foo`")
+ .with_status(101)
+ .run();
+
+ const SPACED_VERSION: &str = "a\nb\tc\u{00a0}d";
+ p.cargo("doc")
+ .env_remove("__CARGO_TEST_FORCE_ARGFILE") // Not applicable for argfile.
+ .env(
+ "RUSTDOCFLAGS",
+ format!("--crate-version {}", SPACED_VERSION),
+ )
+ .run();
+
+ let contents = p.read_file("target/doc/foo/index.html");
+ assert!(contents.contains(SPACED_VERSION));
+}
+
+#[cargo_test]
+fn not_affected_by_target_rustflags() {
+ let cfg = if cfg!(windows) { "windows" } else { "unix" };
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [target.'cfg({cfg})']
+ rustflags = ["-D", "missing-docs"]
+
+ [build]
+ rustdocflags = ["--cfg", "foo"]
+ "#,
+ ),
+ )
+ .build();
+
+ // `cargo build` should fail due to missing docs.
+ p.cargo("build -v")
+ .with_status(101)
+ .with_stderr_contains("[RUNNING] `rustc [..] -D missing-docs[..]`")
+ .run();
+
+ // `cargo doc` shouldn't fail.
+ p.cargo("doc -v")
+ .with_stderr_contains("[RUNNING] `rustdoc [..] --cfg foo[..]`")
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/rustflags.rs b/src/tools/cargo/tests/testsuite/rustflags.rs
new file mode 100644
index 000000000..6677beb04
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/rustflags.rs
@@ -0,0 +1,1673 @@
+//! Tests for setting custom rustc flags.
+
+use cargo_test_support::registry::Package;
+use cargo_test_support::{
+ basic_lib_manifest, basic_manifest, paths, project, project_in_home, rustc_host,
+};
+use std::fs;
+
+#[cargo_test]
+fn env_rustflags_normal_source() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file("src/bin/a.rs", "fn main() {}")
+ .file("examples/b.rs", "fn main() {}")
+ .file("tests/c.rs", "#[test] fn f() { }")
+ .file(
+ "benches/d.rs",
+ r#"
+ #![feature(test)]
+ extern crate test;
+ #[bench] fn run1(_ben: &mut test::Bencher) { }
+ "#,
+ )
+ .build();
+
+ // Use RUSTFLAGS to pass an argument that will generate an error
+ p.cargo("check --lib")
+ .env("RUSTFLAGS", "-Z bogus")
+ .with_status(101)
+ .with_stderr_contains("[..]bogus[..]")
+ .run();
+ p.cargo("check --bin=a")
+ .env("RUSTFLAGS", "-Z bogus")
+ .with_status(101)
+ .with_stderr_contains("[..]bogus[..]")
+ .run();
+ p.cargo("check --example=b")
+ .env("RUSTFLAGS", "-Z bogus")
+ .with_status(101)
+ .with_stderr_contains("[..]bogus[..]")
+ .run();
+ p.cargo("test")
+ .env("RUSTFLAGS", "-Z bogus")
+ .with_status(101)
+ .with_stderr_contains("[..]bogus[..]")
+ .run();
+ p.cargo("bench")
+ .env("RUSTFLAGS", "-Z bogus")
+ .with_status(101)
+ .with_stderr_contains("[..]bogus[..]")
+ .run();
+}
+
+#[cargo_test]
+fn env_rustflags_build_script() {
+ // RUSTFLAGS should be passed to rustc for build scripts
+ // when --target is not specified.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ build = "build.rs"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ fn main() { assert!(cfg!(foo)); }
+ "#,
+ )
+ .build();
+
+ p.cargo("check").env("RUSTFLAGS", "--cfg foo").run();
+}
+
+#[cargo_test]
+fn env_rustflags_build_script_dep() {
+ // RUSTFLAGS should be passed to rustc for build scripts
+ // when --target is not specified.
+ // In this test if --cfg foo is not passed the build will fail.
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ build = "build.rs"
+
+ [build-dependencies.bar]
+ path = "../bar"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("build.rs", "fn main() {}")
+ .build();
+ let _bar = project()
+ .at("bar")
+ .file("Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file(
+ "src/lib.rs",
+ r#"
+ fn bar() { }
+ #[cfg(not(foo))]
+ fn bar() { }
+ "#,
+ )
+ .build();
+
+ foo.cargo("check").env("RUSTFLAGS", "--cfg foo").run();
+}
+
+#[cargo_test]
+fn env_rustflags_plugin() {
+ // RUSTFLAGS should be passed to rustc for plugins
+ // when --target is not specified.
+ // In this test if --cfg foo is not passed the build will fail.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [lib]
+ name = "foo"
+ plugin = true
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ fn main() { }
+ #[cfg(not(foo))]
+ fn main() { }
+ "#,
+ )
+ .build();
+
+ p.cargo("check").env("RUSTFLAGS", "--cfg foo").run();
+}
+
+#[cargo_test]
+fn env_rustflags_plugin_dep() {
+ // RUSTFLAGS should be passed to rustc for plugins
+ // when --target is not specified.
+ // In this test if --cfg foo is not passed the build will fail.
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [lib]
+ name = "foo"
+ plugin = true
+
+ [dependencies.bar]
+ path = "../bar"
+ "#,
+ )
+ .file("src/lib.rs", "fn foo() {}")
+ .build();
+ let _bar = project()
+ .at("bar")
+ .file("Cargo.toml", &basic_lib_manifest("bar"))
+ .file(
+ "src/lib.rs",
+ r#"
+ fn bar() { }
+ #[cfg(not(foo))]
+ fn bar() { }
+ "#,
+ )
+ .build();
+
+ foo.cargo("check").env("RUSTFLAGS", "--cfg foo").run();
+}
+
+#[cargo_test]
+fn env_rustflags_normal_source_with_target() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file("src/bin/a.rs", "fn main() {}")
+ .file("examples/b.rs", "fn main() {}")
+ .file("tests/c.rs", "#[test] fn f() { }")
+ .file(
+ "benches/d.rs",
+ r#"
+ #![feature(test)]
+ extern crate test;
+ #[bench] fn run1(_ben: &mut test::Bencher) { }
+ "#,
+ )
+ .build();
+
+ let host = &rustc_host();
+
+ // Use RUSTFLAGS to pass an argument that will generate an error
+ p.cargo("check --lib --target")
+ .arg(host)
+ .env("RUSTFLAGS", "-Z bogus")
+ .with_status(101)
+ .with_stderr_contains("[..]bogus[..]")
+ .run();
+ p.cargo("check --bin=a --target")
+ .arg(host)
+ .env("RUSTFLAGS", "-Z bogus")
+ .with_status(101)
+ .with_stderr_contains("[..]bogus[..]")
+ .run();
+ p.cargo("check --example=b --target")
+ .arg(host)
+ .env("RUSTFLAGS", "-Z bogus")
+ .with_status(101)
+ .with_stderr_contains("[..]bogus[..]")
+ .run();
+ p.cargo("test --target")
+ .arg(host)
+ .env("RUSTFLAGS", "-Z bogus")
+ .with_status(101)
+ .with_stderr_contains("[..]bogus[..]")
+ .run();
+ p.cargo("bench --target")
+ .arg(host)
+ .env("RUSTFLAGS", "-Z bogus")
+ .with_status(101)
+ .with_stderr_contains("[..]bogus[..]")
+ .run();
+}
+
+#[cargo_test]
+fn env_rustflags_build_script_with_target() {
+ // RUSTFLAGS should not be passed to rustc for build scripts
+ // when --target is specified.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ build = "build.rs"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ fn main() { assert!(!cfg!(foo)); }
+ "#,
+ )
+ .build();
+
+ let host = rustc_host();
+ p.cargo("check --target")
+ .arg(host)
+ .env("RUSTFLAGS", "--cfg foo")
+ .run();
+}
+
+#[cargo_test]
+fn env_rustflags_build_script_with_target_doesnt_apply_to_host_kind() {
+ // RUSTFLAGS should *not* be passed to rustc for build scripts when --target is specified as the
+ // host triple even if target-applies-to-host-kind is enabled, to match legacy Cargo behavior.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ build = "build.rs"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ fn main() { assert!(!cfg!(foo)); }
+ "#,
+ )
+ .file(
+ ".cargo/config.toml",
+ r#"
+ target-applies-to-host = true
+ "#,
+ )
+ .build();
+
+ let host = rustc_host();
+ p.cargo("check --target")
+ .masquerade_as_nightly_cargo(&["target-applies-to-host"])
+ .arg(host)
+ .arg("-Ztarget-applies-to-host")
+ .env("RUSTFLAGS", "--cfg foo")
+ .run();
+}
+
+#[cargo_test]
+fn env_rustflags_build_script_dep_with_target() {
+ // RUSTFLAGS should not be passed to rustc for build scripts
+ // when --target is specified.
+ // In this test if --cfg foo is passed the build will fail.
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ build = "build.rs"
+
+ [build-dependencies.bar]
+ path = "../bar"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("build.rs", "fn main() {}")
+ .build();
+ let _bar = project()
+ .at("bar")
+ .file("Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file(
+ "src/lib.rs",
+ r#"
+ fn bar() { }
+ #[cfg(foo)]
+ fn bar() { }
+ "#,
+ )
+ .build();
+
+ let host = rustc_host();
+ foo.cargo("check --target")
+ .arg(host)
+ .env("RUSTFLAGS", "--cfg foo")
+ .run();
+}
+
+#[cargo_test]
+fn env_rustflags_plugin_with_target() {
+ // RUSTFLAGS should not be passed to rustc for plugins
+ // when --target is specified.
+ // In this test if --cfg foo is passed the build will fail.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [lib]
+ name = "foo"
+ plugin = true
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ fn main() { }
+ #[cfg(foo)]
+ fn main() { }
+ "#,
+ )
+ .build();
+
+ let host = rustc_host();
+ p.cargo("check --target")
+ .arg(host)
+ .env("RUSTFLAGS", "--cfg foo")
+ .run();
+}
+
+#[cargo_test]
+fn env_rustflags_plugin_dep_with_target() {
+ // RUSTFLAGS should not be passed to rustc for plugins
+ // when --target is specified.
+ // In this test if --cfg foo is passed the build will fail.
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [lib]
+ name = "foo"
+ plugin = true
+
+ [dependencies.bar]
+ path = "../bar"
+ "#,
+ )
+ .file("src/lib.rs", "fn foo() {}")
+ .build();
+ let _bar = project()
+ .at("bar")
+ .file("Cargo.toml", &basic_lib_manifest("bar"))
+ .file(
+ "src/lib.rs",
+ r#"
+ fn bar() { }
+ #[cfg(foo)]
+ fn bar() { }
+ "#,
+ )
+ .build();
+
+ let host = rustc_host();
+ foo.cargo("check --target")
+ .arg(host)
+ .env("RUSTFLAGS", "--cfg foo")
+ .run();
+}
+
+#[cargo_test]
+fn env_rustflags_recompile() {
+ let p = project().file("src/lib.rs", "").build();
+
+ p.cargo("check").run();
+ // Setting RUSTFLAGS forces a recompile
+ p.cargo("check")
+ .env("RUSTFLAGS", "-Z bogus")
+ .with_status(101)
+ .with_stderr_contains("[..]bogus[..]")
+ .run();
+}
+
+#[cargo_test]
+fn env_rustflags_recompile2() {
+ let p = project().file("src/lib.rs", "").build();
+
+ p.cargo("check").env("RUSTFLAGS", "--cfg foo").run();
+ // Setting RUSTFLAGS forces a recompile
+ p.cargo("check")
+ .env("RUSTFLAGS", "-Z bogus")
+ .with_status(101)
+ .with_stderr_contains("[..]bogus[..]")
+ .run();
+}
+
+#[cargo_test]
+fn env_rustflags_no_recompile() {
+ let p = project().file("src/lib.rs", "").build();
+
+ p.cargo("check").env("RUSTFLAGS", "--cfg foo").run();
+ p.cargo("check")
+ .env("RUSTFLAGS", "--cfg foo")
+ .with_stdout("")
+ .run();
+}
+
+#[cargo_test]
+fn build_rustflags_normal_source() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file("src/bin/a.rs", "fn main() {}")
+ .file("examples/b.rs", "fn main() {}")
+ .file("tests/c.rs", "#[test] fn f() { }")
+ .file(
+ "benches/d.rs",
+ r#"
+ #![feature(test)]
+ extern crate test;
+ #[bench] fn run1(_ben: &mut test::Bencher) { }
+ "#,
+ )
+ .file(
+ ".cargo/config",
+ r#"
+ [build]
+ rustflags = ["-Z", "bogus"]
+ "#,
+ )
+ .build();
+
+ p.cargo("check --lib")
+ .with_status(101)
+ .with_stderr_contains("[..]bogus[..]")
+ .run();
+ p.cargo("check --bin=a")
+ .with_status(101)
+ .with_stderr_contains("[..]bogus[..]")
+ .run();
+ p.cargo("check --example=b")
+ .with_status(101)
+ .with_stderr_contains("[..]bogus[..]")
+ .run();
+ p.cargo("test")
+ .with_status(101)
+ .with_stderr_contains("[..]bogus[..]")
+ .run();
+ p.cargo("bench")
+ .with_status(101)
+ .with_stderr_contains("[..]bogus[..]")
+ .run();
+}
+
+#[cargo_test]
+fn build_rustflags_build_script() {
+ // RUSTFLAGS should be passed to rustc for build scripts
+ // when --target is not specified.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ build = "build.rs"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ fn main() { assert!(cfg!(foo)); }
+ "#,
+ )
+ .file(
+ ".cargo/config",
+ r#"
+ [build]
+ rustflags = ["--cfg", "foo"]
+ "#,
+ )
+ .build();
+
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn build_rustflags_build_script_dep() {
+ // RUSTFLAGS should be passed to rustc for build scripts
+ // when --target is not specified.
+ // In this test if --cfg foo is not passed the build will fail.
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ build = "build.rs"
+
+ [build-dependencies.bar]
+ path = "../bar"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("build.rs", "fn main() {}")
+ .file(
+ ".cargo/config",
+ r#"
+ [build]
+ rustflags = ["--cfg", "foo"]
+ "#,
+ )
+ .build();
+ let _bar = project()
+ .at("bar")
+ .file("Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file(
+ "src/lib.rs",
+ r#"
+ fn bar() { }
+ #[cfg(not(foo))]
+ fn bar() { }
+ "#,
+ )
+ .build();
+
+ foo.cargo("check").run();
+}
+
+#[cargo_test]
+fn build_rustflags_plugin() {
+ // RUSTFLAGS should be passed to rustc for plugins
+ // when --target is not specified.
+ // In this test if --cfg foo is not passed the build will fail.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [lib]
+ name = "foo"
+ plugin = true
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ fn main() { }
+ #[cfg(not(foo))]
+ fn main() { }
+ "#,
+ )
+ .file(
+ ".cargo/config",
+ r#"
+ [build]
+ rustflags = ["--cfg", "foo"]
+ "#,
+ )
+ .build();
+
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn build_rustflags_plugin_dep() {
+ // RUSTFLAGS should be passed to rustc for plugins
+ // when --target is not specified.
+ // In this test if --cfg foo is not passed the build will fail.
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [lib]
+ name = "foo"
+ plugin = true
+
+ [dependencies.bar]
+ path = "../bar"
+ "#,
+ )
+ .file("src/lib.rs", "fn foo() {}")
+ .file(
+ ".cargo/config",
+ r#"
+ [build]
+ rustflags = ["--cfg", "foo"]
+ "#,
+ )
+ .build();
+ let _bar = project()
+ .at("bar")
+ .file("Cargo.toml", &basic_lib_manifest("bar"))
+ .file(
+ "src/lib.rs",
+ r#"
+ fn bar() { }
+ #[cfg(not(foo))]
+ fn bar() { }
+ "#,
+ )
+ .build();
+
+ foo.cargo("check").run();
+}
+
+#[cargo_test]
+fn build_rustflags_normal_source_with_target() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file("src/bin/a.rs", "fn main() {}")
+ .file("examples/b.rs", "fn main() {}")
+ .file("tests/c.rs", "#[test] fn f() { }")
+ .file(
+ "benches/d.rs",
+ r#"
+ #![feature(test)]
+ extern crate test;
+ #[bench] fn run1(_ben: &mut test::Bencher) { }
+ "#,
+ )
+ .file(
+ ".cargo/config",
+ r#"
+ [build]
+ rustflags = ["-Z", "bogus"]
+ "#,
+ )
+ .build();
+
+ let host = &rustc_host();
+
+ // Use build.rustflags to pass an argument that will generate an error
+ p.cargo("check --lib --target")
+ .arg(host)
+ .with_status(101)
+ .with_stderr_contains("[..]bogus[..]")
+ .run();
+ p.cargo("check --bin=a --target")
+ .arg(host)
+ .with_status(101)
+ .with_stderr_contains("[..]bogus[..]")
+ .run();
+ p.cargo("check --example=b --target")
+ .arg(host)
+ .with_status(101)
+ .with_stderr_contains("[..]bogus[..]")
+ .run();
+ p.cargo("test --target")
+ .arg(host)
+ .with_status(101)
+ .with_stderr_contains("[..]bogus[..]")
+ .run();
+ p.cargo("bench --target")
+ .arg(host)
+ .with_status(101)
+ .with_stderr_contains("[..]bogus[..]")
+ .run();
+}
+
+#[cargo_test]
+fn build_rustflags_build_script_with_target() {
+ // RUSTFLAGS should not be passed to rustc for build scripts
+ // when --target is specified.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ build = "build.rs"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ fn main() { assert!(!cfg!(foo)); }
+ "#,
+ )
+ .file(
+ ".cargo/config",
+ r#"
+ [build]
+ rustflags = ["--cfg", "foo"]
+ "#,
+ )
+ .build();
+
+ let host = rustc_host();
+ p.cargo("check --target").arg(host).run();
+}
+
+#[cargo_test]
+fn build_rustflags_build_script_dep_with_target() {
+ // RUSTFLAGS should not be passed to rustc for build scripts
+ // when --target is specified.
+ // In this test if --cfg foo is passed the build will fail.
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ build = "build.rs"
+
+ [build-dependencies.bar]
+ path = "../bar"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("build.rs", "fn main() {}")
+ .file(
+ ".cargo/config",
+ r#"
+ [build]
+ rustflags = ["--cfg", "foo"]
+ "#,
+ )
+ .build();
+ let _bar = project()
+ .at("bar")
+ .file("Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file(
+ "src/lib.rs",
+ r#"
+ fn bar() { }
+ #[cfg(foo)]
+ fn bar() { }
+ "#,
+ )
+ .build();
+
+ let host = rustc_host();
+ foo.cargo("check --target").arg(host).run();
+}
+
+#[cargo_test]
+fn build_rustflags_plugin_with_target() {
+ // RUSTFLAGS should not be passed to rustc for plugins
+ // when --target is specified.
+ // In this test if --cfg foo is passed the build will fail.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [lib]
+ name = "foo"
+ plugin = true
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ fn main() { }
+ #[cfg(foo)]
+ fn main() { }
+ "#,
+ )
+ .file(
+ ".cargo/config",
+ r#"
+ [build]
+ rustflags = ["--cfg", "foo"]
+ "#,
+ )
+ .build();
+
+ let host = rustc_host();
+ p.cargo("check --target").arg(host).run();
+}
+
+#[cargo_test]
+fn build_rustflags_plugin_dep_with_target() {
+ // RUSTFLAGS should not be passed to rustc for plugins
+ // when --target is specified.
+ // In this test if --cfg foo is passed the build will fail.
+ let foo = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [lib]
+ name = "foo"
+ plugin = true
+
+ [dependencies.bar]
+ path = "../bar"
+ "#,
+ )
+ .file("src/lib.rs", "fn foo() {}")
+ .file(
+ ".cargo/config",
+ r#"
+ [build]
+ rustflags = ["--cfg", "foo"]
+ "#,
+ )
+ .build();
+ let _bar = project()
+ .at("bar")
+ .file("Cargo.toml", &basic_lib_manifest("bar"))
+ .file(
+ "src/lib.rs",
+ r#"
+ fn bar() { }
+ #[cfg(foo)]
+ fn bar() { }
+ "#,
+ )
+ .build();
+
+ let host = rustc_host();
+ foo.cargo("check --target").arg(host).run();
+}
+
+#[cargo_test]
+fn build_rustflags_recompile() {
+ let p = project().file("src/lib.rs", "").build();
+
+ p.cargo("check").run();
+
+ // Setting RUSTFLAGS forces a recompile
+ let config = r#"
+ [build]
+ rustflags = ["-Z", "bogus"]
+ "#;
+ let config_file = paths::root().join("foo/.cargo/config");
+ fs::create_dir_all(config_file.parent().unwrap()).unwrap();
+ fs::write(config_file, config).unwrap();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr_contains("[..]bogus[..]")
+ .run();
+}
+
+#[cargo_test]
+fn build_rustflags_recompile2() {
+ let p = project().file("src/lib.rs", "").build();
+
+ p.cargo("check").env("RUSTFLAGS", "--cfg foo").run();
+
+ // Setting RUSTFLAGS forces a recompile
+ let config = r#"
+ [build]
+ rustflags = ["-Z", "bogus"]
+ "#;
+ let config_file = paths::root().join("foo/.cargo/config");
+ fs::create_dir_all(config_file.parent().unwrap()).unwrap();
+ fs::write(config_file, config).unwrap();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr_contains("[..]bogus[..]")
+ .run();
+}
+
+#[cargo_test]
+fn build_rustflags_no_recompile() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ r#"
+ [build]
+ rustflags = ["--cfg", "foo"]
+ "#,
+ )
+ .build();
+
+ p.cargo("check").env("RUSTFLAGS", "--cfg foo").run();
+ p.cargo("check")
+ .env("RUSTFLAGS", "--cfg foo")
+ .with_stdout("")
+ .run();
+}
+
+#[cargo_test]
+fn build_rustflags_with_home_config() {
+ // We need a config file inside the home directory
+ let home = paths::home();
+ let home_config = home.join(".cargo");
+ fs::create_dir(&home_config).unwrap();
+ fs::write(
+ &home_config.join("config"),
+ r#"
+ [build]
+ rustflags = ["-Cllvm-args=-x86-asm-syntax=intel"]
+ "#,
+ )
+ .unwrap();
+
+ // And we need the project to be inside the home directory
+ // so the walking process finds the home project twice.
+ let p = project_in_home("foo").file("src/lib.rs", "").build();
+
+ p.cargo("check -v").run();
+}
+
+#[cargo_test]
+fn target_rustflags_normal_source() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file("src/bin/a.rs", "fn main() {}")
+ .file("examples/b.rs", "fn main() {}")
+ .file("tests/c.rs", "#[test] fn f() { }")
+ .file(
+ "benches/d.rs",
+ r#"
+ #![feature(test)]
+ extern crate test;
+ #[bench] fn run1(_ben: &mut test::Bencher) { }
+ "#,
+ )
+ .file(
+ ".cargo/config",
+ &format!(
+ "
+ [target.{}]
+ rustflags = [\"-Z\", \"bogus\"]
+ ",
+ rustc_host()
+ ),
+ )
+ .build();
+
+ p.cargo("check --lib")
+ .with_status(101)
+ .with_stderr_contains("[..]bogus[..]")
+ .run();
+ p.cargo("check --bin=a")
+ .with_status(101)
+ .with_stderr_contains("[..]bogus[..]")
+ .run();
+ p.cargo("check --example=b")
+ .with_status(101)
+ .with_stderr_contains("[..]bogus[..]")
+ .run();
+ p.cargo("test")
+ .with_status(101)
+ .with_stderr_contains("[..]bogus[..]")
+ .run();
+ p.cargo("bench")
+ .with_status(101)
+ .with_stderr_contains("[..]bogus[..]")
+ .run();
+}
+
+#[cargo_test]
+fn target_rustflags_also_for_build_scripts() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ fn main() { assert!(cfg!(foo)); }
+ "#,
+ )
+ .file(
+ ".cargo/config",
+ &format!(
+ "
+ [target.{}]
+ rustflags = [\"--cfg=foo\"]
+ ",
+ rustc_host()
+ ),
+ )
+ .build();
+
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn target_rustflags_not_for_build_scripts_with_target() {
+ let host = rustc_host();
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ fn main() { assert!(!cfg!(foo)); }
+ "#,
+ )
+ .file(
+ ".cargo/config",
+ &format!(
+ "
+ [target.{}]
+ rustflags = [\"--cfg=foo\"]
+ ",
+ host
+ ),
+ )
+ .build();
+
+ p.cargo("check --target").arg(host).run();
+
+ // Enabling -Ztarget-applies-to-host should not make a difference without the config setting
+ p.cargo("check --target")
+ .arg(host)
+ .masquerade_as_nightly_cargo(&["target-applies-to-host"])
+ .arg("-Ztarget-applies-to-host")
+ .run();
+
+ // Even with the setting, the rustflags from `target.` should not apply, to match the legacy
+ // Cargo behavior.
+ p.change_file(
+ ".cargo/config",
+ &format!(
+ "
+ target-applies-to-host = true
+
+ [target.{}]
+ rustflags = [\"--cfg=foo\"]
+ ",
+ host
+ ),
+ );
+ p.cargo("check --target")
+ .arg(host)
+ .masquerade_as_nightly_cargo(&["target-applies-to-host"])
+ .arg("-Ztarget-applies-to-host")
+ .run();
+}
+
+#[cargo_test]
+fn build_rustflags_for_build_scripts() {
+ let host = rustc_host();
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ fn main() { assert!(cfg!(foo)); }
+ "#,
+ )
+ .file(
+ ".cargo/config",
+ "
+ [build]
+ rustflags = [\"--cfg=foo\"]
+ ",
+ )
+ .build();
+
+ // With "legacy" behavior, build.rustflags should apply to build scripts without --target
+ p.cargo("check").run();
+
+ // But should _not_ apply _with_ --target
+ p.cargo("check --target")
+ .arg(host)
+ .with_status(101)
+ .with_stderr_contains("[..]assertion failed[..]")
+ .run();
+
+ // Enabling -Ztarget-applies-to-host should not make a difference without the config setting
+ p.cargo("check")
+ .masquerade_as_nightly_cargo(&["target-applies-to-host"])
+ .arg("-Ztarget-applies-to-host")
+ .run();
+ p.cargo("check --target")
+ .arg(host)
+ .masquerade_as_nightly_cargo(&["target-applies-to-host"])
+ .arg("-Ztarget-applies-to-host")
+ .with_status(101)
+ .with_stderr_contains("[..]assertion failed[..]")
+ .run();
+
+ // When set to false though, the "proper" behavior where host artifacts _only_ pick up on
+ // [host] should be applied.
+ p.change_file(
+ ".cargo/config",
+ "
+ target-applies-to-host = false
+
+ [build]
+ rustflags = [\"--cfg=foo\"]
+ ",
+ );
+ p.cargo("check")
+ .masquerade_as_nightly_cargo(&["target-applies-to-host"])
+ .arg("-Ztarget-applies-to-host")
+ .with_status(101)
+ .with_stderr_contains("[..]assertion failed[..]")
+ .run();
+ p.cargo("check --target")
+ .arg(host)
+ .masquerade_as_nightly_cargo(&["target-applies-to-host"])
+ .arg("-Ztarget-applies-to-host")
+ .with_status(101)
+ .with_stderr_contains("[..]assertion failed[..]")
+ .run();
+}
+
+#[cargo_test]
+fn host_rustflags_for_build_scripts() {
+ let host = rustc_host();
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ // Ensure that --cfg=foo is passed.
+ fn main() { assert!(cfg!(foo)); }
+ "#,
+ )
+ .file(
+ ".cargo/config",
+ &format!(
+ "
+ target-applies-to-host = false
+
+ [host.{}]
+ rustflags = [\"--cfg=foo\"]
+ ",
+ host
+ ),
+ )
+ .build();
+
+ p.cargo("check --target")
+ .arg(host)
+ .masquerade_as_nightly_cargo(&["target-applies-to-host", "host-config"])
+ .arg("-Ztarget-applies-to-host")
+ .arg("-Zhost-config")
+ .run();
+}
+
+// target.{}.rustflags takes precedence over build.rustflags
+#[cargo_test]
+fn target_rustflags_precedence() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file("src/bin/a.rs", "fn main() {}")
+ .file("examples/b.rs", "fn main() {}")
+ .file("tests/c.rs", "#[test] fn f() { }")
+ .file(
+ ".cargo/config",
+ &format!(
+ "
+ [build]
+ rustflags = [\"--cfg\", \"foo\"]
+
+ [target.{}]
+ rustflags = [\"-Z\", \"bogus\"]
+ ",
+ rustc_host()
+ ),
+ )
+ .build();
+
+ p.cargo("check --lib")
+ .with_status(101)
+ .with_stderr_contains("[..]bogus[..]")
+ .run();
+ p.cargo("check --bin=a")
+ .with_status(101)
+ .with_stderr_contains("[..]bogus[..]")
+ .run();
+ p.cargo("check --example=b")
+ .with_status(101)
+ .with_stderr_contains("[..]bogus[..]")
+ .run();
+ p.cargo("test")
+ .with_status(101)
+ .with_stderr_contains("[..]bogus[..]")
+ .run();
+ p.cargo("bench")
+ .with_status(101)
+ .with_stderr_contains("[..]bogus[..]")
+ .run();
+}
+
+#[cargo_test]
+fn cfg_rustflags_normal_source() {
+ let p = project()
+ .file("src/lib.rs", "pub fn t() {}")
+ .file("src/bin/a.rs", "fn main() {}")
+ .file("examples/b.rs", "fn main() {}")
+ .file("tests/c.rs", "#[test] fn f() { }")
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [target.'cfg({})']
+ rustflags = ["--cfg", "bar"]
+ "#,
+ if rustc_host().contains("-windows-") {
+ "windows"
+ } else {
+ "not(windows)"
+ }
+ ),
+ )
+ .build();
+
+ p.cargo("build --lib -v")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[RUNNING] `rustc [..] --cfg bar[..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("build --bin=a -v")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[RUNNING] `rustc [..] --cfg bar[..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("build --example=b -v")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[RUNNING] `rustc [..] --cfg bar[..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("test --no-run -v")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[RUNNING] `rustc [..] --cfg bar[..]`
+[RUNNING] `rustc [..] --cfg bar[..]`
+[RUNNING] `rustc [..] --cfg bar[..]`
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[EXECUTABLE] `[..]/target/debug/deps/foo-[..][EXE]`
+[EXECUTABLE] `[..]/target/debug/deps/a-[..][EXE]`
+[EXECUTABLE] `[..]/target/debug/deps/c-[..][EXE]`
+",
+ )
+ .run();
+
+ p.cargo("bench --no-run -v")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[RUNNING] `rustc [..] --cfg bar[..]`
+[RUNNING] `rustc [..] --cfg bar[..]`
+[RUNNING] `rustc [..] --cfg bar[..]`
+[FINISHED] bench [optimized] target(s) in [..]
+[EXECUTABLE] `[..]/target/release/deps/foo-[..][EXE]`
+[EXECUTABLE] `[..]/target/release/deps/a-[..][EXE]`
+",
+ )
+ .run();
+}
+
+// target.'cfg(...)'.rustflags takes precedence over build.rustflags
+#[cargo_test]
+fn cfg_rustflags_precedence() {
+ let p = project()
+ .file("src/lib.rs", "pub fn t() {}")
+ .file("src/bin/a.rs", "fn main() {}")
+ .file("examples/b.rs", "fn main() {}")
+ .file("tests/c.rs", "#[test] fn f() { }")
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [build]
+ rustflags = ["--cfg", "foo"]
+
+ [target.'cfg({})']
+ rustflags = ["--cfg", "bar"]
+ "#,
+ if rustc_host().contains("-windows-") {
+ "windows"
+ } else {
+ "not(windows)"
+ }
+ ),
+ )
+ .build();
+
+ p.cargo("build --lib -v")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[RUNNING] `rustc [..] --cfg bar[..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("build --bin=a -v")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[RUNNING] `rustc [..] --cfg bar[..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("build --example=b -v")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[RUNNING] `rustc [..] --cfg bar[..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("test --no-run -v")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[RUNNING] `rustc [..] --cfg bar[..]`
+[RUNNING] `rustc [..] --cfg bar[..]`
+[RUNNING] `rustc [..] --cfg bar[..]`
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[EXECUTABLE] `[..]/target/debug/deps/foo-[..][EXE]`
+[EXECUTABLE] `[..]/target/debug/deps/a-[..][EXE]`
+[EXECUTABLE] `[..]/target/debug/deps/c-[..][EXE]`
+",
+ )
+ .run();
+
+ p.cargo("bench --no-run -v")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[RUNNING] `rustc [..] --cfg bar[..]`
+[RUNNING] `rustc [..] --cfg bar[..]`
+[RUNNING] `rustc [..] --cfg bar[..]`
+[FINISHED] bench [optimized] target(s) in [..]
+[EXECUTABLE] `[..]/target/release/deps/foo-[..][EXE]`
+[EXECUTABLE] `[..]/target/release/deps/a-[..][EXE]`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn target_rustflags_string_and_array_form1() {
+ let p1 = project()
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ r#"
+ [build]
+ rustflags = ["--cfg", "foo"]
+ "#,
+ )
+ .build();
+
+ p1.cargo("check -v")
+ .with_stderr(
+ "\
+[CHECKING] foo v0.0.1 ([..])
+[RUNNING] `rustc [..] --cfg foo[..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ let p2 = project()
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ r#"
+ [build]
+ rustflags = "--cfg foo"
+ "#,
+ )
+ .build();
+
+ p2.cargo("check -v")
+ .with_stderr(
+ "\
+[CHECKING] foo v0.0.1 ([..])
+[RUNNING] `rustc [..] --cfg foo[..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn target_rustflags_string_and_array_form2() {
+ let p1 = project()
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [target.{}]
+ rustflags = ["--cfg", "foo"]
+ "#,
+ rustc_host()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p1.cargo("check -v")
+ .with_stderr(
+ "\
+[CHECKING] foo v0.0.1 ([..])
+[RUNNING] `rustc [..] --cfg foo[..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ let p2 = project()
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [target.{}]
+ rustflags = "--cfg foo"
+ "#,
+ rustc_host()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p2.cargo("check -v")
+ .with_stderr(
+ "\
+[CHECKING] foo v0.0.1 ([..])
+[RUNNING] `rustc [..] --cfg foo[..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn two_matching_in_config() {
+ let p1 = project()
+ .file(
+ ".cargo/config",
+ r#"
+ [target.'cfg(unix)']
+ rustflags = ["--cfg", 'foo="a"']
+ [target.'cfg(windows)']
+ rustflags = ["--cfg", 'foo="a"']
+ [target.'cfg(target_pointer_width = "32")']
+ rustflags = ["--cfg", 'foo="b"']
+ [target.'cfg(target_pointer_width = "64")']
+ rustflags = ["--cfg", 'foo="b"']
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ if cfg!(foo = "a") {
+ println!("a");
+ } else if cfg!(foo = "b") {
+ println!("b");
+ } else {
+ panic!()
+ }
+ }
+ "#,
+ )
+ .build();
+
+ p1.cargo("run").run();
+ p1.cargo("build").with_stderr("[FINISHED] [..]").run();
+}
+
+#[cargo_test]
+fn env_rustflags_misspelled() {
+ let p = project().file("src/main.rs", "fn main() { }").build();
+
+ for cmd in &["check", "build", "run", "test", "bench"] {
+ p.cargo(cmd)
+ .env("RUST_FLAGS", "foo")
+ .with_stderr_contains("[WARNING] Cargo does not read `RUST_FLAGS` environment variable. Did you mean `RUSTFLAGS`?")
+ .run();
+ }
+}
+
+#[cargo_test]
+fn env_rustflags_misspelled_build_script() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ build = "build.rs"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("build.rs", "fn main() { }")
+ .build();
+
+ p.cargo("check")
+ .env("RUST_FLAGS", "foo")
+ .with_stderr_contains("[WARNING] Cargo does not read `RUST_FLAGS` environment variable. Did you mean `RUSTFLAGS`?")
+ .run();
+}
+
+#[cargo_test]
+fn remap_path_prefix_ignored() {
+ // Ensure that --remap-path-prefix does not affect metadata hash.
+ let p = project().file("src/lib.rs", "").build();
+ p.cargo("build").run();
+ let rlibs = p
+ .glob("target/debug/deps/*.rlib")
+ .collect::<Result<Vec<_>, _>>()
+ .unwrap();
+ assert_eq!(rlibs.len(), 1);
+ p.cargo("clean").run();
+
+ let check_metadata_same = || {
+ let rlibs2 = p
+ .glob("target/debug/deps/*.rlib")
+ .collect::<Result<Vec<_>, _>>()
+ .unwrap();
+ assert_eq!(rlibs, rlibs2);
+ };
+
+ p.cargo("build")
+ .env(
+ "RUSTFLAGS",
+ "--remap-path-prefix=/abc=/zoo --remap-path-prefix /spaced=/zoo",
+ )
+ .run();
+ check_metadata_same();
+
+ p.cargo("clean").run();
+ p.cargo("rustc -- --remap-path-prefix=/abc=/zoo --remap-path-prefix /spaced=/zoo")
+ .run();
+ check_metadata_same();
+}
+
+#[cargo_test]
+fn remap_path_prefix_works() {
+ // Check that remap-path-prefix works.
+ Package::new("bar", "0.1.0")
+ .file("src/lib.rs", "pub fn f() -> &'static str { file!() }")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "0.1"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ println!("{}", bar::f());
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("run")
+ .env(
+ "RUSTFLAGS",
+ format!("--remap-path-prefix={}=/foo", paths::root().display()),
+ )
+ .with_stdout("/foo/home/.cargo/registry/src/[..]/bar-0.1.0/src/lib.rs")
+ .run();
+}
+
+#[cargo_test]
+fn host_config_rustflags_with_target() {
+ // regression test for https://github.com/rust-lang/cargo/issues/10206
+ let p = project()
+ .file("src/lib.rs", "")
+ .file("build.rs.rs", "fn main() { assert!(cfg!(foo)); }")
+ .file(".cargo/config.toml", "target-applies-to-host = false")
+ .build();
+
+ p.cargo("check")
+ .masquerade_as_nightly_cargo(&["target-applies-to-host", "host-config"])
+ .arg("-Zhost-config")
+ .arg("-Ztarget-applies-to-host")
+ .arg("-Zunstable-options")
+ .arg("--config")
+ .arg("host.rustflags=[\"--cfg=foo\"]")
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/search.rs b/src/tools/cargo/tests/testsuite/search.rs
new file mode 100644
index 000000000..1f6f40327
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/search.rs
@@ -0,0 +1,192 @@
+//! Tests for the `cargo search` command.
+
+use cargo_test_support::cargo_process;
+use cargo_test_support::paths;
+use cargo_test_support::registry::{RegistryBuilder, Response};
+use std::collections::HashSet;
+
+const SEARCH_API_RESPONSE: &[u8] = br#"
+{
+ "crates": [{
+ "created_at": "2014-11-16T20:17:35Z",
+ "description": "Design by contract style assertions for Rust",
+ "documentation": null,
+ "downloads": 2,
+ "homepage": null,
+ "id": "hoare",
+ "keywords": [],
+ "license": null,
+ "links": {
+ "owners": "/api/v1/crates/hoare/owners",
+ "reverse_dependencies": "/api/v1/crates/hoare/reverse_dependencies",
+ "version_downloads": "/api/v1/crates/hoare/downloads",
+ "versions": "/api/v1/crates/hoare/versions"
+ },
+ "max_version": "0.1.1",
+ "name": "hoare",
+ "repository": "https://github.com/nick29581/libhoare",
+ "updated_at": "2014-11-20T21:49:21Z",
+ "versions": null
+ },
+ {
+ "id": "postgres",
+ "name": "postgres",
+ "updated_at": "2020-05-01T23:17:54.335921+00:00",
+ "versions": null,
+ "keywords": null,
+ "categories": null,
+ "badges": [
+ {
+ "badge_type": "circle-ci",
+ "attributes": {
+ "repository": "sfackler/rust-postgres",
+ "branch": null
+ }
+ }
+ ],
+ "created_at": "2014-11-24T02:34:44.756689+00:00",
+ "downloads": 535491,
+ "recent_downloads": 88321,
+ "max_version": "0.17.3",
+ "newest_version": "0.17.3",
+ "description": "A native, synchronous PostgreSQL client",
+ "homepage": null,
+ "documentation": null,
+ "repository": "https://github.com/sfackler/rust-postgres",
+ "links": {
+ "version_downloads": "/api/v1/crates/postgres/downloads",
+ "versions": "/api/v1/crates/postgres/versions",
+ "owners": "/api/v1/crates/postgres/owners",
+ "owner_team": "/api/v1/crates/postgres/owner_team",
+ "owner_user": "/api/v1/crates/postgres/owner_user",
+ "reverse_dependencies": "/api/v1/crates/postgres/reverse_dependencies"
+ },
+ "exact_match": true
+ }
+ ],
+ "meta": {
+ "total": 2
+ }
+}"#;
+
+const SEARCH_RESULTS: &str = "\
+hoare = \"0.1.1\" # Design by contract style assertions for Rust
+postgres = \"0.17.3\" # A native, synchronous PostgreSQL client
+";
+
+#[must_use]
+fn setup() -> RegistryBuilder {
+ RegistryBuilder::new()
+ .http_api()
+ .add_responder("/api/v1/crates", |_, _| Response {
+ code: 200,
+ headers: vec![],
+ body: SEARCH_API_RESPONSE.to_vec(),
+ })
+}
+
+#[cargo_test]
+fn not_update() {
+ let registry = setup().build();
+
+ use cargo::core::{Shell, Source, SourceId};
+ use cargo::sources::RegistrySource;
+ use cargo::util::Config;
+
+ let sid = SourceId::for_registry(registry.index_url()).unwrap();
+ let cfg = Config::new(
+ Shell::from_write(Box::new(Vec::new())),
+ paths::root(),
+ paths::home().join(".cargo"),
+ );
+ let lock = cfg.acquire_package_cache_lock().unwrap();
+ let mut regsrc = RegistrySource::remote(sid, &HashSet::new(), &cfg).unwrap();
+ regsrc.invalidate_cache();
+ regsrc.block_until_ready().unwrap();
+ drop(lock);
+
+ cargo_process("search postgres")
+ .replace_crates_io(registry.index_url())
+ .with_stdout_contains(SEARCH_RESULTS)
+ .with_stderr("") // without "Updating ... index"
+ .run();
+}
+
+#[cargo_test]
+fn replace_default() {
+ let registry = setup().build();
+
+ cargo_process("search postgres")
+ .replace_crates_io(registry.index_url())
+ .with_stdout_contains(SEARCH_RESULTS)
+ .with_stderr_contains("[..]Updating [..] index")
+ .run();
+}
+
+#[cargo_test]
+fn simple() {
+ let registry = setup().build();
+
+ cargo_process("search postgres --index")
+ .arg(registry.index_url().as_str())
+ .with_stdout_contains(SEARCH_RESULTS)
+ .run();
+}
+
+#[cargo_test]
+fn multiple_query_params() {
+ let registry = setup().build();
+
+ cargo_process("search postgres sql --index")
+ .arg(registry.index_url().as_str())
+ .with_stdout_contains(SEARCH_RESULTS)
+ .run();
+}
+
+#[cargo_test]
+fn ignore_quiet() {
+ let registry = setup().build();
+
+ cargo_process("search -q postgres")
+ .replace_crates_io(registry.index_url())
+ .with_stdout_contains(SEARCH_RESULTS)
+ .run();
+}
+
+#[cargo_test]
+fn colored_results() {
+ let registry = setup().build();
+
+ cargo_process("search --color=never postgres")
+ .replace_crates_io(registry.index_url())
+ .with_stdout_does_not_contain("[..]\x1b[[..]")
+ .run();
+
+ cargo_process("search --color=always postgres")
+ .replace_crates_io(registry.index_url())
+ .with_stdout_contains("[..]\x1b[[..]")
+ .run();
+}
+
+#[cargo_test]
+fn auth_required_failure() {
+ let server = setup().auth_required().no_configure_token().build();
+
+ cargo_process("-Zregistry-auth search postgres")
+ .masquerade_as_nightly_cargo(&["registry-auth"])
+ .replace_crates_io(server.index_url())
+ .with_status(101)
+ .with_stderr_contains("[ERROR] no token found, please run `cargo login`")
+ .run();
+}
+
+#[cargo_test]
+fn auth_required() {
+ let server = setup().auth_required().build();
+
+ cargo_process("-Zregistry-auth search postgres")
+ .masquerade_as_nightly_cargo(&["registry-auth"])
+ .replace_crates_io(server.index_url())
+ .with_stdout_contains(SEARCH_RESULTS)
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/shell_quoting.rs b/src/tools/cargo/tests/testsuite/shell_quoting.rs
new file mode 100644
index 000000000..bff333389
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/shell_quoting.rs
@@ -0,0 +1,37 @@
+//! This file tests that when the commands being run are shown
+//! in the output, their arguments are quoted properly
+//! so that the command can be run in a terminal.
+
+use cargo_test_support::project;
+
+#[cargo_test]
+fn features_are_quoted() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = ["mikeyhew@example.com"]
+
+ [features]
+ some_feature = []
+ default = ["some_feature"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {error}")
+ .build();
+
+ p.cargo("check -v")
+ .env("MSYSTEM", "1")
+ .with_status(101)
+ .with_stderr_contains(
+ r#"[RUNNING] `rustc [..] --cfg 'feature="default"' --cfg 'feature="some_feature"' [..]`"#
+ ).with_stderr_contains(
+ r#"
+Caused by:
+ process didn't exit successfully: [..] --cfg 'feature="default"' --cfg 'feature="some_feature"' [..]"#
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/source_replacement.rs b/src/tools/cargo/tests/testsuite/source_replacement.rs
new file mode 100644
index 000000000..24f2ca3e3
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/source_replacement.rs
@@ -0,0 +1,250 @@
+//! Tests for `[source]` table (source replacement).
+
+use std::fs;
+
+use cargo_test_support::registry::{Package, RegistryBuilder, TestRegistry};
+use cargo_test_support::{cargo_process, paths, project, t};
+
+fn setup_replacement(config: &str) -> TestRegistry {
+ let crates_io = RegistryBuilder::new()
+ .no_configure_registry()
+ .http_api()
+ .build();
+
+ let root = paths::root();
+ t!(fs::create_dir(&root.join(".cargo")));
+ t!(fs::write(root.join(".cargo/config"), config,));
+ crates_io
+}
+
+#[cargo_test]
+fn crates_io_token_not_sent_to_replacement() {
+ // verifies that the crates.io token is not sent to a replacement registry during publish.
+ let crates_io = setup_replacement(
+ r#"
+ [source.crates-io]
+ replace-with = 'alternative'
+ "#,
+ );
+ let _alternative = RegistryBuilder::new()
+ .alternative()
+ .http_api()
+ .no_configure_token()
+ .build();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("publish --no-verify --registry crates-io")
+ .replace_crates_io(crates_io.index_url())
+ .with_stderr_contains("[UPDATING] crates.io index")
+ .run();
+}
+
+#[cargo_test]
+fn token_sent_to_correct_registry() {
+ // verifies that the crates.io token is not sent to a replacement registry during yank.
+ let crates_io = setup_replacement(
+ r#"
+ [source.crates-io]
+ replace-with = 'alternative'
+ "#,
+ );
+ let _alternative = RegistryBuilder::new().alternative().http_api().build();
+
+ cargo_process("yank foo@0.0.1 --registry crates-io")
+ .replace_crates_io(crates_io.index_url())
+ .with_stderr(
+ "\
+[UPDATING] crates.io index
+[YANK] foo@0.0.1
+",
+ )
+ .run();
+
+ cargo_process("yank foo@0.0.1 --registry alternative")
+ .replace_crates_io(crates_io.index_url())
+ .with_stderr(
+ "\
+[UPDATING] `alternative` index
+[YANK] foo@0.0.1
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn ambiguous_registry() {
+ // verifies that an error is issued when a source-replacement is configured
+ // and no --registry argument is given.
+ let crates_io = setup_replacement(
+ r#"
+ [source.crates-io]
+ replace-with = 'alternative'
+ "#,
+ );
+ let _alternative = RegistryBuilder::new()
+ .alternative()
+ .http_api()
+ .no_configure_token()
+ .build();
+
+ cargo_process("yank foo@0.0.1")
+ .replace_crates_io(crates_io.index_url())
+ .with_status(101)
+ .with_stderr(
+ "\
+error: crates-io is replaced with remote registry alternative;
+include `--registry alternative` or `--registry crates-io`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn yank_with_default_crates_io() {
+ // verifies that no error is given when registry.default is used.
+ let crates_io = setup_replacement(
+ r#"
+ [source.crates-io]
+ replace-with = 'alternative'
+
+ [registry]
+ default = 'crates-io'
+ "#,
+ );
+ let _alternative = RegistryBuilder::new().alternative().http_api().build();
+
+ cargo_process("yank foo@0.0.1")
+ .replace_crates_io(crates_io.index_url())
+ .with_stderr(
+ "\
+[UPDATING] crates.io index
+[YANK] foo@0.0.1
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn yank_with_default_alternative() {
+ // verifies that no error is given when registry.default is an alt registry.
+ let crates_io = setup_replacement(
+ r#"
+ [source.crates-io]
+ replace-with = 'alternative'
+
+ [registry]
+ default = 'alternative'
+ "#,
+ );
+ let _alternative = RegistryBuilder::new().alternative().http_api().build();
+
+ cargo_process("yank foo@0.0.1")
+ .replace_crates_io(crates_io.index_url())
+ .with_stderr(
+ "\
+[UPDATING] `alternative` index
+[YANK] foo@0.0.1
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn publish_with_replacement() {
+ // verifies that the crates.io token is not sent to a replacement registry during publish.
+ let crates_io = setup_replacement(
+ r#"
+ [source.crates-io]
+ replace-with = 'alternative'
+ "#,
+ );
+ let _alternative = RegistryBuilder::new()
+ .alternative()
+ .http_api()
+ .no_configure_token()
+ .build();
+
+ // Publish bar only to alternative. This tests that the publish verification build
+ // does uses the source replacement.
+ Package::new("bar", "1.0.0").alternative(true).publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+
+ [dependencies]
+ bar = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ // Verifies that the crates.io index is used to find the publishing endpoint
+ // and that the crate is sent to crates.io. The source replacement is only used
+ // for the verification step.
+ p.cargo("publish --registry crates-io")
+ .replace_crates_io(crates_io.index_url())
+ .with_stderr(
+ "\
+[UPDATING] crates.io index
+[WARNING] manifest has no documentation, homepage or repository.
+See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info.
+[PACKAGING] foo v0.0.1 ([..])
+[VERIFYING] foo v0.0.1 ([..])
+[UPDATING] `alternative` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v1.0.0 (registry `alternative`)
+[COMPILING] bar v1.0.0
+[COMPILING] foo v0.0.1 ([..]foo-0.0.1)
+[FINISHED] dev [..]
+[PACKAGED] [..]
+[UPLOADING] foo v0.0.1 ([..])
+[UPLOADED] foo v0.0.1 to registry `crates-io`
+note: Waiting for `foo v0.0.1` to be available at registry `crates-io`.
+You may press ctrl-c to skip waiting; the crate should be available shortly.
+[PUBLISHED] foo v0.0.1 at registry `crates-io`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn undefined_default() {
+ // verifies that no error is given when registry.default is used.
+ let crates_io = setup_replacement(
+ r#"
+ [registry]
+ default = 'undefined'
+ "#,
+ );
+
+ cargo_process("yank foo@0.0.1")
+ .replace_crates_io(crates_io.index_url())
+ .with_status(101)
+ .with_stderr(
+ "[ERROR] no index found for registry: `undefined`
+",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/ssh.rs b/src/tools/cargo/tests/testsuite/ssh.rs
new file mode 100644
index 000000000..d1701d32d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/ssh.rs
@@ -0,0 +1,592 @@
+//! Network tests for SSH connections.
+//!
+//! Note that these tests will generally require setting CARGO_CONTAINER_TESTS
+//! or CARGO_PUBLIC_NETWORK_TESTS.
+//!
+//! NOTE: The container tests almost certainly won't work on Windows.
+
+use cargo_test_support::containers::{Container, ContainerHandle, MkFile};
+use cargo_test_support::git::cargo_uses_gitoxide;
+use cargo_test_support::{paths, process, project, Project};
+use std::fs;
+use std::io::Write;
+use std::path::PathBuf;
+
+fn ssh_repo_url(container: &ContainerHandle, name: &str) -> String {
+ let port = container.port_mappings[&22];
+ format!("ssh://testuser@127.0.0.1:{port}/repos/{name}.git")
+}
+
+/// The path to the client's private key.
+fn key_path() -> PathBuf {
+ paths::home().join(".ssh/id_ed25519")
+}
+
+/// Generates the SSH keys for authenticating into the container.
+fn gen_ssh_keys() -> String {
+ let path = key_path();
+ process("ssh-keygen")
+ .args(&["-t", "ed25519", "-N", "", "-f"])
+ .arg(&path)
+ .exec_with_output()
+ .unwrap();
+ let pub_key = path.with_extension("pub");
+ fs::read_to_string(pub_key).unwrap()
+}
+
+/// Handler for running ssh-agent for SSH authentication.
+///
+/// Be sure to set `SSH_AUTH_SOCK` when running a process in order to use the
+/// agent. Keys will need to be copied into the container with the
+/// `authorized_keys()` method.
+struct Agent {
+ sock: PathBuf,
+ pid: String,
+ ssh_dir: PathBuf,
+ pub_key: String,
+}
+
+impl Agent {
+ fn launch() -> Agent {
+ let ssh_dir = paths::home().join(".ssh");
+ fs::create_dir(&ssh_dir).unwrap();
+ let pub_key = gen_ssh_keys();
+
+ let sock = paths::root().join("agent");
+ let output = process("ssh-agent")
+ .args(&["-s", "-a"])
+ .arg(&sock)
+ .exec_with_output()
+ .unwrap();
+ let stdout = std::str::from_utf8(&output.stdout).unwrap();
+ let start = stdout.find("SSH_AGENT_PID=").unwrap() + 14;
+ let end = &stdout[start..].find(';').unwrap();
+ let pid = (&stdout[start..start + end]).to_string();
+ eprintln!("SSH_AGENT_PID={pid}");
+ process("ssh-add")
+ .arg(key_path())
+ .env("SSH_AUTH_SOCK", &sock)
+ .exec_with_output()
+ .unwrap();
+ Agent {
+ sock,
+ pid,
+ ssh_dir,
+ pub_key,
+ }
+ }
+
+ /// Returns a `MkFile` which can be passed into the `Container` builder to
+ /// copy an `authorized_keys` file containing this agent's public key.
+ fn authorized_keys(&self) -> MkFile {
+ MkFile::path("home/testuser/.ssh/authorized_keys")
+ .contents(self.pub_key.as_bytes())
+ .mode(0o600)
+ .uid(100)
+ .gid(101)
+ }
+}
+
+impl Drop for Agent {
+ fn drop(&mut self) {
+ if let Err(e) = process("ssh-agent")
+ .args(&["-k", "-a"])
+ .arg(&self.sock)
+ .env("SSH_AGENT_PID", &self.pid)
+ .exec_with_output()
+ {
+ eprintln!("failed to stop ssh-agent: {e:?}");
+ }
+ }
+}
+
+/// Common project used for several tests.
+fn foo_bar_project(url: &str) -> Project {
+ project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = {{ git = "{url}" }}
+ "#
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build()
+}
+
+#[cargo_test(container_test)]
+fn no_known_host() {
+ // When host is not known, it should show an error.
+ let sshd = Container::new("sshd").launch();
+ let url = ssh_repo_url(&sshd, "bar");
+ let p = foo_bar_project(&url);
+ p.cargo("fetch")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] git repository `ssh://testuser@127.0.0.1:[..]/repos/bar.git`
+error: failed to get `bar` as a dependency of package `foo v0.1.0 ([ROOT]/foo)`
+
+Caused by:
+ failed to load source for dependency `bar`
+
+Caused by:
+ Unable to update ssh://testuser@127.0.0.1:[..]/repos/bar.git
+
+Caused by:
+ failed to clone into: [ROOT]/home/.cargo/git/db/bar-[..]
+
+Caused by:
+ error: unknown SSH host key
+ The SSH host key for `[127.0.0.1]:[..]` is not known and cannot be validated.
+
+ To resolve this issue, add the host key to the `net.ssh.known-hosts` array in \
+ your Cargo configuration (such as [ROOT]/home/.cargo/config.toml) or in your \
+ OpenSSH known_hosts file at [ROOT]/home/.ssh/known_hosts
+
+ The key to add is:
+
+ [127.0.0.1]:[..] ecdsa-sha2-nistp256 AAAA[..]
+
+ The ECDSA key fingerprint is: SHA256:[..]
+ This fingerprint should be validated with the server administrator that it is correct.
+
+ See https://doc.rust-lang.org/stable/cargo/appendix/git-authentication.html#ssh-known-hosts \
+ for more information.
+",
+ )
+ .run();
+}
+
+#[cargo_test(container_test)]
+fn known_host_works() {
+ // The key displayed in the error message should work when added to known_hosts.
+ let agent = Agent::launch();
+ let sshd = Container::new("sshd")
+ .file(agent.authorized_keys())
+ .launch();
+ let url = ssh_repo_url(&sshd, "bar");
+ let p = foo_bar_project(&url);
+ let output = p
+ .cargo("fetch")
+ .env("SSH_AUTH_SOCK", &agent.sock)
+ .build_command()
+ .output()
+ .unwrap();
+ let stderr = std::str::from_utf8(&output.stderr).unwrap();
+
+ // Validate the fingerprint while we're here.
+ let fingerprint = stderr
+ .lines()
+ .find(|line| line.starts_with(" The ECDSA key fingerprint"))
+ .unwrap()
+ .trim();
+ let fingerprint = &fingerprint[30..];
+ let finger_out = sshd.exec(&["ssh-keygen", "-l", "-f", "/etc/ssh/ssh_host_ecdsa_key.pub"]);
+ let gen_finger = std::str::from_utf8(&finger_out.stdout).unwrap();
+ // <key-size> <fingerprint> <comments…>
+ let gen_finger = gen_finger.split_whitespace().nth(1).unwrap();
+ assert_eq!(fingerprint, gen_finger);
+
+ // Add the key to known_hosts, and try again.
+ let key = stderr
+ .lines()
+ .find(|line| line.starts_with(" [127.0.0.1]:"))
+ .unwrap()
+ .trim();
+ fs::write(agent.ssh_dir.join("known_hosts"), key).unwrap();
+ p.cargo("fetch")
+ .env("SSH_AUTH_SOCK", &agent.sock)
+ .with_stderr("[UPDATING] git repository `ssh://testuser@127.0.0.1:[..]/repos/bar.git`")
+ .run();
+}
+
+#[cargo_test(container_test)]
+fn same_key_different_hostname() {
+ // The error message should mention if an identical key was found.
+ let agent = Agent::launch();
+ let sshd = Container::new("sshd").launch();
+
+ let hostkey = sshd.read_file("/etc/ssh/ssh_host_ecdsa_key.pub");
+ let known_hosts = format!("example.com {hostkey}");
+ fs::write(agent.ssh_dir.join("known_hosts"), known_hosts).unwrap();
+
+ let url = ssh_repo_url(&sshd, "bar");
+ let p = foo_bar_project(&url);
+ p.cargo("fetch")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] git repository `ssh://testuser@127.0.0.1:[..]/repos/bar.git`
+error: failed to get `bar` as a dependency of package `foo v0.1.0 ([ROOT]/foo)`
+
+Caused by:
+ failed to load source for dependency `bar`
+
+Caused by:
+ Unable to update ssh://testuser@127.0.0.1:[..]/repos/bar.git
+
+Caused by:
+ failed to clone into: [ROOT]/home/.cargo/git/db/bar-[..]
+
+Caused by:
+ error: unknown SSH host key
+ The SSH host key for `[127.0.0.1]:[..]` is not known and cannot be validated.
+
+ To resolve this issue, add the host key to the `net.ssh.known-hosts` array in \
+ your Cargo configuration (such as [ROOT]/home/.cargo/config.toml) or in your \
+ OpenSSH known_hosts file at [ROOT]/home/.ssh/known_hosts
+
+ The key to add is:
+
+ [127.0.0.1]:[..] ecdsa-sha2-nistp256 AAAA[..]
+
+ The ECDSA key fingerprint is: SHA256:[..]
+ This fingerprint should be validated with the server administrator that it is correct.
+ Note: This host key was found, but is associated with a different host:
+ [ROOT]/home/.ssh/known_hosts line 1: example.com
+
+ See https://doc.rust-lang.org/stable/cargo/appendix/git-authentication.html#ssh-known-hosts \
+ for more information.
+",
+ )
+ .run();
+}
+
+#[cargo_test(container_test)]
+fn known_host_without_port() {
+ // A known_host entry without a port should match a connection to a non-standard port.
+ let agent = Agent::launch();
+ let sshd = Container::new("sshd")
+ .file(agent.authorized_keys())
+ .launch();
+
+ let hostkey = sshd.read_file("/etc/ssh/ssh_host_ecdsa_key.pub");
+ // The important part of this test is that this line does not have a port.
+ let known_hosts = format!("127.0.0.1 {hostkey}");
+ fs::write(agent.ssh_dir.join("known_hosts"), known_hosts).unwrap();
+ let url = ssh_repo_url(&sshd, "bar");
+ let p = foo_bar_project(&url);
+ p.cargo("fetch")
+ .env("SSH_AUTH_SOCK", &agent.sock)
+ .with_stderr("[UPDATING] git repository `ssh://testuser@127.0.0.1:[..]/repos/bar.git`")
+ .run();
+}
+
+#[cargo_test(container_test)]
+fn hostname_case_insensitive() {
+ // hostname checking should be case-insensitive.
+ let agent = Agent::launch();
+ let sshd = Container::new("sshd")
+ .file(agent.authorized_keys())
+ .launch();
+
+ // Consider using `gethostname-rs` instead?
+ let hostname = process("hostname").exec_with_output().unwrap();
+ let hostname = std::str::from_utf8(&hostname.stdout).unwrap().trim();
+ let inv_hostname = if hostname.chars().any(|c| c.is_lowercase()) {
+ hostname.to_uppercase()
+ } else {
+ // There should be *some* chars in the name.
+ assert!(hostname.chars().any(|c| c.is_uppercase()));
+ hostname.to_lowercase()
+ };
+ eprintln!("converted {hostname} to {inv_hostname}");
+
+ let hostkey = sshd.read_file("/etc/ssh/ssh_host_ecdsa_key.pub");
+ let known_hosts = format!("{inv_hostname} {hostkey}");
+ fs::write(agent.ssh_dir.join("known_hosts"), known_hosts).unwrap();
+ let port = sshd.port_mappings[&22];
+ let url = format!("ssh://testuser@{hostname}:{port}/repos/bar.git");
+ let p = foo_bar_project(&url);
+ p.cargo("fetch")
+ .env("SSH_AUTH_SOCK", &agent.sock)
+ .with_stderr(&format!(
+ "[UPDATING] git repository `ssh://testuser@{hostname}:{port}/repos/bar.git`"
+ ))
+ .run();
+}
+
+#[cargo_test(container_test)]
+fn invalid_key_error() {
+ // An error when a known_host value doesn't match.
+ let agent = Agent::launch();
+ let sshd = Container::new("sshd")
+ .file(agent.authorized_keys())
+ .launch();
+
+ let port = sshd.port_mappings[&22];
+ let known_hosts = format!(
+ "[127.0.0.1]:{port} ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLqLMclVr7MDuaVsm3sEnnq2OrGxTFiHSw90wd6N14BU8xVC9cZldC3rJ58Wmw6bEVKPjk7foNG0lHwS5bCKX+U=\n"
+ );
+ fs::write(agent.ssh_dir.join("known_hosts"), known_hosts).unwrap();
+ let url = ssh_repo_url(&sshd, "bar");
+ let p = foo_bar_project(&url);
+ p.cargo("fetch")
+ .env("SSH_AUTH_SOCK", &agent.sock)
+ .with_status(101)
+ .with_stderr(&format!("\
+[UPDATING] git repository `ssh://testuser@127.0.0.1:{port}/repos/bar.git`
+error: failed to get `bar` as a dependency of package `foo v0.1.0 ([ROOT]/foo)`
+
+Caused by:
+ failed to load source for dependency `bar`
+
+Caused by:
+ Unable to update ssh://testuser@127.0.0.1:{port}/repos/bar.git
+
+Caused by:
+ failed to clone into: [ROOT]/home/.cargo/git/db/bar-[..]
+
+Caused by:
+ error: SSH host key has changed for `[127.0.0.1]:{port}`
+ *********************************
+ * WARNING: HOST KEY HAS CHANGED *
+ *********************************
+ This may be caused by a man-in-the-middle attack, or the server may have changed its host key.
+
+ The ECDSA fingerprint for the key from the remote host is:
+ SHA256:[..]
+
+ You are strongly encouraged to contact the server administrator for `[127.0.0.1]:{port}` \
+ to verify that this new key is correct.
+
+ If you can verify that the server has a new key, you can resolve this error by \
+ removing the old ecdsa-sha2-nistp256 key for `[127.0.0.1]:{port}` located at \
+ [ROOT]/home/.ssh/known_hosts line 1, and adding the new key to the \
+ `net.ssh.known-hosts` array in your Cargo configuration (such as \
+ [ROOT]/home/.cargo/config.toml) or in your OpenSSH known_hosts file at \
+ [ROOT]/home/.ssh/known_hosts
+
+ The key provided by the remote host is:
+
+ [127.0.0.1]:{port} ecdsa-sha2-nistp256 [..]
+
+ See https://doc.rust-lang.org/stable/cargo/appendix/git-authentication.html#ssh-known-hosts for more information.
+"))
+ .run();
+ // Add the key, it should work even with the old key left behind.
+ let hostkey = sshd.read_file("/etc/ssh/ssh_host_ecdsa_key.pub");
+ let known_hosts_path = agent.ssh_dir.join("known_hosts");
+ let mut f = fs::OpenOptions::new()
+ .append(true)
+ .open(known_hosts_path)
+ .unwrap();
+ write!(f, "[127.0.0.1]:{port} {hostkey}").unwrap();
+ drop(f);
+ p.cargo("fetch")
+ .env("SSH_AUTH_SOCK", &agent.sock)
+ .with_stderr("[UPDATING] git repository `ssh://testuser@127.0.0.1:[..]/repos/bar.git`")
+ .run();
+}
+
+// For unknown reasons, this test occasionally fails on Windows with a
+// LIBSSH2_ERROR_KEY_EXCHANGE_FAILURE error:
+// failed to start SSH session: Unable to exchange encryption keys; class=Ssh (23)
+#[cargo_test(public_network_test, ignore_windows = "test is flaky on windows")]
+fn invalid_github_key() {
+ // A key for github.com in known_hosts should override the built-in key.
+ // This uses a bogus key which should result in an error.
+ let ssh_dir = paths::home().join(".ssh");
+ fs::create_dir(&ssh_dir).unwrap();
+ let known_hosts = "\
+ github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLqLMclVr7MDuaVsm3sEnnq2OrGxTFiHSw90wd6N14BU8xVC9cZldC3rJ58Wmw6bEVKPjk7foNG0lHwS5bCKX+U=\n\
+ github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDgi+8rMcyFCBq5y7BXrb2aaYGhMjlU3QDy7YDvtNL5KSecYOsaqQHaXr87Bbx0EEkgbhK4kVMkmThlCoNITQS9Vc3zIMQ+Tg6+O4qXx719uCzywl50Tb5tDqPGMj54jcq3VUiu/dvse0yeehyvzoPNWewgGWLx11KI4A4wOwMnc6guhculEWe9DjGEjUQ34lPbmdfu/Hza7ZVu/RhgF/wc43uzXWB2KpMEqtuY1SgRlCZqTASoEtfKZi0AuM7AEdOwE5aTotS4CQZHWimb1bMFpF4DAq92CZ8Jhrm4rWETbO29WmjviCJEA3KNQyd3oA7H9AE9z/22PJaVEmjiZZ+wyLgwyIpOlsnHYNEdGeQMQ4SgLRkARLwcnKmByv1AAxsBW4LI3Os4FpwxVPdXHcBebydtvxIsbtUVkkq99nbsIlnSRFSTvb0alrdzRuKTdWpHtN1v9hagFqmeCx/kJfH76NXYBbtaWZhSOnxfEbhLYuOb+IS4jYzHAIkzy9FjVuk=\n\
+ ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEeMB6BUAW6FfvfLxRO3kGASe0yXnrRT4kpqncsup2b2\n";
+ fs::write(ssh_dir.join("known_hosts"), known_hosts).unwrap();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bitflags = { git = "ssh://git@github.com/rust-lang/bitflags.git", tag = "1.3.2" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("fetch")
+ .with_status(101)
+ .with_stderr_contains(if cargo_uses_gitoxide() {
+ " git@github.com: Permission denied (publickey)."
+ } else {
+ " error: SSH host key has changed for `github.com`"
+ })
+ .run();
+}
+
+// For unknown reasons, this test occasionally fails on Windows with a
+// LIBSSH2_ERROR_KEY_EXCHANGE_FAILURE error:
+// failed to start SSH session: Unable to exchange encryption keys; class=Ssh (23)
+#[cargo_test(public_network_test, ignore_windows = "test is flaky on windows")]
+fn bundled_github_works() {
+ // The bundled key for github.com works.
+ //
+ // Use a bogus auth sock to force an authentication error.
+ // On Windows, if the agent service is running, it could allow a
+ // successful authentication.
+ //
+ // If the bundled hostkey did not work, it would result in an "unknown SSH
+ // host key" instead.
+ let bogus_auth_sock = paths::home().join("ssh_auth_sock");
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bitflags = { git = "ssh://git@github.com/rust-lang/bitflags.git", tag = "1.3.2" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ let shared_stderr = "\
+[UPDATING] git repository `ssh://git@github.com/rust-lang/bitflags.git`
+error: failed to get `bitflags` as a dependency of package `foo v0.1.0 ([ROOT]/foo)`
+
+Caused by:
+ failed to load source for dependency `bitflags`
+
+Caused by:
+ Unable to update ssh://git@github.com/rust-lang/bitflags.git?tag=1.3.2
+
+Caused by:
+ failed to clone into: [ROOT]/home/.cargo/git/db/bitflags-[..]
+
+Caused by:
+ failed to authenticate when downloading repository
+
+ *";
+ let expected = if cargo_uses_gitoxide() {
+ format!(
+ "{shared_stderr} attempted to find username/password via `credential.helper`, but maybe the found credentials were incorrect
+
+ if the git CLI succeeds then `net.git-fetch-with-cli` may help here
+ https://doc.rust-lang.org/cargo/reference/config.html#netgit-fetch-with-cli
+
+Caused by:
+ Credentials provided for \"ssh://git@github.com/rust-lang/bitflags.git\" were not accepted by the remote
+
+Caused by:
+ git@github.com: Permission denied (publickey).
+"
+ )
+ } else {
+ format!(
+ "{shared_stderr} attempted ssh-agent authentication, but no usernames succeeded: `git`
+
+ if the git CLI succeeds then `net.git-fetch-with-cli` may help here
+ https://doc.rust-lang.org/cargo/reference/config.html#netgit-fetch-with-cli
+
+Caused by:
+ no authentication methods succeeded
+"
+ )
+ };
+ p.cargo("fetch")
+ .env("SSH_AUTH_SOCK", &bogus_auth_sock)
+ .with_status(101)
+ .with_stderr(&expected)
+ .run();
+
+ let shared_stderr = "\
+[UPDATING] git repository `ssh://git@github.com:22/rust-lang/bitflags.git`
+error: failed to get `bitflags` as a dependency of package `foo v0.1.0 ([ROOT]/foo)`
+
+Caused by:
+ failed to load source for dependency `bitflags`
+
+Caused by:
+ Unable to update ssh://git@github.com:22/rust-lang/bitflags.git?tag=1.3.2
+
+Caused by:
+ failed to clone into: [ROOT]/home/.cargo/git/db/bitflags-[..]
+
+Caused by:
+ failed to authenticate when downloading repository
+
+ *";
+
+ let expected = if cargo_uses_gitoxide() {
+ format!(
+ "{shared_stderr} attempted to find username/password via `credential.helper`, but maybe the found credentials were incorrect
+
+ if the git CLI succeeds then `net.git-fetch-with-cli` may help here
+ https://doc.rust-lang.org/cargo/reference/config.html#netgit-fetch-with-cli
+
+Caused by:
+ Credentials provided for \"ssh://git@github.com:22/rust-lang/bitflags.git\" were not accepted by the remote
+
+Caused by:
+ git@github.com: Permission denied (publickey).
+"
+ )
+ } else {
+ format!(
+ "{shared_stderr} attempted ssh-agent authentication, but no usernames succeeded: `git`
+
+ if the git CLI succeeds then `net.git-fetch-with-cli` may help here
+ https://doc.rust-lang.org/cargo/reference/config.html#netgit-fetch-with-cli
+
+Caused by:
+ no authentication methods succeeded
+"
+ )
+ };
+
+ // Explicit :22 should also work with bundled.
+ p.change_file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bitflags = { git = "ssh://git@github.com:22/rust-lang/bitflags.git", tag = "1.3.2" }
+ "#,
+ );
+ p.cargo("fetch")
+ .env("SSH_AUTH_SOCK", &bogus_auth_sock)
+ .with_status(101)
+ .with_stderr(&expected)
+ .run();
+}
+
+#[cargo_test(container_test)]
+fn ssh_key_in_config() {
+ // known_host in config works.
+ let agent = Agent::launch();
+ let sshd = Container::new("sshd")
+ .file(agent.authorized_keys())
+ .launch();
+ let hostkey = sshd.read_file("/etc/ssh/ssh_host_ecdsa_key.pub");
+ let url = ssh_repo_url(&sshd, "bar");
+ let p = foo_bar_project(&url);
+ p.change_file(
+ ".cargo/config.toml",
+ &format!(
+ r#"
+ [net.ssh]
+ known-hosts = ['127.0.0.1 {}']
+ "#,
+ hostkey.trim()
+ ),
+ );
+ p.cargo("fetch")
+ .env("SSH_AUTH_SOCK", &agent.sock)
+ .with_stderr("[UPDATING] git repository `ssh://testuser@127.0.0.1:[..]/repos/bar.git`")
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/standard_lib.rs b/src/tools/cargo/tests/testsuite/standard_lib.rs
new file mode 100644
index 000000000..d3be303ea
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/standard_lib.rs
@@ -0,0 +1,657 @@
+//! Tests for building the standard library (-Zbuild-std).
+//!
+//! These tests all use a "mock" standard library so that we don't have to
+//! rebuild the real one. There is a separate integration test `build-std`
+//! which builds the real thing, but that should be avoided if possible.
+
+use cargo_test_support::registry::{Dependency, Package};
+use cargo_test_support::ProjectBuilder;
+use cargo_test_support::{paths, project, rustc_host, Execs};
+use std::path::{Path, PathBuf};
+
+struct Setup {
+ rustc_wrapper: PathBuf,
+ real_sysroot: String,
+}
+
+fn setup() -> Setup {
+ // Our mock sysroot requires a few packages from crates.io, so make sure
+ // they're "published" to crates.io. Also edit their code a bit to make sure
+ // that they have access to our custom crates with custom apis.
+ Package::new("registry-dep-using-core", "1.0.0")
+ .file(
+ "src/lib.rs",
+ "
+ #![no_std]
+
+ #[cfg(feature = \"mockbuild\")]
+ pub fn custom_api() {
+ }
+
+ #[cfg(not(feature = \"mockbuild\"))]
+ pub fn non_sysroot_api() {
+ core::custom_api();
+ }
+ ",
+ )
+ .add_dep(Dependency::new("rustc-std-workspace-core", "*").optional(true))
+ .feature("mockbuild", &["rustc-std-workspace-core"])
+ .publish();
+ Package::new("registry-dep-using-alloc", "1.0.0")
+ .file(
+ "src/lib.rs",
+ "
+ #![no_std]
+
+ extern crate alloc;
+
+ #[cfg(feature = \"mockbuild\")]
+ pub fn custom_api() {
+ }
+
+ #[cfg(not(feature = \"mockbuild\"))]
+ pub fn non_sysroot_api() {
+ core::custom_api();
+ alloc::custom_api();
+ }
+ ",
+ )
+ .add_dep(Dependency::new("rustc-std-workspace-core", "*").optional(true))
+ .add_dep(Dependency::new("rustc-std-workspace-alloc", "*").optional(true))
+ .feature(
+ "mockbuild",
+ &["rustc-std-workspace-core", "rustc-std-workspace-alloc"],
+ )
+ .publish();
+ Package::new("registry-dep-using-std", "1.0.0")
+ .file(
+ "src/lib.rs",
+ "
+ #[cfg(feature = \"mockbuild\")]
+ pub fn custom_api() {
+ }
+
+ #[cfg(not(feature = \"mockbuild\"))]
+ pub fn non_sysroot_api() {
+ std::custom_api();
+ }
+ ",
+ )
+ .add_dep(Dependency::new("rustc-std-workspace-std", "*").optional(true))
+ .feature("mockbuild", &["rustc-std-workspace-std"])
+ .publish();
+
+ let p = ProjectBuilder::new(paths::root().join("rustc-wrapper"))
+ .file(
+ "src/main.rs",
+ r#"
+ use std::process::Command;
+ use std::env;
+ fn main() {
+ let mut args = env::args().skip(1).collect::<Vec<_>>();
+
+ let is_sysroot_crate = env::var_os("RUSTC_BOOTSTRAP").is_some();
+ if is_sysroot_crate {
+ args.push("--sysroot".to_string());
+ args.push(env::var("REAL_SYSROOT").unwrap());
+ } else if args.iter().any(|arg| arg == "--target") {
+ // build-std target unit
+ args.push("--sysroot".to_string());
+ args.push("/path/to/nowhere".to_string());
+ } else {
+ // host unit, do not use sysroot
+ }
+
+ let ret = Command::new(&args[0]).args(&args[1..]).status().unwrap();
+ std::process::exit(ret.code().unwrap_or(1));
+ }
+ "#,
+ )
+ .build();
+ p.cargo("build").run();
+
+ Setup {
+ rustc_wrapper: p.bin("foo"),
+ real_sysroot: paths::sysroot(),
+ }
+}
+
+fn enable_build_std(e: &mut Execs, setup: &Setup) {
+ // First up, force Cargo to use our "mock sysroot" which mimics what
+ // libstd looks like upstream.
+ let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/testsuite/mock-std");
+ e.env("__CARGO_TESTS_ONLY_SRC_ROOT", &root);
+
+ e.masquerade_as_nightly_cargo(&["build-std"]);
+
+ // We do various shenanigans to ensure our "mock sysroot" actually links
+ // with the real sysroot, so we don't have to actually recompile std for
+ // each test. Perform all that logic here, namely:
+ //
+ // * RUSTC_WRAPPER - uses our shim executable built above to control rustc
+ // * REAL_SYSROOT - used by the shim executable to swap out to the real
+ // sysroot temporarily for some compilations
+ // * RUST{,DOC}FLAGS - an extra `-L` argument to ensure we can always load
+ // crates from the sysroot, but only indirectly through other crates.
+ e.env("RUSTC_WRAPPER", &setup.rustc_wrapper);
+ e.env("REAL_SYSROOT", &setup.real_sysroot);
+ let libdir = format!("/lib/rustlib/{}/lib", rustc_host());
+ e.env(
+ "RUSTFLAGS",
+ format!("-Ldependency={}{}", setup.real_sysroot, libdir),
+ );
+ e.env(
+ "RUSTDOCFLAGS",
+ format!("-Ldependency={}{}", setup.real_sysroot, libdir),
+ );
+}
+
+// Helper methods used in the tests below
+trait BuildStd: Sized {
+ fn build_std(&mut self, setup: &Setup) -> &mut Self;
+ fn build_std_arg(&mut self, setup: &Setup, arg: &str) -> &mut Self;
+ fn target_host(&mut self) -> &mut Self;
+}
+
+impl BuildStd for Execs {
+ fn build_std(&mut self, setup: &Setup) -> &mut Self {
+ enable_build_std(self, setup);
+ self.arg("-Zbuild-std");
+ self
+ }
+
+ fn build_std_arg(&mut self, setup: &Setup, arg: &str) -> &mut Self {
+ enable_build_std(self, setup);
+ self.arg(format!("-Zbuild-std={}", arg));
+ self
+ }
+
+ fn target_host(&mut self) -> &mut Self {
+ self.arg("--target").arg(rustc_host());
+ self
+ }
+}
+
+#[cargo_test(build_std_mock)]
+fn basic() {
+ let setup = setup();
+
+ let p = project()
+ .file(
+ "src/main.rs",
+ "
+ fn main() {
+ std::custom_api();
+ foo::f();
+ }
+
+ #[test]
+ fn smoke_bin_unit() {
+ std::custom_api();
+ foo::f();
+ }
+ ",
+ )
+ .file(
+ "src/lib.rs",
+ "
+ extern crate alloc;
+ extern crate proc_macro;
+
+ /// ```
+ /// foo::f();
+ /// ```
+ pub fn f() {
+ core::custom_api();
+ std::custom_api();
+ alloc::custom_api();
+ proc_macro::custom_api();
+ }
+
+ #[test]
+ fn smoke_lib_unit() {
+ std::custom_api();
+ f();
+ }
+ ",
+ )
+ .file(
+ "tests/smoke.rs",
+ "
+ #[test]
+ fn smoke_integration() {
+ std::custom_api();
+ foo::f();
+ }
+ ",
+ )
+ .build();
+
+ p.cargo("check -v").build_std(&setup).target_host().run();
+ p.cargo("build").build_std(&setup).target_host().run();
+ p.cargo("run").build_std(&setup).target_host().run();
+ p.cargo("test").build_std(&setup).target_host().run();
+}
+
+#[cargo_test(build_std_mock)]
+fn simple_lib_std() {
+ let setup = setup();
+
+ let p = project().file("src/lib.rs", "").build();
+ p.cargo("build -v")
+ .build_std(&setup)
+ .target_host()
+ .with_stderr_contains("[RUNNING] `[..]--crate-name std [..]`")
+ .run();
+ // Check freshness.
+ p.change_file("src/lib.rs", " ");
+ p.cargo("build -v")
+ .build_std(&setup)
+ .target_host()
+ .with_stderr_contains("[FRESH] std[..]")
+ .run();
+}
+
+#[cargo_test(build_std_mock)]
+fn simple_bin_std() {
+ let setup = setup();
+
+ let p = project().file("src/main.rs", "fn main() {}").build();
+ p.cargo("run -v").build_std(&setup).target_host().run();
+}
+
+#[cargo_test(build_std_mock)]
+fn lib_nostd() {
+ let setup = setup();
+
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ #![no_std]
+ pub fn foo() {
+ assert_eq!(u8::MIN, 0);
+ }
+ "#,
+ )
+ .build();
+ p.cargo("build -v --lib")
+ .build_std_arg(&setup, "core")
+ .target_host()
+ .with_stderr_does_not_contain("[..]libstd[..]")
+ .run();
+}
+
+#[cargo_test(build_std_mock)]
+fn check_core() {
+ let setup = setup();
+
+ let p = project()
+ .file("src/lib.rs", "#![no_std] fn unused_fn() {}")
+ .build();
+
+ p.cargo("check -v")
+ .build_std_arg(&setup, "core")
+ .target_host()
+ .with_stderr_contains("[WARNING] [..]unused_fn[..]")
+ .run();
+}
+
+#[cargo_test(build_std_mock)]
+fn depend_same_as_std() {
+ let setup = setup();
+
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn f() {
+ registry_dep_using_core::non_sysroot_api();
+ registry_dep_using_alloc::non_sysroot_api();
+ registry_dep_using_std::non_sysroot_api();
+ }
+ "#,
+ )
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [dependencies]
+ registry-dep-using-core = "1.0"
+ registry-dep-using-alloc = "1.0"
+ registry-dep-using-std = "1.0"
+ "#,
+ )
+ .build();
+
+ p.cargo("build -v").build_std(&setup).target_host().run();
+}
+
+#[cargo_test(build_std_mock)]
+fn test() {
+ let setup = setup();
+
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ #[cfg(test)]
+ mod tests {
+ #[test]
+ fn it_works() {
+ assert_eq!(2 + 2, 4);
+ }
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("test -v")
+ .build_std(&setup)
+ .target_host()
+ .with_stdout_contains("test tests::it_works ... ok")
+ .run();
+}
+
+#[cargo_test(build_std_mock)]
+fn target_proc_macro() {
+ let setup = setup();
+
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ extern crate proc_macro;
+ pub fn f() {
+ let _ts = proc_macro::TokenStream::new();
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build -v").build_std(&setup).target_host().run();
+}
+
+#[cargo_test(build_std_mock)]
+fn bench() {
+ let setup = setup();
+
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ #![feature(test)]
+ extern crate test;
+
+ #[bench]
+ fn b1(b: &mut test::Bencher) {
+ b.iter(|| ())
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("bench -v").build_std(&setup).target_host().run();
+}
+
+#[cargo_test(build_std_mock)]
+fn doc() {
+ let setup = setup();
+
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ /// Doc
+ pub fn f() -> Result<(), ()> {Ok(())}
+ "#,
+ )
+ .build();
+
+ p.cargo("doc -v").build_std(&setup).target_host().run();
+}
+
+#[cargo_test(build_std_mock)]
+fn check_std() {
+ let setup = setup();
+
+ let p = project()
+ .file(
+ "src/lib.rs",
+ "
+ extern crate core;
+ extern crate alloc;
+ extern crate proc_macro;
+ pub fn f() {}
+ ",
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "tests/t1.rs",
+ r#"
+ #[test]
+ fn t1() {
+ assert_eq!(1, 2);
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("check -v --all-targets")
+ .build_std(&setup)
+ .target_host()
+ .run();
+ p.cargo("check -v --all-targets --profile=test")
+ .build_std(&setup)
+ .target_host()
+ .run();
+}
+
+#[cargo_test(build_std_mock)]
+fn doctest() {
+ let setup = setup();
+
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ /// Doc
+ /// ```
+ /// std::custom_api();
+ /// ```
+ pub fn f() {}
+ "#,
+ )
+ .build();
+
+ p.cargo("test --doc -v -Zdoctest-xcompile")
+ .build_std(&setup)
+ .with_stdout_contains("test src/lib.rs - f [..] ... ok")
+ .target_host()
+ .run();
+}
+
+#[cargo_test(build_std_mock)]
+fn no_implicit_alloc() {
+ // Demonstrate that alloc is not implicitly in scope.
+ let setup = setup();
+
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn f() {
+ let _: Vec<i32> = alloc::vec::Vec::new();
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build -v")
+ .build_std(&setup)
+ .target_host()
+ .with_stderr_contains("[..]use of undeclared [..]`alloc`")
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test(build_std_mock)]
+fn macro_expanded_shadow() {
+ // This tests a bug caused by the previous use of `--extern` to directly
+ // load sysroot crates. This necessitated the switch to `--sysroot` to
+ // retain existing behavior. See
+ // https://github.com/rust-lang/wg-cargo-std-aware/issues/40 for more
+ // detail.
+ let setup = setup();
+
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ macro_rules! a {
+ () => (extern crate std as alloc;)
+ }
+ a!();
+ "#,
+ )
+ .build();
+
+ p.cargo("build -v").build_std(&setup).target_host().run();
+}
+
+#[cargo_test(build_std_mock)]
+fn ignores_incremental() {
+ // Incremental is not really needed for std, make sure it is disabled.
+ // Incremental also tends to have bugs that affect std libraries more than
+ // any other crate.
+ let setup = setup();
+
+ let p = project().file("src/lib.rs", "").build();
+ p.cargo("build")
+ .env("CARGO_INCREMENTAL", "1")
+ .build_std(&setup)
+ .target_host()
+ .run();
+ let incremental: Vec<_> = p
+ .glob(format!("target/{}/debug/incremental/*", rustc_host()))
+ .map(|e| e.unwrap())
+ .collect();
+ assert_eq!(incremental.len(), 1);
+ assert!(incremental[0]
+ .file_name()
+ .unwrap()
+ .to_str()
+ .unwrap()
+ .starts_with("foo-"));
+}
+
+#[cargo_test(build_std_mock)]
+fn cargo_config_injects_compiler_builtins() {
+ let setup = setup();
+
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ #![no_std]
+ pub fn foo() {
+ assert_eq!(u8::MIN, 0);
+ }
+ "#,
+ )
+ .file(
+ ".cargo/config.toml",
+ r#"
+ [unstable]
+ build-std = ['core']
+ "#,
+ )
+ .build();
+ let mut build = p.cargo("build -v --lib");
+ enable_build_std(&mut build, &setup);
+ build
+ .target_host()
+ .with_stderr_does_not_contain("[..]libstd[..]")
+ .run();
+}
+
+#[cargo_test(build_std_mock)]
+fn different_features() {
+ let setup = setup();
+
+ let p = project()
+ .file(
+ "src/lib.rs",
+ "
+ pub fn foo() {
+ std::conditional_function();
+ }
+ ",
+ )
+ .build();
+ p.cargo("build")
+ .build_std(&setup)
+ .arg("-Zbuild-std-features=feature1")
+ .target_host()
+ .run();
+}
+
+#[cargo_test(build_std_mock)]
+fn no_roots() {
+ // Checks for a bug where it would panic if there are no roots.
+ let setup = setup();
+
+ let p = project().file("tests/t1.rs", "").build();
+ p.cargo("build")
+ .build_std(&setup)
+ .target_host()
+ .with_stderr_contains("[FINISHED] [..]")
+ .run();
+}
+
+#[cargo_test(build_std_mock)]
+fn proc_macro_only() {
+ // Checks for a bug where it would panic if building a proc-macro only
+ let setup = setup();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "pm"
+ version = "0.1.0"
+
+ [lib]
+ proc-macro = true
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("build")
+ .build_std(&setup)
+ .target_host()
+ .with_stderr_contains("[FINISHED] [..]")
+ .run();
+}
+
+#[cargo_test(build_std_mock)]
+fn fetch() {
+ let setup = setup();
+
+ let p = project().file("src/main.rs", "fn main() {}").build();
+ p.cargo("fetch")
+ .build_std(&setup)
+ .target_host()
+ .with_stderr_contains("[DOWNLOADED] [..]")
+ .run();
+ p.cargo("build")
+ .build_std(&setup)
+ .target_host()
+ .with_stderr_does_not_contain("[DOWNLOADED] [..]")
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/test.rs b/src/tools/cargo/tests/testsuite/test.rs
new file mode 100644
index 000000000..add0a991f
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/test.rs
@@ -0,0 +1,4820 @@
+//! Tests for the `cargo test` command.
+
+use cargo_test_support::paths::CargoPathExt;
+use cargo_test_support::registry::Package;
+use cargo_test_support::{
+ basic_bin_manifest, basic_lib_manifest, basic_manifest, cargo_exe, project,
+};
+use cargo_test_support::{cross_compile, paths};
+use cargo_test_support::{rustc_host, rustc_host_env, sleep_ms};
+use std::fs;
+
+#[cargo_test]
+fn cargo_test_simple() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file(
+ "src/main.rs",
+ r#"
+ fn hello() -> &'static str {
+ "hello"
+ }
+
+ pub fn main() {
+ println!("{}", hello())
+ }
+
+ #[test]
+ fn test_hello() {
+ assert_eq!(hello(), "hello")
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build").run();
+ assert!(p.bin("foo").is_file());
+
+ p.process(&p.bin("foo")).with_stdout("hello\n").run();
+
+ p.cargo("test")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.5.0 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo-[..][EXE])",
+ )
+ .with_stdout_contains("test test_hello ... ok")
+ .run();
+}
+
+#[cargo_test]
+fn cargo_test_release() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { path = "bar" }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ extern crate bar;
+ pub fn foo() { bar::bar(); }
+
+ #[test]
+ fn test() { foo(); }
+ "#,
+ )
+ .file(
+ "tests/test.rs",
+ r#"
+ extern crate foo;
+
+ #[test]
+ fn test() { foo::foo(); }
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ p.cargo("test -v --release")
+ .with_stderr(
+ "\
+[COMPILING] bar v0.0.1 ([CWD]/bar)
+[RUNNING] [..] -C opt-level=3 [..]
+[COMPILING] foo v0.1.0 ([CWD])
+[RUNNING] [..] -C opt-level=3 [..]
+[RUNNING] [..] -C opt-level=3 [..]
+[RUNNING] [..] -C opt-level=3 [..]
+[FINISHED] release [optimized] target(s) in [..]
+[RUNNING] `[..]target/release/deps/foo-[..][EXE]`
+[RUNNING] `[..]target/release/deps/test-[..][EXE]`
+[DOCTEST] foo
+[RUNNING] `rustdoc [..]--test [..]lib.rs[..]`",
+ )
+ .with_stdout_contains_n("test test ... ok", 2)
+ .with_stdout_contains("running 0 tests")
+ .run();
+}
+
+#[cargo_test]
+fn cargo_test_overflow_checks() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+
+ [[bin]]
+ name = "foo"
+
+ [profile.release]
+ overflow-checks = true
+ "#,
+ )
+ .file(
+ "src/foo.rs",
+ r#"
+ use std::panic;
+ pub fn main() {
+ let r = panic::catch_unwind(|| {
+ [1, i32::MAX].iter().sum::<i32>();
+ });
+ assert!(r.is_err());
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build --release").run();
+ assert!(p.release_bin("foo").is_file());
+
+ p.process(&p.release_bin("foo")).with_stdout("").run();
+}
+
+#[cargo_test]
+fn cargo_test_quiet_with_harness() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [[test]]
+ name = "foo"
+ path = "src/foo.rs"
+ harness = true
+ "#,
+ )
+ .file(
+ "src/foo.rs",
+ r#"
+ fn main() {}
+ #[test] fn test_hello() {}
+ "#,
+ )
+ .build();
+
+ p.cargo("test -q")
+ .with_stdout(
+ "
+running 1 test
+.
+test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out[..]
+
+",
+ )
+ .with_stderr("")
+ .run();
+}
+
+#[cargo_test]
+fn cargo_test_quiet_no_harness() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [[bin]]
+ name = "foo"
+ test = false
+
+ [[test]]
+ name = "foo"
+ path = "src/main.rs"
+ harness = false
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {}
+ #[test] fn test_hello() {}
+ "#,
+ )
+ .build();
+
+ p.cargo("test -q").with_stdout("").with_stderr("").run();
+}
+
+#[cargo_test]
+fn cargo_doc_test_quiet() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ /// ```
+ /// let result = foo::add(2, 3);
+ /// assert_eq!(result, 5);
+ /// ```
+ pub fn add(a: i32, b: i32) -> i32 {
+ a + b
+ }
+
+ /// ```
+ /// let result = foo::div(10, 2);
+ /// assert_eq!(result, 5);
+ /// ```
+ ///
+ /// # Panics
+ ///
+ /// The function panics if the second argument is zero.
+ ///
+ /// ```rust,should_panic
+ /// // panics on division by zero
+ /// foo::div(10, 0);
+ /// ```
+ pub fn div(a: i32, b: i32) -> i32 {
+ if b == 0 {
+ panic!("Divide-by-zero error");
+ }
+
+ a / b
+ }
+
+ #[test] fn test_hello() {}
+ "#,
+ )
+ .build();
+
+ p.cargo("test -q")
+ .with_stdout(
+ "
+running 1 test
+.
+test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out[..]
+
+
+running 3 tests
+...
+test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out[..]
+
+",
+ )
+ .with_stderr("")
+ .run();
+}
+
+#[cargo_test]
+fn cargo_test_verbose() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {}
+ #[test] fn test_hello() {}
+ "#,
+ )
+ .build();
+
+ p.cargo("test -v hello")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.5.0 ([CWD])
+[RUNNING] `rustc [..] src/main.rs [..]`
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `[CWD]/target/debug/deps/foo-[..] hello`
+",
+ )
+ .with_stdout_contains("test test_hello ... ok")
+ .run();
+}
+
+#[cargo_test]
+fn many_similar_names() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ "
+ pub fn foo() {}
+ #[test] fn lib_test() {}
+ ",
+ )
+ .file(
+ "src/main.rs",
+ "
+ extern crate foo;
+ fn main() {}
+ #[test] fn bin_test() { foo::foo() }
+ ",
+ )
+ .file(
+ "tests/foo.rs",
+ r#"
+ extern crate foo;
+ #[test] fn test_test() { foo::foo() }
+ "#,
+ )
+ .build();
+
+ p.cargo("test -v")
+ .with_stdout_contains("test bin_test ... ok")
+ .with_stdout_contains("test lib_test ... ok")
+ .with_stdout_contains("test test_test ... ok")
+ .run();
+}
+
+#[cargo_test]
+fn cargo_test_failing_test_in_bin() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file(
+ "src/main.rs",
+ r#"
+ fn hello() -> &'static str {
+ "hello"
+ }
+
+ pub fn main() {
+ println!("{}", hello())
+ }
+
+ #[test]
+ fn test_hello() {
+ assert_eq!(hello(), "nope")
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build").run();
+ assert!(p.bin("foo").is_file());
+
+ p.process(&p.bin("foo")).with_stdout("hello\n").run();
+
+ p.cargo("test")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.5.0 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo-[..][EXE])
+[ERROR] test failed, to rerun pass `--bin foo`",
+ )
+ .with_stdout_contains(
+ "
+running 1 test
+test test_hello ... FAILED
+
+failures:
+
+---- test_hello stdout ----
+[..]thread '[..]' panicked at 'assertion failed:[..]",
+ )
+ .with_stdout_contains("[..]`(left == right)`[..]")
+ .with_stdout_contains("[..]left: `\"hello\"`,[..]")
+ .with_stdout_contains("[..]right: `\"nope\"`[..]")
+ .with_stdout_contains("[..]src/main.rs:12[..]")
+ .with_stdout_contains(
+ "\
+failures:
+ test_hello
+",
+ )
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn cargo_test_failing_test_in_test() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/main.rs", r#"pub fn main() { println!("hello"); }"#)
+ .file(
+ "tests/footest.rs",
+ "#[test] fn test_hello() { assert!(false) }",
+ )
+ .build();
+
+ p.cargo("build").run();
+ assert!(p.bin("foo").is_file());
+
+ p.process(&p.bin("foo")).with_stdout("hello\n").run();
+
+ p.cargo("test")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.5.0 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo-[..][EXE])
+[RUNNING] [..] (target/debug/deps/footest-[..][EXE])
+[ERROR] test failed, to rerun pass `--test footest`",
+ )
+ .with_stdout_contains("running 0 tests")
+ .with_stdout_contains(
+ "\
+running 1 test
+test test_hello ... FAILED
+
+failures:
+
+---- test_hello stdout ----
+[..]thread '[..]' panicked at 'assertion failed: false', \
+ tests/footest.rs:1[..]
+",
+ )
+ .with_stdout_contains(
+ "\
+failures:
+ test_hello
+",
+ )
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn cargo_test_failing_test_in_lib() {
+ let p = project()
+ .file("Cargo.toml", &basic_lib_manifest("foo"))
+ .file("src/lib.rs", "#[test] fn test_hello() { assert!(false) }")
+ .build();
+
+ p.cargo("test")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.5.0 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo-[..][EXE])
+[ERROR] test failed, to rerun pass `--lib`",
+ )
+ .with_stdout_contains(
+ "\
+test test_hello ... FAILED
+
+failures:
+
+---- test_hello stdout ----
+[..]thread '[..]' panicked at 'assertion failed: false', \
+ src/lib.rs:1[..]
+",
+ )
+ .with_stdout_contains(
+ "\
+failures:
+ test_hello
+",
+ )
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn test_with_lib_dep() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [[bin]]
+ name = "baz"
+ path = "src/main.rs"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ ///
+ /// ```rust
+ /// extern crate foo;
+ /// fn main() {
+ /// println!("{:?}", foo::foo());
+ /// }
+ /// ```
+ ///
+ pub fn foo(){}
+ #[test] fn lib_test() {}
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ "
+ #[allow(unused_extern_crates)]
+ extern crate foo;
+
+ fn main() {}
+
+ #[test]
+ fn bin_test() {}
+ ",
+ )
+ .build();
+
+ p.cargo("test")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo-[..][EXE])
+[RUNNING] [..] (target/debug/deps/baz-[..][EXE])
+[DOCTEST] foo",
+ )
+ .with_stdout_contains("test lib_test ... ok")
+ .with_stdout_contains("test bin_test ... ok")
+ .with_stdout_contains_n("test [..] ... ok", 3)
+ .run();
+}
+
+#[cargo_test]
+fn test_with_deep_lib_dep() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "../bar"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "
+ #[cfg(test)]
+ extern crate bar;
+ /// ```
+ /// foo::foo();
+ /// ```
+ pub fn foo() {}
+
+ #[test]
+ fn bar_test() {
+ bar::bar();
+ }
+ ",
+ )
+ .build();
+ let _p2 = project()
+ .at("bar")
+ .file("Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("src/lib.rs", "pub fn bar() {} #[test] fn foo_test() {}")
+ .build();
+
+ p.cargo("test")
+ .with_stderr(
+ "\
+[COMPILING] bar v0.0.1 ([..])
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target[..])
+[DOCTEST] foo",
+ )
+ .with_stdout_contains("test bar_test ... ok")
+ .with_stdout_contains_n("test [..] ... ok", 2)
+ .run();
+}
+
+#[cargo_test]
+fn external_test_explicit() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [[test]]
+ name = "test"
+ path = "src/test.rs"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn get_hello() -> &'static str { "Hello" }
+
+ #[test]
+ fn internal_test() {}
+ "#,
+ )
+ .file(
+ "src/test.rs",
+ r#"
+ extern crate foo;
+
+ #[test]
+ fn external_test() { assert_eq!(foo::get_hello(), "Hello") }
+ "#,
+ )
+ .build();
+
+ p.cargo("test")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo-[..][EXE])
+[RUNNING] [..] (target/debug/deps/test-[..][EXE])
+[DOCTEST] foo",
+ )
+ .with_stdout_contains("test internal_test ... ok")
+ .with_stdout_contains("test external_test ... ok")
+ .with_stdout_contains("running 0 tests")
+ .run();
+}
+
+#[cargo_test]
+fn external_test_named_test() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [[test]]
+ name = "test"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("tests/test.rs", "#[test] fn foo() {}")
+ .build();
+
+ p.cargo("test").run();
+}
+
+#[cargo_test]
+fn external_test_implicit() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn get_hello() -> &'static str { "Hello" }
+
+ #[test]
+ fn internal_test() {}
+ "#,
+ )
+ .file(
+ "tests/external.rs",
+ r#"
+ extern crate foo;
+
+ #[test]
+ fn external_test() { assert_eq!(foo::get_hello(), "Hello") }
+ "#,
+ )
+ .build();
+
+ p.cargo("test")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo-[..][EXE])
+[RUNNING] [..] (target/debug/deps/external-[..][EXE])
+[DOCTEST] foo",
+ )
+ .with_stdout_contains("test internal_test ... ok")
+ .with_stdout_contains("test external_test ... ok")
+ .with_stdout_contains("running 0 tests")
+ .run();
+}
+
+#[cargo_test]
+fn dont_run_examples() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(
+ "examples/dont-run-me-i-will-fail.rs",
+ r#"
+ fn main() { panic!("Examples should not be run by 'cargo test'"); }
+ "#,
+ )
+ .build();
+ p.cargo("test").run();
+}
+
+#[cargo_test]
+fn pass_through_escaped() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ "
+ /// ```rust
+ /// assert!(foo::foo());
+ /// ```
+ pub fn foo() -> bool {
+ true
+ }
+
+ /// ```rust
+ /// assert!(!foo::bar());
+ /// ```
+ pub fn bar() -> bool {
+ false
+ }
+
+ #[test] fn test_foo() {
+ assert!(foo());
+ }
+ #[test] fn test_bar() {
+ assert!(!bar());
+ }
+ ",
+ )
+ .build();
+
+ p.cargo("test -- bar")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo-[..][EXE])
+[DOCTEST] foo
+",
+ )
+ .with_stdout_contains("running 1 test")
+ .with_stdout_contains("test test_bar ... ok")
+ .run();
+
+ p.cargo("test -- foo")
+ .with_stderr(
+ "\
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo-[..][EXE])
+[DOCTEST] foo
+",
+ )
+ .with_stdout_contains("running 1 test")
+ .with_stdout_contains("test test_foo ... ok")
+ .run();
+
+ p.cargo("test -- foo bar")
+ .with_stderr(
+ "\
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo-[..][EXE])
+[DOCTEST] foo
+",
+ )
+ .with_stdout_contains("running 2 tests")
+ .with_stdout_contains("test test_foo ... ok")
+ .with_stdout_contains("test test_bar ... ok")
+ .run();
+}
+
+// Unlike `pass_through_escaped`, doctests won't run when using `testname` as an optimization
+#[cargo_test]
+fn pass_through_testname() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ "
+ /// ```rust
+ /// assert!(foo::foo());
+ /// ```
+ pub fn foo() -> bool {
+ true
+ }
+
+ /// ```rust
+ /// assert!(!foo::bar());
+ /// ```
+ pub fn bar() -> bool {
+ false
+ }
+
+ #[test] fn test_foo() {
+ assert!(foo());
+ }
+ #[test] fn test_bar() {
+ assert!(!bar());
+ }
+ ",
+ )
+ .build();
+
+ p.cargo("test bar")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo-[..][EXE])
+",
+ )
+ .with_stdout_contains("running 1 test")
+ .with_stdout_contains("test test_bar ... ok")
+ .run();
+
+ p.cargo("test foo")
+ .with_stderr(
+ "\
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo-[..][EXE])
+",
+ )
+ .with_stdout_contains("running 1 test")
+ .with_stdout_contains("test test_foo ... ok")
+ .run();
+
+ p.cargo("test foo -- bar")
+ .with_stderr(
+ "\
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo-[..][EXE])
+",
+ )
+ .with_stdout_contains("running 2 tests")
+ .with_stdout_contains("test test_foo ... ok")
+ .with_stdout_contains("test test_bar ... ok")
+ .run();
+}
+
+// Regression test for running cargo-test twice with
+// tests in an rlib
+#[cargo_test]
+fn cargo_test_twice() {
+ let p = project()
+ .file("Cargo.toml", &basic_lib_manifest("foo"))
+ .file(
+ "src/foo.rs",
+ r#"
+ #![crate_type = "rlib"]
+
+ #[test]
+ fn dummy_test() { }
+ "#,
+ )
+ .build();
+
+ for _ in 0..2 {
+ p.cargo("test").run();
+ }
+}
+
+#[cargo_test]
+fn lib_bin_same_name() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ name = "foo"
+ [[bin]]
+ name = "foo"
+ "#,
+ )
+ .file("src/lib.rs", "#[test] fn lib_test() {}")
+ .file(
+ "src/main.rs",
+ "
+ #[allow(unused_extern_crates)]
+ extern crate foo;
+
+ #[test]
+ fn bin_test() {}
+ ",
+ )
+ .build();
+
+ p.cargo("test")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo-[..][EXE])
+[RUNNING] [..] (target/debug/deps/foo-[..][EXE])
+[DOCTEST] foo",
+ )
+ .with_stdout_contains_n("test [..] ... ok", 2)
+ .with_stdout_contains("running 0 tests")
+ .run();
+}
+
+#[cargo_test]
+fn lib_with_standard_name() {
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("syntax", "0.0.1"))
+ .file(
+ "src/lib.rs",
+ "
+ /// ```
+ /// syntax::foo();
+ /// ```
+ pub fn foo() {}
+
+ #[test]
+ fn foo_test() {}
+ ",
+ )
+ .file(
+ "tests/test.rs",
+ "
+ extern crate syntax;
+
+ #[test]
+ fn test() { syntax::foo() }
+ ",
+ )
+ .build();
+
+ p.cargo("test")
+ .with_stderr(
+ "\
+[COMPILING] syntax v0.0.1 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/syntax-[..][EXE])
+[RUNNING] [..] (target/debug/deps/test-[..][EXE])
+[DOCTEST] syntax",
+ )
+ .with_stdout_contains("test foo_test ... ok")
+ .with_stdout_contains("test test ... ok")
+ .with_stdout_contains_n("test [..] ... ok", 3)
+ .run();
+}
+
+#[cargo_test]
+fn lib_with_standard_name2() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "syntax"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ name = "syntax"
+ test = false
+ doctest = false
+ "#,
+ )
+ .file("src/lib.rs", "pub fn foo() {}")
+ .file(
+ "src/main.rs",
+ "
+ extern crate syntax;
+
+ fn main() {}
+
+ #[test]
+ fn test() { syntax::foo() }
+ ",
+ )
+ .build();
+
+ p.cargo("test")
+ .with_stderr(
+ "\
+[COMPILING] syntax v0.0.1 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/syntax-[..][EXE])",
+ )
+ .with_stdout_contains("test test ... ok")
+ .run();
+}
+
+#[cargo_test]
+fn lib_without_name() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "syntax"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ test = false
+ doctest = false
+ "#,
+ )
+ .file("src/lib.rs", "pub fn foo() {}")
+ .file(
+ "src/main.rs",
+ "
+ extern crate syntax;
+
+ fn main() {}
+
+ #[test]
+ fn test() { syntax::foo() }
+ ",
+ )
+ .build();
+
+ p.cargo("test")
+ .with_stderr(
+ "\
+[COMPILING] syntax v0.0.1 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/syntax-[..][EXE])",
+ )
+ .with_stdout_contains("test test ... ok")
+ .run();
+}
+
+#[cargo_test]
+fn bin_without_name() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "syntax"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ test = false
+ doctest = false
+
+ [[bin]]
+ path = "src/main.rs"
+ "#,
+ )
+ .file("src/lib.rs", "pub fn foo() {}")
+ .file(
+ "src/main.rs",
+ "
+ extern crate syntax;
+
+ fn main() {}
+
+ #[test]
+ fn test() { syntax::foo() }
+ ",
+ )
+ .build();
+
+ p.cargo("test")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ binary target bin.name is required",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bench_without_name() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "syntax"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ test = false
+ doctest = false
+
+ [[bench]]
+ path = "src/bench.rs"
+ "#,
+ )
+ .file("src/lib.rs", "pub fn foo() {}")
+ .file(
+ "src/main.rs",
+ "
+ extern crate syntax;
+
+ fn main() {}
+
+ #[test]
+ fn test() { syntax::foo() }
+ ",
+ )
+ .file(
+ "src/bench.rs",
+ "
+ #![feature(test)]
+ extern crate syntax;
+ extern crate test;
+
+ #[bench]
+ fn external_bench(_b: &mut test::Bencher) {}
+ ",
+ )
+ .build();
+
+ p.cargo("test")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ benchmark target bench.name is required",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn test_without_name() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "syntax"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ test = false
+ doctest = false
+
+ [[test]]
+ path = "src/test.rs"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn foo() {}
+ pub fn get_hello() -> &'static str { "Hello" }
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ "
+ extern crate syntax;
+
+ fn main() {}
+
+ #[test]
+ fn test() { syntax::foo() }
+ ",
+ )
+ .file(
+ "src/test.rs",
+ r#"
+ extern crate syntax;
+
+ #[test]
+ fn external_test() { assert_eq!(syntax::get_hello(), "Hello") }
+ "#,
+ )
+ .build();
+
+ p.cargo("test")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ test target test.name is required",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn example_without_name() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "syntax"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ test = false
+ doctest = false
+
+ [[example]]
+ path = "examples/example.rs"
+ "#,
+ )
+ .file("src/lib.rs", "pub fn foo() {}")
+ .file(
+ "src/main.rs",
+ "
+ extern crate syntax;
+
+ fn main() {}
+
+ #[test]
+ fn test() { syntax::foo() }
+ ",
+ )
+ .file(
+ "examples/example.rs",
+ r#"
+ extern crate syntax;
+
+ fn main() {
+ println!("example1");
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("test")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to parse manifest at `[..]`
+
+Caused by:
+ example target example.name is required",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bin_there_for_integration() {
+ let p = project()
+ .file(
+ "src/main.rs",
+ "
+ fn main() { std::process::exit(101); }
+ #[test] fn main_test() {}
+ ",
+ )
+ .file(
+ "tests/foo.rs",
+ r#"
+ use std::process::Command;
+ #[test]
+ fn test_test() {
+ let status = Command::new("target/debug/foo").status().unwrap();
+ assert_eq!(status.code(), Some(101));
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("test -v")
+ .with_stdout_contains("test main_test ... ok")
+ .with_stdout_contains("test test_test ... ok")
+ .run();
+}
+
+#[cargo_test]
+fn test_dylib() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ name = "foo"
+ crate_type = ["dylib"]
+
+ [dependencies.bar]
+ path = "bar"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ extern crate bar as the_bar;
+
+ pub fn bar() { the_bar::baz(); }
+
+ #[test]
+ fn foo() { bar(); }
+ "#,
+ )
+ .file(
+ "tests/test.rs",
+ r#"
+ extern crate foo as the_foo;
+
+ #[test]
+ fn foo() { the_foo::bar(); }
+ "#,
+ )
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ name = "bar"
+ crate_type = ["dylib"]
+ "#,
+ )
+ .file("bar/src/lib.rs", "pub fn baz() {}")
+ .build();
+
+ p.cargo("test")
+ .with_stderr(
+ "\
+[COMPILING] bar v0.0.1 ([CWD]/bar)
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo-[..][EXE])
+[RUNNING] [..] (target/debug/deps/test-[..][EXE])",
+ )
+ .with_stdout_contains_n("test foo ... ok", 2)
+ .run();
+
+ p.root().move_into_the_past();
+ p.cargo("test")
+ .with_stderr(
+ "\
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo-[..][EXE])
+[RUNNING] [..] (target/debug/deps/test-[..][EXE])",
+ )
+ .with_stdout_contains_n("test foo ... ok", 2)
+ .run();
+}
+
+#[cargo_test]
+fn test_twice_with_build_cmd() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file("build.rs", "fn main() {}")
+ .file("src/lib.rs", "#[test] fn foo() {}")
+ .build();
+
+ p.cargo("test")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo-[..][EXE])
+[DOCTEST] foo",
+ )
+ .with_stdout_contains("test foo ... ok")
+ .with_stdout_contains("running 0 tests")
+ .run();
+
+ p.cargo("test")
+ .with_stderr(
+ "\
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo-[..][EXE])
+[DOCTEST] foo",
+ )
+ .with_stdout_contains("test foo ... ok")
+ .with_stdout_contains("running 0 tests")
+ .run();
+}
+
+#[cargo_test]
+fn test_then_build() {
+ let p = project().file("src/lib.rs", "#[test] fn foo() {}").build();
+
+ p.cargo("test")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo-[..][EXE])
+[DOCTEST] foo",
+ )
+ .with_stdout_contains("test foo ... ok")
+ .with_stdout_contains("running 0 tests")
+ .run();
+
+ p.cargo("build").with_stdout("").run();
+}
+
+#[cargo_test]
+fn test_no_run() {
+ let p = project()
+ .file("src/lib.rs", "#[test] fn foo() { panic!() }")
+ .build();
+
+ p.cargo("test --no-run")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[EXECUTABLE] unittests src/lib.rs (target/debug/deps/foo-[..][EXE])
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn test_no_run_emit_json() {
+ let p = project()
+ .file("src/lib.rs", "#[test] fn foo() { panic!() }")
+ .build();
+
+ p.cargo("test --no-run --message-format json")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn test_run_specific_bin_target() {
+ let prj = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [[bin]]
+ name="bin1"
+ path="src/bin1.rs"
+
+ [[bin]]
+ name="bin2"
+ path="src/bin2.rs"
+ "#,
+ )
+ .file("src/bin1.rs", "#[test] fn test1() { }")
+ .file("src/bin2.rs", "#[test] fn test2() { }")
+ .build();
+
+ prj.cargo("test --bin bin2")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/bin2-[..][EXE])",
+ )
+ .with_stdout_contains("test test2 ... ok")
+ .run();
+}
+
+#[cargo_test]
+fn test_run_implicit_bin_target() {
+ let prj = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [[bin]]
+ name="mybin"
+ path="src/mybin.rs"
+ "#,
+ )
+ .file(
+ "src/mybin.rs",
+ "#[test] fn test_in_bin() { }
+ fn main() { panic!(\"Don't execute me!\"); }",
+ )
+ .file("tests/mytest.rs", "#[test] fn test_in_test() { }")
+ .file("benches/mybench.rs", "#[test] fn test_in_bench() { }")
+ .file(
+ "examples/myexm.rs",
+ "#[test] fn test_in_exm() { }
+ fn main() { panic!(\"Don't execute me!\"); }",
+ )
+ .build();
+
+ prj.cargo("test --bins")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/mybin-[..][EXE])",
+ )
+ .with_stdout_contains("test test_in_bin ... ok")
+ .run();
+}
+
+#[cargo_test]
+fn test_run_specific_test_target() {
+ let prj = project()
+ .file("src/bin/a.rs", "fn main() { }")
+ .file("src/bin/b.rs", "#[test] fn test_b() { } fn main() { }")
+ .file("tests/a.rs", "#[test] fn test_a() { }")
+ .file("tests/b.rs", "#[test] fn test_b() { }")
+ .build();
+
+ prj.cargo("test --test b")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/b-[..][EXE])",
+ )
+ .with_stdout_contains("test test_b ... ok")
+ .run();
+}
+
+#[cargo_test]
+fn test_run_implicit_test_target() {
+ let prj = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [[bin]]
+ name="mybin"
+ path="src/mybin.rs"
+ "#,
+ )
+ .file(
+ "src/mybin.rs",
+ "#[test] fn test_in_bin() { }
+ fn main() { panic!(\"Don't execute me!\"); }",
+ )
+ .file("tests/mytest.rs", "#[test] fn test_in_test() { }")
+ .file("benches/mybench.rs", "#[test] fn test_in_bench() { }")
+ .file(
+ "examples/myexm.rs",
+ "fn main() { compile_error!(\"Don't build me!\"); }",
+ )
+ .build();
+
+ prj.cargo("test --tests")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/mybin-[..][EXE])
+[RUNNING] [..] (target/debug/deps/mytest-[..][EXE])",
+ )
+ .with_stdout_contains("test test_in_test ... ok")
+ .run();
+}
+
+#[cargo_test]
+fn test_run_implicit_bench_target() {
+ let prj = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [[bin]]
+ name="mybin"
+ path="src/mybin.rs"
+ "#,
+ )
+ .file(
+ "src/mybin.rs",
+ "#[test] fn test_in_bin() { }
+ fn main() { panic!(\"Don't execute me!\"); }",
+ )
+ .file("tests/mytest.rs", "#[test] fn test_in_test() { }")
+ .file("benches/mybench.rs", "#[test] fn test_in_bench() { }")
+ .file(
+ "examples/myexm.rs",
+ "fn main() { compile_error!(\"Don't build me!\"); }",
+ )
+ .build();
+
+ prj.cargo("test --benches")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/mybin-[..][EXE])
+[RUNNING] [..] (target/debug/deps/mybench-[..][EXE])",
+ )
+ .with_stdout_contains("test test_in_bench ... ok")
+ .run();
+}
+
+#[cargo_test]
+fn test_run_implicit_example_target() {
+ let prj = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [[bin]]
+ name = "mybin"
+ path = "src/mybin.rs"
+
+ [[example]]
+ name = "myexm1"
+
+ [[example]]
+ name = "myexm2"
+ test = true
+ "#,
+ )
+ .file(
+ "src/mybin.rs",
+ "#[test] fn test_in_bin() { }
+ fn main() { panic!(\"Don't execute me!\"); }",
+ )
+ .file("tests/mytest.rs", "#[test] fn test_in_test() { }")
+ .file("benches/mybench.rs", "#[test] fn test_in_bench() { }")
+ .file(
+ "examples/myexm1.rs",
+ "#[test] fn test_in_exm() { }
+ fn main() { panic!(\"Don't execute me!\"); }",
+ )
+ .file(
+ "examples/myexm2.rs",
+ "#[test] fn test_in_exm() { }
+ fn main() { panic!(\"Don't execute me!\"); }",
+ )
+ .build();
+
+ // Compiles myexm1 as normal, but does not run it.
+ prj.cargo("test -v")
+ .with_stderr_contains("[RUNNING] `rustc [..]myexm1.rs [..]--crate-type bin[..]")
+ .with_stderr_contains("[RUNNING] `rustc [..]myexm2.rs [..]--test[..]")
+ .with_stderr_does_not_contain("[RUNNING] [..]myexm1-[..]")
+ .with_stderr_contains("[RUNNING] [..]target/debug/examples/myexm2-[..]")
+ .run();
+
+ // Only tests myexm2.
+ prj.cargo("test --tests")
+ .with_stderr_does_not_contain("[RUNNING] [..]myexm1-[..]")
+ .with_stderr_contains("[RUNNING] [..]target/debug/examples/myexm2-[..]")
+ .run();
+
+ // Tests all examples.
+ prj.cargo("test --examples")
+ .with_stderr_contains("[RUNNING] [..]target/debug/examples/myexm1-[..]")
+ .with_stderr_contains("[RUNNING] [..]target/debug/examples/myexm2-[..]")
+ .run();
+
+ // Test an example, even without `test` set.
+ prj.cargo("test --example myexm1")
+ .with_stderr_contains("[RUNNING] [..]target/debug/examples/myexm1-[..]")
+ .run();
+
+ // Tests all examples.
+ prj.cargo("test --all-targets")
+ .with_stderr_contains("[RUNNING] [..]target/debug/examples/myexm1-[..]")
+ .with_stderr_contains("[RUNNING] [..]target/debug/examples/myexm2-[..]")
+ .run();
+}
+
+#[cargo_test]
+fn test_filtered_excludes_compiling_examples() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [[bin]]
+ name = "mybin"
+ test = false
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "#[cfg(test)] mod tests { #[test] fn test_in_lib() { } }",
+ )
+ .file(
+ "src/bin/mybin.rs",
+ "#[test] fn test_in_bin() { }
+ fn main() { panic!(\"Don't execute me!\"); }",
+ )
+ .file("tests/mytest.rs", "#[test] fn test_in_test() { }")
+ .file(
+ "benches/mybench.rs",
+ "#[test] fn test_in_bench() { assert!(false) }",
+ )
+ .file(
+ "examples/myexm1.rs",
+ "#[test] fn test_in_exm() { assert!(false) }
+ fn main() { panic!(\"Don't execute me!\"); }",
+ )
+ .build();
+
+ p.cargo("test -v test_in_")
+ .with_stdout(
+ "
+running 1 test
+test tests::test_in_lib ... ok
+
+test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out[..]
+
+
+running 1 test
+test test_in_test ... ok
+
+test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out[..]
+
+",
+ )
+ .with_stderr_unordered(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name foo src/lib.rs [..] --crate-type lib [..]`
+[RUNNING] `rustc --crate-name foo src/lib.rs [..] --test [..]`
+[RUNNING] `rustc --crate-name mybin src/bin/mybin.rs [..] --crate-type bin [..]`
+[RUNNING] `rustc --crate-name mytest tests/mytest.rs [..] --test [..]`
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `[CWD]/target/debug/deps/foo-[..] test_in_`
+[RUNNING] `[CWD]/target/debug/deps/mytest-[..] test_in_`
+",
+ )
+ .with_stderr_does_not_contain("[RUNNING][..]rustc[..]myexm1[..]")
+ .with_stderr_does_not_contain("[RUNNING][..]deps/mybin-[..] test_in_")
+ .run();
+}
+
+#[cargo_test]
+fn test_no_harness() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [[bin]]
+ name = "foo"
+ test = false
+
+ [[test]]
+ name = "bar"
+ path = "foo.rs"
+ harness = false
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("foo.rs", "fn main() {}")
+ .build();
+
+ p.cargo("test -- --nocapture")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/bar-[..][EXE])
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn selective_testing() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.d1]
+ path = "d1"
+ [dependencies.d2]
+ path = "d2"
+
+ [lib]
+ name = "foo"
+ doctest = false
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "d1/Cargo.toml",
+ r#"
+ [package]
+ name = "d1"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ name = "d1"
+ doctest = false
+ "#,
+ )
+ .file("d1/src/lib.rs", "")
+ .file(
+ "d1/src/main.rs",
+ "#[allow(unused_extern_crates)] extern crate d1; fn main() {}",
+ )
+ .file(
+ "d2/Cargo.toml",
+ r#"
+ [package]
+ name = "d2"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ name = "d2"
+ doctest = false
+ "#,
+ )
+ .file("d2/src/lib.rs", "")
+ .file(
+ "d2/src/main.rs",
+ "#[allow(unused_extern_crates)] extern crate d2; fn main() {}",
+ );
+ let p = p.build();
+
+ println!("d1");
+ p.cargo("test -p d1")
+ .with_stderr(
+ "\
+[COMPILING] d1 v0.0.1 ([CWD]/d1)
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/d1-[..][EXE])
+[RUNNING] [..] (target/debug/deps/d1-[..][EXE])",
+ )
+ .with_stdout_contains_n("running 0 tests", 2)
+ .run();
+
+ println!("d2");
+ p.cargo("test -p d2")
+ .with_stderr(
+ "\
+[COMPILING] d2 v0.0.1 ([CWD]/d2)
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/d2-[..][EXE])
+[RUNNING] [..] (target/debug/deps/d2-[..][EXE])",
+ )
+ .with_stdout_contains_n("running 0 tests", 2)
+ .run();
+
+ println!("whole");
+ p.cargo("test")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo-[..][EXE])",
+ )
+ .with_stdout_contains("running 0 tests")
+ .run();
+}
+
+#[cargo_test]
+fn almost_cyclic_but_not_quite() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dev-dependencies.b]
+ path = "b"
+ [dev-dependencies.c]
+ path = "c"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ #[cfg(test)] extern crate b;
+ #[cfg(test)] extern crate c;
+ "#,
+ )
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.foo]
+ path = ".."
+ "#,
+ )
+ .file(
+ "b/src/lib.rs",
+ r#"
+ #[allow(unused_extern_crates)]
+ extern crate foo;
+ "#,
+ )
+ .file("c/Cargo.toml", &basic_manifest("c", "0.0.1"))
+ .file("c/src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+ p.cargo("test").run();
+}
+
+#[cargo_test]
+fn build_then_selective_test() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.b]
+ path = "b"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "#[allow(unused_extern_crates)] extern crate b;",
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ #[allow(unused_extern_crates)]
+ extern crate b;
+ #[allow(unused_extern_crates)]
+ extern crate foo;
+ fn main() {}
+ "#,
+ )
+ .file("b/Cargo.toml", &basic_manifest("b", "0.0.1"))
+ .file("b/src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+ p.root().move_into_the_past();
+ p.cargo("test -p b").run();
+}
+
+#[cargo_test]
+fn example_dev_dep() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dev-dependencies.bar]
+ path = "bar"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("examples/e1.rs", "extern crate bar; fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file(
+ "bar/src/lib.rs",
+ r#"
+ // make sure this file takes awhile to compile
+ macro_rules! f0( () => (1) );
+ macro_rules! f1( () => ({(f0!()) + (f0!())}) );
+ macro_rules! f2( () => ({(f1!()) + (f1!())}) );
+ macro_rules! f3( () => ({(f2!()) + (f2!())}) );
+ macro_rules! f4( () => ({(f3!()) + (f3!())}) );
+ macro_rules! f5( () => ({(f4!()) + (f4!())}) );
+ macro_rules! f6( () => ({(f5!()) + (f5!())}) );
+ macro_rules! f7( () => ({(f6!()) + (f6!())}) );
+ macro_rules! f8( () => ({(f7!()) + (f7!())}) );
+ pub fn bar() {
+ f8!();
+ }
+ "#,
+ )
+ .build();
+ p.cargo("test").run();
+ p.cargo("run --example e1 --release -v").run();
+}
+
+#[cargo_test]
+fn selective_testing_with_docs() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.d1]
+ path = "d1"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ /// ```
+ /// not valid rust
+ /// ```
+ pub fn foo() {}
+ "#,
+ )
+ .file(
+ "d1/Cargo.toml",
+ r#"
+ [package]
+ name = "d1"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ name = "d1"
+ path = "d1.rs"
+ "#,
+ )
+ .file("d1/d1.rs", "");
+ let p = p.build();
+
+ p.cargo("test -p d1")
+ .with_stderr(
+ "\
+[COMPILING] d1 v0.0.1 ([CWD]/d1)
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/d1[..][EXE])
+[DOCTEST] d1",
+ )
+ .with_stdout_contains_n("running 0 tests", 2)
+ .run();
+}
+
+#[cargo_test]
+fn example_bin_same_name() {
+ let p = project()
+ .file("src/bin/foo.rs", r#"fn main() { println!("bin"); }"#)
+ .file("examples/foo.rs", r#"fn main() { println!("example"); }"#)
+ .build();
+
+ p.cargo("test --no-run -v")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc [..]`
+[RUNNING] `rustc [..]`
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[EXECUTABLE] `[..]/target/debug/deps/foo-[..][EXE]`
+",
+ )
+ .run();
+
+ assert!(!p.bin("foo").is_file());
+ assert!(p.bin("examples/foo").is_file());
+
+ p.process(&p.bin("examples/foo"))
+ .with_stdout("example\n")
+ .run();
+
+ p.cargo("run")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..]",
+ )
+ .with_stdout("bin")
+ .run();
+ assert!(p.bin("foo").is_file());
+}
+
+#[cargo_test]
+fn test_with_example_twice() {
+ let p = project()
+ .file("src/bin/foo.rs", r#"fn main() { println!("bin"); }"#)
+ .file("examples/foo.rs", r#"fn main() { println!("example"); }"#)
+ .build();
+
+ println!("first");
+ p.cargo("test -v").run();
+ assert!(p.bin("examples/foo").is_file());
+ println!("second");
+ p.cargo("test -v").run();
+ assert!(p.bin("examples/foo").is_file());
+}
+
+#[cargo_test]
+fn example_with_dev_dep() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ name = "foo"
+ test = false
+ doctest = false
+
+ [dev-dependencies.a]
+ path = "a"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "examples/ex.rs",
+ "#[allow(unused_extern_crates)] extern crate a; fn main() {}",
+ )
+ .file("a/Cargo.toml", &basic_manifest("a", "0.0.1"))
+ .file("a/src/lib.rs", "")
+ .build();
+
+ p.cargo("test -v")
+ .with_stderr(
+ "\
+[..]
+[..]
+[..]
+[..]
+[RUNNING] `rustc --crate-name ex [..] --extern a=[..]`
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bin_is_preserved() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build -v").run();
+ assert!(p.bin("foo").is_file());
+
+ println!("test");
+ p.cargo("test -v").run();
+ assert!(p.bin("foo").is_file());
+}
+
+#[cargo_test]
+fn bad_example() {
+ let p = project().file("src/lib.rs", "");
+ let p = p.build();
+
+ p.cargo("run --example foo")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] no example target named `foo`.
+
+",
+ )
+ .run();
+ p.cargo("run --bin foo")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] no bin target named `foo`.
+
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn doctest_feature() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ [features]
+ bar = []
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ /// ```rust
+ /// assert_eq!(foo::foo(), 1);
+ /// ```
+ #[cfg(feature = "bar")]
+ pub fn foo() -> i32 { 1 }
+ "#,
+ )
+ .build();
+
+ p.cargo("test --features bar")
+ .with_stderr(
+ "\
+[COMPILING] foo [..]
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo[..][EXE])
+[DOCTEST] foo",
+ )
+ .with_stdout_contains("running 0 tests")
+ .with_stdout_contains("test [..] ... ok")
+ .run();
+}
+
+#[cargo_test]
+fn dashes_to_underscores() {
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo-bar", "0.0.1"))
+ .file(
+ "src/lib.rs",
+ r#"
+ /// ```
+ /// assert_eq!(foo_bar::foo(), 1);
+ /// ```
+ pub fn foo() -> i32 { 1 }
+ "#,
+ )
+ .build();
+
+ p.cargo("test -v").run();
+}
+
+#[cargo_test]
+fn doctest_dev_dep() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dev-dependencies]
+ b = { path = "b" }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ /// ```
+ /// extern crate b;
+ /// ```
+ pub fn foo() {}
+ "#,
+ )
+ .file("b/Cargo.toml", &basic_manifest("b", "0.0.1"))
+ .file("b/src/lib.rs", "")
+ .build();
+
+ p.cargo("test -v").run();
+}
+
+#[cargo_test]
+fn filter_no_doc_tests() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ /// ```
+ /// extern crate b;
+ /// ```
+ pub fn foo() {}
+ "#,
+ )
+ .file("tests/foo.rs", "")
+ .build();
+
+ p.cargo("test --test=foo")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo[..][EXE])",
+ )
+ .with_stdout_contains("running 0 tests")
+ .run();
+}
+
+#[cargo_test]
+fn dylib_doctest() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ name = "foo"
+ crate-type = ["rlib", "dylib"]
+ test = false
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ /// ```
+ /// foo::foo();
+ /// ```
+ pub fn foo() {}
+ "#,
+ )
+ .build();
+
+ p.cargo("test")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[DOCTEST] foo",
+ )
+ .with_stdout_contains("test [..] ... ok")
+ .run();
+}
+
+#[cargo_test]
+fn dylib_doctest2() {
+ // Can't doc-test dylibs, as they're statically linked together.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ name = "foo"
+ crate-type = ["dylib"]
+ test = false
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ /// ```
+ /// foo::foo();
+ /// ```
+ pub fn foo() {}
+ "#,
+ )
+ .build();
+
+ p.cargo("test").with_stdout("").run();
+}
+
+#[cargo_test]
+fn cyclic_dev_dep_doc_test() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dev-dependencies]
+ bar = { path = "bar" }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ //! ```
+ //! extern crate bar;
+ //! ```
+ "#,
+ )
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ foo = { path = ".." }
+ "#,
+ )
+ .file(
+ "bar/src/lib.rs",
+ r#"
+ #[allow(unused_extern_crates)]
+ extern crate foo;
+ "#,
+ )
+ .build();
+ p.cargo("test")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[COMPILING] bar v0.0.1 ([..])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo[..][EXE])
+[DOCTEST] foo",
+ )
+ .with_stdout_contains("running 0 tests")
+ .with_stdout_contains("test [..] ... ok")
+ .run();
+}
+
+#[cargo_test]
+fn dev_dep_with_build_script() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dev-dependencies]
+ bar = { path = "bar" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("examples/foo.rs", "fn main() {}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .file("bar/build.rs", "fn main() {}")
+ .build();
+ p.cargo("test").run();
+}
+
+#[cargo_test]
+fn no_fail_fast() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn add_one(x: i32) -> i32{
+ x + 1
+ }
+
+ /// ```rust
+ /// use foo::sub_one;
+ /// assert_eq!(sub_one(101), 100);
+ /// ```
+ pub fn sub_one(x: i32) -> i32{
+ x - 1
+ }
+ "#,
+ )
+ .file(
+ "tests/test_add_one.rs",
+ r#"
+ extern crate foo;
+ use foo::*;
+
+ #[test]
+ fn add_one_test() {
+ assert_eq!(add_one(1), 2);
+ }
+
+ #[test]
+ fn fail_add_one_test() {
+ assert_eq!(add_one(1), 1);
+ }
+ "#,
+ )
+ .file(
+ "tests/test_sub_one.rs",
+ r#"
+ extern crate foo;
+ use foo::*;
+
+ #[test]
+ fn sub_one_test() {
+ assert_eq!(sub_one(1), 0);
+ }
+ "#,
+ )
+ .build();
+ p.cargo("test --no-fail-fast")
+ .with_status(101)
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 [..]
+[FINISHED] test [..]
+[RUNNING] unittests src/lib.rs (target/debug/deps/foo[..])
+[RUNNING] tests/test_add_one.rs (target/debug/deps/test_add_one[..])
+[ERROR] test failed, to rerun pass `--test test_add_one`
+[RUNNING] tests/test_sub_one.rs (target/debug/deps/test_sub_one[..])
+[DOCTEST] foo
+[ERROR] 1 target failed:
+ `--test test_add_one`
+",
+ )
+ .with_stdout_contains("running 0 tests")
+ .with_stdout_contains("test result: FAILED. [..]")
+ .with_stdout_contains("test sub_one_test ... ok")
+ .with_stdout_contains_n("test [..] ... ok", 3)
+ .run();
+}
+
+#[cargo_test]
+fn test_multiple_packages() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.d1]
+ path = "d1"
+ [dependencies.d2]
+ path = "d2"
+
+ [lib]
+ name = "foo"
+ doctest = false
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "d1/Cargo.toml",
+ r#"
+ [package]
+ name = "d1"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ name = "d1"
+ doctest = false
+ "#,
+ )
+ .file("d1/src/lib.rs", "")
+ .file(
+ "d2/Cargo.toml",
+ r#"
+ [package]
+ name = "d2"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ name = "d2"
+ doctest = false
+ "#,
+ )
+ .file("d2/src/lib.rs", "");
+ let p = p.build();
+
+ p.cargo("test -p d1 -p d2")
+ .with_stderr_contains("[RUNNING] [..] (target/debug/deps/d1-[..][EXE])")
+ .with_stderr_contains("[RUNNING] [..] (target/debug/deps/d2-[..][EXE])")
+ .with_stdout_contains_n("running 0 tests", 2)
+ .run();
+}
+
+#[cargo_test]
+fn bin_does_not_rebuild_tests() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file("src/main.rs", "fn main() {}")
+ .file("tests/foo.rs", "");
+ let p = p.build();
+
+ p.cargo("test -v").run();
+
+ sleep_ms(1000);
+ fs::write(p.root().join("src/main.rs"), "fn main() { 3; }").unwrap();
+
+ p.cargo("test -v --no-run")
+ .with_stderr(
+ "\
+[DIRTY] foo v0.0.1 ([..]): the file `src/main.rs` has changed ([..])
+[COMPILING] foo v0.0.1 ([..])
+[RUNNING] `rustc [..] src/main.rs [..]`
+[RUNNING] `rustc [..] src/main.rs [..]`
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[EXECUTABLE] `[..]/target/debug/deps/foo-[..][EXE]`
+[EXECUTABLE] `[..]/target/debug/deps/foo-[..][EXE]`
+[EXECUTABLE] `[..]/target/debug/deps/foo-[..][EXE]`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn selective_test_wonky_profile() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [profile.release]
+ opt-level = 2
+
+ [dependencies]
+ a = { path = "a" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("a/Cargo.toml", &basic_manifest("a", "0.0.1"))
+ .file("a/src/lib.rs", "");
+ let p = p.build();
+
+ p.cargo("test -v --no-run --release -p foo -p a").run();
+}
+
+#[cargo_test]
+fn selective_test_optional_dep() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ a = { path = "a", optional = true }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("a/Cargo.toml", &basic_manifest("a", "0.0.1"))
+ .file("a/src/lib.rs", "");
+ let p = p.build();
+
+ p.cargo("test -v --no-run --features a -p a")
+ .with_stderr(
+ "\
+[COMPILING] a v0.0.1 ([..])
+[RUNNING] `rustc [..] a/src/lib.rs [..]`
+[RUNNING] `rustc [..] a/src/lib.rs [..]`
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[EXECUTABLE] `[..]/target/debug/deps/a-[..][EXE]`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn only_test_docs() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ #[test]
+ fn foo() {
+ let a: u32 = "hello";
+ }
+
+ /// ```
+ /// foo::bar();
+ /// println!("ok");
+ /// ```
+ pub fn bar() {
+ }
+ "#,
+ )
+ .file("tests/foo.rs", "this is not rust");
+ let p = p.build();
+
+ p.cargo("test --doc")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[DOCTEST] foo",
+ )
+ .with_stdout_contains("test [..] ... ok")
+ .run();
+}
+
+#[cargo_test]
+fn test_panic_abort_with_dep() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = { path = "bar" }
+
+ [profile.dev]
+ panic = 'abort'
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ extern crate bar;
+
+ #[test]
+ fn foo() {}
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "")
+ .build();
+ p.cargo("test -v").run();
+}
+
+#[cargo_test]
+fn cfg_test_even_with_no_harness() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [lib]
+ harness = false
+ doctest = false
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"#[cfg(test)] fn main() { println!("hello!"); }"#,
+ )
+ .build();
+ p.cargo("test -v")
+ .with_stdout("hello!\n")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[RUNNING] `rustc [..]`
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `[..]`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn panic_abort_multiple() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ a = { path = "a" }
+
+ [profile.release]
+ panic = 'abort'
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "#[allow(unused_extern_crates)] extern crate a;",
+ )
+ .file("a/Cargo.toml", &basic_manifest("a", "0.0.1"))
+ .file("a/src/lib.rs", "")
+ .build();
+ p.cargo("test --release -v -p foo -p a").run();
+}
+
+#[cargo_test]
+fn pass_correct_cfgs_flags_to_rustdoc() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [features]
+ default = ["feature_a/default"]
+ nightly = ["feature_a/nightly"]
+
+ [dependencies.feature_a]
+ path = "libs/feature_a"
+ default-features = false
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ #[cfg(test)]
+ mod tests {
+ #[test]
+ fn it_works() {
+ assert!(true);
+ }
+ }
+ "#,
+ )
+ .file(
+ "libs/feature_a/Cargo.toml",
+ r#"
+ [package]
+ name = "feature_a"
+ version = "0.1.0"
+ authors = []
+
+ [features]
+ default = ["mock_serde_codegen"]
+ nightly = ["mock_serde_derive"]
+
+ [dependencies]
+ mock_serde_derive = { path = "../mock_serde_derive", optional = true }
+
+ [build-dependencies]
+ mock_serde_codegen = { path = "../mock_serde_codegen", optional = true }
+ "#,
+ )
+ .file(
+ "libs/feature_a/src/lib.rs",
+ r#"
+ #[cfg(feature = "mock_serde_derive")]
+ const MSG: &'static str = "This is safe";
+
+ #[cfg(feature = "mock_serde_codegen")]
+ const MSG: &'static str = "This is risky";
+
+ pub fn get() -> &'static str {
+ MSG
+ }
+ "#,
+ )
+ .file(
+ "libs/mock_serde_derive/Cargo.toml",
+ &basic_manifest("mock_serde_derive", "0.1.0"),
+ )
+ .file("libs/mock_serde_derive/src/lib.rs", "")
+ .file(
+ "libs/mock_serde_codegen/Cargo.toml",
+ &basic_manifest("mock_serde_codegen", "0.1.0"),
+ )
+ .file("libs/mock_serde_codegen/src/lib.rs", "");
+ let p = p.build();
+
+ p.cargo("test --package feature_a --verbose")
+ .with_stderr_contains(
+ "\
+[DOCTEST] feature_a
+[RUNNING] `rustdoc [..]--test [..]mock_serde_codegen[..]`",
+ )
+ .run();
+
+ p.cargo("test --verbose")
+ .with_stderr_contains(
+ "\
+[DOCTEST] foo
+[RUNNING] `rustdoc [..]--test [..]feature_a[..]`",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn test_release_ignore_panic() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ a = { path = "a" }
+
+ [profile.test]
+ panic = 'abort'
+ [profile.release]
+ panic = 'abort'
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "#[allow(unused_extern_crates)] extern crate a;",
+ )
+ .file("a/Cargo.toml", &basic_manifest("a", "0.0.1"))
+ .file("a/src/lib.rs", "");
+ let p = p.build();
+ println!("test");
+ p.cargo("test -v").run();
+ println!("bench");
+ p.cargo("bench -v").run();
+}
+
+#[cargo_test]
+fn test_many_with_features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ a = { path = "a" }
+
+ [features]
+ foo = []
+
+ [workspace]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("a/Cargo.toml", &basic_manifest("a", "0.0.1"))
+ .file("a/src/lib.rs", "")
+ .build();
+
+ p.cargo("test -v -p a -p foo --features foo").run();
+}
+
+#[cargo_test]
+fn test_all_workspace() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { path = "bar" }
+
+ [workspace]
+ "#,
+ )
+ .file("src/main.rs", "#[test] fn foo_test() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "#[test] fn bar_test() {}")
+ .build();
+
+ p.cargo("test --workspace")
+ .with_stdout_contains("test foo_test ... ok")
+ .with_stdout_contains("test bar_test ... ok")
+ .run();
+}
+
+#[cargo_test]
+fn test_all_exclude() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [workspace]
+ members = ["bar", "baz"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "#[test] pub fn bar() {}")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("baz/src/lib.rs", "#[test] pub fn baz() { assert!(false); }")
+ .build();
+
+ p.cargo("test --workspace --exclude baz")
+ .with_stdout_contains(
+ "running 1 test
+test bar ... ok",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn test_all_exclude_not_found() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [workspace]
+ members = ["bar"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "#[test] pub fn bar() {}")
+ .build();
+
+ p.cargo("test --workspace --exclude baz")
+ .with_stderr_contains("[WARNING] excluded package(s) `baz` not found in workspace [..]")
+ .with_stdout_contains(
+ "running 1 test
+test bar ... ok",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn test_all_exclude_glob() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [workspace]
+ members = ["bar", "baz"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "#[test] pub fn bar() {}")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("baz/src/lib.rs", "#[test] pub fn baz() { assert!(false); }")
+ .build();
+
+ p.cargo("test --workspace --exclude '*z'")
+ .with_stdout_contains(
+ "running 1 test
+test bar ... ok",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn test_all_exclude_glob_not_found() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [workspace]
+ members = ["bar"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "#[test] pub fn bar() {}")
+ .build();
+
+ p.cargo("test --workspace --exclude '*z'")
+ .with_stderr_contains(
+ "[WARNING] excluded package pattern(s) `*z` not found in workspace [..]",
+ )
+ .with_stdout_contains(
+ "running 1 test
+test bar ... ok",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn test_all_exclude_broken_glob() {
+ let p = project().file("src/main.rs", "fn main() {}").build();
+
+ p.cargo("test --workspace --exclude '[*z'")
+ .with_status(101)
+ .with_stderr_contains("[ERROR] cannot build glob pattern from `[*z`")
+ .run();
+}
+
+#[cargo_test]
+fn test_all_virtual_manifest() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["a", "b"]
+ "#,
+ )
+ .file("a/Cargo.toml", &basic_manifest("a", "0.1.0"))
+ .file("a/src/lib.rs", "#[test] fn a() {}")
+ .file("b/Cargo.toml", &basic_manifest("b", "0.1.0"))
+ .file("b/src/lib.rs", "#[test] fn b() {}")
+ .build();
+
+ p.cargo("test --workspace")
+ .with_stdout_contains("running 1 test\ntest a ... ok")
+ .with_stdout_contains("running 1 test\ntest b ... ok")
+ .run();
+}
+
+#[cargo_test]
+fn test_virtual_manifest_all_implied() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["a", "b"]
+ "#,
+ )
+ .file("a/Cargo.toml", &basic_manifest("a", "0.1.0"))
+ .file("a/src/lib.rs", "#[test] fn a() {}")
+ .file("b/Cargo.toml", &basic_manifest("b", "0.1.0"))
+ .file("b/src/lib.rs", "#[test] fn b() {}")
+ .build();
+
+ p.cargo("test")
+ .with_stdout_contains("running 1 test\ntest a ... ok")
+ .with_stdout_contains("running 1 test\ntest b ... ok")
+ .run();
+}
+
+#[cargo_test]
+fn test_virtual_manifest_one_project() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar", "baz"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "#[test] fn bar() {}")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("baz/src/lib.rs", "#[test] fn baz() { assert!(false); }")
+ .build();
+
+ p.cargo("test -p bar")
+ .with_stdout_contains("running 1 test\ntest bar ... ok")
+ .with_stdout_does_not_contain("running 1 test\ntest baz ... ok")
+ .run();
+}
+
+#[cargo_test]
+fn test_virtual_manifest_glob() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar", "baz"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "#[test] fn bar() { assert!(false); }")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("baz/src/lib.rs", "#[test] fn baz() {}")
+ .build();
+
+ p.cargo("test -p '*z'")
+ .with_stdout_does_not_contain("running 1 test\ntest bar ... ok")
+ .with_stdout_contains("running 1 test\ntest baz ... ok")
+ .run();
+}
+
+#[cargo_test]
+fn test_virtual_manifest_glob_not_found() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "#[test] fn bar() {}")
+ .build();
+
+ p.cargo("test -p bar -p '*z'")
+ .with_status(101)
+ .with_stderr("[ERROR] package pattern(s) `*z` not found in workspace [..]")
+ .run();
+}
+
+#[cargo_test]
+fn test_virtual_manifest_broken_glob() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "#[test] fn bar() {}")
+ .build();
+
+ p.cargo("test -p '[*z'")
+ .with_status(101)
+ .with_stderr_contains("[ERROR] cannot build glob pattern from `[*z`")
+ .run();
+}
+
+#[cargo_test]
+fn test_all_member_dependency_same_name() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["a"]
+ "#,
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+
+ [dependencies]
+ a = "0.1.0"
+ "#,
+ )
+ .file("a/src/lib.rs", "#[test] fn a() {}")
+ .build();
+
+ Package::new("a", "0.1.0").publish();
+
+ p.cargo("test --workspace")
+ .with_stdout_contains("test a ... ok")
+ .run();
+}
+
+#[cargo_test]
+fn doctest_only_with_dev_dep() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+
+ [dev-dependencies]
+ b = { path = "b" }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ /// ```
+ /// extern crate b;
+ ///
+ /// b::b();
+ /// ```
+ pub fn a() {}
+ "#,
+ )
+ .file("b/Cargo.toml", &basic_manifest("b", "0.1.0"))
+ .file("b/src/lib.rs", "pub fn b() {}")
+ .build();
+
+ p.cargo("test --doc -v").run();
+}
+
+#[cargo_test]
+fn test_many_targets() {
+ let p = project()
+ .file(
+ "src/bin/a.rs",
+ r#"
+ fn main() {}
+ #[test] fn bin_a() {}
+ "#,
+ )
+ .file(
+ "src/bin/b.rs",
+ r#"
+ fn main() {}
+ #[test] fn bin_b() {}
+ "#,
+ )
+ .file(
+ "src/bin/c.rs",
+ r#"
+ fn main() {}
+ #[test] fn bin_c() { panic!(); }
+ "#,
+ )
+ .file(
+ "examples/a.rs",
+ r#"
+ fn main() {}
+ #[test] fn example_a() {}
+ "#,
+ )
+ .file(
+ "examples/b.rs",
+ r#"
+ fn main() {}
+ #[test] fn example_b() {}
+ "#,
+ )
+ .file("examples/c.rs", "#[test] fn example_c() { panic!(); }")
+ .file("tests/a.rs", "#[test] fn test_a() {}")
+ .file("tests/b.rs", "#[test] fn test_b() {}")
+ .file("tests/c.rs", "does not compile")
+ .build();
+
+ p.cargo("test --verbose --bin a --bin b --example a --example b --test a --test b")
+ .with_stdout_contains("test bin_a ... ok")
+ .with_stdout_contains("test bin_b ... ok")
+ .with_stdout_contains("test test_a ... ok")
+ .with_stdout_contains("test test_b ... ok")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name a examples/a.rs [..]`")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name b examples/b.rs [..]`")
+ .run();
+}
+
+#[cargo_test]
+fn doctest_and_registry() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+
+ [dependencies]
+ b = { path = "b" }
+ c = { path = "c" }
+
+ [workspace]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("b/Cargo.toml", &basic_manifest("b", "0.1.0"))
+ .file(
+ "b/src/lib.rs",
+ "
+ /// ```
+ /// b::foo();
+ /// ```
+ pub fn foo() {}
+ ",
+ )
+ .file(
+ "c/Cargo.toml",
+ r#"
+ [package]
+ name = "c"
+ version = "0.1.0"
+
+ [dependencies]
+ b = "0.1"
+ "#,
+ )
+ .file("c/src/lib.rs", "")
+ .build();
+
+ Package::new("b", "0.1.0").publish();
+
+ p.cargo("test --workspace -v").run();
+}
+
+#[cargo_test]
+fn cargo_test_env() {
+ let src = format!(
+ r#"
+ #![crate_type = "rlib"]
+
+ #[test]
+ fn env_test() {{
+ use std::env;
+ eprintln!("{{}}", env::var("{}").unwrap());
+ }}
+ "#,
+ cargo::CARGO_ENV
+ );
+
+ let p = project()
+ .file("Cargo.toml", &basic_lib_manifest("foo"))
+ .file("src/lib.rs", &src)
+ .build();
+
+ let cargo = cargo_exe().canonicalize().unwrap();
+ p.cargo("test --lib -- --nocapture")
+ .with_stderr_contains(cargo.to_str().unwrap())
+ .with_stdout_contains("test env_test ... ok")
+ .run();
+
+ // Check that `cargo test` propagates the environment's $CARGO
+ let rustc = cargo_util::paths::resolve_executable("rustc".as_ref())
+ .unwrap()
+ .canonicalize()
+ .unwrap();
+ let rustc = rustc.to_str().unwrap();
+ p.cargo("test --lib -- --nocapture")
+ // we use rustc since $CARGO is only used if it points to a path that exists
+ .env(cargo::CARGO_ENV, rustc)
+ .with_stderr_contains(rustc)
+ .with_stdout_contains("test env_test ... ok")
+ .run();
+}
+
+#[cargo_test]
+fn test_order() {
+ let p = project()
+ .file("src/lib.rs", "#[test] fn test_lib() {}")
+ .file("tests/a.rs", "#[test] fn test_a() {}")
+ .file("tests/z.rs", "#[test] fn test_z() {}")
+ .build();
+
+ p.cargo("test --workspace")
+ .with_stdout_contains(
+ "
+running 1 test
+test test_lib ... ok
+
+test result: ok. [..]
+
+
+running 1 test
+test test_a ... ok
+
+test result: ok. [..]
+
+
+running 1 test
+test test_z ... ok
+
+test result: ok. [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cyclic_dev() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dev-dependencies]
+ foo = { path = "." }
+ "#,
+ )
+ .file("src/lib.rs", "#[test] fn test_lib() {}")
+ .file("tests/foo.rs", "extern crate foo;")
+ .build();
+
+ p.cargo("test --workspace").run();
+}
+
+#[cargo_test]
+fn publish_a_crate_without_tests() {
+ Package::new("testless", "0.1.0")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "testless"
+ version = "0.1.0"
+ exclude = ["tests/*"]
+
+ [[test]]
+ name = "a_test"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ // In real life, the package will have a test,
+ // which would be excluded from .crate file by the
+ // `exclude` field. Our test harness does not honor
+ // exclude though, so let's just not add the file!
+ // .file("tests/a_test.rs", "")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ testless = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("test").run();
+ p.cargo("test --package testless").run();
+}
+
+#[cargo_test]
+fn find_dependency_of_proc_macro_dependency_with_target() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["root", "proc_macro_dep"]
+ "#,
+ )
+ .file(
+ "root/Cargo.toml",
+ r#"
+ [package]
+ name = "root"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ proc_macro_dep = { path = "../proc_macro_dep" }
+ "#,
+ )
+ .file(
+ "root/src/lib.rs",
+ r#"
+ #[macro_use]
+ extern crate proc_macro_dep;
+
+ #[derive(Noop)]
+ pub struct X;
+ "#,
+ )
+ .file(
+ "proc_macro_dep/Cargo.toml",
+ r#"
+ [package]
+ name = "proc_macro_dep"
+ version = "0.1.0"
+ authors = []
+
+ [lib]
+ proc-macro = true
+
+ [dependencies]
+ baz = "^0.1"
+ "#,
+ )
+ .file(
+ "proc_macro_dep/src/lib.rs",
+ r#"
+ extern crate baz;
+ extern crate proc_macro;
+ use proc_macro::TokenStream;
+
+ #[proc_macro_derive(Noop)]
+ pub fn noop(_input: TokenStream) -> TokenStream {
+ "".parse().unwrap()
+ }
+ "#,
+ )
+ .build();
+ Package::new("bar", "0.1.0").publish();
+ Package::new("baz", "0.1.0")
+ .dep("bar", "0.1")
+ .file("src/lib.rs", "extern crate bar;")
+ .publish();
+ p.cargo("test --workspace --target").arg(rustc_host()).run();
+}
+
+#[cargo_test]
+fn test_hint_not_masked_by_doctest() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ /// ```
+ /// assert_eq!(1, 1);
+ /// ```
+ pub fn this_works() {}
+ "#,
+ )
+ .file(
+ "tests/integ.rs",
+ r#"
+ #[test]
+ fn this_fails() {
+ panic!();
+ }
+ "#,
+ )
+ .build();
+ p.cargo("test --no-fail-fast")
+ .with_status(101)
+ .with_stdout_contains("test this_fails ... FAILED")
+ .with_stdout_contains("[..]this_works (line [..]ok")
+ .with_stderr_contains("[ERROR] test failed, to rerun pass `--test integ`")
+ .run();
+}
+
+#[cargo_test]
+fn test_hint_workspace_virtual() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["a", "b", "c"]
+ "#,
+ )
+ .file("a/Cargo.toml", &basic_manifest("a", "0.1.0"))
+ .file("a/src/lib.rs", "#[test] fn t1() {}")
+ .file("b/Cargo.toml", &basic_manifest("b", "0.1.0"))
+ .file("b/src/lib.rs", "#[test] fn t1() {assert!(false)}")
+ .file("c/Cargo.toml", &basic_manifest("c", "0.1.0"))
+ .file(
+ "c/src/lib.rs",
+ r#"
+ /// ```rust
+ /// assert_eq!(1, 2);
+ /// ```
+ pub fn foo() {}
+ "#,
+ )
+ .file(
+ "c/src/main.rs",
+ r#"
+ fn main() {}
+
+ #[test]
+ fn from_main() { assert_eq!(1, 2); }
+ "#,
+ )
+ .file(
+ "c/tests/t1.rs",
+ r#"
+ #[test]
+ fn from_int_test() { assert_eq!(1, 2); }
+ "#,
+ )
+ .file(
+ "c/examples/ex1.rs",
+ r#"
+ fn main() {}
+
+ #[test]
+ fn from_example() { assert_eq!(1, 2); }
+ "#,
+ )
+ // This does not use #[bench] since it is unstable. #[test] works just
+ // the same for our purpose of checking the hint.
+ .file(
+ "c/benches/b1.rs",
+ r#"
+ #[test]
+ fn from_bench() { assert_eq!(1, 2); }
+ "#,
+ )
+ .build();
+
+ // This depends on Units being sorted so that `b` fails first.
+ p.cargo("test")
+ .with_stderr_unordered(
+ "\
+[COMPILING] c v0.1.0 [..]
+[COMPILING] a v0.1.0 [..]
+[COMPILING] b v0.1.0 [..]
+[FINISHED] test [..]
+[RUNNING] unittests src/lib.rs (target/debug/deps/a[..])
+[RUNNING] unittests src/lib.rs (target/debug/deps/b[..])
+[ERROR] test failed, to rerun pass `-p b --lib`
+",
+ )
+ .with_status(101)
+ .run();
+ p.cargo("test")
+ .cwd("b")
+ .with_stderr(
+ "\
+[FINISHED] test [..]
+[RUNNING] unittests src/lib.rs ([ROOT]/foo/target/debug/deps/b[..])
+[ERROR] test failed, to rerun pass `--lib`
+",
+ )
+ .with_status(101)
+ .run();
+ p.cargo("test --no-fail-fast")
+ .with_stderr(
+ "\
+[FINISHED] test [..]
+[RUNNING] unittests src/lib.rs (target/debug/deps/a[..])
+[RUNNING] unittests src/lib.rs (target/debug/deps/b[..])
+[ERROR] test failed, to rerun pass `-p b --lib`
+[RUNNING] unittests src/lib.rs (target/debug/deps/c[..])
+[RUNNING] unittests src/main.rs (target/debug/deps/c[..])
+[ERROR] test failed, to rerun pass `-p c --bin c`
+[RUNNING] tests/t1.rs (target/debug/deps/t1[..])
+[ERROR] test failed, to rerun pass `-p c --test t1`
+[DOCTEST] a
+[DOCTEST] b
+[DOCTEST] c
+[ERROR] doctest failed, to rerun pass `-p c --doc`
+[ERROR] 4 targets failed:
+ `-p b --lib`
+ `-p c --bin c`
+ `-p c --test t1`
+ `-p c --doc`
+",
+ )
+ .with_status(101)
+ .run();
+ // Check others that are not in the default set.
+ p.cargo("test -p c --examples --benches --no-fail-fast")
+ .with_stderr(
+ "\
+[COMPILING] c v0.1.0 [..]
+[FINISHED] test [..]
+[RUNNING] unittests src/lib.rs (target/debug/deps/c[..])
+[RUNNING] unittests src/main.rs (target/debug/deps/c[..])
+[ERROR] test failed, to rerun pass `-p c --bin c`
+[RUNNING] benches/b1.rs (target/debug/deps/b1[..])
+[ERROR] test failed, to rerun pass `-p c --bench b1`
+[RUNNING] unittests examples/ex1.rs (target/debug/examples/ex1[..])
+[ERROR] test failed, to rerun pass `-p c --example ex1`
+[ERROR] 3 targets failed:
+ `-p c --bin c`
+ `-p c --bench b1`
+ `-p c --example ex1`
+",
+ )
+ .with_status(101)
+ .run()
+}
+
+#[cargo_test]
+fn test_hint_workspace_nonvirtual() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [workspace]
+ members = ["a"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("a/Cargo.toml", &basic_manifest("a", "0.1.0"))
+ .file("a/src/lib.rs", "#[test] fn t1() {assert!(false)}")
+ .build();
+
+ p.cargo("test --workspace")
+ .with_stderr_contains("[ERROR] test failed, to rerun pass `-p a --lib`")
+ .with_status(101)
+ .run();
+ p.cargo("test -p a")
+ .with_stderr_contains("[ERROR] test failed, to rerun pass `-p a --lib`")
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn json_artifact_includes_test_flag() {
+ // Verify that the JSON artifact output includes `test` flag.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [profile.test]
+ opt-level = 1
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("test --lib -v --message-format=json")
+ .with_json(
+ r#"
+ {
+ "reason":"compiler-artifact",
+ "profile": {
+ "debug_assertions": true,
+ "debuginfo": 2,
+ "opt_level": "1",
+ "overflow_checks": true,
+ "test": true
+ },
+ "executable": "[..]/foo-[..]",
+ "features": [],
+ "package_id":"foo 0.0.1 ([..])",
+ "manifest_path": "[..]",
+ "target":{
+ "kind":["lib"],
+ "crate_types":["lib"],
+ "doc": true,
+ "doctest": true,
+ "edition": "2015",
+ "name":"foo",
+ "src_path":"[..]lib.rs",
+ "test": true
+ },
+ "filenames":"{...}",
+ "fresh": false
+ }
+
+ {"reason": "build-finished", "success": true}
+ "#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn json_artifact_includes_executable_for_library_tests() {
+ let p = project()
+ .file("src/main.rs", "fn main() { }")
+ .file("src/lib.rs", r#"#[test] fn lib_test() {}"#)
+ .build();
+
+ p.cargo("test --lib -v --no-run --message-format=json")
+ .with_json(
+ r#"
+ {
+ "executable": "[..]/foo/target/debug/deps/foo-[..][EXE]",
+ "features": [],
+ "filenames": "{...}",
+ "fresh": false,
+ "package_id": "foo 0.0.1 ([..])",
+ "manifest_path": "[..]",
+ "profile": "{...}",
+ "reason": "compiler-artifact",
+ "target": {
+ "crate_types": [ "lib" ],
+ "kind": [ "lib" ],
+ "doc": true,
+ "doctest": true,
+ "edition": "2015",
+ "name": "foo",
+ "src_path": "[..]/foo/src/lib.rs",
+ "test": true
+ }
+ }
+
+ {"reason": "build-finished", "success": true}
+ "#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn json_artifact_includes_executable_for_integration_tests() {
+ let p = project()
+ .file(
+ "tests/integration_test.rs",
+ r#"#[test] fn integration_test() {}"#,
+ )
+ .build();
+
+ p.cargo("test -v --no-run --message-format=json --test integration_test")
+ .with_json(
+ r#"
+ {
+ "executable": "[..]/foo/target/debug/deps/integration_test-[..][EXE]",
+ "features": [],
+ "filenames": "{...}",
+ "fresh": false,
+ "package_id": "foo 0.0.1 ([..])",
+ "manifest_path": "[..]",
+ "profile": "{...}",
+ "reason": "compiler-artifact",
+ "target": {
+ "crate_types": [ "bin" ],
+ "kind": [ "test" ],
+ "doc": false,
+ "doctest": false,
+ "edition": "2015",
+ "name": "integration_test",
+ "src_path": "[..]/foo/tests/integration_test.rs",
+ "test": true
+ }
+ }
+
+ {"reason": "build-finished", "success": true}
+ "#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn test_build_script_links() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ links = 'something'
+
+ [lib]
+ test = false
+ "#,
+ )
+ .file("build.rs", "fn main() {}")
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("test --no-run").run();
+}
+
+#[cargo_test]
+fn doctest_skip_staticlib() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [lib]
+ crate-type = ["staticlib"]
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ //! ```
+ //! assert_eq!(1,2);
+ //! ```
+ "#,
+ )
+ .build();
+
+ p.cargo("test --doc")
+ .with_status(101)
+ .with_stderr(
+ "\
+[WARNING] doc tests are not supported for crate type(s) `staticlib` in package `foo`
+[ERROR] no library targets found in package `foo`",
+ )
+ .run();
+
+ p.cargo("test")
+ .with_stderr(
+ "\
+[COMPILING] foo [..]
+[FINISHED] test [..]
+[RUNNING] [..] (target/debug/deps/foo-[..])",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn can_not_mix_doc_tests_and_regular_tests() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ "\
+/// ```
+/// assert_eq!(1, 1)
+/// ```
+pub fn foo() -> u8 { 1 }
+
+#[cfg(test)] mod tests {
+ #[test] fn it_works() { assert_eq!(2 + 2, 4); }
+}
+",
+ )
+ .build();
+
+ p.cargo("test")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo-[..])
+[DOCTEST] foo
+",
+ )
+ .with_stdout(
+ "
+running 1 test
+test tests::it_works ... ok
+
+test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out[..]
+
+
+running 1 test
+test src/lib.rs - foo (line 1) ... ok
+
+test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out[..]
+\n",
+ )
+ .run();
+
+ p.cargo("test --lib")
+ .with_stderr(
+ "\
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target/debug/deps/foo-[..])\n",
+ )
+ .with_stdout(
+ "
+running 1 test
+test tests::it_works ... ok
+
+test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out[..]
+\n",
+ )
+ .run();
+
+ // This has been modified to attempt to diagnose spurious errors on CI.
+ // For some reason, this is recompiling the lib when it shouldn't. If the
+ // root cause is ever found, the changes here should be reverted.
+ // See https://github.com/rust-lang/cargo/issues/6887
+ p.cargo("test --doc -vv")
+ .with_stderr_does_not_contain("[COMPILING] foo [..]")
+ .with_stderr_contains("[DOCTEST] foo")
+ .with_stdout(
+ "
+running 1 test
+test src/lib.rs - foo (line 1) ... ok
+
+test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out[..]
+
+",
+ )
+ .env("CARGO_LOG", "cargo=trace")
+ .run();
+
+ p.cargo("test --lib --doc")
+ .with_status(101)
+ .with_stderr("[ERROR] Can't mix --doc with other target selecting options\n")
+ .run();
+}
+
+#[cargo_test]
+fn can_not_no_run_doc_tests() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ /// ```
+ /// let _x = 1 + "foo";
+ /// ```
+ pub fn foo() -> u8 { 1 }
+ "#,
+ )
+ .build();
+
+ p.cargo("test --doc --no-run")
+ .with_status(101)
+ .with_stderr("[ERROR] Can't skip running doc tests with --no-run")
+ .run();
+}
+
+#[cargo_test]
+fn test_all_targets_lib() {
+ let p = project().file("src/lib.rs", "").build();
+
+ p.cargo("test --all-targets")
+ .with_stderr(
+ "\
+[COMPILING] foo [..]
+[FINISHED] test [..]
+[RUNNING] [..]foo[..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn test_dep_with_dev() {
+ Package::new("devdep", "0.1.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies]
+ bar = { path = "bar" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+
+ [dev-dependencies]
+ devdep = "0.1"
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("test -p bar")
+ .with_status(101)
+ .with_stderr(
+ "[ERROR] package `bar` cannot be tested because it requires dev-dependencies \
+ and is not a member of the workspace",
+ )
+ .run();
+}
+
+#[cargo_test(nightly, reason = "-Zdoctest-xcompile is unstable")]
+fn cargo_test_doctest_xcompile_ignores() {
+ // -Zdoctest-xcompile also enables --enable-per-target-ignores which
+ // allows the ignore-TARGET syntax.
+ let p = project()
+ .file("Cargo.toml", &basic_lib_manifest("foo"))
+ .file(
+ "src/lib.rs",
+ r#"
+ ///```ignore-x86_64
+ ///assert!(cfg!(not(target_arch = "x86_64")));
+ ///```
+ pub fn foo() -> u8 {
+ 4
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build").run();
+ #[cfg(not(target_arch = "x86_64"))]
+ p.cargo("test")
+ .with_stdout_contains(
+ "test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out[..]",
+ )
+ .run();
+ #[cfg(target_arch = "x86_64")]
+ p.cargo("test")
+ .with_status(101)
+ .with_stdout_contains(
+ "test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out[..]",
+ )
+ .run();
+
+ #[cfg(not(target_arch = "x86_64"))]
+ p.cargo("test -Zdoctest-xcompile")
+ .masquerade_as_nightly_cargo(&["doctest-xcompile"])
+ .with_stdout_contains(
+ "test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out[..]",
+ )
+ .run();
+
+ #[cfg(target_arch = "x86_64")]
+ p.cargo("test -Zdoctest-xcompile")
+ .masquerade_as_nightly_cargo(&["doctest-xcompile"])
+ .with_stdout_contains(
+ "test result: ok. 0 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out[..]",
+ )
+ .run();
+}
+
+#[cargo_test(nightly, reason = "-Zdoctest-xcompile is unstable")]
+fn cargo_test_doctest_xcompile() {
+ if !cross_compile::can_run_on_host() {
+ return;
+ }
+ let p = project()
+ .file("Cargo.toml", &basic_lib_manifest("foo"))
+ .file(
+ "src/lib.rs",
+ r#"
+
+ ///```
+ ///assert!(1 == 1);
+ ///```
+ pub fn foo() -> u8 {
+ 4
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build").run();
+ p.cargo(&format!("test --target {}", cross_compile::alternate()))
+ .with_stdout_contains("running 0 tests")
+ .run();
+ p.cargo(&format!(
+ "test --target {} -Zdoctest-xcompile",
+ cross_compile::alternate()
+ ))
+ .masquerade_as_nightly_cargo(&["doctest-xcompile"])
+ .with_stdout_contains(
+ "test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out[..]",
+ )
+ .run();
+}
+
+#[cargo_test(nightly, reason = "-Zdoctest-xcompile is unstable")]
+fn cargo_test_doctest_xcompile_runner() {
+ if !cross_compile::can_run_on_host() {
+ return;
+ }
+
+ let runner = project()
+ .file("Cargo.toml", &basic_bin_manifest("runner"))
+ .file(
+ "src/main.rs",
+ r#"
+ pub fn main() {
+ eprintln!("this is a runner");
+ let args: Vec<String> = std::env::args().collect();
+ std::process::Command::new(&args[1]).spawn();
+ }
+ "#,
+ )
+ .build();
+
+ runner.cargo("build").run();
+ assert!(runner.bin("runner").is_file());
+ let runner_path = paths::root().join("runner");
+ fs::copy(&runner.bin("runner"), &runner_path).unwrap();
+
+ let config = paths::root().join(".cargo/config");
+
+ fs::create_dir_all(config.parent().unwrap()).unwrap();
+ // Escape Windows backslashes for TOML config.
+ let runner_str = runner_path.to_str().unwrap().replace('\\', "\\\\");
+ fs::write(
+ config,
+ format!(
+ r#"
+ [target.'cfg(target_arch = "{}")']
+ runner = "{}"
+ "#,
+ cross_compile::alternate_arch(),
+ runner_str
+ ),
+ )
+ .unwrap();
+
+ let p = project()
+ .file("Cargo.toml", &basic_lib_manifest("foo"))
+ .file(
+ "src/lib.rs",
+ &format!(
+ r#"
+ ///```
+ ///assert!(cfg!(target_arch = "{}"));
+ ///```
+ pub fn foo() -> u8 {{
+ 4
+ }}
+ "#,
+ cross_compile::alternate_arch()
+ ),
+ )
+ .build();
+
+ p.cargo("build").run();
+ p.cargo(&format!("test --target {}", cross_compile::alternate()))
+ .with_stdout_contains("running 0 tests")
+ .run();
+ p.cargo(&format!(
+ "test --target {} -Zdoctest-xcompile",
+ cross_compile::alternate()
+ ))
+ .masquerade_as_nightly_cargo(&["doctest-xcompile"])
+ .with_stdout_contains(
+ "test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out[..]",
+ )
+ .with_stderr_contains("this is a runner")
+ .run();
+}
+
+#[cargo_test(nightly, reason = "-Zdoctest-xcompile is unstable")]
+fn cargo_test_doctest_xcompile_no_runner() {
+ if !cross_compile::can_run_on_host() {
+ return;
+ }
+
+ let p = project()
+ .file("Cargo.toml", &basic_lib_manifest("foo"))
+ .file(
+ "src/lib.rs",
+ &format!(
+ r#"
+ ///```
+ ///assert!(cfg!(target_arch = "{}"));
+ ///```
+ pub fn foo() -> u8 {{
+ 4
+ }}
+ "#,
+ cross_compile::alternate_arch()
+ ),
+ )
+ .build();
+
+ p.cargo("build").run();
+ p.cargo(&format!("test --target {}", cross_compile::alternate()))
+ .with_stdout_contains("running 0 tests")
+ .run();
+ p.cargo(&format!(
+ "test --target {} -Zdoctest-xcompile",
+ cross_compile::alternate()
+ ))
+ .masquerade_as_nightly_cargo(&["doctest-xcompile"])
+ .with_stdout_contains(
+ "test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out[..]",
+ )
+ .run();
+}
+
+#[cargo_test(nightly, reason = "-Zpanic-abort-tests in rustc is unstable")]
+fn panic_abort_tests() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = 'foo'
+ version = '0.1.0'
+
+ [dependencies]
+ a = { path = 'a' }
+
+ [profile.dev]
+ panic = 'abort'
+ [profile.test]
+ panic = 'abort'
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ #[test]
+ fn foo() {
+ a::foo();
+ }
+ "#,
+ )
+ .file("a/Cargo.toml", &basic_lib_manifest("a"))
+ .file("a/src/lib.rs", "pub fn foo() {}")
+ .build();
+
+ p.cargo("test -Z panic-abort-tests -v")
+ .with_stderr_contains("[..]--crate-name a [..]-C panic=abort[..]")
+ .with_stderr_contains("[..]--crate-name foo [..]-C panic=abort[..]")
+ .with_stderr_contains("[..]--crate-name foo [..]-C panic=abort[..]--test[..]")
+ .masquerade_as_nightly_cargo(&["panic-abort-tests"])
+ .run();
+}
+
+#[cargo_test(nightly, reason = "-Zpanic-abort-tests in rustc is unstable")]
+fn panic_abort_only_test() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = 'foo'
+ version = '0.1.0'
+
+ [dependencies]
+ a = { path = 'a' }
+
+ [profile.test]
+ panic = 'abort'
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ #[test]
+ fn foo() {
+ a::foo();
+ }
+ "#,
+ )
+ .file("a/Cargo.toml", &basic_lib_manifest("a"))
+ .file("a/src/lib.rs", "pub fn foo() {}")
+ .build();
+
+ p.cargo("test -Z panic-abort-tests -v")
+ .with_stderr_contains("warning: `panic` setting is ignored for `test` profile")
+ .masquerade_as_nightly_cargo(&["panic-abort-tests"])
+ .run();
+}
+
+#[cargo_test(nightly, reason = "-Zpanic-abort-tests in rustc is unstable")]
+fn panic_abort_test_profile_inherits() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = 'foo'
+ version = '0.1.0'
+
+ [dependencies]
+ a = { path = 'a' }
+
+ [profile.dev]
+ panic = 'abort'
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ #[test]
+ fn foo() {
+ a::foo();
+ }
+ "#,
+ )
+ .file("a/Cargo.toml", &basic_lib_manifest("a"))
+ .file("a/src/lib.rs", "pub fn foo() {}")
+ .build();
+
+ p.cargo("test -Z panic-abort-tests -v")
+ .masquerade_as_nightly_cargo(&["panic-abort-tests"])
+ .with_status(0)
+ .run();
+}
+
+#[cargo_test]
+fn bin_env_for_test() {
+ // Test for the `CARGO_BIN_EXE_` environment variables for tests.
+ //
+ // Note: The Unicode binary uses a `[[bin]]` definition because different
+ // filesystems normalize utf-8 in different ways. For example, HFS uses
+ // "gru\u{308}ßen" and APFS uses "gr\u{fc}ßen". Defining it in TOML forces
+ // one form to be used.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [[bin]]
+ name = 'grüßen'
+ path = 'src/bin/grussen.rs'
+ "#,
+ )
+ .file("src/bin/foo.rs", "fn main() {}")
+ .file("src/bin/with-dash.rs", "fn main() {}")
+ .file("src/bin/grussen.rs", "fn main() {}")
+ .build();
+
+ let bin_path = |name| p.bin(name).to_string_lossy().replace("\\", "\\\\");
+ p.change_file(
+ "tests/check_env.rs",
+ &r#"
+ #[test]
+ fn run_bins() {
+ assert_eq!(env!("CARGO_BIN_EXE_foo"), "<FOO_PATH>");
+ assert_eq!(env!("CARGO_BIN_EXE_with-dash"), "<WITH_DASH_PATH>");
+ assert_eq!(env!("CARGO_BIN_EXE_grüßen"), "<GRÜSSEN_PATH>");
+ }
+ "#
+ .replace("<FOO_PATH>", &bin_path("foo"))
+ .replace("<WITH_DASH_PATH>", &bin_path("with-dash"))
+ .replace("<GRÜSSEN_PATH>", &bin_path("grüßen")),
+ );
+
+ p.cargo("test --test check_env").run();
+ p.cargo("check --test check_env").run();
+}
+
+#[cargo_test]
+fn test_workspaces_cwd() {
+ // This tests that all the different test types are executed from the
+ // crate directory (manifest_dir), and not from the workspace root.
+
+ let make_lib_file = |expected| {
+ format!(
+ r#"
+ //! ```
+ //! assert_eq!("{expected}", std::fs::read_to_string("file.txt").unwrap());
+ //! assert_eq!("{expected}", include_str!("../file.txt"));
+ //! assert_eq!(
+ //! std::path::PathBuf::from(std::env!("CARGO_MANIFEST_DIR")),
+ //! std::env::current_dir().unwrap(),
+ //! );
+ //! ```
+
+ #[test]
+ fn test_unit_{expected}_cwd() {{
+ assert_eq!("{expected}", std::fs::read_to_string("file.txt").unwrap());
+ assert_eq!("{expected}", include_str!("../file.txt"));
+ assert_eq!(
+ std::path::PathBuf::from(std::env!("CARGO_MANIFEST_DIR")),
+ std::env::current_dir().unwrap(),
+ );
+ }}
+ "#,
+ expected = expected
+ )
+ };
+ let make_test_file = |expected| {
+ format!(
+ r#"
+ #[test]
+ fn test_integration_{expected}_cwd() {{
+ assert_eq!("{expected}", std::fs::read_to_string("file.txt").unwrap());
+ assert_eq!("{expected}", include_str!("../file.txt"));
+ assert_eq!(
+ std::path::PathBuf::from(std::env!("CARGO_MANIFEST_DIR")),
+ std::env::current_dir().unwrap(),
+ );
+ }}
+ "#,
+ expected = expected
+ )
+ };
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "root-crate"
+ version = "0.0.0"
+
+ [workspace]
+ members = [".", "nested-crate", "very/deeply/nested/deep-crate"]
+ "#,
+ )
+ .file("file.txt", "root")
+ .file("src/lib.rs", &make_lib_file("root"))
+ .file("tests/integration.rs", &make_test_file("root"))
+ .file(
+ "nested-crate/Cargo.toml",
+ r#"
+ [package]
+ name = "nested-crate"
+ version = "0.0.0"
+ "#,
+ )
+ .file("nested-crate/file.txt", "nested")
+ .file("nested-crate/src/lib.rs", &make_lib_file("nested"))
+ .file(
+ "nested-crate/tests/integration.rs",
+ &make_test_file("nested"),
+ )
+ .file(
+ "very/deeply/nested/deep-crate/Cargo.toml",
+ r#"
+ [package]
+ name = "deep-crate"
+ version = "0.0.0"
+ "#,
+ )
+ .file("very/deeply/nested/deep-crate/file.txt", "deep")
+ .file(
+ "very/deeply/nested/deep-crate/src/lib.rs",
+ &make_lib_file("deep"),
+ )
+ .file(
+ "very/deeply/nested/deep-crate/tests/integration.rs",
+ &make_test_file("deep"),
+ )
+ .build();
+
+ p.cargo("test --workspace --all")
+ .with_stderr_contains("[DOCTEST] root-crate")
+ .with_stderr_contains("[DOCTEST] nested-crate")
+ .with_stderr_contains("[DOCTEST] deep-crate")
+ .with_stdout_contains("test test_unit_root_cwd ... ok")
+ .with_stdout_contains("test test_unit_nested_cwd ... ok")
+ .with_stdout_contains("test test_unit_deep_cwd ... ok")
+ .with_stdout_contains("test test_integration_root_cwd ... ok")
+ .with_stdout_contains("test test_integration_nested_cwd ... ok")
+ .with_stdout_contains("test test_integration_deep_cwd ... ok")
+ .run();
+
+ p.cargo("test -p root-crate --all")
+ .with_stderr_contains("[DOCTEST] root-crate")
+ .with_stdout_contains("test test_unit_root_cwd ... ok")
+ .with_stdout_contains("test test_integration_root_cwd ... ok")
+ .run();
+
+ p.cargo("test -p nested-crate --all")
+ .with_stderr_contains("[DOCTEST] nested-crate")
+ .with_stdout_contains("test test_unit_nested_cwd ... ok")
+ .with_stdout_contains("test test_integration_nested_cwd ... ok")
+ .run();
+
+ p.cargo("test -p deep-crate --all")
+ .with_stderr_contains("[DOCTEST] deep-crate")
+ .with_stdout_contains("test test_unit_deep_cwd ... ok")
+ .with_stdout_contains("test test_integration_deep_cwd ... ok")
+ .run();
+
+ p.cargo("test --all")
+ .cwd("nested-crate")
+ .with_stderr_contains("[DOCTEST] nested-crate")
+ .with_stdout_contains("test test_unit_nested_cwd ... ok")
+ .with_stdout_contains("test test_integration_nested_cwd ... ok")
+ .run();
+
+ p.cargo("test --all")
+ .cwd("very/deeply/nested/deep-crate")
+ .with_stderr_contains("[DOCTEST] deep-crate")
+ .with_stdout_contains("test test_unit_deep_cwd ... ok")
+ .with_stdout_contains("test test_integration_deep_cwd ... ok")
+ .run();
+}
+
+#[cargo_test]
+fn execution_error() {
+ // Checks the behavior when a test fails to launch.
+ let p = project()
+ .file(
+ "tests/t1.rs",
+ r#"
+ #[test]
+ fn foo() {}
+ "#,
+ )
+ .build();
+ let key = format!("CARGO_TARGET_{}_RUNNER", rustc_host_env());
+ p.cargo("test")
+ .env(&key, "does_not_exist")
+ // The actual error is usually "no such file", but on Windows it has a
+ // custom message. Since matching against the error string produced by
+ // Rust is not very reliable, this just uses `[..]`.
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 [..]
+[FINISHED] test [..]
+[RUNNING] tests/t1.rs (target/debug/deps/t1[..])
+error: test failed, to rerun pass `--test t1`
+
+Caused by:
+ could not execute process `does_not_exist [ROOT]/foo/target/debug/deps/t1[..]` (never executed)
+
+Caused by:
+ [..]
+",
+ )
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn nonzero_exit_status() {
+ // Tests for nonzero exit codes from tests.
+ let p = project()
+ .file(
+ "tests/t1.rs",
+ r#"
+ #[test]
+ fn t() { panic!("this is a normal error") }
+ "#,
+ )
+ .file(
+ "tests/t2.rs",
+ r#"
+ #[test]
+ fn t() { std::process::exit(4) }
+ "#,
+ )
+ .build();
+
+ p.cargo("test --test t1")
+ .with_stderr(
+ "\
+[COMPILING] foo [..]
+[FINISHED] test [..]
+[RUNNING] tests/t1.rs (target/debug/deps/t1[..])
+error: test failed, to rerun pass `--test t1`
+",
+ )
+ .with_stdout_contains("[..]this is a normal error[..]")
+ .with_status(101)
+ .run();
+
+ p.cargo("test --test t2")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 [..]
+[FINISHED] test [..]
+[RUNNING] tests/t2.rs (target/debug/deps/t2[..])
+error: test failed, to rerun pass `--test t2`
+
+Caused by:
+ process didn't exit successfully: `[ROOT]/foo/target/debug/deps/t2[..]` (exit [..]: 4)
+",
+ )
+ .with_status(4)
+ .run();
+
+ // no-fail-fast always uses 101
+ p.cargo("test --no-fail-fast")
+ .with_stderr(
+ "\
+[FINISHED] test [..]
+[RUNNING] tests/t1.rs (target/debug/deps/t1[..])
+error: test failed, to rerun pass `--test t1`
+[RUNNING] tests/t2.rs (target/debug/deps/t2[..])
+error: test failed, to rerun pass `--test t2`
+
+Caused by:
+ process didn't exit successfully: `[ROOT]/foo/target/debug/deps/t2[..]` (exit [..]: 4)
+error: 2 targets failed:
+ `--test t1`
+ `--test t2`
+",
+ )
+ .with_status(101)
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/timings.rs b/src/tools/cargo/tests/testsuite/timings.rs
new file mode 100644
index 000000000..8f06ac69b
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/timings.rs
@@ -0,0 +1,53 @@
+//! Tests for --timings.
+
+use cargo_test_support::project;
+use cargo_test_support::registry::Package;
+
+#[cargo_test]
+fn timings_works() {
+ Package::new("dep", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ dep = "0.1"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("src/main.rs", "fn main() {}")
+ .file("tests/t1.rs", "")
+ .file("examples/ex1.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build --all-targets --timings")
+ .with_stderr_unordered(
+ "\
+[UPDATING] [..]
+[DOWNLOADING] crates ...
+[DOWNLOADED] dep v0.1.0 [..]
+[COMPILING] dep v0.1.0
+[COMPILING] foo v0.1.0 [..]
+[FINISHED] [..]
+ Timing report saved to [..]/foo/target/cargo-timings/cargo-timing-[..].html
+",
+ )
+ .run();
+
+ p.cargo("clean").run();
+
+ p.cargo("test --timings").run();
+
+ p.cargo("clean").run();
+
+ p.cargo("check --timings").run();
+
+ p.cargo("clean").run();
+
+ p.cargo("doc --timings").run();
+}
diff --git a/src/tools/cargo/tests/testsuite/tool_paths.rs b/src/tools/cargo/tests/testsuite/tool_paths.rs
new file mode 100644
index 000000000..a211b5328
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/tool_paths.rs
@@ -0,0 +1,402 @@
+//! Tests for configuration values that point to programs.
+
+use cargo_test_support::{basic_lib_manifest, project, rustc_host, rustc_host_env};
+
+#[cargo_test]
+fn pathless_tools() {
+ let target = rustc_host();
+
+ let foo = project()
+ .file("Cargo.toml", &basic_lib_manifest("foo"))
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [target.{}]
+ linker = "nonexistent-linker"
+ "#,
+ target
+ ),
+ )
+ .build();
+
+ foo.cargo("build --verbose")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.5.0 ([CWD])
+[RUNNING] `rustc [..] -C linker=nonexistent-linker [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn absolute_tools() {
+ let target = rustc_host();
+
+ // Escaped as they appear within a TOML config file
+ let linker = if cfg!(windows) {
+ r#"C:\\bogus\\nonexistent-linker"#
+ } else {
+ r#"/bogus/nonexistent-linker"#
+ };
+
+ let foo = project()
+ .file("Cargo.toml", &basic_lib_manifest("foo"))
+ .file("src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [target.{target}]
+ linker = "{linker}"
+ "#,
+ target = target,
+ linker = linker
+ ),
+ )
+ .build();
+
+ foo.cargo("build --verbose")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.5.0 ([CWD])
+[RUNNING] `rustc [..] -C linker=[..]bogus/nonexistent-linker [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn relative_tools() {
+ let target = rustc_host();
+
+ // Escaped as they appear within a TOML config file
+ let linker = if cfg!(windows) {
+ r#".\\tools\\nonexistent-linker"#
+ } else {
+ r#"./tools/nonexistent-linker"#
+ };
+
+ // Funky directory structure to test that relative tool paths are made absolute
+ // by reference to the `.cargo/..` directory and not to (for example) the CWD.
+ let p = project()
+ .no_manifest()
+ .file("bar/Cargo.toml", &basic_lib_manifest("bar"))
+ .file("bar/src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [target.{target}]
+ linker = "{linker}"
+ "#,
+ target = target,
+ linker = linker
+ ),
+ )
+ .build();
+
+ let prefix = p.root().into_os_string().into_string().unwrap();
+
+ p.cargo("build --verbose")
+ .cwd("bar")
+ .with_stderr(&format!(
+ "\
+[COMPILING] bar v0.5.0 ([CWD])
+[RUNNING] `rustc [..] -C linker={prefix}/./tools/nonexistent-linker [..]`
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ prefix = prefix,
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn custom_runner() {
+ let target = rustc_host();
+
+ let p = project()
+ .file("src/main.rs", "fn main() {}")
+ .file("tests/test.rs", "")
+ .file("benches/bench.rs", "")
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [target.{}]
+ runner = "nonexistent-runner -r"
+ "#,
+ target
+ ),
+ )
+ .build();
+
+ p.cargo("run -- --param")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `nonexistent-runner -r target/debug/foo[EXE] --param`
+",
+ )
+ .run();
+
+ p.cargo("test --test test --verbose -- --param")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc [..]`
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `nonexistent-runner -r [..]/target/debug/deps/test-[..][EXE] --param`
+",
+ )
+ .run();
+
+ p.cargo("bench --bench bench --verbose -- --param")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc [..]`
+[RUNNING] `rustc [..]`
+[FINISHED] bench [optimized] target(s) in [..]
+[RUNNING] `nonexistent-runner -r [..]/target/release/deps/bench-[..][EXE] --param --bench`
+",
+ )
+ .run();
+}
+
+// can set a custom runner via `target.'cfg(..)'.runner`
+#[cargo_test]
+fn custom_runner_cfg() {
+ let p = project()
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ ".cargo/config",
+ r#"
+ [target.'cfg(not(target_os = "none"))']
+ runner = "nonexistent-runner -r"
+ "#,
+ )
+ .build();
+
+ p.cargo("run -- --param")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `nonexistent-runner -r target/debug/foo[EXE] --param`
+",
+ )
+ .run();
+}
+
+// custom runner set via `target.$triple.runner` have precedence over `target.'cfg(..)'.runner`
+#[cargo_test]
+fn custom_runner_cfg_precedence() {
+ let target = rustc_host();
+
+ let p = project()
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [target.'cfg(not(target_os = "none"))']
+ runner = "ignored-runner"
+
+ [target.{}]
+ runner = "nonexistent-runner -r"
+ "#,
+ target
+ ),
+ )
+ .build();
+
+ p.cargo("run -- --param")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `nonexistent-runner -r target/debug/foo[EXE] --param`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn custom_runner_cfg_collision() {
+ let p = project()
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ ".cargo/config",
+ r#"
+ [target.'cfg(not(target_arch = "avr"))']
+ runner = "true"
+
+ [target.'cfg(not(target_os = "none"))']
+ runner = "false"
+ "#,
+ )
+ .build();
+
+ p.cargo("run -- --param")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] several matching instances of `target.'cfg(..)'.runner` in configurations
+first match `cfg(not(target_arch = \"avr\"))` located in [..]/foo/.cargo/config
+second match `cfg(not(target_os = \"none\"))` located in [..]/foo/.cargo/config
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn custom_runner_env() {
+ let p = project().file("src/main.rs", "fn main() {}").build();
+
+ let key = format!("CARGO_TARGET_{}_RUNNER", rustc_host_env());
+
+ p.cargo("run")
+ .env(&key, "nonexistent-runner --foo")
+ .with_status(101)
+ // FIXME: Update "Caused by" error message once rust/pull/87704 is merged.
+ // On Windows, changing to a custom executable resolver has changed the
+ // error messages.
+ .with_stderr(&format!(
+ "\
+[COMPILING] foo [..]
+[FINISHED] dev [..]
+[RUNNING] `nonexistent-runner --foo target/debug/foo[EXE]`
+[ERROR] could not execute process `nonexistent-runner --foo target/debug/foo[EXE]` (never executed)
+
+Caused by:
+ [..]
+"
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn custom_runner_env_overrides_config() {
+ let target = rustc_host();
+ let p = project()
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ ".cargo/config.toml",
+ &format!(
+ r#"
+ [target.{}]
+ runner = "should-not-run -r"
+ "#,
+ target
+ ),
+ )
+ .build();
+
+ let key = format!("CARGO_TARGET_{}_RUNNER", rustc_host_env());
+
+ p.cargo("run")
+ .env(&key, "should-run --foo")
+ .with_status(101)
+ .with_stderr_contains("[RUNNING] `should-run --foo target/debug/foo[EXE]`")
+ .run();
+}
+
+#[cargo_test]
+#[cfg(unix)] // Assumes `true` is in PATH.
+fn custom_runner_env_true() {
+ // Check for a bug where "true" was interpreted as a boolean instead of
+ // the executable.
+ let p = project().file("src/main.rs", "fn main() {}").build();
+
+ let key = format!("CARGO_TARGET_{}_RUNNER", rustc_host_env());
+
+ p.cargo("run")
+ .env(&key, "true")
+ .with_stderr_contains("[RUNNING] `true target/debug/foo[EXE]`")
+ .run();
+}
+
+#[cargo_test]
+fn custom_linker_env() {
+ let p = project().file("src/main.rs", "fn main() {}").build();
+
+ let key = format!("CARGO_TARGET_{}_LINKER", rustc_host_env());
+
+ p.cargo("build -v")
+ .env(&key, "nonexistent-linker")
+ .with_status(101)
+ .with_stderr_contains("[RUNNING] `rustc [..]-C linker=nonexistent-linker [..]")
+ .run();
+}
+
+#[cargo_test]
+fn target_in_environment_contains_lower_case() {
+ let p = project().file("src/main.rs", "fn main() {}").build();
+
+ let target = rustc_host();
+ let env_key = format!(
+ "CARGO_TARGET_{}_LINKER",
+ target.to_lowercase().replace('-', "_")
+ );
+
+ p.cargo("build -v --target")
+ .arg(target)
+ .env(&env_key, "nonexistent-linker")
+ .with_stderr_contains(format!(
+ "warning: Environment variables are expected to use uppercase \
+ letters and underscores, the variable `{}` will be ignored and \
+ have no effect",
+ env_key
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn cfg_ignored_fields() {
+ // Test for some ignored fields in [target.'cfg()'] tables.
+ let p = project()
+ .file(
+ ".cargo/config",
+ r#"
+ # Try some empty tables.
+ [target.'cfg(not(foo))']
+ [target.'cfg(not(bar))'.somelib]
+
+ # A bunch of unused fields.
+ [target.'cfg(not(target_os = "none"))']
+ linker = 'false'
+ ar = 'false'
+ foo = {rustc-flags = "-l foo"}
+ invalid = 1
+ runner = 'false'
+ rustflags = ''
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[WARNING] unused key `somelib` in [target] config table `cfg(not(bar))`
+[WARNING] unused key `ar` in [target] config table `cfg(not(target_os = \"none\"))`
+[WARNING] unused key `foo` in [target] config table `cfg(not(target_os = \"none\"))`
+[WARNING] unused key `invalid` in [target] config table `cfg(not(target_os = \"none\"))`
+[WARNING] unused key `linker` in [target] config table `cfg(not(target_os = \"none\"))`
+[CHECKING] foo v0.0.1 ([..])
+[FINISHED] [..]
+",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/tree.rs b/src/tools/cargo/tests/testsuite/tree.rs
new file mode 100644
index 000000000..c3c1ca6d3
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/tree.rs
@@ -0,0 +1,2150 @@
+//! Tests for the `cargo tree` command.
+
+use super::features2::switch_to_resolver_2;
+use cargo_test_support::cross_compile::{self, alternate};
+use cargo_test_support::registry::{Dependency, Package};
+use cargo_test_support::{basic_manifest, git, project, rustc_host, Project};
+
+fn make_simple_proj() -> Project {
+ Package::new("c", "1.0.0").publish();
+ Package::new("b", "1.0.0").dep("c", "1.0").publish();
+ Package::new("a", "1.0.0").dep("b", "1.0").publish();
+ Package::new("bdep", "1.0.0").dep("b", "1.0").publish();
+ Package::new("devdep", "1.0.0").dep("b", "1.0.0").publish();
+
+ project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ a = "1.0"
+ c = "1.0"
+
+ [build-dependencies]
+ bdep = "1.0"
+
+ [dev-dependencies]
+ devdep = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("build.rs", "fn main() {}")
+ .build()
+}
+
+#[cargo_test]
+fn simple() {
+ // A simple test with a few different dependencies.
+ let p = make_simple_proj();
+
+ p.cargo("tree")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+├── a v1.0.0
+│ └── b v1.0.0
+│ └── c v1.0.0
+└── c v1.0.0
+[build-dependencies]
+└── bdep v1.0.0
+ └── b v1.0.0 (*)
+[dev-dependencies]
+└── devdep v1.0.0
+ └── b v1.0.0 (*)
+",
+ )
+ .run();
+
+ p.cargo("tree -p bdep")
+ .with_stdout(
+ "\
+bdep v1.0.0
+└── b v1.0.0
+ └── c v1.0.0
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn virtual_workspace() {
+ // Multiple packages in a virtual workspace.
+ Package::new("somedep", "1.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["a", "baz", "c"]
+ "#,
+ )
+ .file("a/Cargo.toml", &basic_manifest("a", "1.0.0"))
+ .file("a/src/lib.rs", "")
+ .file(
+ "baz/Cargo.toml",
+ r#"
+ [package]
+ name = "baz"
+ version = "0.1.0"
+
+ [dependencies]
+ c = { path = "../c" }
+ somedep = "1.0"
+ "#,
+ )
+ .file("baz/src/lib.rs", "")
+ .file("c/Cargo.toml", &basic_manifest("c", "1.0.0"))
+ .file("c/src/lib.rs", "")
+ .build();
+
+ p.cargo("tree")
+ .with_stdout(
+ "\
+a v1.0.0 ([..]/foo/a)
+
+baz v0.1.0 ([..]/foo/baz)
+├── c v1.0.0 ([..]/foo/c)
+└── somedep v1.0.0
+
+c v1.0.0 ([..]/foo/c)
+",
+ )
+ .run();
+
+ p.cargo("tree -p a").with_stdout("a v1.0.0 [..]").run();
+
+ p.cargo("tree")
+ .cwd("baz")
+ .with_stdout(
+ "\
+baz v0.1.0 ([..]/foo/baz)
+├── c v1.0.0 ([..]/foo/c)
+└── somedep v1.0.0
+",
+ )
+ .run();
+
+ // exclude baz
+ p.cargo("tree --workspace --exclude baz")
+ .with_stdout(
+ "\
+a v1.0.0 ([..]/foo/a)
+
+c v1.0.0 ([..]/foo/c)
+",
+ )
+ .run();
+
+ // exclude glob '*z'
+ p.cargo("tree --workspace --exclude '*z'")
+ .with_stdout(
+ "\
+a v1.0.0 ([..]/foo/a)
+
+c v1.0.0 ([..]/foo/c)
+",
+ )
+ .run();
+
+ // include glob '*z'
+ p.cargo("tree -p '*z'")
+ .with_stdout(
+ "\
+baz v0.1.0 ([..]/foo/baz)
+├── c v1.0.0 ([..]/foo/c)
+└── somedep v1.0.0
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn dedupe_edges() {
+ // Works around https://github.com/rust-lang/cargo/issues/7985
+ Package::new("bitflags", "1.0.0").publish();
+ Package::new("manyfeat", "1.0.0")
+ .feature("f1", &[])
+ .feature("f2", &[])
+ .feature("f3", &[])
+ .dep("bitflags", "1.0")
+ .publish();
+ Package::new("a", "1.0.0")
+ .feature_dep("manyfeat", "1.0", &["f1"])
+ .publish();
+ Package::new("b", "1.0.0")
+ .feature_dep("manyfeat", "1.0", &["f2"])
+ .publish();
+ Package::new("c", "1.0.0")
+ .feature_dep("manyfeat", "1.0", &["f3"])
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ a = "1.0"
+ b = "1.0"
+ c = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("tree")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+├── a v1.0.0
+│ └── manyfeat v1.0.0
+│ └── bitflags v1.0.0
+├── b v1.0.0
+│ └── manyfeat v1.0.0 (*)
+└── c v1.0.0
+ └── manyfeat v1.0.0 (*)
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn renamed_deps() {
+ // Handles renamed dependencies.
+ Package::new("one", "1.0.0").publish();
+ Package::new("two", "1.0.0").publish();
+ Package::new("bar", "1.0.0").dep("one", "1.0").publish();
+ Package::new("bar", "2.0.0").dep("two", "1.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "1.0.0"
+
+ [dependencies]
+ bar1 = {version = "1.0", package="bar"}
+ bar2 = {version = "2.0", package="bar"}
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("tree")
+ .with_stdout(
+ "\
+foo v1.0.0 ([..]/foo)
+├── bar v1.0.0
+│ └── one v1.0.0
+└── bar v2.0.0
+ └── two v1.0.0
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn source_kinds() {
+ // Handles git and path sources.
+ Package::new("regdep", "1.0.0").publish();
+ let git_project = git::new("gitdep", |p| {
+ p.file("Cargo.toml", &basic_manifest("gitdep", "1.0.0"))
+ .file("src/lib.rs", "")
+ });
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ regdep = "1.0"
+ pathdep = {{ path = "pathdep" }}
+ gitdep = {{ git = "{}" }}
+ "#,
+ git_project.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .file("pathdep/Cargo.toml", &basic_manifest("pathdep", "1.0.0"))
+ .file("pathdep/src/lib.rs", "")
+ .build();
+
+ p.cargo("tree")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+├── gitdep v1.0.0 (file://[..]/gitdep#[..])
+├── pathdep v1.0.0 ([..]/foo/pathdep)
+└── regdep v1.0.0
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn features() {
+ // Exercises a variety of feature behaviors.
+ Package::new("optdep_default", "1.0.0").publish();
+ Package::new("optdep", "1.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+
+ [dependencies]
+ optdep_default = { version = "1.0", optional = true }
+ optdep = { version = "1.0", optional = true }
+
+ [features]
+ default = ["optdep_default"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("tree")
+ .with_stdout(
+ "\
+a v0.1.0 ([..]/foo)
+└── optdep_default v1.0.0
+",
+ )
+ .run();
+
+ p.cargo("tree --no-default-features")
+ .with_stdout(
+ "\
+a v0.1.0 ([..]/foo)
+",
+ )
+ .run();
+
+ p.cargo("tree --all-features")
+ .with_stdout(
+ "\
+a v0.1.0 ([..]/foo)
+├── optdep v1.0.0
+└── optdep_default v1.0.0
+",
+ )
+ .run();
+
+ p.cargo("tree --features optdep")
+ .with_stdout(
+ "\
+a v0.1.0 ([..]/foo)
+├── optdep v1.0.0
+└── optdep_default v1.0.0
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn filters_target() {
+ // --target flag
+ if cross_compile::disabled() {
+ return;
+ }
+ Package::new("targetdep", "1.0.0").publish();
+ Package::new("hostdep", "1.0.0").publish();
+ Package::new("devdep", "1.0.0").publish();
+ Package::new("build_target_dep", "1.0.0").publish();
+ Package::new("build_host_dep", "1.0.0")
+ .target_dep("targetdep", "1.0", alternate())
+ .target_dep("hostdep", "1.0", rustc_host())
+ .publish();
+ Package::new("pm_target", "1.0.0")
+ .proc_macro(true)
+ .publish();
+ Package::new("pm_host", "1.0.0").proc_macro(true).publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [target.'{alt}'.dependencies]
+ targetdep = "1.0"
+ pm_target = "1.0"
+
+ [target.'{host}'.dependencies]
+ hostdep = "1.0"
+ pm_host = "1.0"
+
+ [target.'{alt}'.dev-dependencies]
+ devdep = "1.0"
+
+ [target.'{alt}'.build-dependencies]
+ build_target_dep = "1.0"
+
+ [target.'{host}'.build-dependencies]
+ build_host_dep = "1.0"
+ "#,
+ alt = alternate(),
+ host = rustc_host()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .file("build.rs", "fn main() {}")
+ .build();
+
+ p.cargo("tree")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+├── hostdep v1.0.0
+└── pm_host v1.0.0 (proc-macro)
+[build-dependencies]
+└── build_host_dep v1.0.0
+ └── hostdep v1.0.0
+",
+ )
+ .run();
+
+ p.cargo("tree --target")
+ .arg(alternate())
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+├── pm_target v1.0.0 (proc-macro)
+└── targetdep v1.0.0
+[build-dependencies]
+└── build_host_dep v1.0.0
+ └── hostdep v1.0.0
+[dev-dependencies]
+└── devdep v1.0.0
+",
+ )
+ .run();
+
+ p.cargo("tree --target")
+ .arg(rustc_host())
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+├── hostdep v1.0.0
+└── pm_host v1.0.0 (proc-macro)
+[build-dependencies]
+└── build_host_dep v1.0.0
+ └── hostdep v1.0.0
+",
+ )
+ .run();
+
+ p.cargo("tree --target=all")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+├── hostdep v1.0.0
+├── pm_host v1.0.0 (proc-macro)
+├── pm_target v1.0.0 (proc-macro)
+└── targetdep v1.0.0
+[build-dependencies]
+├── build_host_dep v1.0.0
+│ ├── hostdep v1.0.0
+│ └── targetdep v1.0.0
+└── build_target_dep v1.0.0
+[dev-dependencies]
+└── devdep v1.0.0
+",
+ )
+ .run();
+
+ // no-proc-macro
+ p.cargo("tree --target=all -e no-proc-macro")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+├── hostdep v1.0.0
+└── targetdep v1.0.0
+[build-dependencies]
+├── build_host_dep v1.0.0
+│ ├── hostdep v1.0.0
+│ └── targetdep v1.0.0
+└── build_target_dep v1.0.0
+[dev-dependencies]
+└── devdep v1.0.0
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn no_selected_target_dependency() {
+ // --target flag
+ if cross_compile::disabled() {
+ return;
+ }
+ Package::new("targetdep", "1.0.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [target.'{alt}'.dependencies]
+ targetdep = "1.0"
+
+ "#,
+ alt = alternate(),
+ ),
+ )
+ .file("src/lib.rs", "")
+ .file("build.rs", "fn main() {}")
+ .build();
+
+ p.cargo("tree")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+",
+ )
+ .run();
+
+ p.cargo("tree -i targetdep")
+ .with_stderr(
+ "\
+[WARNING] nothing to print.
+
+To find dependencies that require specific target platforms, \
+try to use option `--target all` first, and then narrow your search scope accordingly.
+",
+ )
+ .run();
+ p.cargo("tree -i targetdep --target all")
+ .with_stdout(
+ "\
+targetdep v1.0.0
+└── foo v0.1.0 ([..]/foo)
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn dep_kinds() {
+ Package::new("inner-devdep", "1.0.0").publish();
+ Package::new("inner-builddep", "1.0.0").publish();
+ Package::new("inner-normal", "1.0.0").publish();
+ Package::new("inner-pm", "1.0.0").proc_macro(true).publish();
+ Package::new("inner-buildpm", "1.0.0")
+ .proc_macro(true)
+ .publish();
+ Package::new("normaldep", "1.0.0")
+ .dep("inner-normal", "1.0")
+ .dev_dep("inner-devdep", "1.0")
+ .build_dep("inner-builddep", "1.0")
+ .publish();
+ Package::new("devdep", "1.0.0")
+ .dep("inner-normal", "1.0")
+ .dep("inner-pm", "1.0")
+ .dev_dep("inner-devdep", "1.0")
+ .build_dep("inner-builddep", "1.0")
+ .build_dep("inner-buildpm", "1.0")
+ .publish();
+ Package::new("builddep", "1.0.0")
+ .dep("inner-normal", "1.0")
+ .dev_dep("inner-devdep", "1.0")
+ .build_dep("inner-builddep", "1.0")
+ .publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ normaldep = "1.0"
+
+ [dev-dependencies]
+ devdep = "1.0"
+
+ [build-dependencies]
+ builddep = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("tree")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+└── normaldep v1.0.0
+ └── inner-normal v1.0.0
+ [build-dependencies]
+ └── inner-builddep v1.0.0
+[build-dependencies]
+└── builddep v1.0.0
+ └── inner-normal v1.0.0
+ [build-dependencies]
+ └── inner-builddep v1.0.0
+[dev-dependencies]
+└── devdep v1.0.0
+ ├── inner-normal v1.0.0
+ └── inner-pm v1.0.0 (proc-macro)
+ [build-dependencies]
+ ├── inner-builddep v1.0.0
+ └── inner-buildpm v1.0.0 (proc-macro)
+",
+ )
+ .run();
+
+ p.cargo("tree -e no-dev")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+└── normaldep v1.0.0
+ └── inner-normal v1.0.0
+ [build-dependencies]
+ └── inner-builddep v1.0.0
+[build-dependencies]
+└── builddep v1.0.0
+ └── inner-normal v1.0.0
+ [build-dependencies]
+ └── inner-builddep v1.0.0
+",
+ )
+ .run();
+
+ p.cargo("tree -e normal")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+└── normaldep v1.0.0
+ └── inner-normal v1.0.0
+",
+ )
+ .run();
+
+ p.cargo("tree -e dev,build")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+[build-dependencies]
+└── builddep v1.0.0
+ [build-dependencies]
+ └── inner-builddep v1.0.0
+[dev-dependencies]
+└── devdep v1.0.0
+ [build-dependencies]
+ ├── inner-builddep v1.0.0
+ └── inner-buildpm v1.0.0 (proc-macro)
+",
+ )
+ .run();
+
+ p.cargo("tree -e dev,build,no-proc-macro")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+[build-dependencies]
+└── builddep v1.0.0
+ [build-dependencies]
+ └── inner-builddep v1.0.0
+[dev-dependencies]
+└── devdep v1.0.0
+ [build-dependencies]
+ └── inner-builddep v1.0.0
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cyclic_dev_dep() {
+ // Cyclical dev-dependency and inverse flag.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dev-dependencies]
+ dev-dep = { path = "dev-dep" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "dev-dep/Cargo.toml",
+ r#"
+ [package]
+ name = "dev-dep"
+ version = "0.1.0"
+
+ [dependencies]
+ foo = { path=".." }
+ "#,
+ )
+ .file("dev-dep/src/lib.rs", "")
+ .build();
+
+ p.cargo("tree")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+[dev-dependencies]
+└── dev-dep v0.1.0 ([..]/foo/dev-dep)
+ └── foo v0.1.0 ([..]/foo) (*)
+",
+ )
+ .run();
+
+ p.cargo("tree --invert foo")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+└── dev-dep v0.1.0 ([..]/foo/dev-dep)
+ [dev-dependencies]
+ └── foo v0.1.0 ([..]/foo) (*)
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn invert() {
+ Package::new("b1", "1.0.0").dep("c", "1.0").publish();
+ Package::new("b2", "1.0.0").dep("d", "1.0").publish();
+ Package::new("c", "1.0.0").publish();
+ Package::new("d", "1.0.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ b1 = "1.0"
+ b2 = "1.0"
+ c = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("tree")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+├── b1 v1.0.0
+│ └── c v1.0.0
+├── b2 v1.0.0
+│ └── d v1.0.0
+└── c v1.0.0
+",
+ )
+ .run();
+
+ p.cargo("tree --invert c")
+ .with_stdout(
+ "\
+c v1.0.0
+├── b1 v1.0.0
+│ └── foo v0.1.0 ([..]/foo)
+└── foo v0.1.0 ([..]/foo)
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn invert_with_build_dep() {
+ // -i for a common dependency between normal and build deps.
+ Package::new("common", "1.0.0").publish();
+ Package::new("bdep", "1.0.0").dep("common", "1.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ common = "1.0"
+
+ [build-dependencies]
+ bdep = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("tree")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+└── common v1.0.0
+[build-dependencies]
+└── bdep v1.0.0
+ └── common v1.0.0
+",
+ )
+ .run();
+
+ p.cargo("tree -i common")
+ .with_stdout(
+ "\
+common v1.0.0
+├── bdep v1.0.0
+│ [build-dependencies]
+│ └── foo v0.1.0 ([..]/foo)
+└── foo v0.1.0 ([..]/foo)
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn no_indent() {
+ let p = make_simple_proj();
+
+ p.cargo("tree --prefix=none")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+a v1.0.0
+b v1.0.0
+c v1.0.0
+c v1.0.0
+bdep v1.0.0
+b v1.0.0 (*)
+devdep v1.0.0
+b v1.0.0 (*)
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn prefix_depth() {
+ let p = make_simple_proj();
+
+ p.cargo("tree --prefix=depth")
+ .with_stdout(
+ "\
+0foo v0.1.0 ([..]/foo)
+1a v1.0.0
+2b v1.0.0
+3c v1.0.0
+1c v1.0.0
+1bdep v1.0.0
+2b v1.0.0 (*)
+1devdep v1.0.0
+2b v1.0.0 (*)
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn no_dedupe() {
+ let p = make_simple_proj();
+
+ p.cargo("tree --no-dedupe")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+├── a v1.0.0
+│ └── b v1.0.0
+│ └── c v1.0.0
+└── c v1.0.0
+[build-dependencies]
+└── bdep v1.0.0
+ └── b v1.0.0
+ └── c v1.0.0
+[dev-dependencies]
+└── devdep v1.0.0
+ └── b v1.0.0
+ └── c v1.0.0
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn no_dedupe_cycle() {
+ // --no-dedupe with a dependency cycle
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dev-dependencies]
+ bar = {path = "bar"}
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+
+ [dependencies]
+ foo = {path=".."}
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("tree")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+[dev-dependencies]
+└── bar v0.1.0 ([..]/foo/bar)
+ └── foo v0.1.0 ([..]/foo) (*)
+",
+ )
+ .run();
+
+ p.cargo("tree --no-dedupe")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+[dev-dependencies]
+└── bar v0.1.0 ([..]/foo/bar)
+ └── foo v0.1.0 ([..]/foo) (*)
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn duplicates() {
+ Package::new("dog", "1.0.0").publish();
+ Package::new("dog", "2.0.0").publish();
+ Package::new("cat", "1.0.0").publish();
+ Package::new("cat", "2.0.0").publish();
+ Package::new("dep", "1.0.0")
+ .dep("dog", "1.0")
+ .dep("cat", "1.0")
+ .publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["a", "b"]
+ "#,
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+
+ [dependencies]
+ dog1 = { version = "1.0", package = "dog" }
+ dog2 = { version = "2.0", package = "dog" }
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.1.0"
+
+ [dependencies]
+ dep = "1.0"
+ cat = "2.0"
+ "#,
+ )
+ .file("b/src/lib.rs", "")
+ .build();
+
+ p.cargo("tree -p a")
+ .with_stdout(
+ "\
+a v0.1.0 ([..]/foo/a)
+├── dog v1.0.0
+└── dog v2.0.0
+",
+ )
+ .run();
+
+ p.cargo("tree -p b")
+ .with_stdout(
+ "\
+b v0.1.0 ([..]/foo/b)
+├── cat v2.0.0
+└── dep v1.0.0
+ ├── cat v1.0.0
+ └── dog v1.0.0
+",
+ )
+ .run();
+
+ p.cargo("tree -p a -d")
+ .with_stdout(
+ "\
+dog v1.0.0
+└── a v0.1.0 ([..]/foo/a)
+
+dog v2.0.0
+└── a v0.1.0 ([..]/foo/a)
+",
+ )
+ .run();
+
+ p.cargo("tree -p b -d")
+ .with_stdout(
+ "\
+cat v1.0.0
+└── dep v1.0.0
+ └── b v0.1.0 ([..]/foo/b)
+
+cat v2.0.0
+└── b v0.1.0 ([..]/foo/b)
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn duplicates_with_target() {
+ // --target flag
+ if cross_compile::disabled() {
+ return;
+ }
+ Package::new("a", "1.0.0").publish();
+ Package::new("dog", "1.0.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ a = "1.0"
+ dog = "1.0"
+
+ [build-dependencies]
+ a = "1.0"
+ dog = "1.0"
+
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("build.rs", "fn main() {}")
+ .build();
+ p.cargo("tree -d").with_stdout("").run();
+
+ p.cargo("tree -d --target")
+ .arg(alternate())
+ .with_stdout("")
+ .run();
+
+ p.cargo("tree -d --target")
+ .arg(rustc_host())
+ .with_stdout("")
+ .run();
+
+ p.cargo("tree -d --target=all").with_stdout("").run();
+}
+
+#[cargo_test]
+fn charset() {
+ let p = make_simple_proj();
+ p.cargo("tree --charset ascii")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+|-- a v1.0.0
+| `-- b v1.0.0
+| `-- c v1.0.0
+`-- c v1.0.0
+[build-dependencies]
+`-- bdep v1.0.0
+ `-- b v1.0.0 (*)
+[dev-dependencies]
+`-- devdep v1.0.0
+ `-- b v1.0.0 (*)
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn format() {
+ Package::new("dep", "1.0.0").publish();
+ Package::new("other-dep", "1.0.0").publish();
+
+ Package::new("dep_that_is_awesome", "1.0.0")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "dep_that_is_awesome"
+ version = "1.0.0"
+
+ [lib]
+ name = "awesome_dep"
+ "#,
+ )
+ .file("src/lib.rs", "pub struct Straw;")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ license = "MIT"
+ repository = "https://github.com/rust-lang/cargo"
+
+ [dependencies]
+ dep = {version="1.0", optional=true}
+ other-dep = {version="1.0", optional=true}
+ dep_that_is_awesome = {version="1.0", optional=true}
+
+
+ [features]
+ default = ["foo"]
+ foo = ["bar"]
+ bar = []
+ "#,
+ )
+ .file("src/main.rs", "")
+ .build();
+
+ p.cargo("tree --format <<<{p}>>>")
+ .with_stdout("<<<foo v0.1.0 ([..]/foo)>>>")
+ .run();
+
+ p.cargo("tree --format {}")
+ .with_stderr(
+ "\
+[ERROR] tree format `{}` not valid
+
+Caused by:
+ unsupported pattern ``
+",
+ )
+ .with_status(101)
+ .run();
+
+ p.cargo("tree --format {p}-{{hello}}")
+ .with_stdout("foo v0.1.0 ([..]/foo)-{hello}")
+ .run();
+
+ p.cargo("tree --format")
+ .arg("{p} {l} {r}")
+ .with_stdout("foo v0.1.0 ([..]/foo) MIT https://github.com/rust-lang/cargo")
+ .run();
+
+ p.cargo("tree --format")
+ .arg("{p} {f}")
+ .with_stdout("foo v0.1.0 ([..]/foo) bar,default,foo")
+ .run();
+
+ p.cargo("tree --all-features --format")
+ .arg("{p} [{f}]")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo) [bar,default,dep,dep_that_is_awesome,foo,other-dep]
+├── dep v1.0.0 []
+├── dep_that_is_awesome v1.0.0 []
+└── other-dep v1.0.0 []
+",
+ )
+ .run();
+
+ p.cargo("tree")
+ .arg("--features=other-dep,dep_that_is_awesome")
+ .arg("--format={lib}")
+ .with_stdout(
+ "
+├── awesome_dep
+└── other_dep
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn dev_dep_feature() {
+ // New feature resolver with optional dep
+ Package::new("optdep", "1.0.0").publish();
+ Package::new("bar", "1.0.0")
+ .add_dep(Dependency::new("optdep", "1.0").optional(true))
+ .publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dev-dependencies]
+ bar = { version = "1.0", features = ["optdep"] }
+
+ [dependencies]
+ bar = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ // Old behavior.
+ p.cargo("tree")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+└── bar v1.0.0
+ └── optdep v1.0.0
+[dev-dependencies]
+└── bar v1.0.0 (*)
+",
+ )
+ .run();
+
+ p.cargo("tree -e normal")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+└── bar v1.0.0
+ └── optdep v1.0.0
+",
+ )
+ .run();
+
+ // New behavior.
+ switch_to_resolver_2(&p);
+
+ p.cargo("tree")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+└── bar v1.0.0
+ └── optdep v1.0.0
+[dev-dependencies]
+└── bar v1.0.0 (*)
+",
+ )
+ .run();
+
+ p.cargo("tree -e normal")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+└── bar v1.0.0
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn host_dep_feature() {
+ // New feature resolver with optional build dep
+ Package::new("optdep", "1.0.0").publish();
+ Package::new("bar", "1.0.0")
+ .add_dep(Dependency::new("optdep", "1.0").optional(true))
+ .publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [build-dependencies]
+ bar = { version = "1.0", features = ["optdep"] }
+
+ [dependencies]
+ bar = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("build.rs", "fn main() {}")
+ .build();
+
+ // Old behavior
+ p.cargo("tree")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+└── bar v1.0.0
+ └── optdep v1.0.0
+[build-dependencies]
+└── bar v1.0.0 (*)
+",
+ )
+ .run();
+
+ // -p
+ p.cargo("tree -p bar")
+ .with_stdout(
+ "\
+bar v1.0.0
+└── optdep v1.0.0
+",
+ )
+ .run();
+
+ // invert
+ p.cargo("tree -i optdep")
+ .with_stdout(
+ "\
+optdep v1.0.0
+└── bar v1.0.0
+ └── foo v0.1.0 ([..]/foo)
+ [build-dependencies]
+ └── foo v0.1.0 ([..]/foo)
+",
+ )
+ .run();
+
+ // New behavior.
+ switch_to_resolver_2(&p);
+
+ p.cargo("tree")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+└── bar v1.0.0
+[build-dependencies]
+└── bar v1.0.0
+ └── optdep v1.0.0
+",
+ )
+ .run();
+
+ p.cargo("tree -p bar")
+ .with_stdout(
+ "\
+bar v1.0.0
+
+bar v1.0.0
+└── optdep v1.0.0
+",
+ )
+ .run();
+
+ p.cargo("tree -i optdep")
+ .with_stdout(
+ "\
+optdep v1.0.0
+└── bar v1.0.0
+ [build-dependencies]
+ └── foo v0.1.0 ([..]/foo)
+",
+ )
+ .run();
+
+ // Check that -d handles duplicates with features.
+ p.cargo("tree -d")
+ .with_stdout(
+ "\
+bar v1.0.0
+└── foo v0.1.0 ([..]/foo)
+
+bar v1.0.0
+[build-dependencies]
+└── foo v0.1.0 ([..]/foo)
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn proc_macro_features() {
+ // New feature resolver with a proc-macro
+ Package::new("optdep", "1.0.0").publish();
+ Package::new("somedep", "1.0.0")
+ .add_dep(Dependency::new("optdep", "1.0").optional(true))
+ .publish();
+ Package::new("pm", "1.0.0")
+ .proc_macro(true)
+ .feature_dep("somedep", "1.0", &["optdep"])
+ .publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ pm = "1.0"
+ somedep = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ // Old behavior
+ p.cargo("tree")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+├── pm v1.0.0 (proc-macro)
+│ └── somedep v1.0.0
+│ └── optdep v1.0.0
+└── somedep v1.0.0 (*)
+",
+ )
+ .run();
+
+ // Old behavior + no-proc-macro
+ p.cargo("tree -e no-proc-macro")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+└── somedep v1.0.0
+ └── optdep v1.0.0
+",
+ )
+ .run();
+
+ // -p
+ p.cargo("tree -p somedep")
+ .with_stdout(
+ "\
+somedep v1.0.0
+└── optdep v1.0.0
+",
+ )
+ .run();
+
+ // -p -e no-proc-macro
+ p.cargo("tree -p somedep -e no-proc-macro")
+ .with_stdout(
+ "\
+somedep v1.0.0
+└── optdep v1.0.0
+",
+ )
+ .run();
+
+ // invert
+ p.cargo("tree -i somedep")
+ .with_stdout(
+ "\
+somedep v1.0.0
+├── foo v0.1.0 ([..]/foo)
+└── pm v1.0.0 (proc-macro)
+ └── foo v0.1.0 ([..]/foo)
+",
+ )
+ .run();
+
+ // invert + no-proc-macro
+ p.cargo("tree -i somedep -e no-proc-macro")
+ .with_stdout(
+ "\
+somedep v1.0.0
+└── foo v0.1.0 ([..]/foo)
+",
+ )
+ .run();
+
+ // New behavior.
+ switch_to_resolver_2(&p);
+
+ // Note the missing (*)
+ p.cargo("tree")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+├── pm v1.0.0 (proc-macro)
+│ └── somedep v1.0.0
+│ └── optdep v1.0.0
+└── somedep v1.0.0
+",
+ )
+ .run();
+
+ p.cargo("tree -e no-proc-macro")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+└── somedep v1.0.0
+",
+ )
+ .run();
+
+ p.cargo("tree -p somedep")
+ .with_stdout(
+ "\
+somedep v1.0.0
+
+somedep v1.0.0
+└── optdep v1.0.0
+",
+ )
+ .run();
+
+ p.cargo("tree -i somedep")
+ .with_stdout(
+ "\
+somedep v1.0.0
+└── foo v0.1.0 ([..]/foo)
+
+somedep v1.0.0
+└── pm v1.0.0 (proc-macro)
+ └── foo v0.1.0 ([..]/foo)
+",
+ )
+ .run();
+
+ p.cargo("tree -i somedep -e no-proc-macro")
+ .with_stdout(
+ "\
+somedep v1.0.0
+└── foo v0.1.0 ([..]/foo)
+
+somedep v1.0.0
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn itarget_opt_dep() {
+ // New feature resolver with optional target dep
+ Package::new("optdep", "1.0.0").publish();
+ Package::new("common", "1.0.0")
+ .add_dep(Dependency::new("optdep", "1.0").optional(true))
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "1.0.0"
+
+ [dependencies]
+ common = "1.0"
+
+ [target.'cfg(whatever)'.dependencies]
+ common = { version = "1.0", features = ["optdep"] }
+
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ // Old behavior
+ p.cargo("tree")
+ .with_stdout(
+ "\
+foo v1.0.0 ([..]/foo)
+└── common v1.0.0
+ └── optdep v1.0.0
+",
+ )
+ .run();
+
+ // New behavior.
+ switch_to_resolver_2(&p);
+
+ p.cargo("tree")
+ .with_stdout(
+ "\
+foo v1.0.0 ([..]/foo)
+└── common v1.0.0
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn ambiguous_name() {
+ // -p that is ambiguous.
+ Package::new("dep", "1.0.0").publish();
+ Package::new("dep", "2.0.0").publish();
+ Package::new("bar", "1.0.0").dep("dep", "2.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ dep = "1.0"
+ bar = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("tree -p dep")
+ .with_stderr_contains(
+ "\
+error: There are multiple `dep` packages in your project, and the specification `dep` is ambiguous.
+Please re-run this command with `-p <spec>` where `<spec>` is one of the following:
+ dep@1.0.0
+ dep@2.0.0
+",
+ )
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn workspace_features_are_local() {
+ // The features for workspace packages should be the same as `cargo build`
+ // (i.e., the features selected depend on the "current" package).
+ Package::new("optdep", "1.0.0").publish();
+ Package::new("somedep", "1.0.0")
+ .add_dep(Dependency::new("optdep", "1.0").optional(true))
+ .publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["a", "b"]
+ "#,
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+
+ [dependencies]
+ somedep = {version="1.0", features=["optdep"]}
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.1.0"
+
+ [dependencies]
+ somedep = "1.0"
+ "#,
+ )
+ .file("b/src/lib.rs", "")
+ .build();
+
+ p.cargo("tree")
+ .with_stdout(
+ "\
+a v0.1.0 ([..]/foo/a)
+└── somedep v1.0.0
+ └── optdep v1.0.0
+
+b v0.1.0 ([..]/foo/b)
+└── somedep v1.0.0 (*)
+",
+ )
+ .run();
+
+ p.cargo("tree -p a")
+ .with_stdout(
+ "\
+a v0.1.0 ([..]/foo/a)
+└── somedep v1.0.0
+ └── optdep v1.0.0
+",
+ )
+ .run();
+
+ p.cargo("tree -p b")
+ .with_stdout(
+ "\
+b v0.1.0 ([..]/foo/b)
+└── somedep v1.0.0
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn unknown_edge_kind() {
+ let p = project()
+ .file("Cargo.toml", "")
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("tree -e unknown")
+ .with_stderr(
+ "\
+[ERROR] unknown edge kind `unknown`, valid values are \
+\"normal\", \"build\", \"dev\", \
+\"no-normal\", \"no-build\", \"no-dev\", \"no-proc-macro\", \
+\"features\", or \"all\"
+",
+ )
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn mixed_no_edge_kinds() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("tree -e no-build,normal")
+ .with_stderr(
+ "\
+[ERROR] `normal` dependency kind cannot be mixed with \
+\"no-normal\", \"no-build\", or \"no-dev\" dependency kinds
+",
+ )
+ .with_status(101)
+ .run();
+
+ // `no-proc-macro` can be mixed with others
+ p.cargo("tree -e no-proc-macro,normal")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn depth_limit() {
+ let p = make_simple_proj();
+
+ p.cargo("tree --depth 0")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+[build-dependencies]
+[dev-dependencies]
+",
+ )
+ .run();
+
+ p.cargo("tree --depth 1")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+├── a v1.0.0
+└── c v1.0.0
+[build-dependencies]
+└── bdep v1.0.0
+[dev-dependencies]
+└── devdep v1.0.0
+",
+ )
+ .run();
+
+ p.cargo("tree --depth 2")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+├── a v1.0.0
+│ └── b v1.0.0
+└── c v1.0.0
+[build-dependencies]
+└── bdep v1.0.0
+ └── b v1.0.0 (*)
+[dev-dependencies]
+└── devdep v1.0.0
+ └── b v1.0.0 (*)
+",
+ )
+ .run();
+
+ // specify a package
+ p.cargo("tree -p bdep --depth 1")
+ .with_stdout(
+ "\
+bdep v1.0.0
+└── b v1.0.0
+",
+ )
+ .run();
+
+ // different prefix
+ p.cargo("tree --depth 1 --prefix depth")
+ .with_stdout(
+ "\
+0foo v0.1.0 ([..]/foo)
+1a v1.0.0
+1c v1.0.0
+1bdep v1.0.0
+1devdep v1.0.0
+",
+ )
+ .run();
+
+ // with edge-kinds
+ p.cargo("tree --depth 1 -e no-dev")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+├── a v1.0.0
+└── c v1.0.0
+[build-dependencies]
+└── bdep v1.0.0
+",
+ )
+ .run();
+
+ // invert
+ p.cargo("tree --depth 1 --invert c")
+ .with_stdout(
+ "\
+c v1.0.0
+├── b v1.0.0
+└── foo v0.1.0 ([..]/foo)
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn prune() {
+ let p = make_simple_proj();
+
+ p.cargo("tree --prune c")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+└── a v1.0.0
+ └── b v1.0.0
+[build-dependencies]
+└── bdep v1.0.0
+ └── b v1.0.0 (*)
+[dev-dependencies]
+└── devdep v1.0.0
+ └── b v1.0.0 (*)
+",
+ )
+ .run();
+
+ // multiple prune
+ p.cargo("tree --prune c --prune bdep")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+└── a v1.0.0
+ └── b v1.0.0
+[build-dependencies]
+[dev-dependencies]
+└── devdep v1.0.0
+ └── b v1.0.0 (*)
+",
+ )
+ .run();
+
+ // with edge-kinds
+ p.cargo("tree --prune c -e normal")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+└── a v1.0.0
+ └── b v1.0.0
+",
+ )
+ .run();
+
+ // pruning self does not works
+ p.cargo("tree --prune foo")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+├── a v1.0.0
+│ └── b v1.0.0
+│ └── c v1.0.0
+└── c v1.0.0
+[build-dependencies]
+└── bdep v1.0.0
+ └── b v1.0.0 (*)
+[dev-dependencies]
+└── devdep v1.0.0
+ └── b v1.0.0 (*)
+",
+ )
+ .run();
+
+ // dep not exist
+ p.cargo("tree --prune no-dep")
+ .with_stderr(
+ "\
+[ERROR] package ID specification `no-dep` did not match any packages
+
+<tab>Did you mean `bdep`?
+",
+ )
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn cyclic_features() {
+ // Check for stack overflow with cyclic features (oops!).
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "1.0.0"
+
+ [features]
+ a = ["b"]
+ b = ["a"]
+ default = ["a"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("tree -e features")
+ .with_stdout("foo v1.0.0 ([ROOT]/foo)")
+ .run();
+
+ p.cargo("tree -e features -i foo")
+ .with_stdout(
+ "\
+foo v1.0.0 ([ROOT]/foo)
+├── foo feature \"a\"
+│ ├── foo feature \"b\"
+│ │ └── foo feature \"a\" (*)
+│ └── foo feature \"default\" (command-line)
+├── foo feature \"b\" (*)
+└── foo feature \"default\" (command-line)
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn dev_dep_cycle_with_feature() {
+ // Cycle with features and a dev-dependency.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "1.0.0"
+
+ [dev-dependencies]
+ bar = { path = "bar" }
+
+ [features]
+ a = ["bar/feat1"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "1.0.0"
+
+ [dependencies]
+ foo = { path = ".." }
+
+ [features]
+ feat1 = ["foo/a"]
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("tree -e features --features a")
+ .with_stdout(
+ "\
+foo v1.0.0 ([ROOT]/foo)
+[dev-dependencies]
+└── bar feature \"default\"
+ └── bar v1.0.0 ([ROOT]/foo/bar)
+ └── foo feature \"default\" (command-line)
+ └── foo v1.0.0 ([ROOT]/foo) (*)
+",
+ )
+ .run();
+
+ p.cargo("tree -e features --features a -i foo")
+ .with_stdout(
+ "\
+foo v1.0.0 ([ROOT]/foo)
+├── foo feature \"a\" (command-line)
+│ └── bar feature \"feat1\"
+│ └── foo feature \"a\" (command-line) (*)
+└── foo feature \"default\" (command-line)
+ └── bar v1.0.0 ([ROOT]/foo/bar)
+ ├── bar feature \"default\"
+ │ [dev-dependencies]
+ │ └── foo v1.0.0 ([ROOT]/foo) (*)
+ └── bar feature \"feat1\" (*)
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn dev_dep_cycle_with_feature_nested() {
+ // Checks for an issue where a cyclic dev dependency tries to activate a
+ // feature on its parent that tries to activate the feature back on the
+ // dev-dependency.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "1.0.0"
+
+ [dev-dependencies]
+ bar = { path = "bar" }
+
+ [features]
+ a = ["bar/feat1"]
+ b = ["a"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "1.0.0"
+
+ [dependencies]
+ foo = { path = ".." }
+
+ [features]
+ feat1 = ["foo/b"]
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("tree -e features")
+ .with_stdout(
+ "\
+foo v1.0.0 ([ROOT]/foo)
+[dev-dependencies]
+└── bar feature \"default\"
+ └── bar v1.0.0 ([ROOT]/foo/bar)
+ └── foo feature \"default\" (command-line)
+ └── foo v1.0.0 ([ROOT]/foo) (*)
+",
+ )
+ .run();
+
+ p.cargo("tree -e features --features a -i foo")
+ .with_stdout(
+ "\
+foo v1.0.0 ([ROOT]/foo)
+├── foo feature \"a\" (command-line)
+│ └── foo feature \"b\"
+│ └── bar feature \"feat1\"
+│ └── foo feature \"a\" (command-line) (*)
+├── foo feature \"b\" (*)
+└── foo feature \"default\" (command-line)
+ └── bar v1.0.0 ([ROOT]/foo/bar)
+ ├── bar feature \"default\"
+ │ [dev-dependencies]
+ │ └── foo v1.0.0 ([ROOT]/foo) (*)
+ └── bar feature \"feat1\" (*)
+",
+ )
+ .run();
+
+ p.cargo("tree -e features --features b -i foo")
+ .with_stdout(
+ "\
+foo v1.0.0 ([ROOT]/foo)
+├── foo feature \"a\"
+│ └── foo feature \"b\" (command-line)
+│ └── bar feature \"feat1\"
+│ └── foo feature \"a\" (*)
+├── foo feature \"b\" (command-line) (*)
+└── foo feature \"default\" (command-line)
+ └── bar v1.0.0 ([ROOT]/foo/bar)
+ ├── bar feature \"default\"
+ │ [dev-dependencies]
+ │ └── foo v1.0.0 ([ROOT]/foo) (*)
+ └── bar feature \"feat1\" (*)
+",
+ )
+ .run();
+
+ p.cargo("tree -e features --features bar/feat1 -i foo")
+ .with_stdout(
+ "\
+foo v1.0.0 ([ROOT]/foo)
+├── foo feature \"a\"
+│ └── foo feature \"b\"
+│ └── bar feature \"feat1\" (command-line)
+│ └── foo feature \"a\" (*)
+├── foo feature \"b\" (*)
+└── foo feature \"default\" (command-line)
+ └── bar v1.0.0 ([ROOT]/foo/bar)
+ ├── bar feature \"default\"
+ │ [dev-dependencies]
+ │ └── foo v1.0.0 ([ROOT]/foo) (*)
+ └── bar feature \"feat1\" (command-line) (*)
+",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/tree_graph_features.rs b/src/tools/cargo/tests/testsuite/tree_graph_features.rs
new file mode 100644
index 000000000..48d654c06
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/tree_graph_features.rs
@@ -0,0 +1,362 @@
+//! Tests for the `cargo tree` command with -e features option.
+
+use cargo_test_support::project;
+use cargo_test_support::registry::{Dependency, Package};
+
+#[cargo_test]
+fn dep_feature_various() {
+ // Checks different ways of setting features via dependencies.
+ Package::new("optdep", "1.0.0")
+ .feature("default", &["cat"])
+ .feature("cat", &[])
+ .publish();
+ Package::new("defaultdep", "1.0.0")
+ .feature("default", &["f1"])
+ .feature("f1", &["optdep"])
+ .add_dep(Dependency::new("optdep", "1.0").optional(true))
+ .publish();
+ Package::new("nodefaultdep", "1.0.0")
+ .feature("default", &["f1"])
+ .feature("f1", &[])
+ .publish();
+ Package::new("nameddep", "1.0.0")
+ .add_dep(Dependency::new("serde", "1.0").optional(true))
+ .feature("default", &["serde-stuff"])
+ .feature("serde-stuff", &["serde/derive"])
+ .feature("vehicle", &["car"])
+ .feature("car", &[])
+ .publish();
+ Package::new("serde_derive", "1.0.0").publish();
+ Package::new("serde", "1.0.0")
+ .feature("derive", &["serde_derive"])
+ .add_dep(Dependency::new("serde_derive", "1.0").optional(true))
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ defaultdep = "1.0"
+ nodefaultdep = {version="1.0", default-features = false}
+ nameddep = {version="1.0", features = ["vehicle", "serde"]}
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("tree -e features")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+├── nodefaultdep v1.0.0
+├── defaultdep feature \"default\"
+│ ├── defaultdep v1.0.0
+│ │ └── optdep feature \"default\"
+│ │ ├── optdep v1.0.0
+│ │ └── optdep feature \"cat\"
+│ │ └── optdep v1.0.0
+│ └── defaultdep feature \"f1\"
+│ ├── defaultdep v1.0.0 (*)
+│ └── defaultdep feature \"optdep\"
+│ └── defaultdep v1.0.0 (*)
+├── nameddep feature \"default\"
+│ ├── nameddep v1.0.0
+│ │ └── serde feature \"default\"
+│ │ └── serde v1.0.0
+│ │ └── serde_derive feature \"default\"
+│ │ └── serde_derive v1.0.0
+│ └── nameddep feature \"serde-stuff\"
+│ ├── nameddep v1.0.0 (*)
+│ ├── nameddep feature \"serde\"
+│ │ └── nameddep v1.0.0 (*)
+│ └── serde feature \"derive\"
+│ ├── serde v1.0.0 (*)
+│ └── serde feature \"serde_derive\"
+│ └── serde v1.0.0 (*)
+├── nameddep feature \"serde\" (*)
+└── nameddep feature \"vehicle\"
+ ├── nameddep v1.0.0 (*)
+ └── nameddep feature \"car\"
+ └── nameddep v1.0.0 (*)
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn graph_features_ws_interdependent() {
+ // A workspace with interdependent crates.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["a", "b"]
+ "#,
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+
+ [dependencies]
+ b = {path="../b", features=["feat2"]}
+
+ [features]
+ default = ["a1"]
+ a1 = []
+ a2 = []
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.1.0"
+
+ [features]
+ default = ["feat1"]
+ feat1 = []
+ feat2 = []
+ "#,
+ )
+ .file("b/src/lib.rs", "")
+ .build();
+
+ p.cargo("tree -e features")
+ .with_stdout(
+ "\
+a v0.1.0 ([..]/foo/a)
+├── b feature \"default\" (command-line)
+│ ├── b v0.1.0 ([..]/foo/b)
+│ └── b feature \"feat1\"
+│ └── b v0.1.0 ([..]/foo/b)
+└── b feature \"feat2\"
+ └── b v0.1.0 ([..]/foo/b)
+
+b v0.1.0 ([..]/foo/b)
+",
+ )
+ .run();
+
+ p.cargo("tree -e features -i a -i b")
+ .with_stdout(
+ "\
+a v0.1.0 ([..]/foo/a)
+├── a feature \"a1\"
+│ └── a feature \"default\" (command-line)
+└── a feature \"default\" (command-line)
+
+b v0.1.0 ([..]/foo/b)
+├── b feature \"default\" (command-line)
+│ └── a v0.1.0 ([..]/foo/a) (*)
+├── b feature \"feat1\"
+│ └── b feature \"default\" (command-line) (*)
+└── b feature \"feat2\"
+ └── a v0.1.0 ([..]/foo/a) (*)
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn slash_feature_name() {
+ // dep_name/feat_name syntax
+ Package::new("opt", "1.0.0").feature("feat1", &[]).publish();
+ Package::new("notopt", "1.0.0")
+ .feature("cat", &[])
+ .feature("animal", &["cat"])
+ .publish();
+ Package::new("opt2", "1.0.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ opt = {version = "1.0", optional=true}
+ opt2 = {version = "1.0", optional=true}
+ notopt = "1.0"
+
+ [features]
+ f1 = ["opt/feat1", "notopt/animal"]
+ f2 = ["f1"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("tree -e features --features f1")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+├── notopt feature \"default\"
+│ └── notopt v1.0.0
+└── opt feature \"default\"
+ └── opt v1.0.0
+",
+ )
+ .run();
+
+ p.cargo("tree -e features --features f1 -i foo")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+├── foo feature \"default\" (command-line)
+├── foo feature \"f1\" (command-line)
+└── foo feature \"opt\"
+ └── foo feature \"f1\" (command-line)
+",
+ )
+ .run();
+
+ p.cargo("tree -e features --features f1 -i notopt")
+ .with_stdout(
+ "\
+notopt v1.0.0
+├── notopt feature \"animal\"
+│ └── foo feature \"f1\" (command-line)
+├── notopt feature \"cat\"
+│ └── notopt feature \"animal\" (*)
+└── notopt feature \"default\"
+ └── foo v0.1.0 ([..]/foo)
+ ├── foo feature \"default\" (command-line)
+ ├── foo feature \"f1\" (command-line)
+ └── foo feature \"opt\"
+ └── foo feature \"f1\" (command-line)
+",
+ )
+ .run();
+
+ p.cargo("tree -e features --features notopt/animal -i notopt")
+ .with_stdout(
+ "\
+notopt v1.0.0
+├── notopt feature \"animal\" (command-line)
+├── notopt feature \"cat\"
+│ └── notopt feature \"animal\" (command-line)
+└── notopt feature \"default\"
+ └── foo v0.1.0 ([..]/foo)
+ └── foo feature \"default\" (command-line)
+",
+ )
+ .run();
+
+ p.cargo("tree -e features --all-features")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+├── notopt feature \"default\"
+│ └── notopt v1.0.0
+├── opt feature \"default\"
+│ └── opt v1.0.0
+└── opt2 feature \"default\"
+ └── opt2 v1.0.0
+",
+ )
+ .run();
+
+ p.cargo("tree -e features --all-features -i opt2")
+ .with_stdout(
+ "\
+opt2 v1.0.0
+└── opt2 feature \"default\"
+ └── foo v0.1.0 ([..]/foo)
+ ├── foo feature \"default\" (command-line)
+ ├── foo feature \"f1\" (command-line)
+ │ └── foo feature \"f2\" (command-line)
+ ├── foo feature \"f2\" (command-line)
+ ├── foo feature \"opt\" (command-line)
+ │ └── foo feature \"f1\" (command-line) (*)
+ └── foo feature \"opt2\" (command-line)
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn features_enables_inactive_target() {
+ // Features that enable things on targets that are not enabled.
+ Package::new("optdep", "1.0.0")
+ .feature("feat1", &[])
+ .publish();
+ Package::new("dep1", "1.0.0")
+ .feature("somefeat", &[])
+ .publish();
+ Package::new("dep2", "1.0.0")
+ .add_dep(
+ Dependency::new("optdep", "1.0.0")
+ .optional(true)
+ .target("cfg(whatever)"),
+ )
+ .publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [target.'cfg(whatever)'.dependencies]
+ optdep = {version="1.0", optional=true}
+ dep1 = "1.0"
+
+ [dependencies]
+ dep2 = "1.0"
+
+ [features]
+ f1 = ["optdep"]
+ f2 = ["optdep/feat1"]
+ f3 = ["dep1/somefeat"]
+ f4 = ["dep2/optdep"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("tree -e features")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+└── dep2 feature \"default\"
+ └── dep2 v1.0.0
+",
+ )
+ .run();
+ p.cargo("tree -e features --all-features")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+└── dep2 feature \"default\"
+ └── dep2 v1.0.0
+",
+ )
+ .run();
+ p.cargo("tree -e features --all-features --target=all")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo)
+├── dep1 feature \"default\"
+│ └── dep1 v1.0.0
+├── dep2 feature \"default\"
+│ └── dep2 v1.0.0
+│ └── optdep feature \"default\"
+│ └── optdep v1.0.0
+└── optdep feature \"default\" (*)
+",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/unit_graph.rs b/src/tools/cargo/tests/testsuite/unit_graph.rs
new file mode 100644
index 000000000..91451177a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/unit_graph.rs
@@ -0,0 +1,233 @@
+//! Tests for --unit-graph option.
+
+use cargo_test_support::project;
+use cargo_test_support::registry::Package;
+
+#[cargo_test]
+fn gated() {
+ let p = project().file("src/lib.rs", "").build();
+ p.cargo("build --unit-graph")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] the `--unit-graph` flag is unstable[..]
+See [..]
+See [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn simple() {
+ Package::new("a", "1.0.0")
+ .dep("b", "1.0")
+ .feature("feata", &["b/featb"])
+ .publish();
+ Package::new("b", "1.0.0")
+ .dep("c", "1.0")
+ .feature("featb", &["c/featc"])
+ .publish();
+ Package::new("c", "1.0.0").feature("featc", &[]).publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ a = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build --features a/feata --unit-graph -Zunstable-options")
+ .masquerade_as_nightly_cargo(&["unit-graph"])
+ .with_json(
+ r#"{
+ "roots": [
+ 3
+ ],
+ "units": [
+ {
+ "dependencies": [
+ {
+ "extern_crate_name": "b",
+ "index": 1,
+ "noprelude": false,
+ "public": false
+ }
+ ],
+ "features": [
+ "feata"
+ ],
+ "mode": "build",
+ "pkg_id": "a 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "platform": null,
+ "profile": {
+ "codegen_backend": null,
+ "codegen_units": null,
+ "debug_assertions": true,
+ "debuginfo": 2,
+ "incremental": false,
+ "lto": "false",
+ "name": "dev",
+ "opt_level": "0",
+ "overflow_checks": true,
+ "panic": "unwind",
+ "rpath": false,
+ "split_debuginfo": "{...}",
+ "strip": "none"
+ },
+ "target": {
+ "crate_types": [
+ "lib"
+ ],
+ "doc": true,
+ "doctest": true,
+ "edition": "2015",
+ "kind": [
+ "lib"
+ ],
+ "name": "a",
+ "src_path": "[..]/a-1.0.0/src/lib.rs",
+ "test": true
+ }
+ },
+ {
+ "dependencies": [
+ {
+ "extern_crate_name": "c",
+ "index": 2,
+ "noprelude": false,
+ "public": false
+ }
+ ],
+ "features": [
+ "featb"
+ ],
+ "mode": "build",
+ "pkg_id": "b 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "platform": null,
+ "profile": {
+ "codegen_backend": null,
+ "codegen_units": null,
+ "debug_assertions": true,
+ "debuginfo": 2,
+ "incremental": false,
+ "lto": "false",
+ "name": "dev",
+ "opt_level": "0",
+ "overflow_checks": true,
+ "panic": "unwind",
+ "rpath": false,
+ "split_debuginfo": "{...}",
+ "strip": "none"
+ },
+ "target": {
+ "crate_types": [
+ "lib"
+ ],
+ "doc": true,
+ "doctest": true,
+ "edition": "2015",
+ "kind": [
+ "lib"
+ ],
+ "name": "b",
+ "src_path": "[..]/b-1.0.0/src/lib.rs",
+ "test": true
+ }
+ },
+ {
+ "dependencies": [],
+ "features": [
+ "featc"
+ ],
+ "mode": "build",
+ "pkg_id": "c 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "platform": null,
+ "profile": {
+ "codegen_backend": null,
+ "codegen_units": null,
+ "debug_assertions": true,
+ "debuginfo": 2,
+ "incremental": false,
+ "lto": "false",
+ "name": "dev",
+ "opt_level": "0",
+ "overflow_checks": true,
+ "panic": "unwind",
+ "rpath": false,
+ "split_debuginfo": "{...}",
+ "strip": "none"
+ },
+ "target": {
+ "crate_types": [
+ "lib"
+ ],
+ "doc": true,
+ "doctest": true,
+ "edition": "2015",
+ "kind": [
+ "lib"
+ ],
+ "name": "c",
+ "src_path": "[..]/c-1.0.0/src/lib.rs",
+ "test": true
+ }
+ },
+ {
+ "dependencies": [
+ {
+ "extern_crate_name": "a",
+ "index": 0,
+ "noprelude": false,
+ "public": false
+ }
+ ],
+ "features": [],
+ "mode": "build",
+ "pkg_id": "foo 0.1.0 (path+file://[..]/foo)",
+ "platform": null,
+ "profile": {
+ "codegen_backend": null,
+ "codegen_units": null,
+ "debug_assertions": true,
+ "debuginfo": 2,
+ "incremental": false,
+ "lto": "false",
+ "name": "dev",
+ "opt_level": "0",
+ "overflow_checks": true,
+ "panic": "unwind",
+ "rpath": false,
+ "split_debuginfo": "{...}",
+ "strip": "none"
+ },
+ "target": {
+ "crate_types": [
+ "lib"
+ ],
+ "doc": true,
+ "doctest": true,
+ "edition": "2015",
+ "kind": [
+ "lib"
+ ],
+ "name": "foo",
+ "src_path": "[..]/foo/src/lib.rs",
+ "test": true
+ }
+ }
+ ],
+ "version": 1
+ }
+ "#,
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/update.rs b/src/tools/cargo/tests/testsuite/update.rs
new file mode 100644
index 000000000..057c8fca4
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/update.rs
@@ -0,0 +1,832 @@
+//! Tests for the `cargo update` command.
+
+use cargo_test_support::registry::Package;
+use cargo_test_support::{basic_manifest, project};
+
+#[cargo_test]
+fn minor_update_two_places() {
+ Package::new("log", "0.1.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ log = "0.1"
+ foo = { path = "foo" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ log = "0.1"
+ "#,
+ )
+ .file("foo/src/lib.rs", "")
+ .build();
+
+ p.cargo("check").run();
+ Package::new("log", "0.1.1").publish();
+
+ p.change_file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ log = "0.1.1"
+ "#,
+ );
+
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn transitive_minor_update() {
+ Package::new("log", "0.1.0").publish();
+ Package::new("serde", "0.1.0").dep("log", "0.1").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ serde = "0.1"
+ log = "0.1"
+ foo = { path = "foo" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ serde = "0.1"
+ "#,
+ )
+ .file("foo/src/lib.rs", "")
+ .build();
+
+ p.cargo("check").run();
+
+ Package::new("log", "0.1.1").publish();
+ Package::new("serde", "0.1.1").dep("log", "0.1.1").publish();
+
+ // Note that `serde` isn't actually updated here! The default behavior for
+ // `update` right now is to as conservatively as possible attempt to satisfy
+ // an update. In this case we previously locked the dependency graph to `log
+ // 0.1.0`, but nothing on the command line says we're allowed to update
+ // that. As a result the update of `serde` here shouldn't update to `serde
+ // 0.1.1` as that would also force an update to `log 0.1.1`.
+ //
+ // Also note that this is probably counterintuitive and weird. We may wish
+ // to change this one day.
+ p.cargo("update -p serde")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn conservative() {
+ Package::new("log", "0.1.0").publish();
+ Package::new("serde", "0.1.0").dep("log", "0.1").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ serde = "0.1"
+ log = "0.1"
+ foo = { path = "foo" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ serde = "0.1"
+ "#,
+ )
+ .file("foo/src/lib.rs", "")
+ .build();
+
+ p.cargo("check").run();
+
+ Package::new("log", "0.1.1").publish();
+ Package::new("serde", "0.1.1").dep("log", "0.1").publish();
+
+ p.cargo("update -p serde")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[UPDATING] serde v0.1.0 -> v0.1.1
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn update_via_new_dep() {
+ Package::new("log", "0.1.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ log = "0.1"
+ # foo = { path = "foo" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ log = "0.1.1"
+ "#,
+ )
+ .file("foo/src/lib.rs", "")
+ .build();
+
+ p.cargo("check").run();
+ Package::new("log", "0.1.1").publish();
+
+ p.uncomment_root_manifest();
+ p.cargo("check").env("CARGO_LOG", "cargo=trace").run();
+}
+
+#[cargo_test]
+fn update_via_new_member() {
+ Package::new("log", "0.1.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [workspace]
+ # members = [ "foo" ]
+
+ [dependencies]
+ log = "0.1"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ log = "0.1.1"
+ "#,
+ )
+ .file("foo/src/lib.rs", "")
+ .build();
+
+ p.cargo("check").run();
+ Package::new("log", "0.1.1").publish();
+
+ p.uncomment_root_manifest();
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn add_dep_deep_new_requirement() {
+ Package::new("log", "0.1.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ log = "0.1"
+ # bar = "0.1"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check").run();
+
+ Package::new("log", "0.1.1").publish();
+ Package::new("bar", "0.1.0").dep("log", "0.1.1").publish();
+
+ p.uncomment_root_manifest();
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn everything_real_deep() {
+ Package::new("log", "0.1.0").publish();
+ Package::new("foo", "0.1.0").dep("log", "0.1").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ foo = "0.1"
+ # bar = "0.1"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check").run();
+
+ Package::new("log", "0.1.1").publish();
+ Package::new("bar", "0.1.0").dep("log", "0.1.1").publish();
+
+ p.uncomment_root_manifest();
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn change_package_version() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "a-foo"
+ version = "0.2.0-alpha"
+ authors = []
+
+ [dependencies]
+ bar = { path = "bar", version = "0.2.0-alpha" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.2.0-alpha"))
+ .file("bar/src/lib.rs", "")
+ .file(
+ "Cargo.lock",
+ r#"
+ [[package]]
+ name = "foo"
+ version = "0.2.0"
+ dependencies = ["bar 0.2.0"]
+
+ [[package]]
+ name = "bar"
+ version = "0.2.0"
+ "#,
+ )
+ .build();
+
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn update_precise() {
+ Package::new("serde", "0.1.0").publish();
+ Package::new("serde", "0.2.1").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ serde = "0.2"
+ foo = { path = "foo" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ serde = "0.1"
+ "#,
+ )
+ .file("foo/src/lib.rs", "")
+ .build();
+
+ p.cargo("check").run();
+
+ Package::new("serde", "0.2.0").publish();
+
+ p.cargo("update -p serde:0.2.1 --precise 0.2.0")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNGRADING] serde v0.2.1 -> v0.2.0
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn update_precise_do_not_force_update_deps() {
+ Package::new("log", "0.1.0").publish();
+ Package::new("serde", "0.2.1").dep("log", "0.1").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ serde = "0.2"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check").run();
+
+ Package::new("log", "0.1.1").publish();
+ Package::new("serde", "0.2.2").dep("log", "0.1").publish();
+
+ p.cargo("update -p serde:0.2.1 --precise 0.2.2")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[UPDATING] serde v0.2.1 -> v0.2.2
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn update_aggressive() {
+ Package::new("log", "0.1.0").publish();
+ Package::new("serde", "0.2.1").dep("log", "0.1").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ serde = "0.2"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check").run();
+
+ Package::new("log", "0.1.1").publish();
+ Package::new("serde", "0.2.2").dep("log", "0.1").publish();
+
+ p.cargo("update -p serde:0.2.1 --aggressive")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[UPDATING] log v0.1.0 -> v0.1.1
+[UPDATING] serde v0.2.1 -> v0.2.2
+",
+ )
+ .run();
+}
+
+// cargo update should respect its arguments even without a lockfile.
+// See issue "Running cargo update without a Cargo.lock ignores arguments"
+// at <https://github.com/rust-lang/cargo/issues/6872>.
+#[cargo_test]
+fn update_precise_first_run() {
+ Package::new("serde", "0.1.0").publish();
+ Package::new("serde", "0.2.0").publish();
+ Package::new("serde", "0.2.1").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+
+ [dependencies]
+ serde = "0.2"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("update -p serde --precise 0.2.0")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNGRADING] serde v0.2.1 -> v0.2.0
+",
+ )
+ .run();
+
+ // Assert `cargo metadata` shows serde 0.2.0
+ p.cargo("metadata")
+ .with_json(
+ r#"{
+ "packages": [
+ {
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "dependencies": [
+ {
+ "features": [],
+ "kind": null,
+ "name": "serde",
+ "optional": false,
+ "registry": null,
+ "rename": null,
+ "req": "^0.2",
+ "source": "registry+https://github.com/rust-lang/crates.io-index",
+ "target": null,
+ "uses_default_features": true
+ }
+ ],
+ "description": null,
+ "documentation": null,
+ "edition": "2015",
+ "features": {},
+ "homepage": null,
+ "id": "bar 0.0.1 (path+file://[..]/foo)",
+ "keywords": [],
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "manifest_path": "[..]/foo/Cargo.toml",
+ "metadata": null,
+ "publish": null,
+ "name": "bar",
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "source": null,
+ "targets": [
+ {
+ "crate_types": [
+ "lib"
+ ],
+ "doc": true,
+ "doctest": true,
+ "test": true,
+ "edition": "2015",
+ "kind": [
+ "lib"
+ ],
+ "name": "bar",
+ "src_path": "[..]/foo/src/lib.rs"
+ }
+ ],
+ "version": "0.0.1"
+ },
+ {
+ "authors": [],
+ "categories": [],
+ "default_run": null,
+ "dependencies": [],
+ "description": null,
+ "documentation": null,
+ "edition": "2015",
+ "features": {},
+ "homepage": null,
+ "id": "serde 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "keywords": [],
+ "license": null,
+ "license_file": null,
+ "links": null,
+ "manifest_path": "[..]/home/.cargo/registry/src/-[..]/serde-0.2.0/Cargo.toml",
+ "metadata": null,
+ "publish": null,
+ "name": "serde",
+ "readme": null,
+ "repository": null,
+ "rust_version": null,
+ "source": "registry+https://github.com/rust-lang/crates.io-index",
+ "targets": [
+ {
+ "crate_types": [
+ "lib"
+ ],
+ "doc": true,
+ "doctest": true,
+ "edition": "2015",
+ "kind": [
+ "lib"
+ ],
+ "name": "serde",
+ "src_path": "[..]/home/.cargo/registry/src/-[..]/serde-0.2.0/src/lib.rs",
+ "test": true
+ }
+ ],
+ "version": "0.2.0"
+ }
+ ],
+ "resolve": {
+ "nodes": [
+ {
+ "dependencies": [
+ "serde 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)"
+ ],
+ "deps": [
+ {
+ "dep_kinds": [
+ {
+ "kind": null,
+ "target": null
+ }
+ ],
+ "name": "serde",
+ "pkg": "serde 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)"
+ }
+ ],
+ "features": [],
+ "id": "bar 0.0.1 (path+file://[..]/foo)"
+ },
+ {
+ "dependencies": [],
+ "deps": [],
+ "features": [],
+ "id": "serde 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)"
+ }
+ ],
+ "root": "bar 0.0.1 (path+file://[..]/foo)"
+ },
+ "target_directory": "[..]/foo/target",
+ "version": 1,
+ "workspace_members": [
+ "bar 0.0.1 (path+file://[..]/foo)"
+ ],
+ "workspace_root": "[..]/foo",
+ "metadata": null
+}"#,
+ )
+ .run();
+
+ p.cargo("update -p serde --precise 0.2.0")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn preserve_top_comment() {
+ let p = project().file("src/lib.rs", "").build();
+
+ p.cargo("update").run();
+
+ let lockfile = p.read_lockfile();
+ assert!(lockfile.starts_with("# This file is automatically @generated by Cargo.\n# It is not intended for manual editing.\n"));
+
+ let mut lines = lockfile.lines().collect::<Vec<_>>();
+ lines.insert(2, "# some other comment");
+ let mut lockfile = lines.join("\n");
+ lockfile.push('\n'); // .lines/.join loses the last newline
+ println!("saving Cargo.lock contents:\n{}", lockfile);
+
+ p.change_file("Cargo.lock", &lockfile);
+
+ p.cargo("update").run();
+
+ let lockfile2 = p.read_lockfile();
+ println!("loaded Cargo.lock contents:\n{}", lockfile2);
+
+ assert_eq!(lockfile, lockfile2);
+}
+
+#[cargo_test]
+fn dry_run_update() {
+ Package::new("log", "0.1.0").publish();
+ Package::new("serde", "0.1.0").dep("log", "0.1").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ serde = "0.1"
+ log = "0.1"
+ foo = { path = "foo" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ serde = "0.1"
+ "#,
+ )
+ .file("foo/src/lib.rs", "")
+ .build();
+
+ p.cargo("check").run();
+ let old_lockfile = p.read_lockfile();
+
+ Package::new("log", "0.1.1").publish();
+ Package::new("serde", "0.1.1").dep("log", "0.1").publish();
+
+ p.cargo("update -p serde --dry-run")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[UPDATING] serde v0.1.0 -> v0.1.1
+[WARNING] not updating lockfile due to dry run
+",
+ )
+ .run();
+ let new_lockfile = p.read_lockfile();
+ assert_eq!(old_lockfile, new_lockfile)
+}
+
+#[cargo_test]
+fn workspace_only() {
+ let p = project().file("src/main.rs", "fn main() {}").build();
+ p.cargo("generate-lockfile").run();
+ let lock1 = p.read_lockfile();
+
+ p.change_file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.2"
+ "#,
+ );
+ p.cargo("update --workspace").run();
+ let lock2 = p.read_lockfile();
+
+ assert_ne!(lock1, lock2);
+ assert!(lock1.contains("0.0.1"));
+ assert!(lock2.contains("0.0.2"));
+ assert!(!lock1.contains("0.0.2"));
+ assert!(!lock2.contains("0.0.1"));
+}
+
+#[cargo_test]
+fn precise_with_build_metadata() {
+ // +foo syntax shouldn't be necessary with --precise
+ Package::new("bar", "0.1.0+extra-stuff.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "0.1"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("generate-lockfile").run();
+ Package::new("bar", "0.1.1+extra-stuff.1").publish();
+ Package::new("bar", "0.1.2+extra-stuff.2").publish();
+
+ p.cargo("update -p bar --precise 0.1")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: invalid version format for precise version `0.1`
+
+Caused by:
+ unexpected end of input while parsing minor version number
+",
+ )
+ .run();
+
+ p.cargo("update -p bar --precise 0.1.1+does-not-match")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] [..] index
+error: no matching package named `bar` found
+location searched: registry `crates-io`
+required by package `foo v0.1.0 ([ROOT]/foo)`
+",
+ )
+ .run();
+
+ p.cargo("update -p bar --precise 0.1.1")
+ .with_stderr(
+ "\
+[UPDATING] [..] index
+[UPDATING] bar v0.1.0+extra-stuff.0 -> v0.1.1+extra-stuff.1
+",
+ )
+ .run();
+
+ Package::new("bar", "0.1.3").publish();
+ p.cargo("update -p bar --precise 0.1.3+foo")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] [..] index
+error: no matching package named `bar` found
+location searched: registry `crates-io`
+required by package `foo v0.1.0 ([ROOT]/foo)`
+",
+ )
+ .run();
+
+ p.cargo("update -p bar --precise 0.1.3")
+ .with_stderr(
+ "\
+[UPDATING] [..] index
+[UPDATING] bar v0.1.1+extra-stuff.1 -> v0.1.3
+",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/vendor.rs b/src/tools/cargo/tests/testsuite/vendor.rs
new file mode 100644
index 000000000..21a1c097c
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/vendor.rs
@@ -0,0 +1,1152 @@
+//! Tests for the `cargo vendor` command.
+//!
+//! Note that every test here uses `--respect-source-config` so that the
+//! "fake" crates.io is used. Otherwise `vendor` would download the crates.io
+//! index from the network.
+
+use std::fs;
+
+use cargo_test_support::git;
+use cargo_test_support::registry::{self, Package, RegistryBuilder};
+use cargo_test_support::{basic_lib_manifest, basic_manifest, paths, project, Project};
+
+#[cargo_test]
+fn vendor_simple() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ log = "0.3.5"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ Package::new("log", "0.3.5").publish();
+
+ p.cargo("vendor --respect-source-config").run();
+ let lock = p.read_file("vendor/log/Cargo.toml");
+ assert!(lock.contains("version = \"0.3.5\""));
+
+ add_vendor_config(&p);
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn vendor_sample_config() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ log = "0.3.5"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ Package::new("log", "0.3.5").publish();
+
+ p.cargo("vendor --respect-source-config")
+ .with_stdout(
+ r#"[source.crates-io]
+replace-with = "vendored-sources"
+
+[source.vendored-sources]
+directory = "vendor"
+"#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn vendor_sample_config_alt_registry() {
+ let registry = RegistryBuilder::new().alternative().http_index().build();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ log = { version = "0.3.5", registry = "alternative" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ Package::new("log", "0.3.5").alternative(true).publish();
+
+ p.cargo("vendor --respect-source-config")
+ .with_stdout(format!(
+ r#"[source."{0}"]
+registry = "{0}"
+replace-with = "vendored-sources"
+
+[source.vendored-sources]
+directory = "vendor"
+"#,
+ registry.index_url()
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn vendor_path_specified() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ log = "0.3.5"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ Package::new("log", "0.3.5").publish();
+
+ let path = if cfg!(windows) {
+ r#"deps\.vendor"#
+ } else {
+ "deps/.vendor"
+ };
+
+ let output = p
+ .cargo("vendor --respect-source-config")
+ .arg(path)
+ .exec_with_output()
+ .unwrap();
+ // Assert against original output to ensure that
+ // path is normalized by `ops::vendor` on Windows.
+ assert_eq!(
+ &String::from_utf8(output.stdout).unwrap(),
+ r#"[source.crates-io]
+replace-with = "vendored-sources"
+
+[source.vendored-sources]
+directory = "deps/.vendor"
+"#
+ );
+
+ let lock = p.read_file("deps/.vendor/log/Cargo.toml");
+ assert!(lock.contains("version = \"0.3.5\""));
+}
+
+fn add_vendor_config(p: &Project) {
+ p.change_file(
+ ".cargo/config",
+ r#"
+ [source.crates-io]
+ replace-with = 'vendor'
+
+ [source.vendor]
+ directory = 'vendor'
+ "#,
+ );
+}
+
+#[cargo_test]
+fn package_exclude() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ Package::new("bar", "0.1.0")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ exclude = [".*", "!.include", "!.dotdir/include"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(".exclude", "")
+ .file(".include", "")
+ .file(".dotdir/exclude", "")
+ .file(".dotdir/include", "")
+ .publish();
+
+ p.cargo("vendor --respect-source-config").run();
+ let csum = p.read_file("vendor/bar/.cargo-checksum.json");
+ assert!(csum.contains(".include"));
+ assert!(!csum.contains(".exclude"));
+ assert!(!csum.contains(".dotdir/exclude"));
+ // Gitignore doesn't re-include a file in an excluded parent directory,
+ // even if negating it explicitly.
+ assert!(!csum.contains(".dotdir/include"));
+}
+
+#[cargo_test]
+fn two_versions() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bitflags = "0.8.0"
+ bar = { path = "bar" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+
+ [dependencies]
+ bitflags = "0.7.0"
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ Package::new("bitflags", "0.7.0").publish();
+ Package::new("bitflags", "0.8.0").publish();
+
+ p.cargo("vendor --respect-source-config").run();
+
+ let lock = p.read_file("vendor/bitflags/Cargo.toml");
+ assert!(lock.contains("version = \"0.8.0\""));
+ let lock = p.read_file("vendor/bitflags-0.7.0/Cargo.toml");
+ assert!(lock.contains("version = \"0.7.0\""));
+
+ add_vendor_config(&p);
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn two_explicit_versions() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bitflags = "0.8.0"
+ bar = { path = "bar" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+
+ [dependencies]
+ bitflags = "0.7.0"
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ Package::new("bitflags", "0.7.0").publish();
+ Package::new("bitflags", "0.8.0").publish();
+
+ p.cargo("vendor --respect-source-config --versioned-dirs")
+ .run();
+
+ let lock = p.read_file("vendor/bitflags-0.8.0/Cargo.toml");
+ assert!(lock.contains("version = \"0.8.0\""));
+ let lock = p.read_file("vendor/bitflags-0.7.0/Cargo.toml");
+ assert!(lock.contains("version = \"0.7.0\""));
+
+ add_vendor_config(&p);
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn help() {
+ let p = project().build();
+ p.cargo("vendor -h").run();
+}
+
+#[cargo_test]
+fn update_versions() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bitflags = "0.7.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ Package::new("bitflags", "0.7.0").publish();
+ Package::new("bitflags", "0.8.0").publish();
+
+ p.cargo("vendor --respect-source-config").run();
+
+ let lock = p.read_file("vendor/bitflags/Cargo.toml");
+ assert!(lock.contains("version = \"0.7.0\""));
+
+ p.change_file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bitflags = "0.8.0"
+ "#,
+ );
+ p.cargo("vendor --respect-source-config").run();
+
+ let lock = p.read_file("vendor/bitflags/Cargo.toml");
+ assert!(lock.contains("version = \"0.8.0\""));
+}
+
+#[cargo_test]
+fn two_lockfiles() {
+ let p = project()
+ .no_manifest()
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bitflags = "=0.7.0"
+ "#,
+ )
+ .file("foo/src/lib.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+
+ [dependencies]
+ bitflags = "=0.8.0"
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ Package::new("bitflags", "0.7.0").publish();
+ Package::new("bitflags", "0.8.0").publish();
+
+ p.cargo("vendor --respect-source-config -s bar/Cargo.toml --manifest-path foo/Cargo.toml")
+ .run();
+
+ let lock = p.read_file("vendor/bitflags/Cargo.toml");
+ assert!(lock.contains("version = \"0.8.0\""));
+ let lock = p.read_file("vendor/bitflags-0.7.0/Cargo.toml");
+ assert!(lock.contains("version = \"0.7.0\""));
+
+ add_vendor_config(&p);
+ p.cargo("check").cwd("foo").run();
+ p.cargo("check").cwd("bar").run();
+}
+
+#[cargo_test]
+fn test_sync_argument() {
+ let p = project()
+ .no_manifest()
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bitflags = "=0.7.0"
+ "#,
+ )
+ .file("foo/src/lib.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+
+ [dependencies]
+ bitflags = "=0.8.0"
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .file(
+ "baz/Cargo.toml",
+ r#"
+ [package]
+ name = "baz"
+ version = "0.1.0"
+
+ [dependencies]
+ bitflags = "=0.8.0"
+ "#,
+ )
+ .file("baz/src/lib.rs", "")
+ .build();
+
+ Package::new("bitflags", "0.7.0").publish();
+ Package::new("bitflags", "0.8.0").publish();
+
+ p.cargo("vendor --respect-source-config --manifest-path foo/Cargo.toml -s bar/Cargo.toml baz/Cargo.toml test_vendor")
+ .with_stderr("\
+error: unexpected argument 'test_vendor' found
+
+Usage: cargo[EXE] vendor [OPTIONS] [path]
+
+For more information, try '--help'.",
+ )
+ .with_status(1)
+ .run();
+
+ p.cargo("vendor --respect-source-config --manifest-path foo/Cargo.toml -s bar/Cargo.toml -s baz/Cargo.toml test_vendor")
+ .run();
+
+ let lock = p.read_file("test_vendor/bitflags/Cargo.toml");
+ assert!(lock.contains("version = \"0.8.0\""));
+ let lock = p.read_file("test_vendor/bitflags-0.7.0/Cargo.toml");
+ assert!(lock.contains("version = \"0.7.0\""));
+}
+
+#[cargo_test]
+fn delete_old_crates() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bitflags = "=0.7.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ Package::new("bitflags", "0.7.0").publish();
+ Package::new("log", "0.3.5").publish();
+
+ p.cargo("vendor --respect-source-config").run();
+ p.read_file("vendor/bitflags/Cargo.toml");
+
+ p.change_file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ log = "=0.3.5"
+ "#,
+ );
+
+ p.cargo("vendor --respect-source-config").run();
+ let lock = p.read_file("vendor/log/Cargo.toml");
+ assert!(lock.contains("version = \"0.3.5\""));
+ assert!(!p.root().join("vendor/bitflags/Cargo.toml").exists());
+}
+
+#[cargo_test]
+fn ignore_files() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ url = "1.4.1"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ Package::new("url", "1.4.1")
+ .file("src/lib.rs", "")
+ .file("foo.orig", "")
+ .file(".gitignore", "")
+ .file(".gitattributes", "")
+ .file("foo.rej", "")
+ .publish();
+
+ p.cargo("vendor --respect-source-config").run();
+ let csum = p.read_file("vendor/url/.cargo-checksum.json");
+ assert!(!csum.contains("foo.orig"));
+ assert!(!csum.contains(".gitignore"));
+ assert!(!csum.contains(".gitattributes"));
+ assert!(!csum.contains(".cargo-ok"));
+ assert!(!csum.contains("foo.rej"));
+}
+
+#[cargo_test]
+fn included_files_only() {
+ let git = git::new("a", |p| {
+ p.file("Cargo.toml", &basic_lib_manifest("a"))
+ .file("src/lib.rs", "")
+ .file(".gitignore", "a")
+ .file("a/b.md", "")
+ });
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ a = {{ git = '{}' }}
+ "#,
+ git.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("vendor --respect-source-config").run();
+ let csum = p.read_file("vendor/a/.cargo-checksum.json");
+ assert!(!csum.contains("a/b.md"));
+}
+
+#[cargo_test]
+fn dependent_crates_in_crates() {
+ let git = git::new("a", |p| {
+ p.file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+
+ [dependencies]
+ b = { path = 'b' }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("b/Cargo.toml", &basic_lib_manifest("b"))
+ .file("b/src/lib.rs", "")
+ });
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ a = {{ git = '{}' }}
+ "#,
+ git.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("vendor --respect-source-config").run();
+ p.read_file("vendor/a/.cargo-checksum.json");
+ p.read_file("vendor/b/.cargo-checksum.json");
+}
+
+#[cargo_test]
+fn vendoring_git_crates() {
+ let git = git::new("git", |p| {
+ p.file("Cargo.toml", &basic_lib_manifest("serde_derive"))
+ .file("src/lib.rs", "")
+ .file("src/wut.rs", "")
+ });
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies.serde]
+ version = "0.5.0"
+
+ [dependencies.serde_derive]
+ version = "0.5.0"
+
+ [patch.crates-io]
+ serde_derive = {{ git = '{}' }}
+ "#,
+ git.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+ Package::new("serde", "0.5.0")
+ .dep("serde_derive", "0.5")
+ .publish();
+ Package::new("serde_derive", "0.5.0").publish();
+
+ p.cargo("vendor --respect-source-config").run();
+ p.read_file("vendor/serde_derive/src/wut.rs");
+
+ add_vendor_config(&p);
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn git_simple() {
+ let git = git::new("git", |p| {
+ p.file("Cargo.toml", &basic_lib_manifest("a"))
+ .file("src/lib.rs", "")
+ });
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ a = {{ git = '{}' }}
+ "#,
+ git.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("vendor --respect-source-config").run();
+ let csum = p.read_file("vendor/a/.cargo-checksum.json");
+ assert!(csum.contains("\"package\":null"));
+}
+
+#[cargo_test]
+fn git_diff_rev() {
+ let (git_project, git_repo) = git::new_repo("git", |p| {
+ p.file("Cargo.toml", &basic_manifest("a", "0.1.0"))
+ .file("src/lib.rs", "")
+ });
+ let url = git_project.url();
+ let ref_1 = "v0.1.0";
+ let ref_2 = "v0.2.0";
+
+ git::tag(&git_repo, ref_1);
+
+ git_project.change_file("Cargo.toml", &basic_manifest("a", "0.2.0"));
+ git::add(&git_repo);
+ git::commit(&git_repo);
+ git::tag(&git_repo, ref_2);
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ a_1 = {{ package = "a", git = '{url}', rev = '{ref_1}' }}
+ a_2 = {{ package = "a", git = '{url}', rev = '{ref_2}' }}
+ "#
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("vendor --respect-source-config")
+ .with_stdout(
+ r#"[source."git+file://[..]/git?rev=v0.1.0"]
+git = [..]
+rev = "v0.1.0"
+replace-with = "vendored-sources"
+
+[source."git+file://[..]/git?rev=v0.2.0"]
+git = [..]
+rev = "v0.2.0"
+replace-with = "vendored-sources"
+
+[source.vendored-sources]
+directory = "vendor"
+"#,
+ )
+ .run();
+}
+
+#[cargo_test]
+fn git_duplicate() {
+ let git = git::new("a", |p| {
+ p.file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+
+ [dependencies]
+ b = { path = 'b' }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("b/Cargo.toml", &basic_lib_manifest("b"))
+ .file("b/src/lib.rs", "")
+ });
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ a = {{ git = '{}' }}
+ b = '0.5.0'
+
+ "#,
+ git.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+ Package::new("b", "0.5.0").publish();
+
+ p.cargo("vendor --respect-source-config")
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[UPDATING] [..]
+[DOWNLOADING] [..]
+[DOWNLOADED] [..]
+error: failed to sync
+
+Caused by:
+ found duplicate version of package `b v0.5.0` vendored from two sources:
+
+ <tab>source 1: [..]
+ <tab>source 2: [..]
+",
+ )
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn git_complex() {
+ let git_b = git::new("git_b", |p| {
+ p.file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.1.0"
+
+ [dependencies]
+ dep_b = { path = 'dep_b' }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("dep_b/Cargo.toml", &basic_lib_manifest("dep_b"))
+ .file("dep_b/src/lib.rs", "")
+ });
+
+ let git_a = git::new("git_a", |p| {
+ p.file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+
+ [dependencies]
+ b = {{ git = '{}' }}
+ dep_a = {{ path = 'dep_a' }}
+ "#,
+ git_b.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .file("dep_a/Cargo.toml", &basic_lib_manifest("dep_a"))
+ .file("dep_a/src/lib.rs", "")
+ });
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ a = {{ git = '{}' }}
+ "#,
+ git_a.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ let output = p
+ .cargo("vendor --respect-source-config")
+ .exec_with_output()
+ .unwrap();
+ let output = String::from_utf8(output.stdout).unwrap();
+ p.change_file(".cargo/config", &output);
+
+ p.cargo("check -v")
+ .with_stderr_contains("[..]foo/vendor/a/src/lib.rs[..]")
+ .with_stderr_contains("[..]foo/vendor/dep_a/src/lib.rs[..]")
+ .with_stderr_contains("[..]foo/vendor/b/src/lib.rs[..]")
+ .with_stderr_contains("[..]foo/vendor/dep_b/src/lib.rs[..]")
+ .run();
+}
+
+#[cargo_test]
+fn depend_on_vendor_dir_not_deleted() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ libc = "0.2.30"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ Package::new("libc", "0.2.30").publish();
+
+ p.cargo("vendor --respect-source-config").run();
+ assert!(p.root().join("vendor/libc").is_dir());
+
+ p.change_file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ libc = "0.2.30"
+
+ [patch.crates-io]
+ libc = { path = 'vendor/libc' }
+ "#,
+ );
+
+ p.cargo("vendor --respect-source-config").run();
+ assert!(p.root().join("vendor/libc").is_dir());
+}
+
+#[cargo_test]
+fn ignore_hidden() {
+ // Don't delete files starting with `.`
+ Package::new("bar", "0.1.0").publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "1.0.0"
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("vendor --respect-source-config").run();
+ // Add a `.git` directory.
+ let repo = git::init(&p.root().join("vendor"));
+ git::add(&repo);
+ git::commit(&repo);
+ assert!(p.root().join("vendor/.git").exists());
+ // Vendor again, shouldn't change anything.
+ p.cargo("vendor --respect-source-config").run();
+ // .git should not be removed.
+ assert!(p.root().join("vendor/.git").exists());
+ // And just for good measure, make sure no files changed.
+ let mut opts = git2::StatusOptions::new();
+ assert!(repo
+ .statuses(Some(&mut opts))
+ .unwrap()
+ .iter()
+ .all(|status| status.status() == git2::Status::CURRENT));
+}
+
+#[cargo_test]
+fn config_instructions_works() {
+ // Check that the config instructions work for all dependency kinds.
+ registry::alt_init();
+ Package::new("dep", "0.1.0").publish();
+ Package::new("altdep", "0.1.0").alternative(true).publish();
+ let git_project = git::new("gitdep", |project| {
+ project
+ .file("Cargo.toml", &basic_lib_manifest("gitdep"))
+ .file("src/lib.rs", "")
+ });
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ dep = "0.1"
+ altdep = {{version="0.1", registry="alternative"}}
+ gitdep = {{git='{}'}}
+ "#,
+ git_project.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+ let output = p
+ .cargo("vendor --respect-source-config")
+ .exec_with_output()
+ .unwrap();
+ let output = String::from_utf8(output.stdout).unwrap();
+ p.change_file(".cargo/config", &output);
+
+ p.cargo("check -v")
+ .with_stderr_contains("[..]foo/vendor/dep/src/lib.rs[..]")
+ .with_stderr_contains("[..]foo/vendor/altdep/src/lib.rs[..]")
+ .with_stderr_contains("[..]foo/vendor/gitdep/src/lib.rs[..]")
+ .run();
+}
+
+#[cargo_test]
+fn git_crlf_preservation() {
+ // Check that newlines don't get changed when you vendor
+ // (will only fail if your system is setup with core.autocrlf=true on windows)
+ let input = "hello \nthere\nmy newline\nfriends";
+ let git_project = git::new("git", |p| {
+ p.file("Cargo.toml", &basic_lib_manifest("a"))
+ .file("src/lib.rs", input)
+ });
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ a = {{ git = '{}' }}
+ "#,
+ git_project.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ fs::write(
+ paths::home().join(".gitconfig"),
+ r#"
+ [core]
+ autocrlf = true
+ "#,
+ )
+ .unwrap();
+
+ p.cargo("vendor --respect-source-config").run();
+ let output = p.read_file("vendor/a/src/lib.rs");
+ assert_eq!(input, output);
+}
+
+#[cargo_test]
+#[cfg(unix)]
+fn vendor_preserves_permissions() {
+ use std::os::unix::fs::MetadataExt;
+
+ Package::new("bar", "1.0.0")
+ .file_with_mode("example.sh", 0o755, "#!/bin/sh")
+ .file("src/lib.rs", "")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("vendor --respect-source-config").run();
+
+ let metadata = fs::metadata(p.root().join("vendor/bar/src/lib.rs")).unwrap();
+ assert_eq!(metadata.mode() & 0o777, 0o644);
+ let metadata = fs::metadata(p.root().join("vendor/bar/example.sh")).unwrap();
+ assert_eq!(metadata.mode() & 0o777, 0o755);
+}
+
+#[cargo_test]
+fn no_remote_dependency_no_vendor() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ [dependencies]
+ bar = { path = "bar" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("vendor")
+ .with_stderr("There is no dependency to vendor in this project.")
+ .run();
+ assert!(!p.root().join("vendor").exists());
+}
+
+#[cargo_test]
+fn vendor_crate_with_ws_inherit() {
+ let git = git::new("ws", |p| {
+ p.file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar"]
+ [workspace.package]
+ version = "0.1.0"
+ "#,
+ )
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version.workspace = true
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ });
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = {{ git = '{}' }}
+ "#,
+ git.url()
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("vendor --respect-source-config").run();
+ p.change_file(
+ ".cargo/config",
+ &format!(
+ r#"
+ [source."{}"]
+ git = "{}"
+ replace-with = "vendor"
+
+ [source.vendor]
+ directory = "vendor"
+ "#,
+ git.url(),
+ git.url()
+ ),
+ );
+
+ p.cargo("check -v")
+ .with_stderr_contains("[..]foo/vendor/bar/src/lib.rs[..]")
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/verify_project.rs b/src/tools/cargo/tests/testsuite/verify_project.rs
new file mode 100644
index 000000000..216808fb5
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/verify_project.rs
@@ -0,0 +1,73 @@
+//! Tests for the `cargo verify-project` command.
+
+use cargo_test_support::{basic_bin_manifest, main_file, project};
+
+fn verify_project_success_output() -> String {
+ r#"{"success":"true"}"#.into()
+}
+
+#[cargo_test]
+fn cargo_verify_project_path_to_cargo_toml_relative() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .build();
+
+ p.cargo("verify-project --manifest-path foo/Cargo.toml")
+ .cwd(p.root().parent().unwrap())
+ .with_stdout(verify_project_success_output())
+ .run();
+}
+
+#[cargo_test]
+fn cargo_verify_project_path_to_cargo_toml_absolute() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .build();
+
+ p.cargo("verify-project --manifest-path")
+ .arg(p.root().join("Cargo.toml"))
+ .cwd(p.root().parent().unwrap())
+ .with_stdout(verify_project_success_output())
+ .run();
+}
+
+#[cargo_test]
+fn cargo_verify_project_cwd() {
+ let p = project()
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/foo.rs", &main_file(r#""i am foo""#, &[]))
+ .build();
+
+ p.cargo("verify-project")
+ .with_stdout(verify_project_success_output())
+ .run();
+}
+
+#[cargo_test]
+fn cargo_verify_project_honours_unstable_features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ cargo-features = ["test-dummy-unstable"]
+
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("verify-project")
+ .masquerade_as_nightly_cargo(&["test-dummy-unstable"])
+ .with_stdout(verify_project_success_output())
+ .run();
+
+ p.cargo("verify-project")
+ .with_status(1)
+ .with_json(r#"{"invalid":"failed to parse manifest at `[CWD]/Cargo.toml`"}"#)
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/version.rs b/src/tools/cargo/tests/testsuite/version.rs
new file mode 100644
index 000000000..f880c75a6
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/version.rs
@@ -0,0 +1,54 @@
+//! Tests for displaying the cargo version.
+
+use cargo_test_support::{cargo_process, project};
+
+#[cargo_test]
+fn simple() {
+ let p = project().build();
+
+ p.cargo("version")
+ .with_stdout(&format!("cargo {}\n", cargo::version()))
+ .run();
+
+ p.cargo("--version")
+ .with_stdout(&format!("cargo {}\n", cargo::version()))
+ .run();
+}
+
+#[cargo_test]
+fn version_works_without_rustc() {
+ let p = project().build();
+ p.cargo("version").env("PATH", "").run();
+}
+
+#[cargo_test]
+fn version_works_with_bad_config() {
+ let p = project().file(".cargo/config", "this is not toml").build();
+ p.cargo("version").run();
+}
+
+#[cargo_test]
+fn version_works_with_bad_target_dir() {
+ let p = project()
+ .file(
+ ".cargo/config",
+ r#"
+ [build]
+ target-dir = 4
+ "#,
+ )
+ .build();
+ p.cargo("version").run();
+}
+
+#[cargo_test]
+fn verbose() {
+ // This is mainly to check that it doesn't explode.
+ cargo_process("-vV")
+ .with_stdout_contains(&format!("cargo {}", cargo::version()))
+ .with_stdout_contains("host: [..]")
+ .with_stdout_contains("libgit2: [..]")
+ .with_stdout_contains("libcurl: [..]")
+ .with_stdout_contains("os: [..]")
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/warn_on_failure.rs b/src/tools/cargo/tests/testsuite/warn_on_failure.rs
new file mode 100644
index 000000000..19cb01813
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/warn_on_failure.rs
@@ -0,0 +1,111 @@
+//! Tests for whether or not warnings are displayed for build scripts.
+
+use cargo_test_support::registry::Package;
+use cargo_test_support::{project, Project};
+
+static WARNING1: &str = "Hello! I'm a warning. :)";
+static WARNING2: &str = "And one more!";
+
+fn make_lib(lib_src: &str) {
+ Package::new("bar", "0.0.1")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ authors = []
+ version = "0.0.1"
+ build = "build.rs"
+ "#,
+ )
+ .file(
+ "build.rs",
+ &format!(
+ r#"
+ fn main() {{
+ use std::io::Write;
+ println!("cargo:warning={{}}", "{}");
+ println!("hidden stdout");
+ write!(&mut ::std::io::stderr(), "hidden stderr");
+ println!("cargo:warning={{}}", "{}");
+ }}
+ "#,
+ WARNING1, WARNING2
+ ),
+ )
+ .file("src/lib.rs", &format!("fn f() {{ {} }}", lib_src))
+ .publish();
+}
+
+fn make_upstream(main_src: &str) -> Project {
+ project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "*"
+ "#,
+ )
+ .file("src/main.rs", &format!("fn main() {{ {} }}", main_src))
+ .build()
+}
+
+#[cargo_test]
+fn no_warning_on_success() {
+ make_lib("");
+ let upstream = make_upstream("");
+ upstream
+ .cargo("build")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v0.0.1 ([..])
+[COMPILING] bar v0.0.1
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn no_warning_on_bin_failure() {
+ make_lib("");
+ let upstream = make_upstream("hi()");
+ upstream
+ .cargo("build")
+ .with_status(101)
+ .with_stdout_does_not_contain("hidden stdout")
+ .with_stderr_does_not_contain("hidden stderr")
+ .with_stderr_does_not_contain(&format!("[WARNING] {}", WARNING1))
+ .with_stderr_does_not_contain(&format!("[WARNING] {}", WARNING2))
+ .with_stderr_contains("[UPDATING] `[..]` index")
+ .with_stderr_contains("[DOWNLOADED] bar v0.0.1 ([..])")
+ .with_stderr_contains("[COMPILING] bar v0.0.1")
+ .with_stderr_contains("[COMPILING] foo v0.0.1 ([..])")
+ .run();
+}
+
+#[cargo_test]
+fn warning_on_lib_failure() {
+ make_lib("err()");
+ let upstream = make_upstream("");
+ upstream
+ .cargo("build")
+ .with_status(101)
+ .with_stdout_does_not_contain("hidden stdout")
+ .with_stderr_does_not_contain("hidden stderr")
+ .with_stderr_does_not_contain("[COMPILING] foo v0.0.1 ([..])")
+ .with_stderr_contains("[UPDATING] `[..]` index")
+ .with_stderr_contains("[DOWNLOADED] bar v0.0.1 ([..])")
+ .with_stderr_contains("[COMPILING] bar v0.0.1")
+ .with_stderr_contains(&format!("[WARNING] {}", WARNING1))
+ .with_stderr_contains(&format!("[WARNING] {}", WARNING2))
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/weak_dep_features.rs b/src/tools/cargo/tests/testsuite/weak_dep_features.rs
new file mode 100644
index 000000000..ee91114df
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/weak_dep_features.rs
@@ -0,0 +1,632 @@
+//! Tests for weak-dep-features.
+
+use super::features2::switch_to_resolver_2;
+use cargo_test_support::paths::CargoPathExt;
+use cargo_test_support::registry::{Dependency, Package, RegistryBuilder};
+use cargo_test_support::{project, publish};
+use std::fmt::Write;
+
+// Helper to create lib.rs files that check features.
+fn require(enabled_features: &[&str], disabled_features: &[&str]) -> String {
+ let mut s = String::new();
+ for feature in enabled_features {
+ writeln!(s, "#[cfg(not(feature=\"{feature}\"))] compile_error!(\"expected feature {feature} to be enabled\");",
+ feature=feature).unwrap();
+ }
+ for feature in disabled_features {
+ writeln!(s, "#[cfg(feature=\"{feature}\")] compile_error!(\"did not expect feature {feature} to be enabled\");",
+ feature=feature).unwrap();
+ }
+ s
+}
+
+#[cargo_test]
+fn simple() {
+ Package::new("bar", "1.0.0")
+ .feature("feat", &[])
+ .file("src/lib.rs", &require(&["feat"], &[]))
+ .publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { version = "1.0", optional = true }
+
+ [features]
+ f1 = ["bar?/feat"]
+ "#,
+ )
+ .file("src/lib.rs", &require(&["f1"], &[]))
+ .build();
+
+ // It's a bit unfortunate that this has to download `bar`, but avoiding
+ // that is extremely difficult.
+ p.cargo("check --features f1")
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v1.0.0 [..]
+[CHECKING] foo v0.1.0 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ p.cargo("check --features f1,bar")
+ .with_stderr(
+ "\
+[CHECKING] bar v1.0.0
+[CHECKING] foo v0.1.0 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn deferred() {
+ // A complex chain that requires deferring enabling the feature due to
+ // another dependency getting enabled.
+ Package::new("bar", "1.0.0")
+ .feature("feat", &[])
+ .file("src/lib.rs", &require(&["feat"], &[]))
+ .publish();
+ Package::new("dep", "1.0.0")
+ .add_dep(Dependency::new("bar", "1.0").optional(true))
+ .feature("feat", &["bar?/feat"])
+ .publish();
+ Package::new("bar_activator", "1.0.0")
+ .feature_dep("dep", "1.0", &["bar"])
+ .publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ dep = { version = "1.0", features = ["feat"] }
+ bar_activator = "1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[DOWNLOADING] crates ...
+[DOWNLOADED] dep v1.0.0 [..]
+[DOWNLOADED] bar_activator v1.0.0 [..]
+[DOWNLOADED] bar v1.0.0 [..]
+[CHECKING] bar v1.0.0
+[CHECKING] dep v1.0.0
+[CHECKING] bar_activator v1.0.0
+[CHECKING] foo v0.1.0 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn not_optional_dep() {
+ // Attempt to use dep_name?/feat where dep_name is not optional.
+ Package::new("dep", "1.0.0").feature("feat", &[]).publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ dep = "1.0"
+
+ [features]
+ feat = ["dep?/feat"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr("\
+error: failed to parse manifest at `[ROOT]/foo/Cargo.toml`
+
+Caused by:
+ feature `feat` includes `dep?/feat` with a `?`, but `dep` is not an optional dependency
+ A non-optional dependency of the same name is defined; consider removing the `?` or changing the dependency to be optional
+")
+ .run();
+}
+
+#[cargo_test]
+fn optional_cli_syntax() {
+ // --features bar?/feat
+ Package::new("bar", "1.0.0")
+ .feature("feat", &[])
+ .file("src/lib.rs", &require(&["feat"], &[]))
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { version = "1.0", optional = true }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ // Does not build bar.
+ p.cargo("check --features bar?/feat")
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v1.0.0 [..]
+[CHECKING] foo v0.1.0 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ // Builds bar.
+ p.cargo("check --features bar?/feat,bar")
+ .with_stderr(
+ "\
+[CHECKING] bar v1.0.0
+[CHECKING] foo v0.1.0 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ eprintln!("check V2 resolver");
+ switch_to_resolver_2(&p);
+ p.build_dir().rm_rf();
+ // Does not build bar.
+ p.cargo("check --features bar?/feat")
+ .with_stderr(
+ "\
+[CHECKING] foo v0.1.0 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ // Builds bar.
+ p.cargo("check --features bar?/feat,bar")
+ .with_stderr(
+ "\
+[CHECKING] bar v1.0.0
+[CHECKING] foo v0.1.0 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn required_features() {
+ // required-features doesn't allow ?
+ Package::new("bar", "1.0.0").feature("feat", &[]).publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { version = "1.0", optional = true }
+
+ [[bin]]
+ name = "foo"
+ required-features = ["bar?/feat"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[ERROR] invalid feature `bar?/feat` in required-features of target `foo`: \
+optional dependency with `?` is not allowed in required-features
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn weak_with_host_decouple() {
+ // weak-dep-features with new resolver
+ //
+ // foo v0.1.0
+ // └── common v1.0.0
+ // └── bar v1.0.0 <-- does not have `feat` enabled
+ // [build-dependencies]
+ // └── bar_activator v1.0.0
+ // └── common v1.0.0
+ // └── bar v1.0.0 <-- does have `feat` enabled
+ Package::new("bar", "1.0.0")
+ .feature("feat", &[])
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn feat() -> bool {
+ cfg!(feature = "feat")
+ }
+ "#,
+ )
+ .publish();
+
+ Package::new("common", "1.0.0")
+ .add_dep(Dependency::new("bar", "1.0").optional(true))
+ .feature("feat", &["bar?/feat"])
+ .file(
+ "src/lib.rs",
+ r#"
+ #[cfg(feature = "bar")]
+ pub fn feat() -> bool { bar::feat() }
+ #[cfg(not(feature = "bar"))]
+ pub fn feat() -> bool { false }
+ "#,
+ )
+ .publish();
+
+ Package::new("bar_activator", "1.0.0")
+ .feature_dep("common", "1.0", &["bar", "feat"])
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn feat() -> bool {
+ common::feat()
+ }
+ "#,
+ )
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ resolver = "2"
+
+ [dependencies]
+ common = { version = "1.0", features = ["feat"] }
+
+ [build-dependencies]
+ bar_activator = "1.0"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ assert!(!common::feat());
+ }
+ "#,
+ )
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ assert!(bar_activator::feat());
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("run")
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[DOWNLOADING] crates ...
+[DOWNLOADED] [..]
+[DOWNLOADED] [..]
+[DOWNLOADED] [..]
+[COMPILING] bar v1.0.0
+[COMPILING] common v1.0.0
+[COMPILING] bar_activator v1.0.0
+[COMPILING] foo v0.1.0 [..]
+[FINISHED] [..]
+[RUNNING] `target/debug/foo[EXE]`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn weak_namespaced() {
+ // Behavior with a dep: dependency.
+ Package::new("bar", "1.0.0")
+ .feature("feat", &[])
+ .file("src/lib.rs", &require(&["feat"], &[]))
+ .publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { version = "1.0", optional = true }
+
+ [features]
+ f1 = ["bar?/feat"]
+ f2 = ["dep:bar"]
+ "#,
+ )
+ .file("src/lib.rs", &require(&["f1"], &["f2", "bar"]))
+ .build();
+
+ p.cargo("check --features f1")
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[DOWNLOADING] crates ...
+[DOWNLOADED] bar v1.0.0 [..]
+[CHECKING] foo v0.1.0 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ p.cargo("tree -f")
+ .arg("{p} feats:{f}")
+ .with_stdout("foo v0.1.0 ([ROOT]/foo) feats:")
+ .run();
+
+ p.cargo("tree --features f1 -f")
+ .arg("{p} feats:{f}")
+ .with_stdout("foo v0.1.0 ([ROOT]/foo) feats:f1")
+ .run();
+
+ p.cargo("tree --features f1,f2 -f")
+ .arg("{p} feats:{f}")
+ .with_stdout(
+ "\
+foo v0.1.0 ([ROOT]/foo) feats:f1,f2
+└── bar v1.0.0 feats:feat
+",
+ )
+ .run();
+
+ // "bar" remains not-a-feature
+ p.change_file("src/lib.rs", &require(&["f1", "f2"], &["bar"]));
+
+ p.cargo("check --features f1,f2")
+ .with_stderr(
+ "\
+[CHECKING] bar v1.0.0
+[CHECKING] foo v0.1.0 [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn tree() {
+ Package::new("bar", "1.0.0")
+ .feature("feat", &[])
+ .file("src/lib.rs", &require(&["feat"], &[]))
+ .publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { version = "1.0", optional = true }
+
+ [features]
+ f1 = ["bar?/feat"]
+ "#,
+ )
+ .file("src/lib.rs", &require(&["f1"], &[]))
+ .build();
+
+ p.cargo("tree --features f1")
+ .with_stdout("foo v0.1.0 ([ROOT]/foo)")
+ .run();
+
+ p.cargo("tree --features f1,bar")
+ .with_stdout(
+ "\
+foo v0.1.0 ([ROOT]/foo)
+└── bar v1.0.0
+",
+ )
+ .run();
+
+ p.cargo("tree --features f1,bar -e features")
+ .with_stdout(
+ "\
+foo v0.1.0 ([ROOT]/foo)
+└── bar feature \"default\"
+ └── bar v1.0.0
+",
+ )
+ .run();
+
+ p.cargo("tree --features f1,bar -e features -i bar")
+ .with_stdout(
+ "\
+bar v1.0.0
+├── bar feature \"default\"
+│ └── foo v0.1.0 ([ROOT]/foo)
+│ ├── foo feature \"bar\" (command-line)
+│ ├── foo feature \"default\" (command-line)
+│ └── foo feature \"f1\" (command-line)
+└── bar feature \"feat\"
+ └── foo feature \"f1\" (command-line)
+",
+ )
+ .run();
+
+ p.cargo("tree -e features --features bar?/feat")
+ .with_stdout("foo v0.1.0 ([ROOT]/foo)")
+ .run();
+
+ // This is a little strange in that it produces no output.
+ // Maybe `cargo tree` should print a note about why?
+ p.cargo("tree -e features -i bar --features bar?/feat")
+ .with_stdout("")
+ .run();
+
+ p.cargo("tree -e features -i bar --features bar?/feat,bar")
+ .with_stdout(
+ "\
+bar v1.0.0
+├── bar feature \"default\"
+│ └── foo v0.1.0 ([ROOT]/foo)
+│ ├── foo feature \"bar\" (command-line)
+│ └── foo feature \"default\" (command-line)
+└── bar feature \"feat\" (command-line)
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn publish() {
+ let registry = RegistryBuilder::new().http_api().http_index().build();
+
+ // Publish behavior with /? syntax.
+ Package::new("bar", "1.0.0").feature("feat", &[]).publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ description = "foo"
+ license = "MIT"
+ homepage = "https://example.com/"
+
+ [dependencies]
+ bar = { version = "1.0", optional = true }
+
+ [features]
+ feat1 = []
+ feat2 = ["bar?/feat"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("publish")
+ .replace_crates_io(registry.index_url())
+ .with_stderr(
+ "\
+[UPDATING] [..]
+[PACKAGING] foo v0.1.0 [..]
+[VERIFYING] foo v0.1.0 [..]
+[UPDATING] [..]
+[COMPILING] foo v0.1.0 [..]
+[FINISHED] [..]
+[PACKAGED] [..]
+[UPLOADING] foo v0.1.0 [..]
+[UPLOADED] foo v0.1.0 to registry `crates-io`
+note: Waiting for `foo v0.1.0` to be available at registry `crates-io`.
+You may press ctrl-c to skip waiting; the crate should be available shortly.
+[PUBLISHED] foo v0.1.0 at registry `crates-io`
+",
+ )
+ .run();
+
+ publish::validate_upload_with_contents(
+ r#"
+ {
+ "authors": [],
+ "badges": {},
+ "categories": [],
+ "deps": [
+ {
+ "default_features": true,
+ "features": [],
+ "kind": "normal",
+ "name": "bar",
+ "optional": true,
+ "target": null,
+ "version_req": "^1.0"
+ }
+ ],
+ "description": "foo",
+ "documentation": null,
+ "features": {
+ "feat1": [],
+ "feat2": ["bar?/feat"]
+ },
+ "homepage": "https://example.com/",
+ "keywords": [],
+ "license": "MIT",
+ "license_file": null,
+ "links": null,
+ "name": "foo",
+ "readme": null,
+ "readme_file": null,
+ "repository": null,
+ "vers": "0.1.0"
+ }
+ "#,
+ "foo-0.1.0.crate",
+ &["Cargo.toml", "Cargo.toml.orig", "src/lib.rs"],
+ &[(
+ "Cargo.toml",
+ &format!(
+ r#"{}
+[package]
+name = "foo"
+version = "0.1.0"
+description = "foo"
+homepage = "https://example.com/"
+license = "MIT"
+
+[dependencies.bar]
+version = "1.0"
+optional = true
+
+[features]
+feat1 = []
+feat2 = ["bar?/feat"]
+"#,
+ cargo::core::package::MANIFEST_PREAMBLE
+ ),
+ )],
+ );
+}
diff --git a/src/tools/cargo/tests/testsuite/workspaces.rs b/src/tools/cargo/tests/testsuite/workspaces.rs
new file mode 100644
index 000000000..c6698f76a
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/workspaces.rs
@@ -0,0 +1,2531 @@
+//! Tests for workspaces.
+
+use cargo_test_support::registry::Package;
+use cargo_test_support::{basic_lib_manifest, basic_manifest, git, project, sleep_ms};
+use std::env;
+use std::fs;
+
+#[cargo_test]
+fn simple_explicit() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [workspace]
+ members = ["bar"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+ workspace = ".."
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}");
+ let p = p.build();
+
+ p.cargo("build").run();
+ assert!(p.bin("foo").is_file());
+ assert!(!p.bin("bar").is_file());
+
+ p.cargo("build").cwd("bar").run();
+ assert!(p.bin("foo").is_file());
+ assert!(p.bin("bar").is_file());
+
+ assert!(p.root().join("Cargo.lock").is_file());
+ assert!(!p.root().join("bar/Cargo.lock").is_file());
+}
+
+#[cargo_test]
+fn simple_explicit_default_members() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [workspace]
+ members = ["bar"]
+ default-members = ["bar"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+ workspace = ".."
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}");
+ let p = p.build();
+
+ p.cargo("build").run();
+ assert!(p.bin("bar").is_file());
+ assert!(!p.bin("foo").is_file());
+}
+
+#[cargo_test]
+fn non_virtual_default_members_build_other_member() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [workspace]
+ members = [".", "bar", "baz"]
+ default-members = ["baz"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("baz/src/lib.rs", "pub fn baz() {}")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "[CHECKING] baz v0.1.0 ([..])\n\
+ [..] Finished dev [unoptimized + debuginfo] target(s) in [..]\n",
+ )
+ .run();
+
+ p.cargo("check --manifest-path bar/Cargo.toml")
+ .with_stderr(
+ "[CHECKING] bar v0.1.0 ([..])\n\
+ [..] Finished dev [unoptimized + debuginfo] target(s) in [..]\n",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn non_virtual_default_members_build_root_project() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [workspace]
+ members = ["bar"]
+ default-members = ["."]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ p.cargo("check")
+ .with_stderr(
+ "[CHECKING] foo v0.1.0 ([..])\n\
+ [..] Finished dev [unoptimized + debuginfo] target(s) in [..]\n",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn inferred_root() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [workspace]
+ members = ["bar"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/main.rs", "fn main() {}");
+ let p = p.build();
+
+ p.cargo("build").run();
+ assert!(p.bin("foo").is_file());
+ assert!(!p.bin("bar").is_file());
+
+ p.cargo("build").cwd("bar").run();
+ assert!(p.bin("foo").is_file());
+ assert!(p.bin("bar").is_file());
+
+ assert!(p.root().join("Cargo.lock").is_file());
+ assert!(!p.root().join("bar/Cargo.lock").is_file());
+}
+
+#[cargo_test]
+fn inferred_path_dep() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ bar = { path = "bar" }
+
+ [workspace]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/main.rs", "fn main() {}")
+ .file("bar/src/lib.rs", "");
+ let p = p.build();
+
+ p.cargo("build").run();
+ assert!(p.bin("foo").is_file());
+ assert!(!p.bin("bar").is_file());
+
+ p.cargo("build").cwd("bar").run();
+ assert!(p.bin("foo").is_file());
+ assert!(p.bin("bar").is_file());
+
+ assert!(p.root().join("Cargo.lock").is_file());
+ assert!(!p.root().join("bar/Cargo.lock").is_file());
+}
+
+#[cargo_test]
+fn transitive_path_dep() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ bar = { path = "bar" }
+
+ [workspace]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ baz = { path = "../baz" }
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}")
+ .file("bar/src/lib.rs", "")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("baz/src/main.rs", "fn main() {}")
+ .file("baz/src/lib.rs", "");
+ let p = p.build();
+
+ p.cargo("build").run();
+ assert!(p.bin("foo").is_file());
+ assert!(!p.bin("bar").is_file());
+ assert!(!p.bin("baz").is_file());
+
+ p.cargo("build").cwd("bar").run();
+ assert!(p.bin("foo").is_file());
+ assert!(p.bin("bar").is_file());
+ assert!(!p.bin("baz").is_file());
+
+ p.cargo("build").cwd("baz").run();
+ assert!(p.bin("foo").is_file());
+ assert!(p.bin("bar").is_file());
+ assert!(p.bin("baz").is_file());
+
+ assert!(p.root().join("Cargo.lock").is_file());
+ assert!(!p.root().join("bar/Cargo.lock").is_file());
+ assert!(!p.root().join("baz/Cargo.lock").is_file());
+}
+
+#[cargo_test]
+fn parent_pointer_works() {
+ let p = project()
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ bar = { path = "../bar" }
+
+ [workspace]
+ "#,
+ )
+ .file("foo/src/main.rs", "fn main() {}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+ workspace = "../foo"
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}")
+ .file("bar/src/lib.rs", "");
+ let p = p.build();
+
+ p.cargo("build").cwd("foo").run();
+ p.cargo("build").cwd("bar").run();
+ assert!(p.root().join("foo/Cargo.lock").is_file());
+ assert!(!p.root().join("bar/Cargo.lock").is_file());
+}
+
+#[cargo_test]
+fn same_names_in_workspace() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [workspace]
+ members = ["bar"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ workspace = ".."
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}");
+ let p = p.build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: two packages named `foo` in this workspace:
+- [..]Cargo.toml
+- [..]Cargo.toml
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn parent_doesnt_point_to_child() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [workspace]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/main.rs", "fn main() {}");
+ let p = p.build();
+
+ p.cargo("check")
+ .cwd("bar")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: current package believes it's in a workspace when it's not:
+current: [..]Cargo.toml
+workspace: [..]Cargo.toml
+
+this may be fixable [..]
+[..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn invalid_parent_pointer() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ workspace = "foo"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}");
+ let p = p.build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: failed to read `[..]Cargo.toml`
+
+Caused by:
+ [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn invalid_members() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [workspace]
+ members = ["foo"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}");
+ let p = p.build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to load manifest for workspace member `[..]/foo`
+
+Caused by:
+ failed to read `[..]foo/foo/Cargo.toml`
+
+Caused by:
+ [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bare_workspace_ok() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [workspace]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}");
+ let p = p.build();
+
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn two_roots() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [workspace]
+ members = ["bar"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+
+ [workspace]
+ members = [".."]
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}");
+ let p = p.build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: multiple workspace roots found in the same workspace:
+ [..]
+ [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn workspace_isnt_root() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ workspace = "bar"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/main.rs", "fn main() {}");
+ let p = p.build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr("error: root of a workspace inferred but wasn't a root: [..]")
+ .run();
+}
+
+#[cargo_test]
+fn dangling_member() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [workspace]
+ members = ["bar"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+ workspace = "../baz"
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}")
+ .file(
+ "baz/Cargo.toml",
+ r#"
+ [package]
+ name = "baz"
+ version = "0.1.0"
+ authors = []
+ workspace = "../baz"
+ "#,
+ )
+ .file("baz/src/main.rs", "fn main() {}");
+ let p = p.build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: package `[..]` is a member of the wrong workspace
+expected: [..]
+actual: [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn cycle() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ workspace = "bar"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+ workspace = ".."
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}");
+ let p = p.build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "[ERROR] root of a workspace inferred but wasn't a root: [..]/foo/bar/Cargo.toml",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn share_dependencies() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ dep1 = "0.1"
+
+ [workspace]
+ members = ["bar"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ dep1 = "< 0.1.5"
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}");
+ let p = p.build();
+
+ Package::new("dep1", "0.1.3").publish();
+ Package::new("dep1", "0.1.8").publish();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] dep1 v0.1.3 ([..])
+[CHECKING] dep1 v0.1.3
+[CHECKING] foo v0.1.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn fetch_fetches_all() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [workspace]
+ members = ["bar"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ dep1 = "*"
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}");
+ let p = p.build();
+
+ Package::new("dep1", "0.1.3").publish();
+
+ p.cargo("fetch")
+ .with_stderr(
+ "\
+[UPDATING] `[..]` index
+[DOWNLOADING] crates ...
+[DOWNLOADED] dep1 v0.1.3 ([..])
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn lock_works_for_everyone() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ dep2 = "0.1"
+
+ [workspace]
+ members = ["bar"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ dep1 = "0.1"
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}");
+ let p = p.build();
+
+ Package::new("dep1", "0.1.0").publish();
+ Package::new("dep2", "0.1.0").publish();
+
+ p.cargo("generate-lockfile")
+ .with_stderr("[UPDATING] `[..]` index")
+ .run();
+
+ Package::new("dep1", "0.1.1").publish();
+ Package::new("dep2", "0.1.1").publish();
+
+ p.cargo("check")
+ .with_stderr(
+ "\
+[DOWNLOADING] crates ...
+[DOWNLOADED] dep2 v0.1.0 ([..])
+[CHECKING] dep2 v0.1.0
+[CHECKING] foo v0.1.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("check")
+ .cwd("bar")
+ .with_stderr(
+ "\
+[DOWNLOADING] crates ...
+[DOWNLOADED] dep1 v0.1.0 ([..])
+[CHECKING] dep1 v0.1.0
+[CHECKING] bar v0.1.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn virtual_works() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/main.rs", "fn main() {}");
+ let p = p.build();
+ p.cargo("build").cwd("bar").run();
+ assert!(p.root().join("Cargo.lock").is_file());
+ assert!(p.bin("bar").is_file());
+ assert!(!p.root().join("bar/Cargo.lock").is_file());
+}
+
+#[cargo_test]
+fn explicit_package_argument_works_with_virtual_manifest() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/main.rs", "fn main() {}");
+ let p = p.build();
+ p.cargo("build --package bar").run();
+ assert!(p.root().join("Cargo.lock").is_file());
+ assert!(p.bin("bar").is_file());
+ assert!(!p.root().join("bar/Cargo.lock").is_file());
+}
+
+#[cargo_test]
+fn virtual_misconfigure() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/main.rs", "fn main() {}");
+ let p = p.build();
+ p.cargo("check")
+ .cwd("bar")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: current package believes it's in a workspace when it's not:
+current: [CWD]/Cargo.toml
+workspace: [..]Cargo.toml
+
+this may be fixable by adding `bar` to the `workspace.members` array of the \
+manifest located at: [..]
+[..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn virtual_build_all_implied() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/main.rs", "fn main() {}");
+ let p = p.build();
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn virtual_default_members() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar", "baz"]
+ default-members = ["bar"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("bar/src/main.rs", "fn main() {}")
+ .file("baz/src/main.rs", "fn main() {}");
+ let p = p.build();
+ p.cargo("build").run();
+ assert!(p.bin("bar").is_file());
+ assert!(!p.bin("baz").is_file());
+}
+
+#[cargo_test]
+fn virtual_default_member_is_not_a_member() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar"]
+ default-members = ["something-else"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/main.rs", "fn main() {}");
+ let p = p.build();
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: package `[..]something-else` is listed in workspace’s default-members \
+but is not a member.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn virtual_default_members_build_other_member() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["bar", "baz"]
+ default-members = ["baz"]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("baz/src/lib.rs", "pub fn baz() {}")
+ .build();
+
+ p.cargo("check --manifest-path bar/Cargo.toml")
+ .with_stderr(
+ "[CHECKING] bar v0.1.0 ([..])\n\
+ [..] Finished dev [unoptimized + debuginfo] target(s) in [..]\n",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn virtual_build_no_members() {
+ let p = project().file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ "#,
+ );
+ let p = p.build();
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: manifest path `[..]` contains no package: The manifest is virtual, \
+and the workspace has no members.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn include_virtual() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+ [workspace]
+ members = ["bar"]
+ "#,
+ )
+ .file("src/main.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [workspace]
+ "#,
+ );
+ let p = p.build();
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: multiple workspace roots found in the same workspace:
+ [..]
+ [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn members_include_path_deps() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [workspace]
+ members = ["p1"]
+
+ [dependencies]
+ p3 = { path = "p3" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "p1/Cargo.toml",
+ r#"
+ [package]
+ name = "p1"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ p2 = { path = "../p2" }
+ "#,
+ )
+ .file("p1/src/lib.rs", "")
+ .file("p2/Cargo.toml", &basic_manifest("p2", "0.1.0"))
+ .file("p2/src/lib.rs", "")
+ .file("p3/Cargo.toml", &basic_manifest("p3", "0.1.0"))
+ .file("p3/src/lib.rs", "");
+ let p = p.build();
+
+ p.cargo("check").cwd("p1").run();
+ p.cargo("check").cwd("p2").run();
+ p.cargo("check").cwd("p3").run();
+ p.cargo("check").run();
+
+ assert!(p.root().join("target").is_dir());
+ assert!(!p.root().join("p1/target").is_dir());
+ assert!(!p.root().join("p2/target").is_dir());
+ assert!(!p.root().join("p3/target").is_dir());
+}
+
+#[cargo_test]
+fn new_warns_you_this_will_not_work() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [workspace]
+ "#,
+ )
+ .file("src/lib.rs", "");
+ let p = p.build();
+
+ p.cargo("new --lib bar")
+ .with_stderr(
+ "\
+warning: compiling this new package may not work due to invalid workspace configuration
+
+current package believes it's in a workspace when it's not:
+current: [..]
+workspace: [..]
+
+this may be fixable by ensuring that this crate is depended on by the workspace \
+root: [..]
+[..]
+[CREATED] library `bar` package
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn new_warning_with_corrupt_ws() {
+ let p = project().file("Cargo.toml", "asdf").build();
+ p.cargo("new bar")
+ .with_stderr(
+ "\
+[WARNING] compiling this new package may not work due to invalid workspace configuration
+
+failed to parse manifest at `[..]foo/Cargo.toml`
+
+Caused by:
+ could not parse input as TOML
+
+Caused by:
+ TOML parse error at line 1, column 5
+ |
+ 1 | asdf
+ | ^
+ expected `.`, `=`
+ Created binary (application) `bar` package
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn lock_doesnt_change_depending_on_crate() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [workspace]
+ members = ['baz']
+
+ [dependencies]
+ foo = "*"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "baz/Cargo.toml",
+ r#"
+ [package]
+ name = "baz"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ bar = "*"
+ "#,
+ )
+ .file("baz/src/lib.rs", "");
+ let p = p.build();
+
+ Package::new("foo", "1.0.0").publish();
+ Package::new("bar", "1.0.0").publish();
+
+ p.cargo("check").run();
+
+ let lockfile = p.read_lockfile();
+
+ p.cargo("check").cwd("baz").run();
+
+ let lockfile2 = p.read_lockfile();
+
+ assert_eq!(lockfile, lockfile2);
+}
+
+#[cargo_test]
+fn rebuild_please() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ['lib', 'bin']
+ "#,
+ )
+ .file("lib/Cargo.toml", &basic_manifest("lib", "0.1.0"))
+ .file(
+ "lib/src/lib.rs",
+ r#"
+ pub fn foo() -> u32 { 0 }
+ "#,
+ )
+ .file(
+ "bin/Cargo.toml",
+ r#"
+ [package]
+ name = "bin"
+ version = "0.1.0"
+
+ [dependencies]
+ lib = { path = "../lib" }
+ "#,
+ )
+ .file(
+ "bin/src/main.rs",
+ r#"
+ extern crate lib;
+
+ fn main() {
+ assert_eq!(lib::foo(), 0);
+ }
+ "#,
+ );
+ let p = p.build();
+
+ p.cargo("run").cwd("bin").run();
+
+ sleep_ms(1000);
+
+ p.change_file("lib/src/lib.rs", "pub fn foo() -> u32 { 1 }");
+
+ p.cargo("build").cwd("lib").run();
+
+ p.cargo("run")
+ .cwd("bin")
+ .with_status(101)
+ .with_stderr_contains("[..]assertion[..]")
+ .run();
+}
+
+#[cargo_test]
+fn workspace_in_git() {
+ let git_project = git::new("dep1", |project| {
+ project
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["foo"]
+ "#,
+ )
+ .file("foo/Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("foo/src/lib.rs", "")
+ });
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "lib"
+ version = "0.1.0"
+
+ [dependencies.foo]
+ git = '{}'
+ "#,
+ git_project.url()
+ ),
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn foo() -> u32 { 0 }
+ "#,
+ );
+ let p = p.build();
+
+ p.cargo("check").run();
+}
+
+#[cargo_test]
+fn lockfile_can_specify_nonexistent_members() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["a"]
+ "#,
+ )
+ .file("a/Cargo.toml", &basic_manifest("a", "0.1.0"))
+ .file("a/src/main.rs", "fn main() {}")
+ .file(
+ "Cargo.lock",
+ r#"
+ [[package]]
+ name = "a"
+ version = "0.1.0"
+
+ [[package]]
+ name = "b"
+ version = "0.1.0"
+ "#,
+ );
+
+ let p = p.build();
+
+ p.cargo("check").cwd("a").run();
+}
+
+#[cargo_test]
+fn you_cannot_generate_lockfile_for_empty_workspaces() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("bar/src/main.rs", "fn main() {}");
+ let p = p.build();
+
+ p.cargo("update")
+ .with_status(101)
+ .with_stderr("error: you can't generate a lockfile for an empty workspace.")
+ .run();
+}
+
+#[cargo_test]
+fn workspace_with_transitive_dev_deps() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = ["mbrubeck@example.com"]
+
+ [dependencies.bar]
+ path = "bar"
+
+ [workspace]
+ "#,
+ )
+ .file("src/main.rs", r#"fn main() {}"#)
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.5.0"
+ authors = ["mbrubeck@example.com"]
+
+ [dev-dependencies.baz]
+ path = "../baz"
+ "#,
+ )
+ .file(
+ "bar/src/lib.rs",
+ r#"
+ pub fn init() {}
+
+ #[cfg(test)]
+
+ #[test]
+ fn test() {
+ extern crate baz;
+ baz::do_stuff();
+ }
+ "#,
+ )
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.5.0"))
+ .file("baz/src/lib.rs", r#"pub fn do_stuff() {}"#);
+ let p = p.build();
+
+ p.cargo("test -p bar").run();
+}
+
+#[cargo_test]
+fn error_if_parent_cargo_toml_is_invalid() {
+ let p = project()
+ .file("Cargo.toml", "Totally not a TOML file")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/main.rs", "fn main() {}");
+ let p = p.build();
+
+ p.cargo("check")
+ .cwd("bar")
+ .with_status(101)
+ .with_stderr_contains("[ERROR] failed to parse manifest at `[..]`")
+ .run();
+}
+
+#[cargo_test]
+fn relative_path_for_member_works() {
+ let p = project()
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [workspace]
+ members = ["../bar"]
+ "#,
+ )
+ .file("foo/src/main.rs", "fn main() {}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+ workspace = "../foo"
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}");
+ let p = p.build();
+
+ p.cargo("check").cwd("foo").run();
+ p.cargo("check").cwd("bar").run();
+}
+
+#[cargo_test]
+fn relative_path_for_root_works() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [workspace]
+
+ [dependencies]
+ subproj = { path = "./subproj" }
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("subproj/Cargo.toml", &basic_manifest("subproj", "0.1.0"))
+ .file("subproj/src/main.rs", "fn main() {}");
+ let p = p.build();
+
+ p.cargo("check --manifest-path ./Cargo.toml").run();
+
+ p.cargo("check --manifest-path ../Cargo.toml")
+ .cwd("subproj")
+ .run();
+}
+
+#[cargo_test]
+fn path_dep_outside_workspace_is_not_member() {
+ let p = project()
+ .no_manifest()
+ .file(
+ "ws/Cargo.toml",
+ r#"
+ [package]
+ name = "ws"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ foo = { path = "../foo" }
+
+ [workspace]
+ "#,
+ )
+ .file("ws/src/lib.rs", "extern crate foo;")
+ .file("foo/Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("foo/src/lib.rs", "");
+ let p = p.build();
+
+ p.cargo("check").cwd("ws").run();
+}
+
+#[cargo_test]
+fn test_in_and_out_of_workspace() {
+ let p = project()
+ .no_manifest()
+ .file(
+ "ws/Cargo.toml",
+ r#"
+ [package]
+ name = "ws"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ foo = { path = "../foo" }
+
+ [workspace]
+ members = [ "../bar" ]
+ "#,
+ )
+ .file("ws/src/lib.rs", "extern crate foo; pub fn f() { foo::f() }")
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ bar = { path = "../bar" }
+ "#,
+ )
+ .file(
+ "foo/src/lib.rs",
+ "extern crate bar; pub fn f() { bar::f() }",
+ )
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ workspace = "../ws"
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+ "#,
+ )
+ .file("bar/src/lib.rs", "pub fn f() { }");
+ let p = p.build();
+
+ p.cargo("check").cwd("ws").run();
+
+ assert!(p.root().join("ws/Cargo.lock").is_file());
+ assert!(p.root().join("ws/target").is_dir());
+ assert!(!p.root().join("foo/Cargo.lock").is_file());
+ assert!(!p.root().join("foo/target").is_dir());
+ assert!(!p.root().join("bar/Cargo.lock").is_file());
+ assert!(!p.root().join("bar/target").is_dir());
+
+ p.cargo("check").cwd("foo").run();
+ assert!(p.root().join("foo/Cargo.lock").is_file());
+ assert!(p.root().join("foo/target").is_dir());
+ assert!(!p.root().join("bar/Cargo.lock").is_file());
+ assert!(!p.root().join("bar/target").is_dir());
+}
+
+#[cargo_test]
+fn test_path_dependency_under_member() {
+ let p = project()
+ .file(
+ "ws/Cargo.toml",
+ r#"
+ [package]
+ name = "ws"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ foo = { path = "../foo" }
+
+ [workspace]
+ "#,
+ )
+ .file("ws/src/lib.rs", "extern crate foo; pub fn f() { foo::f() }")
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ workspace = "../ws"
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ bar = { path = "./bar" }
+ "#,
+ )
+ .file(
+ "foo/src/lib.rs",
+ "extern crate bar; pub fn f() { bar::f() }",
+ )
+ .file("foo/bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("foo/bar/src/lib.rs", "pub fn f() { }");
+ let p = p.build();
+
+ p.cargo("check").cwd("ws").run();
+
+ assert!(!p.root().join("foo/bar/Cargo.lock").is_file());
+ assert!(!p.root().join("foo/bar/target").is_dir());
+
+ p.cargo("check").cwd("foo/bar").run();
+
+ assert!(!p.root().join("foo/bar/Cargo.lock").is_file());
+ assert!(!p.root().join("foo/bar/target").is_dir());
+}
+
+#[cargo_test]
+fn excluded_simple() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "ws"
+ version = "0.1.0"
+ authors = []
+
+ [workspace]
+ exclude = ["foo"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("foo/Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("foo/src/lib.rs", "");
+ let p = p.build();
+
+ p.cargo("check").run();
+ assert!(p.root().join("target").is_dir());
+ p.cargo("check").cwd("foo").run();
+ assert!(p.root().join("foo/target").is_dir());
+}
+
+#[cargo_test]
+fn exclude_members_preferred() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "ws"
+ version = "0.1.0"
+ authors = []
+
+ [workspace]
+ members = ["foo/bar"]
+ exclude = ["foo"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("foo/Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("foo/src/lib.rs", "")
+ .file("foo/bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("foo/bar/src/lib.rs", "");
+ let p = p.build();
+
+ p.cargo("check").run();
+ assert!(p.root().join("target").is_dir());
+ p.cargo("check").cwd("foo").run();
+ assert!(p.root().join("foo/target").is_dir());
+ p.cargo("check").cwd("foo/bar").run();
+ assert!(!p.root().join("foo/bar/target").is_dir());
+}
+
+#[cargo_test]
+fn exclude_but_also_depend() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "ws"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ bar = { path = "foo/bar" }
+
+ [workspace]
+ exclude = ["foo"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("foo/Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("foo/src/lib.rs", "")
+ .file("foo/bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("foo/bar/src/lib.rs", "");
+ let p = p.build();
+
+ p.cargo("check").run();
+ assert!(p.root().join("target").is_dir());
+ p.cargo("check").cwd("foo").run();
+ assert!(p.root().join("foo/target").is_dir());
+ p.cargo("check").cwd("foo/bar").run();
+ assert!(p.root().join("foo/bar/target").is_dir());
+}
+
+#[cargo_test]
+fn excluded_default_members_still_must_be_members() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["foo"]
+ default-members = ["foo", "bar"]
+ exclude = ["bar"]
+ "#,
+ )
+ .file("foo/Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("foo/src/lib.rs", "")
+ .file("bar/something.txt", "");
+ let p = p.build();
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: package `[..]bar` is listed in workspace’s default-members \
+but is not a member.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn excluded_default_members_crate_glob() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["foo", "bar/*"]
+ default-members = ["bar/*"]
+ exclude = ["bar/quux"]
+ "#,
+ )
+ .file("foo/Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("foo/src/main.rs", "fn main() {}")
+ .file("bar/baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("bar/baz/src/main.rs", "fn main() {}")
+ .file("bar/quux/Cargo.toml", &basic_manifest("quux", "0.1.0"))
+ .file("bar/quux/src/main.rs", "fn main() {}");
+
+ let p = p.build();
+ p.cargo("build").run();
+
+ assert!(p.root().join("target").is_dir());
+ assert!(!p.bin("foo").is_file());
+ assert!(p.bin("baz").is_file());
+ assert!(!p.bin("quux").exists());
+
+ p.cargo("build --workspace").run();
+ assert!(p.root().join("target").is_dir());
+ assert!(p.bin("foo").is_file());
+ assert!(!p.bin("quux").exists());
+
+ p.cargo("build").cwd("bar/quux").run();
+ assert!(p.root().join("bar/quux/target").is_dir());
+}
+
+#[cargo_test]
+fn excluded_default_members_not_crate_glob() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["foo", "bar/*"]
+ default-members = ["bar/*"]
+ exclude = ["bar/docs"]
+ "#,
+ )
+ .file("foo/Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("foo/src/main.rs", "fn main() {}")
+ .file("bar/baz/Cargo.toml", &basic_manifest("baz", "0.1.0"))
+ .file("bar/baz/src/main.rs", "fn main() {}")
+ .file("bar/docs/readme.txt", "This folder is not a crate!");
+
+ let p = p.build();
+ p.cargo("build").run();
+
+ assert!(!p.bin("foo").is_file());
+ assert!(p.bin("baz").is_file());
+ p.cargo("build --workspace").run();
+ assert!(p.bin("foo").is_file());
+}
+
+#[cargo_test]
+fn glob_syntax() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [workspace]
+ members = ["crates/*"]
+ exclude = ["crates/qux"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "crates/bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+ workspace = "../.."
+ "#,
+ )
+ .file("crates/bar/src/main.rs", "fn main() {}")
+ .file(
+ "crates/baz/Cargo.toml",
+ r#"
+ [package]
+ name = "baz"
+ version = "0.1.0"
+ authors = []
+ workspace = "../.."
+ "#,
+ )
+ .file("crates/baz/src/main.rs", "fn main() {}")
+ .file(
+ "crates/qux/Cargo.toml",
+ r#"
+ [package]
+ name = "qux"
+ version = "0.1.0"
+ authors = []
+ "#,
+ )
+ .file("crates/qux/src/main.rs", "fn main() {}");
+ let p = p.build();
+
+ p.cargo("build").run();
+ assert!(p.bin("foo").is_file());
+ assert!(!p.bin("bar").is_file());
+ assert!(!p.bin("baz").is_file());
+
+ p.cargo("build").cwd("crates/bar").run();
+ assert!(p.bin("foo").is_file());
+ assert!(p.bin("bar").is_file());
+
+ p.cargo("build").cwd("crates/baz").run();
+ assert!(p.bin("foo").is_file());
+ assert!(p.bin("baz").is_file());
+
+ p.cargo("build").cwd("crates/qux").run();
+ assert!(!p.bin("qux").is_file());
+
+ assert!(p.root().join("Cargo.lock").is_file());
+ assert!(!p.root().join("crates/bar/Cargo.lock").is_file());
+ assert!(!p.root().join("crates/baz/Cargo.lock").is_file());
+ assert!(p.root().join("crates/qux/Cargo.lock").is_file());
+}
+
+/*FIXME: This fails because of how workspace.exclude and workspace.members are working.
+#[cargo_test]
+fn glob_syntax_2() {
+ let p = project()
+ .file("Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [workspace]
+ members = ["crates/b*"]
+ exclude = ["crates/q*"]
+ "#)
+ .file("src/main.rs", "fn main() {}")
+ .file("crates/bar/Cargo.toml", r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+ workspace = "../.."
+ "#)
+ .file("crates/bar/src/main.rs", "fn main() {}")
+ .file("crates/baz/Cargo.toml", r#"
+ [package]
+ name = "baz"
+ version = "0.1.0"
+ authors = []
+ workspace = "../.."
+ "#)
+ .file("crates/baz/src/main.rs", "fn main() {}")
+ .file("crates/qux/Cargo.toml", r#"
+ [package]
+ name = "qux"
+ version = "0.1.0"
+ authors = []
+ "#)
+ .file("crates/qux/src/main.rs", "fn main() {}");
+ p.build();
+
+ p.cargo("build").run();
+ assert!(p.bin("foo").is_file());
+ assert!(!p.bin("bar").is_file());
+ assert!(!p.bin("baz").is_file());
+
+ p.cargo("build").cwd("crates/bar").run();
+ assert!(p.bin("foo").is_file());
+ assert!(p.bin("bar").is_file());
+
+ p.cargo("build").cwd("crates/baz").run();
+ assert!(p.bin("foo").is_file());
+ assert!(p.bin("baz").is_file());
+
+ p.cargo("build").cwd("crates/qux").run();
+ assert!(!p.bin("qux").is_file());
+
+ assert!(p.root().join("Cargo.lock").is_file());
+ assert!(!p.root().join("crates/bar/Cargo.lock").is_file());
+ assert!(!p.root().join("crates/baz/Cargo.lock").is_file());
+ assert!(p.root().join("crates/qux/Cargo.lock").is_file());
+}
+*/
+
+#[cargo_test]
+fn glob_syntax_invalid_members() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [workspace]
+ members = ["crates/*"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("crates/bar/src/main.rs", "fn main() {}");
+ let p = p.build();
+
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to load manifest for workspace member `[..]/crates/bar`
+
+Caused by:
+ failed to read `[..]foo/crates/bar/Cargo.toml`
+
+Caused by:
+ [..]
+",
+ )
+ .run();
+}
+
+/// This is a freshness test for feature use with workspaces.
+///
+/// `feat_lib` is used by `caller1` and `caller2`, but with different features enabled.
+/// This test ensures that alternating building `caller1`, `caller2` doesn't force
+/// recompile of `feat_lib`.
+///
+/// Ideally, once we solve rust-lang/cargo#3620, then a single Cargo build at the top level
+/// will be enough.
+#[cargo_test]
+fn dep_used_with_separate_features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["feat_lib", "caller1", "caller2"]
+ "#,
+ )
+ .file(
+ "feat_lib/Cargo.toml",
+ r#"
+ [package]
+ name = "feat_lib"
+ version = "0.1.0"
+ authors = []
+
+ [features]
+ myfeature = []
+ "#,
+ )
+ .file("feat_lib/src/lib.rs", "")
+ .file(
+ "caller1/Cargo.toml",
+ r#"
+ [package]
+ name = "caller1"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ feat_lib = { path = "../feat_lib" }
+ "#,
+ )
+ .file("caller1/src/main.rs", "fn main() {}")
+ .file("caller1/src/lib.rs", "")
+ .file(
+ "caller2/Cargo.toml",
+ r#"
+ [package]
+ name = "caller2"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ feat_lib = { path = "../feat_lib", features = ["myfeature"] }
+ caller1 = { path = "../caller1" }
+ "#,
+ )
+ .file("caller2/src/main.rs", "fn main() {}")
+ .file("caller2/src/lib.rs", "");
+ let p = p.build();
+
+ // Build the entire workspace.
+ p.cargo("build --workspace")
+ .with_stderr(
+ "\
+[..]Compiling feat_lib v0.1.0 ([..])
+[..]Compiling caller1 v0.1.0 ([..])
+[..]Compiling caller2 v0.1.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ assert!(p.bin("caller1").is_file());
+ assert!(p.bin("caller2").is_file());
+
+ // Build `caller1`. Should build the dep library. Because the features
+ // are different than the full workspace, it rebuilds.
+ // Ideally once we solve rust-lang/cargo#3620, then a single Cargo build at the top level
+ // will be enough.
+ p.cargo("build")
+ .cwd("caller1")
+ .with_stderr(
+ "\
+[..]Compiling feat_lib v0.1.0 ([..])
+[..]Compiling caller1 v0.1.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ // Alternate building `caller2`/`caller1` a few times, just to make sure
+ // features are being built separately. Should not rebuild anything.
+ p.cargo("build")
+ .cwd("caller2")
+ .with_stderr("[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]")
+ .run();
+ p.cargo("build")
+ .cwd("caller1")
+ .with_stderr("[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]")
+ .run();
+ p.cargo("build")
+ .cwd("caller2")
+ .with_stderr("[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]")
+ .run();
+}
+
+#[cargo_test]
+fn dont_recurse_out_of_cargo_home() {
+ let git_project = git::new("dep", |project| {
+ project
+ .file("Cargo.toml", &basic_manifest("dep", "0.1.0"))
+ .file("src/lib.rs", "")
+ .file(
+ "build.rs",
+ r#"
+ use std::env;
+ use std::path::Path;
+ use std::process::{self, Command};
+
+ fn main() {
+ let cargo = env::var_os("CARGO").unwrap();
+ let cargo_manifest_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap();
+ let output = Command::new(cargo)
+ .args(&["metadata", "--format-version", "1", "--manifest-path"])
+ .arg(&Path::new(&cargo_manifest_dir).join("Cargo.toml"))
+ .output()
+ .unwrap();
+ if !output.status.success() {
+ eprintln!("{}", String::from_utf8(output.stderr).unwrap());
+ process::exit(1);
+ }
+ }
+ "#,
+ )
+ });
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies.dep]
+ git = "{}"
+
+ [workspace]
+ "#,
+ git_project.url()
+ ),
+ )
+ .file("src/lib.rs", "");
+ let p = p.build();
+
+ p.cargo("check")
+ .env("CARGO_HOME", p.root().join(".cargo"))
+ .run();
+}
+
+// FIXME: this fails because of how workspace.exclude and workspace.members are working.
+/*
+#[cargo_test]
+fn include_and_exclude() {
+ let p = project()
+ .file("Cargo.toml", r#"
+ [workspace]
+ members = ["foo"]
+ exclude = ["foo/bar"]
+ "#)
+ .file("foo/Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("foo/src/lib.rs", "")
+ .file("foo/bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("foo/bar/src/lib.rs", "");
+ p.build();
+
+ p.cargo("build").cwd("foo").run();
+ assert!(p.root().join("target").is_dir());
+ assert!(!p.root().join("foo/target").is_dir());
+ p.cargo("build").cwd("foo/bar").run();
+ assert!(p.root().join("foo/bar/target").is_dir());
+}
+*/
+
+#[cargo_test]
+fn cargo_home_at_root_works() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [workspace]
+ members = ["a"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("a/Cargo.toml", &basic_manifest("a", "0.1.0"))
+ .file("a/src/lib.rs", "");
+ let p = p.build();
+
+ p.cargo("check").run();
+ p.cargo("check --frozen").env("CARGO_HOME", p.root()).run();
+}
+
+#[cargo_test]
+fn relative_rustc() {
+ let p = project()
+ .file(
+ "src/main.rs",
+ r#"
+ use std::process::Command;
+ use std::env;
+
+ fn main() {
+ let mut cmd = Command::new("rustc");
+ for arg in env::args_os().skip(1) {
+ cmd.arg(arg);
+ }
+ std::process::exit(cmd.status().unwrap().code().unwrap());
+ }
+ "#,
+ )
+ .build();
+ p.cargo("build").run();
+
+ let src = p
+ .root()
+ .join("target/debug/foo")
+ .with_extension(env::consts::EXE_EXTENSION);
+
+ Package::new("a", "0.1.0").publish();
+
+ let p = project()
+ .at("lib")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "lib"
+ version = "0.1.0"
+
+ [dependencies]
+ a = "0.1"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ fs::copy(&src, p.root().join(src.file_name().unwrap())).unwrap();
+
+ let file = format!("./foo{}", env::consts::EXE_SUFFIX);
+ p.cargo("build").env("RUSTC", &file).run();
+}
+
+#[cargo_test]
+fn ws_rustc_err() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["a"]
+ "#,
+ )
+ .file("a/Cargo.toml", &basic_lib_manifest("a"))
+ .file("a/src/lib.rs", "")
+ .build();
+
+ p.cargo("rustc")
+ .with_status(101)
+ .with_stderr("[ERROR] [..]against an actual package[..]")
+ .run();
+
+ p.cargo("rustdoc")
+ .with_status(101)
+ .with_stderr("[ERROR] [..]against an actual package[..]")
+ .run();
+}
+
+#[cargo_test]
+fn ws_err_unused() {
+ for key in &[
+ "[lib]",
+ "[[bin]]",
+ "[[example]]",
+ "[[test]]",
+ "[[bench]]",
+ "[dependencies]",
+ "[dev-dependencies]",
+ "[build-dependencies]",
+ "[features]",
+ "[target]",
+ "[badges]",
+ ] {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [workspace]
+ members = ["a"]
+
+ {}
+ "#,
+ key
+ ),
+ )
+ .file("a/Cargo.toml", &basic_lib_manifest("a"))
+ .file("a/src/lib.rs", "")
+ .build();
+ p.cargo("check")
+ .with_status(101)
+ .with_stderr(&format!(
+ "\
+[ERROR] failed to parse manifest at `[..]/foo/Cargo.toml`
+
+Caused by:
+ this virtual manifest specifies a {} section, which is not allowed
+",
+ key
+ ))
+ .run();
+ }
+}
+
+#[cargo_test]
+fn ws_warn_unused() {
+ for (key, name) in &[
+ ("[profile.dev]\nopt-level = 1", "profiles"),
+ ("[replace]\n\"bar:0.1.0\" = { path = \"bar\" }", "replace"),
+ ("[patch.crates-io]\nbar = { path = \"bar\" }", "patch"),
+ ] {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["a"]
+ "#,
+ )
+ .file(
+ "a/Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+
+ {}
+ "#,
+ key
+ ),
+ )
+ .file("a/src/lib.rs", "")
+ .build();
+ p.cargo("check")
+ .with_stderr_contains(&format!(
+ "\
+[WARNING] {} for the non root package will be ignored, specify {} at the workspace root:
+package: [..]/foo/a/Cargo.toml
+workspace: [..]/foo/Cargo.toml
+",
+ name, name
+ ))
+ .run();
+ }
+}
+
+#[cargo_test]
+fn ws_warn_path() {
+ // Warnings include path to manifest.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["a"]
+ "#,
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ cargo-features = ["edition"]
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .build();
+
+ p.cargo("check")
+ .with_stderr_contains("[WARNING] [..]/foo/a/Cargo.toml: the cargo feature `edition`[..]")
+ .run();
+}
+
+#[cargo_test]
+fn invalid_missing() {
+ // Make sure errors are not suppressed with -q.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ x = { path = 'x' }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("check -q")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to get `x` as a dependency of package `foo v0.1.0 [..]`
+
+Caused by:
+ failed to load source for dependency `x`
+
+Caused by:
+ Unable to update [..]/foo/x
+
+Caused by:
+ failed to read `[..]foo/x/Cargo.toml`
+
+Caused by:
+ [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn member_dep_missing() {
+ // Make sure errors are not suppressed with -q.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [workspace]
+ members = ["bar"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+
+ [dependencies]
+ baz = { path = "baz" }
+ "#,
+ )
+ .file("bar/src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("check -q")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] failed to load manifest for workspace member `[..]/bar`
+
+Caused by:
+ failed to load manifest for dependency `baz`
+
+Caused by:
+ failed to read `[..]foo/bar/baz/Cargo.toml`
+
+Caused by:
+ [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn simple_primary_package_env_var() {
+ let is_primary_package = r#"
+ #[test]
+ fn verify_primary_package() {{
+ assert!(option_env!("CARGO_PRIMARY_PACKAGE").is_some());
+ }}
+ "#;
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [workspace]
+ members = ["bar"]
+ "#,
+ )
+ .file("src/lib.rs", is_primary_package)
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+ workspace = ".."
+ "#,
+ )
+ .file("bar/src/lib.rs", is_primary_package);
+ let p = p.build();
+
+ p.cargo("test").run();
+
+ // Again, this time selecting a specific crate
+ p.cargo("clean").run();
+ p.cargo("test -p bar").run();
+
+ // Again, this time selecting all crates
+ p.cargo("clean").run();
+ p.cargo("test --all").run();
+}
+
+#[cargo_test]
+fn virtual_primary_package_env_var() {
+ let is_primary_package = r#"
+ #[test]
+ fn verify_primary_package() {{
+ assert!(option_env!("CARGO_PRIMARY_PACKAGE").is_some());
+ }}
+ "#;
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["foo", "bar"]
+ "#,
+ )
+ .file("foo/Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("foo/src/lib.rs", is_primary_package)
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", is_primary_package);
+ let p = p.build();
+
+ p.cargo("test").run();
+
+ // Again, this time selecting a specific crate
+ p.cargo("clean").run();
+ p.cargo("test -p foo").run();
+}
+
+#[cargo_test]
+fn ensure_correct_workspace_when_nested() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "sub/Cargo.toml",
+ r#"
+ [workspace]
+ members = ["foo"]
+ "#,
+ )
+ .file(
+ "sub/foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ bar = { path = "../.."}
+ "#,
+ )
+ .file("sub/foo/src/main.rs", "fn main() {}");
+ let p = p.build();
+ p.cargo("tree")
+ .cwd("sub/foo")
+ .with_stdout(
+ "\
+foo v0.1.0 ([..]/foo/sub/foo)
+└── bar v0.1.0 ([..]/foo)\
+ ",
+ )
+ .run();
+}
diff --git a/src/tools/cargo/tests/testsuite/yank.rs b/src/tools/cargo/tests/testsuite/yank.rs
new file mode 100644
index 000000000..684a04508
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/yank.rs
@@ -0,0 +1,202 @@
+//! Tests for the `cargo yank` command.
+
+use std::fs;
+
+use cargo_test_support::paths::CargoPathExt;
+use cargo_test_support::project;
+use cargo_test_support::registry;
+
+fn setup(name: &str, version: &str) {
+ let dir = registry::api_path().join(format!("api/v1/crates/{}/{}", name, version));
+ dir.mkdir_p();
+ fs::write(dir.join("yank"), r#"{"ok": true}"#).unwrap();
+}
+
+#[cargo_test]
+fn explicit_version() {
+ let registry = registry::init();
+ setup("foo", "0.0.1");
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("yank --version 0.0.1")
+ .replace_crates_io(registry.index_url())
+ .run();
+
+ p.cargo("yank --undo --version 0.0.1")
+ .replace_crates_io(registry.index_url())
+ .with_status(101)
+ .with_stderr(
+ " Updating crates.io index
+ Unyank foo@0.0.1
+error: failed to undo a yank from the registry at file:///[..]
+
+Caused by:
+ EOF while parsing a value at line 1 column 0",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn explicit_version_with_asymmetric() {
+ let registry = registry::RegistryBuilder::new()
+ .http_api()
+ .token(cargo_test_support::registry::Token::rfc_key())
+ .build();
+ setup("foo", "0.0.1");
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [project]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ // The http_api server will check that the authorization is correct.
+ // If the authorization was not sent then we would get an unauthorized error.
+ p.cargo("yank --version 0.0.1")
+ .arg("-Zregistry-auth")
+ .masquerade_as_nightly_cargo(&["registry-auth"])
+ .replace_crates_io(registry.index_url())
+ .run();
+
+ p.cargo("yank --undo --version 0.0.1")
+ .arg("-Zregistry-auth")
+ .masquerade_as_nightly_cargo(&["registry-auth"])
+ .replace_crates_io(registry.index_url())
+ .run();
+}
+
+#[cargo_test]
+fn inline_version() {
+ let registry = registry::init();
+ setup("foo", "0.0.1");
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("yank foo@0.0.1")
+ .replace_crates_io(registry.index_url())
+ .run();
+
+ p.cargo("yank --undo foo@0.0.1")
+ .replace_crates_io(registry.index_url())
+ .with_status(101)
+ .with_stderr(
+ " Updating crates.io index
+ Unyank foo@0.0.1
+error: failed to undo a yank from the registry at file:///[..]
+
+Caused by:
+ EOF while parsing a value at line 1 column 0",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn version_required() {
+ setup("foo", "0.0.1");
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("yank foo")
+ .with_status(101)
+ .with_stderr("error: `--version` is required")
+ .run();
+}
+
+#[cargo_test]
+fn inline_version_without_name() {
+ setup("foo", "0.0.1");
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("yank @0.0.1")
+ .with_status(101)
+ .with_stderr("error: missing crate name for `@0.0.1`")
+ .run();
+}
+
+#[cargo_test]
+fn inline_and_explicit_version() {
+ setup("foo", "0.0.1");
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ license = "MIT"
+ description = "foo"
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .build();
+
+ p.cargo("yank foo@0.0.1 --version 0.0.1")
+ .with_status(101)
+ .with_stderr("error: cannot specify both `@0.0.1` and `--version`")
+ .run();
+}