Account for unstable mangling via regex-based checks in tests.

PiperOrigin-RevId: 494752346
diff --git a/cc_bindings_from_rs/BUILD b/cc_bindings_from_rs/BUILD
index 9a59171..5785497 100644
--- a/cc_bindings_from_rs/BUILD
+++ b/cc_bindings_from_rs/BUILD
@@ -74,6 +74,7 @@
     ],
     deps = [
         "//common:token_stream_matchers",
+        "@crate_index//:regex",
         "@crate_index//:tempfile",
         "@rules_rust//tools/runfiles",
     ],
diff --git a/cc_bindings_from_rs/bindings.rs b/cc_bindings_from_rs/bindings.rs
index 3f958ce..383fc6c 100644
--- a/cc_bindings_from_rs/bindings.rs
+++ b/cc_bindings_from_rs/bindings.rs
@@ -1825,6 +1825,9 @@
                 pub fn add(x: f64, y: f64) -> f64 { x * y }
             "#;
         test_format_def(test_src, "add", |result| {
+            // TODO(b/261074843): Re-add thunk name verification once we are using stable name
+            // mangling (which may be coming in Q1 2023).  (This might mean reverting cl/492333432
+            // + manual review and tweaks.)
             let result = result.expect("Test expects success here");
             assert!(result.cc.prereqs.is_empty());
             assert_cc_matches!(
@@ -3000,7 +3003,6 @@
     {
         use rustc_session::config::{
             CodegenOptions, CrateType, Input, Options, OutputType, OutputTypes,
-            SymbolManglingVersion,
         };
 
         const TEST_FILENAME: &str = "crubit_unittests.rs";
@@ -3025,14 +3027,6 @@
                 // As pointed out in `panics_and_exceptions.md` the tool only supports `-C
                 // panic=abort` and therefore we explicitly opt into this config for tests.
                 panic: Some(rustc_target::spec::PanicStrategy::Abort),
-                // To simplify how unit tests are authored we force a specific mangling algorithm -
-                // this way the tests can hardcode mangled-name-depdendent expectations (e.g. names
-                // of thunks expected in test output).  The value below has been chosen based on
-                // <internal link>/llvm-coverage-instrumentation.html#rust-symbol-mangling which
-                // points out that `v0` mangling can be used to "ensure consistent and reversible
-                // name mangling" in situations when "mangled names must be consistent across
-                // compilations".
-                symbol_mangling_version: Some(SymbolManglingVersion::V0),
                 ..Default::default()
             },
             ..Default::default()
diff --git a/cc_bindings_from_rs/cc_bindings_from_rs.rs b/cc_bindings_from_rs/cc_bindings_from_rs.rs
index ee60f3d..e837724 100644
--- a/cc_bindings_from_rs/cc_bindings_from_rs.rs
+++ b/cc_bindings_from_rs/cc_bindings_from_rs.rs
@@ -221,6 +221,7 @@
 
     use crate::bindings::tests::get_sysroot_for_testing;
     use itertools::Itertools;
+    use regex::{Regex, RegexBuilder};
     use std::path::PathBuf;
     use tempfile::{tempdir, TempDir};
     use token_stream_printer::RUSTFMT_EXE_PATH_FOR_TESTING;
@@ -329,9 +330,6 @@
             args.extend([
                 "--".to_string(),
                 format!("--codegen=panic={}", &self.panic_mechanism),
-                // See comments about `SymbolManglingVersion::V0`in `test::run_compiler` in
-                // `bindings.rs` for rationale behind using `symbol-mangling-version=v0`.
-                "--codegen=symbol-mangling-version=v0".to_string(),
                 "--crate-type=lib".to_string(),
                 format!("--sysroot={}", get_sysroot_for_testing().display()),
                 rs_input_path.display().to_string(),
@@ -344,6 +342,51 @@
         }
     }
 
+    // TODO(b/261074843): Go back to exact string matching (and hardcoding thunk
+    // names) once we are using stable name mangling (which may be coming in Q1
+    // 2023).  ("Go back" = more or less revert cl/492292910 + manual review and
+    // tweaks.)
+    fn assert_body_matches(actual: &str, expected: &str) {
+        fn build_regex(expected_body: &str) -> Regex {
+            let patt = regex::escape(expected_body);
+            let patt = format!("^{patt}"); // Not always matching $ enables prefix checks below.
+            let patt = patt.replace("ANY_IDENTIFIER_CHARACTERS", "[a-zA-Z0-9_]*");
+            RegexBuilder::new(&patt).multi_line(false).dot_matches_new_line(false).build().unwrap()
+        }
+        let is_whole_h_body_matching = {
+            match build_regex(expected).shortest_match(&actual) {
+                None => false,
+                Some(len) => len == actual.len(),
+            }
+        };
+        if !is_whole_h_body_matching {
+            let longest_matching_expectation_len = (0..=expected.len())
+                .rev() // Iterating from longest to shortest prefix
+                .filter(|&len| {
+                    expected
+                        .get(0..len) // Only valid UTF-8 boundaries
+                        .filter(|prefix| build_regex(prefix).is_match(&actual))
+                        .is_some()
+                })
+                .next() // Getting the first regex that matched
+                .unwrap(); // We must get a match at least for 0-length expected body
+            let longest_matching_regex =
+                build_regex(&expected[0..longest_matching_expectation_len]);
+            let len_of_longest_match = longest_matching_regex.shortest_match(&actual).unwrap(); // Again - we must get a match at least for 0-length expected body
+            let mut marked_body = actual.to_string();
+            marked_body.insert_str(len_of_longest_match, "!!!>>>");
+            let mut marked_pattern = expected.to_string();
+            marked_pattern.insert_str(longest_matching_expectation_len, "!!!>>>");
+            panic!(
+                "h_body didn't match expectations:\n\
+                    #### Actual body (first mismatch follows the \"!!!>>>\" marker):\n\
+                    {marked_body}\n\
+                    #### Mismatched pattern (mismatch follows the \"!!!>>>\" marker):\n\
+                    {marked_pattern}"
+            );
+        }
+    }
+
     #[test]
     fn test_happy_path() -> anyhow::Result<()> {
         let test_args = TestArgs::default_args()?;
@@ -351,38 +394,39 @@
 
         assert!(test_result.h_path.exists());
         let h_body = std::fs::read_to_string(&test_result.h_path)?;
-        assert_eq!(
-            h_body,
-r#"// Automatically @generated C++ bindings for the following Rust crate:
+        assert_body_matches(
+            &h_body,
+            r#"// Automatically @generated C++ bindings for the following Rust crate:
 // test_crate
 
 #pragma once
 
 namespace test_crate {
 namespace __crubit_internal {
-extern "C" void __crubit_thunk__RNvCsqz8GFuO9cP_10test_crate15public_function();
+extern "C" void
+__crubit_thunk__ANY_IDENTIFIER_CHARACTERS();
 }
 inline void public_function() {
   return __crubit_internal::
-      __crubit_thunk__RNvCsqz8GFuO9cP_10test_crate15public_function();
+      __crubit_thunk__ANY_IDENTIFIER_CHARACTERS();
 }
-}  // namespace test_crate"#
+}  // namespace test_crate"#,
         );
 
         assert!(test_result.rs_path.exists());
         let rs_body = std::fs::read_to_string(&test_result.rs_path)?;
-        assert_eq!(
-            rs_body,
+        assert_body_matches(
+            &rs_body,
             r#"// Automatically @generated C++ bindings for the following Rust crate:
 // test_crate
 
 #![allow(improper_ctypes_definitions)]
 
 #[no_mangle]
-extern "C" fn __crubit_thunk__RNvCsqz8GFuO9cP_10test_crate15public_function() -> () {
+extern "C" fn __crubit_thunk__ANY_IDENTIFIER_CHARACTERS() -> () {
     ::test_crate::public_function()
 }
-"#
+"#,
         );
         Ok(())
     }