blob: cfbf7c34d287adb3335e0e74c0a10f8b7fbf389b [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Verifies that the MojomParse attribute is correctly derived.
//!
//! FOR_RELEASE: Rewrite the below comment to be clearer.
//!
//! Testing strategy: The existing tests for mojom bindings are end-to-end,
//! relying on mojom files to specify their inputs and outputs. However, we
//! want to unit-test the parser and deparser alone. Since they take ASTs as
//! input, we first validate that the bindings generate the correct ASTs, and
//! then we can use the bindings to generate various parser/deparser tests.
//!
//! The types in this file correspond to those defined in
//! //mojo/public/rust/test_mojom/parser_unittests.mojom
chromium::import! {
"//mojo/public/rust/mojom_parser:mojom_parser_core";
"//mojo/public/rust/mojom_parser:parser_unittests_rust";
}
use rust_gtest_interop::prelude::*;
use mojom_parser_core::*;
use parser_unittests_rust::parser_unittests::*;
use std::sync::LazyLock;
/// Represents a type defined in a Mojom file.
///
/// Conceptually, it has three parts, corresponding to the three AST types:
/// 1. The type itself, as written in the Mojom file.
/// 2. The packed representation of the type.
/// 3. A constructor for creating values of that type.
///
/// Unfortunately, the arguments to the constructor will vary based on the
/// MojomType it represents, so we can't write down a single type for the third
/// field. Instead, we must manually write a corresponding constructor for
/// every TestType, and track them by name.
struct TestType {
base_type: MojomType,
/// Human-readable type name for debugging output
type_name: &'static str,
/// Contents of a PackedStructuredType::Struct
expected_packed_fields: Vec<(String, MojomWireType)>,
// Imagine there's a third `constructor` entry here.
}
impl TestType {
/// Return the packed version of this test type, with the given ordinal
fn get_packed_type(&self, ordinal: Ordinal) -> MojomWireType {
MojomWireType::Pointer {
ordinal,
nested_data_type: PackedStructuredType::Struct {
packed_field_types: self.expected_packed_fields.clone(),
},
}
}
/// Given the rust type T corresponding to this TestType, validate that:
/// (1) T's associated MojomType and MojomWireType match ours, and
/// (2) The input value of type T can be converted to and from the input
/// MojomValue
fn validate_mojomparse<T: MojomParse + std::fmt::Debug + Clone + PartialEq>(
&self,
rust_val: T,
mojom_val: MojomValue,
) {
// FOR RELEASE: These assertion macros seem to print a massive (and utterly
// useless) stack trace. See if we can turn that off.
expect_eq!(
T::mojom_type(),
self.base_type,
"Type {} had the wrong associated MojomType!",
self.type_name
);
expect_eq!(
*T::wire_type(),
self.get_packed_type(0),
"Type {} failed to pack correctly!",
self.type_name
);
// Test conversion to/from MojomValues
expect_eq!(mojom_val, rust_val.clone().into());
expect_eq!(rust_val, mojom_val.clone().try_into().unwrap());
}
}
// We'll be creating all our test types at global scope, so we can reference
// them from various testing functions.
// Mojom Definition:
// struct Empty {};
static EMPTY_TY: LazyLock<TestType> = LazyLock::new(|| TestType {
type_name: "Empty",
base_type: MojomType::Struct { fields: vec![] },
expected_packed_fields: vec![],
});
fn empty_mojom() -> MojomValue {
MojomValue::Struct(vec![])
}
// Mojom Definition:
// struct FourInts {
// int8 a;
// int16 b;
// int32 c;
// int64 d;
// };
static FOUR_INTS_TY: LazyLock<TestType> = LazyLock::new(|| TestType {
type_name: "FourInts",
base_type: MojomType::Struct {
fields: vec![
("a".to_string(), MojomType::Int8),
("b".to_string(), MojomType::Int16),
("c".to_string(), MojomType::Int32),
("d".to_string(), MojomType::Int64),
],
},
expected_packed_fields: vec![
("a".to_string(), MojomWireType::Leaf { ordinal: 0, leaf_type: PackedLeafType::Int8 }),
("b".to_string(), MojomWireType::Leaf { ordinal: 1, leaf_type: PackedLeafType::Int16 }),
("c".to_string(), MojomWireType::Leaf { ordinal: 2, leaf_type: PackedLeafType::Int32 }),
("d".to_string(), MojomWireType::Leaf { ordinal: 3, leaf_type: PackedLeafType::Int64 }),
],
});
fn four_ints_mojom(a: i8, b: i16, c: i32, d: i64) -> MojomValue {
MojomValue::Struct(vec![
("a".to_string(), MojomValue::Int8(a)),
("b".to_string(), MojomValue::Int16(b)),
("c".to_string(), MojomValue::Int32(c)),
("d".to_string(), MojomValue::Int64(d)),
])
}
// Mojom Definition:
// struct FourIntsReversed {
// int64 d;
// int32 c;
// int16 b;
// int8 a;
// };
static FOUR_INTS_REVERSED_TY: LazyLock<TestType> = LazyLock::new(|| TestType {
type_name: "FourIntsReversed",
base_type: MojomType::Struct {
fields: vec![
("d".to_string(), MojomType::Int64),
("c".to_string(), MojomType::Int32),
("b".to_string(), MojomType::Int16),
("a".to_string(), MojomType::Int8),
],
},
expected_packed_fields: vec![
("d".to_string(), MojomWireType::Leaf { ordinal: 0, leaf_type: PackedLeafType::Int64 }),
("c".to_string(), MojomWireType::Leaf { ordinal: 1, leaf_type: PackedLeafType::Int32 }),
("b".to_string(), MojomWireType::Leaf { ordinal: 2, leaf_type: PackedLeafType::Int16 }),
("a".to_string(), MojomWireType::Leaf { ordinal: 3, leaf_type: PackedLeafType::Int8 }),
],
});
fn four_ints_reversed_mojom(a: i8, b: i16, c: i32, d: i64) -> MojomValue {
MojomValue::Struct(vec![
("d".to_string(), MojomValue::Int64(d)),
("c".to_string(), MojomValue::Int32(c)),
("b".to_string(), MojomValue::Int16(b)),
("a".to_string(), MojomValue::Int8(a)),
])
}
// Mojom Definition:
// struct FourIntsIntermixed {
// int8 a;
// int32 b;
// int16 c;
// int8 d;
// };
static FOUR_INTS_INTERMIXED_TY: LazyLock<TestType> = LazyLock::new(|| TestType {
type_name: "FourIntsIntermixed",
base_type: MojomType::Struct {
fields: vec![
("a".to_string(), MojomType::Int8),
("b".to_string(), MojomType::Int32),
("c".to_string(), MojomType::Int16),
("d".to_string(), MojomType::Int8),
],
},
expected_packed_fields: vec![
("a".to_string(), MojomWireType::Leaf { ordinal: 0, leaf_type: PackedLeafType::Int8 }),
("d".to_string(), MojomWireType::Leaf { ordinal: 3, leaf_type: PackedLeafType::Int8 }),
("c".to_string(), MojomWireType::Leaf { ordinal: 2, leaf_type: PackedLeafType::Int16 }),
("b".to_string(), MojomWireType::Leaf { ordinal: 1, leaf_type: PackedLeafType::Int32 }),
],
});
fn four_ints_intermixed_mojom(a: i8, b: i32, c: i16, d: i8) -> MojomValue {
MojomValue::Struct(vec![
("a".to_string(), MojomValue::Int8(a)),
("b".to_string(), MojomValue::Int32(b)),
("c".to_string(), MojomValue::Int16(c)),
("d".to_string(), MojomValue::Int8(d)),
])
}
// Mojom Definition:
// struct OnceNested {
// FourInts f1;
// uint32 a;
// int8 b;
// FourIntsReversed f2;
// FourIntsIntermixed f3;
// uint16 c;
// };
static ONCE_NESTED_TY: LazyLock<TestType> = LazyLock::new(|| TestType {
type_name: "OnceNested",
base_type: MojomType::Struct {
fields: vec![
("f1".to_string(), FOUR_INTS_TY.base_type.clone()),
("a".to_string(), MojomType::UInt32),
("b".to_string(), MojomType::Int8),
("f2".to_string(), FOUR_INTS_REVERSED_TY.base_type.clone()),
("f3".to_string(), FOUR_INTS_INTERMIXED_TY.base_type.clone()),
("c".to_string(), MojomType::UInt16),
],
},
expected_packed_fields: vec![
("f1".to_string(), FOUR_INTS_TY.get_packed_type(0)),
("a".to_string(), MojomWireType::Leaf { ordinal: 1, leaf_type: PackedLeafType::UInt32 }),
("b".to_string(), MojomWireType::Leaf { ordinal: 2, leaf_type: PackedLeafType::Int8 }),
("c".to_string(), MojomWireType::Leaf { ordinal: 5, leaf_type: PackedLeafType::UInt16 }),
("f2".to_string(), FOUR_INTS_REVERSED_TY.get_packed_type(3)),
("f3".to_string(), FOUR_INTS_INTERMIXED_TY.get_packed_type(4)),
],
});
fn once_nested_mojom(
f1: MojomValue,
a: u32,
b: i8,
f2: MojomValue,
f3: MojomValue,
c: u16,
) -> MojomValue {
MojomValue::Struct(vec![
("f1".to_string(), f1),
("a".to_string(), MojomValue::UInt32(a)),
("b".to_string(), MojomValue::Int8(b)),
("f2".to_string(), f2),
("f3".to_string(), f3),
("c".to_string(), MojomValue::UInt16(c)),
])
}
// Mojom Definition:
// struct TwiceNested {
// OnceNested o;
// int16 a;
// FourInts f;
// };
static TWICE_NESTED_TY: LazyLock<TestType> = LazyLock::new(|| TestType {
type_name: "TwiceNested",
base_type: MojomType::Struct {
fields: vec![
("o".to_string(), ONCE_NESTED_TY.base_type.clone()),
("a".to_string(), MojomType::Int16),
("f".to_string(), FOUR_INTS_TY.base_type.clone()),
("b".to_string(), MojomType::Int32),
("c".to_string(), MojomType::Int32),
],
},
expected_packed_fields: vec![
("o".to_string(), ONCE_NESTED_TY.get_packed_type(0)),
("a".to_string(), MojomWireType::Leaf { ordinal: 1, leaf_type: PackedLeafType::Int16 }),
("b".to_string(), MojomWireType::Leaf { ordinal: 3, leaf_type: PackedLeafType::Int32 }),
("f".to_string(), FOUR_INTS_TY.get_packed_type(2)),
("c".to_string(), MojomWireType::Leaf { ordinal: 4, leaf_type: PackedLeafType::Int32 }),
],
});
fn twice_nested_mojom(o: MojomValue, a: i16, f: MojomValue, b: i32, c: i32) -> MojomValue {
MojomValue::Struct(vec![
("o".to_string(), o),
("a".to_string(), MojomValue::Int16(a)),
("f".to_string(), f),
("b".to_string(), MojomValue::Int32(b)),
("c".to_string(), MojomValue::Int32(c)),
])
}
// Mojom Definition:
// struct TenBoolsAndAByte {
// bool b0; ... bool b4;
// uint8 n1;
// bool b5; ... bool b9;
// };
static TEN_BOOLS_AND_A_BYTE_TY: LazyLock<TestType> = LazyLock::new(|| TestType {
type_name: "TenBoolsAndAByte",
base_type: MojomType::Struct {
fields: vec![
("b0".to_string(), MojomType::Bool),
("b1".to_string(), MojomType::Bool),
("b2".to_string(), MojomType::Bool),
("b3".to_string(), MojomType::Bool),
("b4".to_string(), MojomType::Bool),
("n1".to_string(), MojomType::UInt8),
("b5".to_string(), MojomType::Bool),
("b6".to_string(), MojomType::Bool),
("b7".to_string(), MojomType::Bool),
("b8".to_string(), MojomType::Bool),
("b9".to_string(), MojomType::Bool),
],
},
expected_packed_fields: vec![
(
"b0".to_string(),
MojomWireType::Bitfield {
ordinals: [Some(0), Some(1), Some(2), Some(3), Some(4), Some(6), Some(7), Some(8)],
},
),
("n1".to_string(), MojomWireType::Leaf { ordinal: 5, leaf_type: PackedLeafType::UInt8 }),
(
"b8".to_string(),
MojomWireType::Bitfield {
ordinals: [Some(9), Some(10), None, None, None, None, None, None],
},
),
],
});
fn ten_bools_and_a_byte_mojom(
b0: bool,
b1: bool,
b2: bool,
b3: bool,
b4: bool,
n1: u8,
b5: bool,
b6: bool,
b7: bool,
b8: bool,
b9: bool,
) -> MojomValue {
MojomValue::Struct(vec![
("b0".to_string(), MojomValue::Bool(b0)),
("b1".to_string(), MojomValue::Bool(b1)),
("b2".to_string(), MojomValue::Bool(b2)),
("b3".to_string(), MojomValue::Bool(b3)),
("b4".to_string(), MojomValue::Bool(b4)),
("n1".to_string(), MojomValue::UInt8(n1)),
("b5".to_string(), MojomValue::Bool(b5)),
("b6".to_string(), MojomValue::Bool(b6)),
("b7".to_string(), MojomValue::Bool(b7)),
("b8".to_string(), MojomValue::Bool(b8)),
("b9".to_string(), MojomValue::Bool(b9)),
])
}
// Mojom Definition:
// struct TenBoolsAndTwoBytes {
// bool b0; ... bool b4;
// uint16 n1;
// bool b5; ... bool b9;
// };
static TEN_BOOLS_AND_TWO_BYTES_TY: LazyLock<TestType> = LazyLock::new(|| TestType {
type_name: "TenBoolsAndTwoBytes",
base_type: MojomType::Struct {
fields: vec![
("b0".to_string(), MojomType::Bool),
("b1".to_string(), MojomType::Bool),
("b2".to_string(), MojomType::Bool),
("b3".to_string(), MojomType::Bool),
("b4".to_string(), MojomType::Bool),
("n1".to_string(), MojomType::UInt16),
("b5".to_string(), MojomType::Bool),
("b6".to_string(), MojomType::Bool),
("b7".to_string(), MojomType::Bool),
("b8".to_string(), MojomType::Bool),
("b9".to_string(), MojomType::Bool),
],
},
expected_packed_fields: vec![
(
"b0".to_string(),
MojomWireType::Bitfield {
ordinals: [Some(0), Some(1), Some(2), Some(3), Some(4), Some(6), Some(7), Some(8)],
},
),
(
"b8".to_string(),
MojomWireType::Bitfield {
ordinals: [Some(9), Some(10), None, None, None, None, None, None],
},
),
("n1".to_string(), MojomWireType::Leaf { ordinal: 5, leaf_type: PackedLeafType::UInt16 }),
],
});
fn ten_bools_and_two_bytes_mojom(
b0: bool,
b1: bool,
b2: bool,
b3: bool,
b4: bool,
n1: u16,
b5: bool,
b6: bool,
b7: bool,
b8: bool,
b9: bool,
) -> MojomValue {
MojomValue::Struct(vec![
("b0".to_string(), MojomValue::Bool(b0)),
("b1".to_string(), MojomValue::Bool(b1)),
("b2".to_string(), MojomValue::Bool(b2)),
("b3".to_string(), MojomValue::Bool(b3)),
("b4".to_string(), MojomValue::Bool(b4)),
("n1".to_string(), MojomValue::UInt16(n1)),
("b5".to_string(), MojomValue::Bool(b5)),
("b6".to_string(), MojomValue::Bool(b6)),
("b7".to_string(), MojomValue::Bool(b7)),
("b8".to_string(), MojomValue::Bool(b8)),
("b9".to_string(), MojomValue::Bool(b9)),
])
}
#[gtest(MojomParser, TestMojomParseDerive)]
/// Tests that the MojomParse trait is correctly
/// derived for each of the types in the mojom file.
fn test_mojomparse() {
FOUR_INTS_TY.validate_mojomparse(
FourInts { a: 10, b: -20, c: 400, d: -8000 },
four_ints_mojom(10, -20, 400, -8000),
);
EMPTY_TY.validate_mojomparse(Empty {}, empty_mojom());
FOUR_INTS_REVERSED_TY.validate_mojomparse(
FourIntsReversed { a: 10, b: -20, c: 400, d: -8000 },
four_ints_reversed_mojom(10, -20, 400, -8000),
);
FOUR_INTS_INTERMIXED_TY.validate_mojomparse(
FourIntsIntermixed { a: 10, b: 400, c: -20, d: -5 },
four_ints_intermixed_mojom(10, 400, -20, -5),
);
let once_nested_val = OnceNested {
f1: FourInts { a: 1, b: 2, c: 3, d: 4 },
a: 13,
b: 14,
f2: FourIntsReversed { a: 5, b: 6, c: 7, d: 8 },
f3: FourIntsIntermixed { a: 9, b: 10, c: 11, d: 12 },
c: 15,
};
let once_nested_mojom_val = once_nested_mojom(
four_ints_mojom(1, 2, 3, 4),
13,
14,
four_ints_reversed_mojom(5, 6, 7, 8),
four_ints_intermixed_mojom(9, 10, 11, 12),
15,
);
ONCE_NESTED_TY.validate_mojomparse(once_nested_val.clone(), once_nested_mojom_val.clone());
TWICE_NESTED_TY.validate_mojomparse(
TwiceNested {
o: once_nested_val,
a: 16,
f: FourInts { a: 17, b: 18, c: 19, d: 20 },
b: 21,
c: 22,
},
twice_nested_mojom(once_nested_mojom_val, 16, four_ints_mojom(17, 18, 19, 20), 21, 22),
);
TEN_BOOLS_AND_A_BYTE_TY.validate_mojomparse(
TenBoolsAndAByte {
b0: true,
b1: false,
b2: true,
b3: false,
b4: true,
n1: 200,
b5: false,
b6: true,
b7: false,
b8: true,
b9: false,
},
ten_bools_and_a_byte_mojom(
true, false, true, false, true, 200, false, true, false, true, false,
),
);
TEN_BOOLS_AND_TWO_BYTES_TY.validate_mojomparse(
TenBoolsAndTwoBytes {
b0: true,
b1: false,
b2: true,
b3: false,
b4: true,
n1: 50000,
b5: false,
b6: true,
b7: false,
b8: true,
b9: false,
},
ten_bools_and_two_bytes_mojom(
true, false, true, false, true, 50000, false, true, false, true, false,
),
);
}
#[gtest(MojomParser, TestBadConversion)]
/// Test that we can't convert between incompatible types
fn test_bad_conversion() {
let empty = empty_mojom();
let four_ints = four_ints_mojom(10, -20, 400, -8000);
let four_ints_reversed = four_ints_reversed_mojom(10, -20, 400, -8000);
let four_ints_intermixed = four_ints_intermixed_mojom(10, 400, -20, -5);
let once_nested = once_nested_mojom(
four_ints_mojom(1, 2, 3, 4),
13,
14,
four_ints_reversed_mojom(5, 6, 7, 8),
four_ints_intermixed_mojom(9, 10, 11, 12),
15,
);
let twice_nested =
twice_nested_mojom(once_nested.clone(), 16, four_ints_mojom(17, 18, 19, 20), 21, 22);
let ten_bools_byte = ten_bools_and_a_byte_mojom(
true, false, true, false, true, 200, false, true, false, true, false,
);
let ten_bools_bytes = ten_bools_and_two_bytes_mojom(
true, false, true, false, true, 50000, false, true, false, true, false,
);
// There are far too many bad paths to test them all, so this is just an
// arbitrary scattershot approach so we have _some_ coverage.
// TODO(crbug.com/456214728) Replace with matchers from the googletest crate
// if we switch to it.
expect_true!(FourInts::try_from(four_ints_reversed).is_err());
expect_true!(FourInts::try_from(once_nested).is_err());
expect_true!(FourInts::try_from(empty).is_err());
expect_true!(TenBoolsAndAByte::try_from(ten_bools_bytes).is_err());
expect_true!(TenBoolsAndAByte::try_from(four_ints_intermixed).is_err());
expect_true!(OnceNested::try_from(twice_nested).is_err());
expect_true!(OnceNested::try_from(ten_bools_byte.clone()).is_err());
expect_true!(Empty::try_from(ten_bools_byte).is_err());
expect_true!(Empty::try_from(four_ints).is_err());
}