PDL: add regression tests for ‘pdl’ output
These new tests compare the output of ‘pdl’ with the output of
‘bluetooth_packetgen’ and ensures that we match this.
We will later decouple the two generators, but for now, it’s useful to
generate identical output.
Bug: 228307941
Test: atest pdl_tests
(cherry picked from https://android-review.googlesource.com/q/commit:0cb1ca8ce1623b7239759a87f94f147562c86b99)
Merged-In: Ie45ddd9fceeeefa9ad25c649bfc4db903f9fe1c2
Change-Id: Ie45ddd9fceeeefa9ad25c649bfc4db903f9fe1c2
diff --git a/tools/pdl/Android.bp b/tools/pdl/Android.bp
index 3447e21..26b0cd7 100644
--- a/tools/pdl/Android.bp
+++ b/tools/pdl/Android.bp
@@ -48,3 +48,25 @@
":rustfmt",
],
}
+
+rust_test_host {
+ name: "pdl_tests",
+ srcs: ["test/pdl_tests.rs"],
+ test_suites: ["general-tests"],
+ enabled: false, // rustfmt is only available on x86.
+ arch: {
+ x86_64: {
+ enabled: true,
+ },
+ },
+ // LINT.IfChange
+ rustlibs: [
+ "libtempfile",
+ ],
+ // LINT.ThenChange(Cargo.toml)
+ data: [
+ ":bluetooth_packetgen",
+ ":pdl",
+ ":rustfmt",
+ ],
+}
diff --git a/tools/pdl/Cargo.toml b/tools/pdl/Cargo.toml
index d44826c..bb3429d 100644
--- a/tools/pdl/Cargo.toml
+++ b/tools/pdl/Cargo.toml
@@ -16,4 +16,6 @@
serde_json = "*"
structopt = "*"
syn = "*"
+
+[dev-dependencies]
tempfile = "*"
diff --git a/tools/pdl/src/generator.rs b/tools/pdl/src/generator.rs
index 51e7cba..1232c52 100644
--- a/tools/pdl/src/generator.rs
+++ b/tools/pdl/src/generator.rs
@@ -328,11 +328,22 @@
writer
})
.collect::<Result<Vec<_>>>()?;
+
let total_field_size = syn::Index::from(fields.iter().map(get_field_size).sum::<usize>());
+ let get_size_adjustment = (total_field_size.index > 0).then(|| {
+ Some(quote! {
+ let ret = ret + #total_field_size;
+ })
+ });
code.push_str("e_block! {
impl #data_name {
fn conforms(bytes: &[u8]) -> bool {
+ // TODO(mgeisler): return Boolean expression directly.
+ // TODO(mgeisler): skip when total_field_size == 0.
+ if bytes.len() < #total_field_size {
+ return false;
+ }
true
}
@@ -351,7 +362,7 @@
fn get_size(&self) -> usize {
let ret = 0;
- let ret = ret + #total_field_size;
+ #get_size_adjustment
ret
}
}
@@ -490,7 +501,19 @@
#[cfg(test)]
mod tests {
use super::*;
- use crate::test_utils::{assert_eq_with_diff, parse_str, rustfmt};
+ use crate::ast;
+ use crate::parser::parse_inline;
+ use crate::test_utils::{assert_eq_with_diff, rustfmt};
+
+ /// Parse a string fragment as a PDL file.
+ ///
+ /// # Panics
+ ///
+ /// Panics on parse errors.
+ pub fn parse_str(text: &str) -> ast::Grammar {
+ let mut db = ast::SourceDatabase::new();
+ parse_inline(&mut db, String::from("stdin"), String::from(text)).expect("parse error")
+ }
#[test]
fn test_generate_preamble() {
diff --git a/tools/pdl/src/test_utils.rs b/tools/pdl/src/test_utils.rs
index bd80a61..0c05d35 100644
--- a/tools/pdl/src/test_utils.rs
+++ b/tools/pdl/src/test_utils.rs
@@ -1,14 +1,17 @@
//! Various utility functions used in tests.
-use crate::ast;
-use crate::parser::parse_inline;
+// This file is included directly into integration tests in the
+// `test/` directory. These tests are compiled without access to the
+// rest of the `pdl` crate. To make this work, avoid `use crate::`
+// statements below.
+
use std::io::Write;
use std::process::{Command, Stdio};
use tempfile::NamedTempFile;
/// Search for a binary in `$PATH` or as a sibling to the current
/// executable (typically the test binary).
-fn find_binary(name: &str) -> Result<std::path::PathBuf, String> {
+pub fn find_binary(name: &str) -> Result<std::path::PathBuf, String> {
let mut current_exe = std::env::current_exe().unwrap();
current_exe.pop();
let paths = std::env::var_os("PATH").unwrap();
@@ -27,16 +30,6 @@
))
}
-/// Parse a string fragment as a PDL file.
-///
-/// # Panics
-///
-/// Panics on parse errors.
-pub fn parse_str(text: &str) -> ast::Grammar {
- let mut db = ast::SourceDatabase::new();
- parse_inline(&mut db, String::from("stdin"), String::from(text)).expect("parse error")
-}
-
/// Run `input` through `rustfmt`.
///
/// # Panics
diff --git a/tools/pdl/test/generated/packet_decl_empty.rs b/tools/pdl/test/generated/packet_decl_empty.rs
index f4a8dcf..8205d01 100644
--- a/tools/pdl/test/generated/packet_decl_empty.rs
+++ b/tools/pdl/test/generated/packet_decl_empty.rs
@@ -11,6 +11,9 @@
impl FooData {
fn conforms(bytes: &[u8]) -> bool {
+ if bytes.len() < 0 {
+ return false;
+ }
true
}
fn parse(bytes: &[u8]) -> Result<Self> {
@@ -22,7 +25,6 @@
}
fn get_size(&self) -> usize {
let ret = 0;
- let ret = ret + 0;
ret
}
}
diff --git a/tools/pdl/test/generated/packet_decl_simple_big_endian.rs b/tools/pdl/test/generated/packet_decl_simple_big_endian.rs
index 89b1bab..8830d77 100644
--- a/tools/pdl/test/generated/packet_decl_simple_big_endian.rs
+++ b/tools/pdl/test/generated/packet_decl_simple_big_endian.rs
@@ -17,6 +17,9 @@
impl FooData {
fn conforms(bytes: &[u8]) -> bool {
+ if bytes.len() < 3 {
+ return false;
+ }
true
}
fn parse(bytes: &[u8]) -> Result<Self> {
diff --git a/tools/pdl/test/generated/packet_decl_simple_little_endian.rs b/tools/pdl/test/generated/packet_decl_simple_little_endian.rs
index 5b81ec8..5dffc6b 100644
--- a/tools/pdl/test/generated/packet_decl_simple_little_endian.rs
+++ b/tools/pdl/test/generated/packet_decl_simple_little_endian.rs
@@ -17,6 +17,9 @@
impl FooData {
fn conforms(bytes: &[u8]) -> bool {
+ if bytes.len() < 3 {
+ return false;
+ }
true
}
fn parse(bytes: &[u8]) -> Result<Self> {
diff --git a/tools/pdl/test/pdl_tests.rs b/tools/pdl/test/pdl_tests.rs
new file mode 100644
index 0000000..89b3b92
--- /dev/null
+++ b/tools/pdl/test/pdl_tests.rs
@@ -0,0 +1,98 @@
+use std::fs;
+use std::process::Command;
+
+// The integration test in this file is not part of the pdl crate, and
+// so we cannot directly depend on anything from pdl. However, we can
+// include the test_utils.rs file directly.
+
+#[path = "../src/test_utils.rs"]
+mod test_utils;
+use test_utils::{assert_eq_with_diff, find_binary, rustfmt};
+
+fn strip_blank_lines(text: &str) -> String {
+ text.lines().filter(|line| !line.trim().is_empty()).collect::<Vec<_>>().join("\n")
+}
+
+/// Run `code` through `pdl`.
+///
+/// # Panics
+///
+/// Panics if `pdl` cannot be found on `$PATH` or if it returns a
+/// non-zero exit code.
+fn pdl(code: &str) -> String {
+ let tempdir = tempfile::tempdir().unwrap();
+ let input = tempdir.path().join("input.pdl");
+ fs::write(&input, code.as_bytes()).unwrap();
+ let pdl_path = find_binary("pdl").unwrap();
+ let output = Command::new(&pdl_path)
+ .arg("--output-format")
+ .arg("rust")
+ .arg(input)
+ .output()
+ .expect("pdl failed");
+ assert!(output.status.success(), "pdl failure: {:?}, input:\n{}", output, code);
+ String::from_utf8(output.stdout).unwrap()
+}
+
+/// Run `code` through `bluetooth_packetgen`.
+///
+/// # Panics
+///
+/// Panics if `bluetooth_packetgen` cannot be found on `$PATH` or if
+/// it returns a non-zero exit code.
+fn bluetooth_packetgen(code: &str) -> String {
+ let tempdir = tempfile::tempdir().unwrap();
+ let tempdir_path = tempdir.path().to_str().unwrap();
+ let input_path = tempdir.path().join("input.pdl");
+ let output_path = input_path.with_extension("rs");
+ fs::write(&input_path, code.as_bytes()).unwrap();
+ let bluetooth_packetgen_path = find_binary("bluetooth_packetgen").unwrap();
+ let output = Command::new(&bluetooth_packetgen_path)
+ .arg(&format!("--include={}", tempdir_path))
+ .arg(&format!("--out={}", tempdir_path))
+ .arg("--rust")
+ .arg(input_path)
+ .output()
+ .expect("bluetooth_packetgen failed");
+ assert!(output.status.success(), "bluetooth_packetgen failure: {:?}, input:\n{}", output, code);
+ fs::read_to_string(output_path).unwrap()
+}
+
+#[track_caller]
+fn assert_equal_compilation(pdl_code: &str) {
+ let old_rust = rustfmt(&bluetooth_packetgen(pdl_code));
+ let new_rust = rustfmt(&pdl(pdl_code));
+ assert_eq_with_diff(&strip_blank_lines(&old_rust), &strip_blank_lines(&new_rust));
+}
+
+#[test]
+fn test_prelude() {
+ let pdl_code = r#"
+ little_endian_packets
+ "#;
+ assert_equal_compilation(pdl_code);
+}
+
+#[test]
+fn test_empty_packet() {
+ let pdl_code = r#"
+ little_endian_packets
+
+ packet Foo {
+ }
+ "#;
+ assert_equal_compilation(pdl_code);
+}
+
+#[test]
+fn test_simple_le_packet() {
+ let pdl_code = r#"
+ little_endian_packets
+
+ packet Foo {
+ a: 8,
+ b: 16,
+ }
+ "#;
+ assert_equal_compilation(pdl_code);
+}