Merge "Add libdts for comparing DTs" into main
diff --git a/tests/libs/libdts/Android.bp b/tests/libs/libdts/Android.bp
new file mode 100644
index 0000000..512c50b
--- /dev/null
+++ b/tests/libs/libdts/Android.bp
@@ -0,0 +1,17 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_library_rlib {
+    name: "libdts",
+    crate_name: "dts",
+    defaults: ["avf_build_flags_rust"],
+    srcs: ["src/lib.rs"],
+    edition: "2021",
+    prefer_rlib: true,
+    rustlibs: [
+        "libanyhow",
+        "liblibfdt",
+    ],
+    apex_available: ["com.android.virt"],
+}
diff --git a/tests/libs/libdts/README.md b/tests/libs/libdts/README.md
new file mode 100644
index 0000000..ed63bd0
--- /dev/null
+++ b/tests/libs/libdts/README.md
@@ -0,0 +1,16 @@
+Device tree source (DTS) decompiler on Android device.
+
+This is alternative to dtdiff, which only support bash.
+
+How to use for rust_test
+========================
+
+Following dependencies are needed in addition to libdts.
+
+```
+rust_test {
+  ...
+  data_bins: ["dtc_static"],
+  compile_multilib: "first",
+}
+```
diff --git a/tests/libs/libdts/src/lib.rs b/tests/libs/libdts/src/lib.rs
new file mode 100644
index 0000000..0ee9b66
--- /dev/null
+++ b/tests/libs/libdts/src/lib.rs
@@ -0,0 +1,75 @@
+// Copyright 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Device tree source (dts) for comparing device tree contents
+//! i.e. sorted dts decompiled by `dtc -s -O dts`.
+
+use anyhow::{anyhow, Result};
+use libfdt::Fdt;
+use std::io::Write;
+use std::path::Path;
+use std::process::{Command, Stdio};
+
+/// Device tree source (dts)
+#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
+pub struct Dts {
+    dts: String,
+}
+
+impl Dts {
+    /// Creates a device tree source from /proc/device-tree style directory
+    pub fn from_fs(path: &Path) -> Result<Self> {
+        let path = path.to_str().unwrap();
+        let res = Command::new("./dtc_static")
+            .args(["-f", "-s", "-I", "fs", "-O", "dts", path])
+            .output()?;
+        if !res.status.success() {
+            return Err(anyhow!("Failed to run dtc_static, res={res:?}"));
+        }
+        Ok(Self { dts: String::from_utf8(res.stdout)? })
+    }
+
+    /// Creates a device tree source from dtb
+    pub fn from_dtb(path: &Path) -> Result<Self> {
+        let path = path.to_str().unwrap();
+        let res = Command::new("./dtc_static")
+            .args(["-f", "-s", "-I", "dtb", "-O", "dts", path])
+            .output()?;
+        if !res.status.success() {
+            return Err(anyhow!("Failed to run dtc_static, res={res:?}"));
+        }
+        Ok(Self { dts: String::from_utf8(res.stdout)? })
+    }
+
+    /// Creates a device tree source from Fdt
+    pub fn from_fdt(fdt: &Fdt) -> Result<Self> {
+        let mut dtc = Command::new("./dtc_static")
+            .args(["-f", "-s", "-I", "dtb", "-O", "dts"])
+            .stdin(Stdio::piped())
+            .stdout(Stdio::piped())
+            .spawn()?;
+
+        {
+            let mut stdin = dtc.stdin.take().unwrap();
+            stdin.write_all(fdt.as_slice())?;
+            // Explicitly drop stdin to avoid indefinite blocking
+        }
+
+        let res = dtc.wait_with_output()?;
+        if !res.status.success() {
+            return Err(anyhow!("Failed to run dtc_static, res={res:?}"));
+        }
+        Ok(Self { dts: String::from_utf8(res.stdout)? })
+    }
+}
diff --git a/virtualizationmanager/fsfdt/Android.bp b/virtualizationmanager/fsfdt/Android.bp
index 7a1e5ed..1d03522 100644
--- a/virtualizationmanager/fsfdt/Android.bp
+++ b/virtualizationmanager/fsfdt/Android.bp
@@ -41,6 +41,7 @@
     defaults: ["libfsfdt_default"],
     data: ["testdata/**/*"],
     data_bins: ["dtc_static"],
-    rustlibs: ["libtempfile"],
+    prefer_rlib: true,
+    rustlibs: ["libdts"],
     compile_multilib: "first",
 }
diff --git a/virtualizationmanager/fsfdt/src/lib.rs b/virtualizationmanager/fsfdt/src/lib.rs
index 84e50c1..e176b7b 100644
--- a/virtualizationmanager/fsfdt/src/lib.rs
+++ b/virtualizationmanager/fsfdt/src/lib.rs
@@ -114,51 +114,20 @@
 #[cfg(test)]
 mod test {
     use super::*;
-    use std::io::Write;
-    use std::process::Command;
-    use tempfile::NamedTempFile;
+    use dts::Dts;
 
     const TEST_FS_FDT_ROOT_PATH: &str = "testdata/fs";
     const BUF_SIZE_MAX: usize = 1024;
 
-    fn dts_from_fs(path: &Path) -> String {
-        let path = path.to_str().unwrap();
-        let res = Command::new("./dtc_static")
-            .args(["-f", "-s", "-I", "fs", "-O", "dts", path])
-            .output()
-            .unwrap();
-        assert!(res.status.success(), "{res:?}");
-        String::from_utf8(res.stdout).unwrap()
-    }
-
-    fn dts_from_dtb(path: &Path) -> String {
-        let path = path.to_str().unwrap();
-        let res = Command::new("./dtc_static")
-            .args(["-f", "-s", "-I", "dtb", "-O", "dts", path])
-            .output()
-            .unwrap();
-        assert!(res.status.success(), "{res:?}");
-        String::from_utf8(res.stdout).unwrap()
-    }
-
-    fn to_temp_file(fdt: &Fdt) -> Result<NamedTempFile> {
-        let mut file = NamedTempFile::new()?;
-        file.as_file_mut().write_all(fdt.as_slice())?;
-        file.as_file_mut().sync_all()?;
-
-        Ok(file)
-    }
-
     #[test]
     fn test_from_fs() {
         let fs_path = Path::new(TEST_FS_FDT_ROOT_PATH);
 
         let mut data = vec![0_u8; BUF_SIZE_MAX];
         let fdt = Fdt::from_fs(fs_path, &mut data).unwrap();
-        let file = to_temp_file(fdt).unwrap();
 
-        let expected = dts_from_fs(fs_path);
-        let actual = dts_from_dtb(file.path());
+        let expected = Dts::from_fs(fs_path).unwrap();
+        let actual = Dts::from_fdt(fdt).unwrap();
 
         assert_eq!(&expected, &actual);
         // Again append fdt from TEST_FS_FDT_ROOT_PATH at root & ensure it succeeds when some