Add regression testing for override_target annotations (#4248)

This is a followup to https://github.com/bazelbuild/rules_rust/pull/4226
diff --git a/crate_universe/src/context/crate_context.rs b/crate_universe/src/context/crate_context.rs
index 8e14efe..28d23b0 100644
--- a/crate_universe/src/context/crate_context.rs
+++ b/crate_universe/src/context/crate_context.rs
@@ -979,6 +979,25 @@
         .unwrap()
     }
 
+    /// `crate.annotation` takes these keys verbatim, and `extensions.bzl`
+    /// spells them out by hand. An unknown key is silently ignored, so pin the
+    /// exact strings.
+    #[test]
+    fn override_target_keys() {
+        let attrs = TargetAttributes::default();
+        let rules = [
+            Rule::Library(attrs.clone()),
+            Rule::ProcMacro(attrs.clone()),
+            Rule::Binary(attrs.clone()),
+            Rule::BuildScript(attrs),
+        ];
+
+        assert_eq!(
+            rules.map(|rule| rule.override_target_key()),
+            ["lib", "proc-macro", "bin", "custom-build"],
+        );
+    }
+
     #[test]
     fn new_context() {
         let annotations = common_annotations();
diff --git a/crate_universe/src/rendering.rs b/crate_universe/src/rendering.rs
index fb33d59..10b9752 100644
--- a/crate_universe/src/rendering.rs
+++ b/crate_universe/src/rendering.rs
@@ -1385,6 +1385,100 @@
         assert!(build_file_content.contains("name = \"_bs\""));
     }
 
+    /// Renders a crate exposing one target of every [`Rule`] kind, so that any
+    /// `override_targets` key has something to match against.
+    fn render_override_targets_build_file(override_targets: BTreeMap<String, Label>) -> String {
+        fn target(crate_name: &str) -> TargetAttributes {
+            TargetAttributes {
+                crate_name: crate_name.to_owned(),
+                ..mock_target_attributes()
+            }
+        }
+
+        let mut context = Context::default();
+        let crate_id = CrateId::new("mock_crate".to_owned(), VERSION_ZERO_ONE_ZERO);
+        context.crates.insert(
+            crate_id.clone(),
+            CrateContext {
+                name: crate_id.name,
+                version: crate_id.version,
+                package_url: None,
+                repository: None,
+                targets: BTreeSet::from([
+                    Rule::Library(target("mock_crate")),
+                    Rule::ProcMacro(target("mock_crate_proc_macro")),
+                    Rule::Binary(target("mock_crate_bin")),
+                    Rule::BuildScript(target("build_script_build")),
+                ]),
+                library_target_name: Some("mock_crate".to_owned()),
+                common_attrs: CommonAttributes::default(),
+                build_script_attrs: Some(BuildScriptAttributes::default()),
+                license: None,
+                license_ids: BTreeSet::default(),
+                license_file: None,
+                additive_build_file_content: None,
+                disable_pipelining: false,
+                extra_aliased_targets: BTreeMap::default(),
+                alias_rule: None,
+                override_targets,
+            },
+        );
+
+        let renderer = Renderer::new(mock_render_config(None), mock_supported_platform_triples());
+        let mut output = renderer.render(&context, None).unwrap();
+
+        output
+            .remove(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel"))
+            .unwrap()
+    }
+
+    /// Every key returned by [`Rule::override_target_key`] must swap the
+    /// generated rule out for an `alias`. `crate.annotation` accepts these
+    /// strings verbatim, so this is the contract `crate_universe/extensions.bzl`
+    /// has to spell correctly.
+    #[test]
+    fn render_override_targets() {
+        // Overrides are applied one at a time so a key can only pass by
+        // aliasing its own target.
+        for (key, target_name, rule) in [
+            ("lib", "mock_crate", "rust_library("),
+            ("proc-macro", "mock_crate_proc_macro", "rust_proc_macro("),
+            ("bin", "mock_crate_bin", "rust_binary("),
+            ("custom-build", "build_script_build", "cargo_build_script("),
+        ] {
+            let content = render_override_targets_build_file(BTreeMap::from([(
+                key.to_owned(),
+                Label::from_str("@//custom:override").unwrap(),
+            )]));
+
+            assert!(
+                content.contains(&format!("name = \"{target_name}\""))
+                    && content.contains("actual = \"@//custom:override\""),
+                "`{key}` did not alias `{target_name}`\n```\n{content}```\n",
+            );
+            assert!(
+                !content.contains(rule),
+                "`{rule}` should have been overridden by `{key}`\n```\n{content}```\n",
+            );
+        }
+    }
+
+    /// Keys that aren't produced by [`Rule::override_target_key`] are silently
+    /// ignored rather than rejected, which is why the underscored spelling
+    /// `extensions.bzl` used to emit went unnoticed.
+    #[test]
+    fn render_override_targets_ignores_unknown_keys() {
+        let content = render_override_targets_build_file(BTreeMap::from([(
+            "proc_macro".to_owned(),
+            Label::from_str("@//custom:override").unwrap(),
+        )]));
+
+        assert!(
+            !content.contains("@//custom:override"),
+            "```\n{content}```\n"
+        );
+    }
+
     #[test]
     fn render_cargo_build_script_complex() {
         let mut context = Context::default();
diff --git a/crate_universe/tests/integration/override_target/Cargo.Bazel.lock b/crate_universe/tests/integration/override_target/Cargo.Bazel.lock
index 105b604..3aebbe5 100644
--- a/crate_universe/tests/integration/override_target/Cargo.Bazel.lock
+++ b/crate_universe/tests/integration/override_target/Cargo.Bazel.lock
@@ -1,12 +1,20 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
-version = 3
+version = 4
+
+[[package]]
+name = "anyhow"
+version = "1.0.104"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
 
 [[package]]
 name = "direct-cargo-bazel-deps"
 version = "0.0.1"
 dependencies = [
+ "anyhow",
  "foo",
+ "paste",
 ]
 
 [[package]]
@@ -14,3 +22,9 @@
 version = "0.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f7dbb6acfeff1d490fba693a402456f76b344fea77a5e7cae43b5970c3332b8f"
+
+[[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
diff --git a/crate_universe/tests/integration/override_target/MODULE.bazel b/crate_universe/tests/integration/override_target/MODULE.bazel
index 854e71e..20782dc 100644
--- a/crate_universe/tests/integration/override_target/MODULE.bazel
+++ b/crate_universe/tests/integration/override_target/MODULE.bazel
@@ -25,6 +25,29 @@
     repositories = ["override_target"],
     version = "0.0.0",
 )
+
+# `proc-macro` and `custom-build` overrides -- the two keys whose spelling
+# differs from the `override_target_*` attribute name.
+crate.annotation(
+    crate = "paste",
+    override_target_proc_macro = "//overrides:proc_macro",
+    repositories = ["override_target"],
+)
+crate.spec(
+    package = "paste",
+    repositories = ["override_target"],
+    version = "=1.0.15",
+)
+crate.annotation(
+    crate = "anyhow",
+    override_target_build_script = "//overrides:build_script",
+    repositories = ["override_target"],
+)
+crate.spec(
+    package = "anyhow",
+    repositories = ["override_target"],
+    version = "=1.0.104",
+)
 crate.splicing_config(
     repositories = ["override_target"],
     resolver_version = "2",
diff --git a/crate_universe/tests/integration/override_target/cargo-bazel-lock.json b/crate_universe/tests/integration/override_target/cargo-bazel-lock.json
index 63a4d36..7bc548a 100644
--- a/crate_universe/tests/integration/override_target/cargo-bazel-lock.json
+++ b/crate_universe/tests/integration/override_target/cargo-bazel-lock.json
@@ -1,5 +1,5 @@
 {
-  "checksum": "3f3f32d2b538dc4750bb48bf7bb274b56c8891b971e4f8d307fc699dcba4a8d5",
+  "checksum": "0fe315db50b993bdec51a927fe0192c1a5cb4205d3652cb02347f1b9811b8c17",
   "conditions": {
     "aarch64-apple-darwin": [
       "aarch64-apple-darwin"
@@ -24,15 +24,71 @@
     ]
   },
   "crates": {
+    "anyhow 1.0.104": {
+      "build_script_attrs": {},
+      "common_attrs": {
+        "crate_features": [
+          "default",
+          "std"
+        ],
+        "edition": "2021",
+        "extra_deps": [
+          ":build_script_build"
+        ]
+      },
+      "library_target_name": "anyhow",
+      "license": "MIT OR Apache-2.0",
+      "license_file": "LICENSE-APACHE",
+      "license_ids": [
+        "Apache-2.0",
+        "MIT"
+      ],
+      "name": "anyhow",
+      "override_targets": {
+        "custom-build": "@@//overrides:build_script"
+      },
+      "package_url": "https://github.com/dtolnay/anyhow",
+      "repository": {
+        "Http": {
+          "sha256": "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470",
+          "url": "https://static.crates.io/crates/anyhow/1.0.104/download"
+        }
+      },
+      "targets": [
+        {
+          "Library": {
+            "crate_name": "anyhow",
+            "crate_root": "src/lib.rs"
+          }
+        },
+        {
+          "BuildScript": {
+            "crate_name": "build_script_build",
+            "crate_root": "build.rs"
+          }
+        }
+      ],
+      "version": "1.0.104"
+    },
     "direct-cargo-bazel-deps 0.0.1": {
       "common_attrs": {
         "deps": [
           {
+            "id": "anyhow 1.0.104",
+            "target": "anyhow"
+          },
+          {
             "id": "foo 0.0.0",
             "target": "foo"
           }
         ],
-        "edition": "2018"
+        "edition": "2018",
+        "proc_macro_deps": [
+          {
+            "id": "paste 1.0.15",
+            "target": "paste"
+          }
+        ]
       },
       "library_target_name": "direct_cargo_bazel_deps",
       "name": "direct-cargo-bazel-deps",
@@ -75,10 +131,54 @@
         }
       ],
       "version": "0.0.0"
+    },
+    "paste 1.0.15": {
+      "build_script_attrs": {},
+      "common_attrs": {
+        "edition": "2018",
+        "extra_deps": [
+          ":build_script_build"
+        ]
+      },
+      "library_target_name": "paste",
+      "license": "MIT OR Apache-2.0",
+      "license_file": "LICENSE-APACHE",
+      "license_ids": [
+        "Apache-2.0",
+        "MIT"
+      ],
+      "name": "paste",
+      "override_targets": {
+        "proc-macro": "@@//overrides:proc_macro"
+      },
+      "package_url": "https://github.com/dtolnay/paste",
+      "repository": {
+        "Http": {
+          "sha256": "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a",
+          "url": "https://static.crates.io/crates/paste/1.0.15/download"
+        }
+      },
+      "targets": [
+        {
+          "ProcMacro": {
+            "crate_name": "paste",
+            "crate_root": "src/lib.rs"
+          }
+        },
+        {
+          "BuildScript": {
+            "crate_name": "build_script_build",
+            "crate_root": "build.rs"
+          }
+        }
+      ],
+      "version": "1.0.15"
     }
   },
   "direct_deps": [
-    "foo 0.0.0"
+    "anyhow 1.0.104",
+    "foo 0.0.0",
+    "paste 1.0.15"
   ],
   "direct_dev_deps": [],
   "workspace_members": {
diff --git a/crate_universe/tests/integration/override_target/overrides/BUILD.bazel b/crate_universe/tests/integration/override_target/overrides/BUILD.bazel
new file mode 100644
index 0000000..01793b8
--- /dev/null
+++ b/crate_universe/tests/integration/override_target/overrides/BUILD.bazel
@@ -0,0 +1,47 @@
+load("@rules_rust//cargo:defs.bzl", "cargo_build_script")
+load("@rules_rust//rust:defs.bzl", "rust_proc_macro", "rust_test")
+
+# Replaces the `proc-macro` target generated for `paste`.
+rust_proc_macro(
+    name = "proc_macro",
+    srcs = ["proc_macro.rs"],
+    crate_name = "paste",
+    edition = "2021",
+    visibility = ["//visibility:public"],
+)
+
+# Replaces the `custom-build` target generated for `anyhow`.
+cargo_build_script(
+    name = "build_script",
+    srcs = ["build_script.rs"],
+    crate_name = "build_script_build",
+    edition = "2021",
+    pkg_name = "anyhow",
+    visibility = ["//visibility:public"],
+)
+
+# `paste` is never compiled from source once its proc macro is overridden, so
+# this only links if the alias generated for it points at `:proc_macro`.
+rust_test(
+    name = "override_test",
+    srcs = ["override_test.rs"],
+    edition = "2021",
+    proc_macro_deps = ["@override_target//paste"],
+)
+
+# The build script override has no runtime signal -- `anyhow` builds either way
+# -- and compiling it from source would pull ~60 crates into this workspace, so
+# assert on the generated dependency graph instead.
+genquery(
+    name = "anyhow_deps",
+    expression = "deps(@override_target//anyhow)",
+    scope = ["@override_target//anyhow"],
+)
+
+genrule(
+    name = "build_script_is_overridden",
+    srcs = [":anyhow_deps"],
+    outs = ["build_script_is_overridden.txt"],
+    # Fails if `anyhow` still depends on its own generated build script.
+    cmd = "grep '//overrides:build_script$$' $< > $@",
+)
diff --git a/crate_universe/tests/integration/override_target/overrides/build_script.rs b/crate_universe/tests/integration/override_target/overrides/build_script.rs
new file mode 100644
index 0000000..5b85097
--- /dev/null
+++ b/crate_universe/tests/integration/override_target/overrides/build_script.rs
@@ -0,0 +1,6 @@
+//! Stands in for `anyhow`'s build script.
+//!
+//! `anyhow` only uses its build script to probe the compiler for optional
+//! `cfg`s, so it still builds against this no-op replacement.
+
+fn main() {}
diff --git a/crate_universe/tests/integration/override_target/overrides/override_test.rs b/crate_universe/tests/integration/override_target/overrides/override_test.rs
new file mode 100644
index 0000000..0b031e5
--- /dev/null
+++ b/crate_universe/tests/integration/override_target/overrides/override_test.rs
@@ -0,0 +1,8 @@
+//! A dropped `override_target_*` annotation leaves the upstream target in
+//! place rather than failing, so this needs a positive signal: `overridden!`
+//! does not exist in the real `paste`.
+
+#[test]
+fn proc_macro_is_overridden() {
+    assert!(paste::overridden!());
+}
diff --git a/crate_universe/tests/integration/override_target/overrides/proc_macro.rs b/crate_universe/tests/integration/override_target/overrides/proc_macro.rs
new file mode 100644
index 0000000..248095a
--- /dev/null
+++ b/crate_universe/tests/integration/override_target/overrides/proc_macro.rs
@@ -0,0 +1,12 @@
+//! Stands in for the `paste` proc macro.
+//!
+//! `paste` has no `overridden` macro, so anything that expands it only
+//! compiles when `crate.annotation(override_target_proc_macro = ...)` was
+//! applied.
+
+use proc_macro::TokenStream;
+
+#[proc_macro]
+pub fn overridden(_item: TokenStream) -> TokenStream {
+    "true".parse().unwrap()
+}