Use `supported | experimental` features everywhere in tests.

This actually was a bit tricky to implement, because it broke the formatting -- I discovered we use rustfmt for formatting the `Debug` output, which, while it mkaes sense, seems probably unnecessary if we do some plumbing. But I couldn't figure out the plumbing right now, so I filed b/272530008 instead.

After reflecting for this bug for a bit, I decided the sensible thing is to try to share the logic for both in one helper function, even if it seems a little awkward to do so -- having _no_ shared functionality is essentially what caused it.

PiperOrigin-RevId: 516681109
diff --git a/rs_bindings_from_cc/BUILD b/rs_bindings_from_cc/BUILD
index a30710c..b5b0139 100644
--- a/rs_bindings_from_cc/BUILD
+++ b/rs_bindings_from_cc/BUILD
@@ -329,7 +329,20 @@
         ":json_from_cc",
         "//common:arc_anyhow",
         "//common:ffi_types",
+        "@crate_index//:flagset",
         "@crate_index//:itertools",
+        "@crate_index//:once_cell",
+    ],
+)
+
+rust_test(
+    name = "ir_testing_test",
+    crate = ":ir_testing",
+    deps = [
+        ":ir_matchers",
+        ":ir_testing",
+        "//common:rust_allocator_shims",
+        "@crate_index//:quote",
     ],
 )
 
diff --git a/rs_bindings_from_cc/ir.rs b/rs_bindings_from_cc/ir.rs
index d2cf11b..43f033e 100644
--- a/rs_bindings_from_cc/ir.rs
+++ b/rs_bindings_from_cc/ir.rs
@@ -779,7 +779,7 @@
     }
 }
 
-#[derive(Debug, PartialEq, Eq, Clone, Deserialize)]
+#[derive(PartialEq, Eq, Clone, Deserialize)]
 #[serde(deny_unknown_fields, rename(deserialize = "IR"))]
 struct FlatIR {
     #[serde(default)]
@@ -795,6 +795,42 @@
     crubit_features: HashMap<BazelLabel, CrubitFeaturesIR>,
 }
 
+/// A custom debug impl that wraps the HashMap in rustfmt-friendly notation.
+///
+/// See b/272530008.
+impl std::fmt::Debug for FlatIR {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        struct DebugHashMap<T: Debug>(pub T);
+        impl<T: Debug> std::fmt::Debug for DebugHashMap<T> {
+            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+                // prefix the hash map with `hash_map!` so that the output can be fed to
+                // rustfmt. The end result is something like `hash_map!{k:v,
+                // k2:v2}`, which reads well.
+                write!(f, "hash_map!")?;
+                std::fmt::Debug::fmt(&self.0, f)
+            }
+        }
+        // exhaustive-match so we don't forget to add fields to Debug when we add to
+        // FlatIR.
+        let FlatIR {
+            public_headers,
+            current_target,
+            items,
+            top_level_item_ids,
+            crate_root_path,
+            crubit_features,
+        } = self;
+        f.debug_struct("FlatIR")
+            .field("public_headers", public_headers)
+            .field("current_target", current_target)
+            .field("items", items)
+            .field("top_level_item_ids", top_level_item_ids)
+            .field("crate_root_path", crate_root_path)
+            .field("crubit_features", &DebugHashMap(crubit_features))
+            .finish()
+    }
+}
+
 /// Struct providing the necessary information about the API of a C++ target to
 /// enable generation of Rust bindings source code (both `rs_api.rs` and
 /// `rs_api_impl.cc` files).
@@ -905,6 +941,22 @@
         self.flat_ir.crubit_features.get(target).cloned().unwrap_or_default().0
     }
 
+    /// Returns a mutable reference to the Crubit features enabled for the given
+    /// `target`.
+    ///
+    /// Since IR is generally only held immutably, this is only useful for
+    /// testing.
+    #[must_use]
+    pub fn target_crubit_features_mut(
+        &mut self,
+        target: &BazelLabel,
+    ) -> &mut flagset::FlagSet<CrubitFeature> {
+        // TODO(jeanpierreda): migrate to raw_entry_mut when stable.
+        // (target is taken by reference exactly because ideally this function would use
+        // the raw entry API.)
+        &mut self.flat_ir.crubit_features.entry(target.clone()).or_default().0
+    }
+
     pub fn current_target(&self) -> &BazelLabel {
         &self.flat_ir.current_target
     }
diff --git a/rs_bindings_from_cc/ir_testing.rs b/rs_bindings_from_cc/ir_testing.rs
index 0b23230..087e24b 100644
--- a/rs_bindings_from_cc/ir_testing.rs
+++ b/rs_bindings_from_cc/ir_testing.rs
@@ -2,11 +2,14 @@
 // Exceptions. See /LICENSE for license information.
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
+use std::collections::hash_map::HashMap;
+
 use arc_anyhow::Result;
+use itertools::Itertools;
+use once_cell::sync::Lazy;
 
 use ffi_types::{FfiU8Slice, FfiU8SliceBox};
 use ir::{self, make_ir_from_parts, Func, Identifier, Item, Record, IR};
-use itertools::Itertools;
 
 /// Generates `IR` from a header containing `header_source`.
 pub fn ir_from_cc(header_source: &str) -> Result<IR> {
@@ -30,20 +33,31 @@
 
 /// Name of the current target used by `ir_from_cc` and `ir_from_cc_dependency`.
 pub const TESTING_TARGET: &str = "//test:testing_target";
+static TESTING_FEATURES: Lazy<flagset::FlagSet<ir::CrubitFeature>> =
+    Lazy::new(|| ir::CrubitFeature::Experimental | ir::CrubitFeature::Supported);
+
+/// Update the IR to have common test-only items.
+///
+/// This provides one place to update the IR that affects both
+/// `make_ir_from_items` and `ir_from_cc_dependency`.
+fn update_test_ir(ir: &mut IR) {
+    *ir.target_crubit_features_mut(&ir.current_target().clone()) = *TESTING_FEATURES;
+}
 
 /// Create a testing `IR` instance from given items, using mock values for other
 /// fields.
 pub fn make_ir_from_items(items: impl IntoIterator<Item = Item>) -> Result<IR> {
-    let target: ir::BazelLabel = TESTING_TARGET.into();
-    make_ir_from_parts(
+    let mut ir = make_ir_from_parts(
         items.into_iter().collect_vec(),
         /* public_headers= */ vec![],
-        /* current_target= */ target.clone(),
+        /* current_target= */ TESTING_TARGET.into(),
         /* top_level_item_ids= */ vec![],
         /* crate_root_path= */ None,
         /* crubit_features= */
-        [(target, ir::CrubitFeature::Experimental | ir::CrubitFeature::Supported)].into(),
-    )
+        <HashMap<ir::BazelLabel, flagset::FlagSet<ir::CrubitFeature>>>::new(),
+    )?;
+    update_test_ir(&mut ir);
+    Ok(ir)
 }
 
 /// Target of the dependency used by `ir_from_cc_dependency`.
@@ -76,7 +90,9 @@
         )
         .into_boxed_slice()
     };
-    ir::deserialize_ir(&*json_utf8)
+    let mut ir = ir::deserialize_ir(&*json_utf8)?;
+    update_test_ir(&mut ir);
+    Ok(ir)
 }
 
 /// Creates an identifier
@@ -119,3 +135,35 @@
     }
     panic!("Didn't find record with cc_name {}", cc_name);
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use ir_matchers::assert_ir_matches;
+    use quote::quote;
+
+    #[test]
+    fn test_features_ir_from_cc() -> Result<()> {
+        assert_ir_matches!(
+            ir_from_cc("")?,
+            quote! {
+                crubit_features: hash_map!{
+                    BazelLabel("//test:testing_target"): CrubitFeaturesIR(FlagSet(Supported|Experimental))
+                }
+            }
+        );
+        Ok(())
+    }
+    #[test]
+    fn test_features_ir_from_items() -> Result<()> {
+        assert_ir_matches!(
+            make_ir_from_items([])?,
+            quote! {
+                crubit_features: hash_map!{
+                    BazelLabel("//test:testing_target"): CrubitFeaturesIR(FlagSet(Supported|Experimental))
+                }
+            }
+        );
+        Ok(())
+    }
+}