Loading tools/aconfig/aconfig/Android.bp +4 −0 Original line number Diff line number Diff line Loading @@ -23,6 +23,10 @@ rust_defaults { "libtinytemplate", "libconvert_finalized_flags", ], cfgs: select(release_flag("RELEASE_ACONFIG_FINGERPRINT_RUST"), { true: ["enable_fingerprint_rust"], default: [], }), } rust_binary_host { Loading tools/aconfig/aconfig/Cargo.toml +3 −0 Original line number Diff line number Diff line Loading @@ -25,3 +25,6 @@ itertools = "0.10.5" serde = { version = "1.0.152", features = ["derive"] } serde_json = "1.0.93" convert_finalized_flags = { path = "../convert_finalized_flags" } [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(enable_fingerprint_rust)'] } tools/aconfig/aconfig/src/codegen/rust.rs +62 −60 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ pub fn generate_rust_code<I>( flag_ids: HashMap<String, u16>, parsed_flags_iter: I, codegen_mode: CodegenMode, package_fingerprint: Option<u64>, ) -> Result<OutputFile> where I: Iterator<Item = ProtoParsedFlag>, Loading @@ -40,12 +41,15 @@ where .collect(); let has_readwrite = template_flags.iter().any(|item| item.readwrite); let container = (template_flags.first().expect("zero template flags").container).to_string(); let use_package_fingerprint = package_fingerprint.is_some(); let context = TemplateContext { package: package.to_string(), template_flags, modules: package.split('.').map(|s| s.to_string()).collect::<Vec<_>>(), has_readwrite, container, use_package_fingerprint, package_fingerprint: package_fingerprint.unwrap_or_default(), }; let mut template = TinyTemplate::new(); template.add_template( Loading @@ -69,6 +73,8 @@ struct TemplateContext { pub modules: Vec<String>, pub has_readwrite: bool, pub container: String, pub use_package_fingerprint: bool, pub package_fingerprint: u64, } #[derive(Serialize)] Loading Loading @@ -131,10 +137,9 @@ use log::{log, LevelFilter, Level}; /// flag provider pub struct FlagProvider; static PACKAGE_OFFSET: LazyLock<Result<Option<u32>, AconfigStorageError>> = LazyLock::new(|| unsafe { static PACKAGE_CONTEXT: LazyLock<Result<Option<PackageReadContext>, AconfigStorageError>> = LazyLock::new(|| unsafe { get_mapped_storage_file("system", StorageFileType::PackageMap) .and_then(|package_map| get_package_read_context(&package_map, "com.android.aconfig.test")) .map(|context| context.map(|c| c.boolean_start_index)) }); static FLAG_VAL_MAP: LazyLock<Result<Mmap, AconfigStorageError>> = LazyLock::new(|| unsafe { Loading @@ -153,13 +158,13 @@ static CACHED_disabled_rw: LazyLock<bool> = LazyLock::new(|| { .as_ref() .map_err(|err| format!("failed to get flag val map: {err}")) .and_then(|flag_val_map| { PACKAGE_OFFSET PACKAGE_CONTEXT .as_ref() .map_err(|err| format!("failed to get package read offset: {err}")) .and_then(|package_offset| { match package_offset { Some(offset) => { get_boolean_flag_value(&flag_val_map, offset + 0) .map_err(|err| format!("failed to get package read context: {err}")) .and_then(|package_context| { match package_context { Some(context) => { get_boolean_flag_value(&flag_val_map, context.boolean_start_index + 0) .map_err(|err| format!("failed to get flag: {err}")) }, None => { Loading Loading @@ -193,13 +198,13 @@ static CACHED_disabled_rw_exported: LazyLock<bool> = LazyLock::new(|| { .as_ref() .map_err(|err| format!("failed to get flag val map: {err}")) .and_then(|flag_val_map| { PACKAGE_OFFSET PACKAGE_CONTEXT .as_ref() .map_err(|err| format!("failed to get package read offset: {err}")) .and_then(|package_offset| { match package_offset { Some(offset) => { get_boolean_flag_value(&flag_val_map, offset + 1) .map_err(|err| format!("failed to get package read context: {err}")) .and_then(|package_context| { match package_context { Some(context) => { get_boolean_flag_value(&flag_val_map, context.boolean_start_index + 1) .map_err(|err| format!("failed to get flag: {err}")) }, None => { Loading Loading @@ -233,13 +238,13 @@ static CACHED_disabled_rw_in_other_namespace: LazyLock<bool> = LazyLock::new(|| .as_ref() .map_err(|err| format!("failed to get flag val map: {err}")) .and_then(|flag_val_map| { PACKAGE_OFFSET PACKAGE_CONTEXT .as_ref() .map_err(|err| format!("failed to get package read offset: {err}")) .and_then(|package_offset| { match package_offset { Some(offset) => { get_boolean_flag_value(&flag_val_map, offset + 2) .map_err(|err| format!("failed to get package read context: {err}")) .and_then(|package_context| { match package_context { Some(context) => { get_boolean_flag_value(&flag_val_map, context.boolean_start_index + 2) .map_err(|err| format!("failed to get flag: {err}")) }, None => { Loading Loading @@ -274,13 +279,13 @@ static CACHED_enabled_rw: LazyLock<bool> = LazyLock::new(|| { .as_ref() .map_err(|err| format!("failed to get flag val map: {err}")) .and_then(|flag_val_map| { PACKAGE_OFFSET PACKAGE_CONTEXT .as_ref() .map_err(|err| format!("failed to get package read offset: {err}")) .and_then(|package_offset| { match package_offset { Some(offset) => { get_boolean_flag_value(&flag_val_map, offset + 7) .map_err(|err| format!("failed to get package read context: {err}")) .and_then(|package_context| { match package_context { Some(context) => { get_boolean_flag_value(&flag_val_map, context.boolean_start_index + 7) .map_err(|err| format!("failed to get flag: {err}")) }, None => { Loading Loading @@ -426,10 +431,9 @@ pub struct FlagProvider { overrides: BTreeMap<&'static str, bool>, } static PACKAGE_OFFSET: LazyLock<Result<Option<u32>, AconfigStorageError>> = LazyLock::new(|| unsafe { static PACKAGE_CONTEXT: LazyLock<Result<Option<PackageReadContext>, AconfigStorageError>> = LazyLock::new(|| unsafe { get_mapped_storage_file("system", StorageFileType::PackageMap) .and_then(|package_map| get_package_read_context(&package_map, "com.android.aconfig.test")) .map(|context| context.map(|c| c.boolean_start_index)) }); static FLAG_VAL_MAP: LazyLock<Result<Mmap, AconfigStorageError>> = LazyLock::new(|| unsafe { Loading @@ -448,13 +452,13 @@ static CACHED_disabled_rw: LazyLock<bool> = LazyLock::new(|| { .as_ref() .map_err(|err| format!("failed to get flag val map: {err}")) .and_then(|flag_val_map| { PACKAGE_OFFSET PACKAGE_CONTEXT .as_ref() .map_err(|err| format!("failed to get package read offset: {err}")) .and_then(|package_offset| { match package_offset { Some(offset) => { get_boolean_flag_value(&flag_val_map, offset + 0) .map_err(|err| format!("failed to get package read context: {err}")) .and_then(|package_context| { match package_context { Some(context) => { get_boolean_flag_value(&flag_val_map, context.boolean_start_index + 0) .map_err(|err| format!("failed to get flag: {err}")) }, None => { Loading Loading @@ -488,13 +492,13 @@ static CACHED_disabled_rw_exported: LazyLock<bool> = LazyLock::new(|| { .as_ref() .map_err(|err| format!("failed to get flag val map: {err}")) .and_then(|flag_val_map| { PACKAGE_OFFSET PACKAGE_CONTEXT .as_ref() .map_err(|err| format!("failed to get package read offset: {err}")) .and_then(|package_offset| { match package_offset { Some(offset) => { get_boolean_flag_value(&flag_val_map, offset + 1) .map_err(|err| format!("failed to get package read context: {err}")) .and_then(|package_context| { match package_context { Some(context) => { get_boolean_flag_value(&flag_val_map, context.boolean_start_index + 1) .map_err(|err| format!("failed to get flag: {err}")) }, None => { Loading Loading @@ -528,13 +532,13 @@ static CACHED_disabled_rw_in_other_namespace: LazyLock<bool> = LazyLock::new(|| .as_ref() .map_err(|err| format!("failed to get flag val map: {err}")) .and_then(|flag_val_map| { PACKAGE_OFFSET PACKAGE_CONTEXT .as_ref() .map_err(|err| format!("failed to get package read offset: {err}")) .and_then(|package_offset| { match package_offset { Some(offset) => { get_boolean_flag_value(&flag_val_map, offset + 2) .map_err(|err| format!("failed to get package read context: {err}")) .and_then(|package_context| { match package_context { Some(context) => { get_boolean_flag_value(&flag_val_map, context.boolean_start_index + 2) .map_err(|err| format!("failed to get flag: {err}")) }, None => { Loading Loading @@ -569,13 +573,13 @@ static CACHED_enabled_rw: LazyLock<bool> = LazyLock::new(|| { .as_ref() .map_err(|err| format!("failed to get flag val map: {err}")) .and_then(|flag_val_map| { PACKAGE_OFFSET PACKAGE_CONTEXT .as_ref() .map_err(|err| format!("failed to get package read offset: {err}")) .and_then(|package_offset| { match package_offset { Some(offset) => { get_boolean_flag_value(&flag_val_map, offset + 7) .map_err(|err| format!("failed to get package read context: {err}")) .and_then(|package_context| { match package_context { Some(context) => { get_boolean_flag_value(&flag_val_map, context.boolean_start_index + 7) .map_err(|err| format!("failed to get flag: {err}")) }, None => { Loading Loading @@ -926,15 +930,13 @@ pub fn enabled_rw() -> bool { flag_ids, modified_parsed_flags.into_iter(), mode, None, ) .unwrap(); assert_eq!("src/lib.rs", format!("{}", generated.path.display())); assert_eq!( None, crate::test::first_significant_code_diff( crate::test::assert_no_significant_code_diff( expected, &String::from_utf8(generated.contents).unwrap() ) &String::from_utf8(generated.contents).unwrap(), ); } Loading tools/aconfig/aconfig/src/commands.rs +17 −2 Original line number Diff line number Diff line Loading @@ -258,13 +258,28 @@ pub fn create_rust_lib(mut input: Input, codegen_mode: CodegenMode) -> Result<Ou "Exported mode for generated rust flag library is disabled" ); let parsed_flags = input.try_parse_flags()?; let modified_parsed_flags = modify_parsed_flags_based_on_mode(parsed_flags, codegen_mode)?; let modified_parsed_flags = modify_parsed_flags_based_on_mode(parsed_flags.clone(), codegen_mode)?; let Some(package) = find_unique_package(&modified_parsed_flags) else { bail!("no parsed flags, or the parsed flags use different packages"); }; let package = package.to_string(); let package_fingerprint: Option<u64> = if cfg!(enable_fingerprint_rust) { let mut flag_names = extract_flag_names(parsed_flags)?; Some(compute_flags_fingerprint(&mut flag_names)) } else { None }; let flag_ids = assign_flag_ids(&package, modified_parsed_flags.iter())?; generate_rust_code(&package, flag_ids, modified_parsed_flags.into_iter(), codegen_mode) generate_rust_code( &package, flag_ids, modified_parsed_flags.into_iter(), codegen_mode, package_fingerprint, ) } pub fn create_storage( Loading tools/aconfig/aconfig/src/test.rs +24 −0 Original line number Diff line number Diff line Loading @@ -333,6 +333,30 @@ parsed_flag { } } /// Asserts that the two strings are equivalent. For use in tests. Fails /// with formatted error message for easier debugging. pub fn assert_no_significant_code_diff(expected: &str, actual: &str) { let expected = expected.lines().map(|line| line.trim_start()).filter(|line| !line.is_empty()); let actual = actual.lines().map(|line| line.trim_start()).filter(|line| !line.is_empty()); let fail_message: Option<String> = match itertools::diff_with(expected, actual, |left, right| left == right) { Some(itertools::Diff::FirstMismatch(_, mut left, mut right)) => Some(format!( "DOES NOT MATCH: 1) expected, 2) actual:\n{}\n{}", left.next().unwrap(), right.next().unwrap() )), Some(itertools::Diff::Shorter(_, mut left)) => { Some(format!("LHS trailing data: '{}'", left.next().unwrap())) } Some(itertools::Diff::Longer(_, mut right)) => { Some(format!("RHS trailing data: '{}'", right.next().unwrap())) } None => None, }; assert!(fail_message.is_none(), "{}", fail_message.unwrap()); } #[test] fn test_first_significant_code_diff() { assert!(first_significant_code_diff("", "").is_none()); Loading Loading
tools/aconfig/aconfig/Android.bp +4 −0 Original line number Diff line number Diff line Loading @@ -23,6 +23,10 @@ rust_defaults { "libtinytemplate", "libconvert_finalized_flags", ], cfgs: select(release_flag("RELEASE_ACONFIG_FINGERPRINT_RUST"), { true: ["enable_fingerprint_rust"], default: [], }), } rust_binary_host { Loading
tools/aconfig/aconfig/Cargo.toml +3 −0 Original line number Diff line number Diff line Loading @@ -25,3 +25,6 @@ itertools = "0.10.5" serde = { version = "1.0.152", features = ["derive"] } serde_json = "1.0.93" convert_finalized_flags = { path = "../convert_finalized_flags" } [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(enable_fingerprint_rust)'] }
tools/aconfig/aconfig/src/codegen/rust.rs +62 −60 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ pub fn generate_rust_code<I>( flag_ids: HashMap<String, u16>, parsed_flags_iter: I, codegen_mode: CodegenMode, package_fingerprint: Option<u64>, ) -> Result<OutputFile> where I: Iterator<Item = ProtoParsedFlag>, Loading @@ -40,12 +41,15 @@ where .collect(); let has_readwrite = template_flags.iter().any(|item| item.readwrite); let container = (template_flags.first().expect("zero template flags").container).to_string(); let use_package_fingerprint = package_fingerprint.is_some(); let context = TemplateContext { package: package.to_string(), template_flags, modules: package.split('.').map(|s| s.to_string()).collect::<Vec<_>>(), has_readwrite, container, use_package_fingerprint, package_fingerprint: package_fingerprint.unwrap_or_default(), }; let mut template = TinyTemplate::new(); template.add_template( Loading @@ -69,6 +73,8 @@ struct TemplateContext { pub modules: Vec<String>, pub has_readwrite: bool, pub container: String, pub use_package_fingerprint: bool, pub package_fingerprint: u64, } #[derive(Serialize)] Loading Loading @@ -131,10 +137,9 @@ use log::{log, LevelFilter, Level}; /// flag provider pub struct FlagProvider; static PACKAGE_OFFSET: LazyLock<Result<Option<u32>, AconfigStorageError>> = LazyLock::new(|| unsafe { static PACKAGE_CONTEXT: LazyLock<Result<Option<PackageReadContext>, AconfigStorageError>> = LazyLock::new(|| unsafe { get_mapped_storage_file("system", StorageFileType::PackageMap) .and_then(|package_map| get_package_read_context(&package_map, "com.android.aconfig.test")) .map(|context| context.map(|c| c.boolean_start_index)) }); static FLAG_VAL_MAP: LazyLock<Result<Mmap, AconfigStorageError>> = LazyLock::new(|| unsafe { Loading @@ -153,13 +158,13 @@ static CACHED_disabled_rw: LazyLock<bool> = LazyLock::new(|| { .as_ref() .map_err(|err| format!("failed to get flag val map: {err}")) .and_then(|flag_val_map| { PACKAGE_OFFSET PACKAGE_CONTEXT .as_ref() .map_err(|err| format!("failed to get package read offset: {err}")) .and_then(|package_offset| { match package_offset { Some(offset) => { get_boolean_flag_value(&flag_val_map, offset + 0) .map_err(|err| format!("failed to get package read context: {err}")) .and_then(|package_context| { match package_context { Some(context) => { get_boolean_flag_value(&flag_val_map, context.boolean_start_index + 0) .map_err(|err| format!("failed to get flag: {err}")) }, None => { Loading Loading @@ -193,13 +198,13 @@ static CACHED_disabled_rw_exported: LazyLock<bool> = LazyLock::new(|| { .as_ref() .map_err(|err| format!("failed to get flag val map: {err}")) .and_then(|flag_val_map| { PACKAGE_OFFSET PACKAGE_CONTEXT .as_ref() .map_err(|err| format!("failed to get package read offset: {err}")) .and_then(|package_offset| { match package_offset { Some(offset) => { get_boolean_flag_value(&flag_val_map, offset + 1) .map_err(|err| format!("failed to get package read context: {err}")) .and_then(|package_context| { match package_context { Some(context) => { get_boolean_flag_value(&flag_val_map, context.boolean_start_index + 1) .map_err(|err| format!("failed to get flag: {err}")) }, None => { Loading Loading @@ -233,13 +238,13 @@ static CACHED_disabled_rw_in_other_namespace: LazyLock<bool> = LazyLock::new(|| .as_ref() .map_err(|err| format!("failed to get flag val map: {err}")) .and_then(|flag_val_map| { PACKAGE_OFFSET PACKAGE_CONTEXT .as_ref() .map_err(|err| format!("failed to get package read offset: {err}")) .and_then(|package_offset| { match package_offset { Some(offset) => { get_boolean_flag_value(&flag_val_map, offset + 2) .map_err(|err| format!("failed to get package read context: {err}")) .and_then(|package_context| { match package_context { Some(context) => { get_boolean_flag_value(&flag_val_map, context.boolean_start_index + 2) .map_err(|err| format!("failed to get flag: {err}")) }, None => { Loading Loading @@ -274,13 +279,13 @@ static CACHED_enabled_rw: LazyLock<bool> = LazyLock::new(|| { .as_ref() .map_err(|err| format!("failed to get flag val map: {err}")) .and_then(|flag_val_map| { PACKAGE_OFFSET PACKAGE_CONTEXT .as_ref() .map_err(|err| format!("failed to get package read offset: {err}")) .and_then(|package_offset| { match package_offset { Some(offset) => { get_boolean_flag_value(&flag_val_map, offset + 7) .map_err(|err| format!("failed to get package read context: {err}")) .and_then(|package_context| { match package_context { Some(context) => { get_boolean_flag_value(&flag_val_map, context.boolean_start_index + 7) .map_err(|err| format!("failed to get flag: {err}")) }, None => { Loading Loading @@ -426,10 +431,9 @@ pub struct FlagProvider { overrides: BTreeMap<&'static str, bool>, } static PACKAGE_OFFSET: LazyLock<Result<Option<u32>, AconfigStorageError>> = LazyLock::new(|| unsafe { static PACKAGE_CONTEXT: LazyLock<Result<Option<PackageReadContext>, AconfigStorageError>> = LazyLock::new(|| unsafe { get_mapped_storage_file("system", StorageFileType::PackageMap) .and_then(|package_map| get_package_read_context(&package_map, "com.android.aconfig.test")) .map(|context| context.map(|c| c.boolean_start_index)) }); static FLAG_VAL_MAP: LazyLock<Result<Mmap, AconfigStorageError>> = LazyLock::new(|| unsafe { Loading @@ -448,13 +452,13 @@ static CACHED_disabled_rw: LazyLock<bool> = LazyLock::new(|| { .as_ref() .map_err(|err| format!("failed to get flag val map: {err}")) .and_then(|flag_val_map| { PACKAGE_OFFSET PACKAGE_CONTEXT .as_ref() .map_err(|err| format!("failed to get package read offset: {err}")) .and_then(|package_offset| { match package_offset { Some(offset) => { get_boolean_flag_value(&flag_val_map, offset + 0) .map_err(|err| format!("failed to get package read context: {err}")) .and_then(|package_context| { match package_context { Some(context) => { get_boolean_flag_value(&flag_val_map, context.boolean_start_index + 0) .map_err(|err| format!("failed to get flag: {err}")) }, None => { Loading Loading @@ -488,13 +492,13 @@ static CACHED_disabled_rw_exported: LazyLock<bool> = LazyLock::new(|| { .as_ref() .map_err(|err| format!("failed to get flag val map: {err}")) .and_then(|flag_val_map| { PACKAGE_OFFSET PACKAGE_CONTEXT .as_ref() .map_err(|err| format!("failed to get package read offset: {err}")) .and_then(|package_offset| { match package_offset { Some(offset) => { get_boolean_flag_value(&flag_val_map, offset + 1) .map_err(|err| format!("failed to get package read context: {err}")) .and_then(|package_context| { match package_context { Some(context) => { get_boolean_flag_value(&flag_val_map, context.boolean_start_index + 1) .map_err(|err| format!("failed to get flag: {err}")) }, None => { Loading Loading @@ -528,13 +532,13 @@ static CACHED_disabled_rw_in_other_namespace: LazyLock<bool> = LazyLock::new(|| .as_ref() .map_err(|err| format!("failed to get flag val map: {err}")) .and_then(|flag_val_map| { PACKAGE_OFFSET PACKAGE_CONTEXT .as_ref() .map_err(|err| format!("failed to get package read offset: {err}")) .and_then(|package_offset| { match package_offset { Some(offset) => { get_boolean_flag_value(&flag_val_map, offset + 2) .map_err(|err| format!("failed to get package read context: {err}")) .and_then(|package_context| { match package_context { Some(context) => { get_boolean_flag_value(&flag_val_map, context.boolean_start_index + 2) .map_err(|err| format!("failed to get flag: {err}")) }, None => { Loading Loading @@ -569,13 +573,13 @@ static CACHED_enabled_rw: LazyLock<bool> = LazyLock::new(|| { .as_ref() .map_err(|err| format!("failed to get flag val map: {err}")) .and_then(|flag_val_map| { PACKAGE_OFFSET PACKAGE_CONTEXT .as_ref() .map_err(|err| format!("failed to get package read offset: {err}")) .and_then(|package_offset| { match package_offset { Some(offset) => { get_boolean_flag_value(&flag_val_map, offset + 7) .map_err(|err| format!("failed to get package read context: {err}")) .and_then(|package_context| { match package_context { Some(context) => { get_boolean_flag_value(&flag_val_map, context.boolean_start_index + 7) .map_err(|err| format!("failed to get flag: {err}")) }, None => { Loading Loading @@ -926,15 +930,13 @@ pub fn enabled_rw() -> bool { flag_ids, modified_parsed_flags.into_iter(), mode, None, ) .unwrap(); assert_eq!("src/lib.rs", format!("{}", generated.path.display())); assert_eq!( None, crate::test::first_significant_code_diff( crate::test::assert_no_significant_code_diff( expected, &String::from_utf8(generated.contents).unwrap() ) &String::from_utf8(generated.contents).unwrap(), ); } Loading
tools/aconfig/aconfig/src/commands.rs +17 −2 Original line number Diff line number Diff line Loading @@ -258,13 +258,28 @@ pub fn create_rust_lib(mut input: Input, codegen_mode: CodegenMode) -> Result<Ou "Exported mode for generated rust flag library is disabled" ); let parsed_flags = input.try_parse_flags()?; let modified_parsed_flags = modify_parsed_flags_based_on_mode(parsed_flags, codegen_mode)?; let modified_parsed_flags = modify_parsed_flags_based_on_mode(parsed_flags.clone(), codegen_mode)?; let Some(package) = find_unique_package(&modified_parsed_flags) else { bail!("no parsed flags, or the parsed flags use different packages"); }; let package = package.to_string(); let package_fingerprint: Option<u64> = if cfg!(enable_fingerprint_rust) { let mut flag_names = extract_flag_names(parsed_flags)?; Some(compute_flags_fingerprint(&mut flag_names)) } else { None }; let flag_ids = assign_flag_ids(&package, modified_parsed_flags.iter())?; generate_rust_code(&package, flag_ids, modified_parsed_flags.into_iter(), codegen_mode) generate_rust_code( &package, flag_ids, modified_parsed_flags.into_iter(), codegen_mode, package_fingerprint, ) } pub fn create_storage( Loading
tools/aconfig/aconfig/src/test.rs +24 −0 Original line number Diff line number Diff line Loading @@ -333,6 +333,30 @@ parsed_flag { } } /// Asserts that the two strings are equivalent. For use in tests. Fails /// with formatted error message for easier debugging. pub fn assert_no_significant_code_diff(expected: &str, actual: &str) { let expected = expected.lines().map(|line| line.trim_start()).filter(|line| !line.is_empty()); let actual = actual.lines().map(|line| line.trim_start()).filter(|line| !line.is_empty()); let fail_message: Option<String> = match itertools::diff_with(expected, actual, |left, right| left == right) { Some(itertools::Diff::FirstMismatch(_, mut left, mut right)) => Some(format!( "DOES NOT MATCH: 1) expected, 2) actual:\n{}\n{}", left.next().unwrap(), right.next().unwrap() )), Some(itertools::Diff::Shorter(_, mut left)) => { Some(format!("LHS trailing data: '{}'", left.next().unwrap())) } Some(itertools::Diff::Longer(_, mut right)) => { Some(format!("RHS trailing data: '{}'", right.next().unwrap())) } None => None, }; assert!(fail_message.is_none(), "{}", fail_message.unwrap()); } #[test] fn test_first_significant_code_diff() { assert!(first_significant_code_diff("", "").is_none()); Loading