aconfig: improve code diffs in tests

Implement a helper function to make it easier for unit tests to diff
(and find the first difference) generated code and expected code.

Bug: 283910447
Test: atest aconfig.test
Change-Id: I460e8fbf05e8f33e8a62ecef67b2d9d77051e876
diff --git a/tools/aconfig/Android.bp b/tools/aconfig/Android.bp
index 9617e0e..b8cce29 100644
--- a/tools/aconfig/Android.bp
+++ b/tools/aconfig/Android.bp
@@ -35,4 +35,7 @@
 rust_test_host {
     name: "aconfig.test",
     defaults: ["aconfig.defaults"],
+    rustlibs: [
+        "libitertools",
+    ],
 }
diff --git a/tools/aconfig/Cargo.toml b/tools/aconfig/Cargo.toml
index 8517dd2..b3c73b8 100644
--- a/tools/aconfig/Cargo.toml
+++ b/tools/aconfig/Cargo.toml
@@ -18,3 +18,6 @@
 
 [build-dependencies]
 protobuf-codegen = "3.2.0"
+
+[dev-dependencies]
+itertools = "0.10.5"
diff --git a/tools/aconfig/src/codegen_cpp.rs b/tools/aconfig/src/codegen_cpp.rs
index 65f95de..2c32bb0 100644
--- a/tools/aconfig/src/codegen_cpp.rs
+++ b/tools/aconfig/src/codegen_cpp.rs
@@ -227,8 +227,11 @@
         let file = generate_cpp_code(&cache).unwrap();
         assert_eq!("aconfig/com_example.h", file.path.to_str().unwrap());
         assert_eq!(
-            expect_content.replace(' ', ""),
-            String::from_utf8(file.contents).unwrap().replace(' ', "")
+            None,
+            crate::test::first_significant_code_diff(
+                expect_content,
+                &String::from_utf8(file.contents).unwrap()
+            )
         );
     }
 }
diff --git a/tools/aconfig/src/codegen_java.rs b/tools/aconfig/src/codegen_java.rs
index 1c1cf77..dfd6766 100644
--- a/tools/aconfig/src/codegen_java.rs
+++ b/tools/aconfig/src/codegen_java.rs
@@ -135,8 +135,11 @@
         let file = generate_java_code(&cache).unwrap();
         assert_eq!("com/example/Flags.java", file.path.to_str().unwrap());
         assert_eq!(
-            expect_content.replace(' ', ""),
-            String::from_utf8(file.contents).unwrap().replace(' ', "")
+            None,
+            crate::test::first_significant_code_diff(
+                expect_content,
+                &String::from_utf8(file.contents).unwrap()
+            )
         );
     }
 }
diff --git a/tools/aconfig/src/codegen_rust.rs b/tools/aconfig/src/codegen_rust.rs
index cfe7316..98caeae 100644
--- a/tools/aconfig/src/codegen_rust.rs
+++ b/tools/aconfig/src/codegen_rust.rs
@@ -126,6 +126,12 @@
 }
 }
 "#;
-        assert_eq!(expected.trim(), String::from_utf8(generated.contents).unwrap().trim());
+        assert_eq!(
+            None,
+            crate::test::first_significant_code_diff(
+                expected,
+                &String::from_utf8(generated.contents).unwrap()
+            )
+        );
     }
 }
diff --git a/tools/aconfig/src/test.rs b/tools/aconfig/src/test.rs
index 21ef74d..cc19c6a 100644
--- a/tools/aconfig/src/test.rs
+++ b/tools/aconfig/src/test.rs
@@ -18,6 +18,7 @@
 pub mod test_utils {
     use crate::cache::Cache;
     use crate::commands::{Input, Source};
+    use itertools;
 
     pub fn create_cache() -> Cache {
         crate::commands::create_cache(
@@ -39,6 +40,54 @@
         )
         .unwrap()
     }
+
+    pub fn first_significant_code_diff(a: &str, b: &str) -> Option<String> {
+        let a = a.lines().map(|line| line.trim_start()).filter(|line| !line.is_empty());
+        let b = b.lines().map(|line| line.trim_start()).filter(|line| !line.is_empty());
+        match itertools::diff_with(a, b, |left, right| left == right) {
+            Some(itertools::Diff::FirstMismatch(_, mut left, mut right)) => {
+                Some(format!("'{}' vs '{}'", 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,
+        }
+    }
+
+    #[test]
+    fn test_first_significant_code_diff() {
+        assert!(first_significant_code_diff("", "").is_none());
+        assert!(first_significant_code_diff("   a", "\n\na\n").is_none());
+        let a = r#"
+        public class A {
+            private static final String FOO = "FOO";
+            public static void main(String[] args) {
+                System.out.println("FOO=" + FOO);
+            }
+        }
+        "#;
+        let b = r#"
+        public class A {
+            private static final String FOO = "BAR";
+            public static void main(String[] args) {
+                System.out.println("foo=" + FOO);
+            }
+        }
+        "#;
+        assert_eq!(Some(r#"'private static final String FOO = "FOO";' vs 'private static final String FOO = "BAR";'"#.to_string()), first_significant_code_diff(a, b));
+        assert_eq!(
+            Some("LHS trailing data: 'b'".to_string()),
+            first_significant_code_diff("a\nb", "a")
+        );
+        assert_eq!(
+            Some("RHS trailing data: 'b'".to_string()),
+            first_significant_code_diff("a", "a\nb")
+        );
+    }
 }
 
 #[cfg(test)]