def test_attribute_expression_substituted() -> None: assert_equal( Val("X", Variable("Y")).substituted( lambda x: Number(42) if x == Val("X", Variable("Y")) else x ), Number(42), ) assert_equal( -Val("X", Variable("Y")).substituted( lambda x: Number(42) if x == Val("X", Variable("Y")) else x ), Number(-42), ) assert_equal( Val("X", Variable("Y")).substituted(lambda x: Call("Y") if x == Variable("Y") else x), Val("X", Call("Y")), ) assert_equal( -Val("X", Variable("Y")).substituted(lambda x: Call("Y") if x == Variable("Y") else x), -Val("X", Call("Y")), ) assert_equal( -Val("X", Variable("Y")).substituted( lambda x: Variable(f"P_{x}") if isinstance(x, Variable) else (Pos(x.prefix, x.expression) if isinstance(x, Val) else x) ), -Pos("P_X", Variable("P_Y")), )
def test_term_simplified(self) -> None: self.assertEqual( Add( Mul(Number(1), Number(6)), Sub(Variable("X"), Number(10)), Add(Number(1), Number(3)) ).simplified(), Variable("X"), )
def test_expr_variables_duplicates(self) -> None: self.assertEqual( And(Variable("X"), Variable("Y"), Variable("X")).variables(), [Variable("X"), Variable("Y")], ) self.assertEqual( Or(Variable("X"), Variable("Y"), Variable("X")).variables(), [Variable("X"), Variable("Y")], ) self.assertEqual( Add(Variable("X"), Variable("Y"), Variable("X")).variables(), [Variable("X"), Variable("Y")], ) self.assertEqual( Mul(Variable("X"), Variable("Y"), Variable("X")).variables(), [Variable("X"), Variable("Y")], ) self.assertEqual(Sub(Variable("X"), Variable("X")).variables(), [Variable("X")]) self.assertEqual(Div(Variable("X"), Variable("X")).variables(), [Variable("X")]) self.assertEqual( Or( Greater(Variable("X"), Number(42)), And(TRUE, Less(Variable("X"), Number(1))) ).variables(), [Variable("X")], )
def test_range_invalid_size_exceeds_limit() -> None: # ISSUE: Componolit/RecordFlux#238 assert_type_error( RangeInteger("P.T", Number(0), Number(256), Number(128), Location((50, 3))), r'^<stdin>:50:3: model: error: size of "T" exceeds limit \(2\*\*64\)$', )
def test_message_unsupported_expression() -> None: x = Field("X") structure = [ Link(INITIAL, x), Link( x, FINAL, condition=LessEqual( Pow( Number(2), Add(Variable("X", location=Location((10, 23))), Number(1)), location=Location((10, 19)), ), Number(1024), ), ), ] types = {x: MODULAR_INTEGER} assert_message_model_error( structure, types, '^<stdin>:10:19: model: error: unsupported expression in "P.M"\n' '<stdin>:10:23: model: info: variable "X" in exponent', )
def test_merge_message_simple_derived() -> None: assert_equal( deepcopy(M_SMPL_REF_DERI).merged(), UnprovenDerivedMessage( "P.Smpl_Ref_Deri", M_SMPL_REF, [ Link(INITIAL, Field("NR_F1"), length=Number(16)), Link(Field("NR_F3"), FINAL, Equal(Variable("NR_F3"), Variable("P.ONE"))), Link(Field("NR_F4"), FINAL), Link(Field("NR_F1"), Field("NR_F2")), Link( Field("NR_F2"), Field("NR_F3"), LessEqual(Variable("NR_F2"), Number(100)), first=First("NR_F2"), ), Link( Field("NR_F2"), Field("NR_F4"), GreaterEqual(Variable("NR_F2"), Number(200)), first=First("NR_F2"), ), ], { Field("NR_F1"): Opaque(), Field("NR_F2"): deepcopy(MODULAR_INTEGER), Field("NR_F3"): deepcopy(ENUMERATION), Field("NR_F4"): deepcopy(RANGE_INTEGER), }, ), )
def test_array_aggregate_invalid_element_type() -> None: inner = Message( "P.I", [Link(INITIAL, Field("F")), Link(Field("F"), FINAL)], {Field("F"): MODULAR_INTEGER}, ) array_type = Array("P.Array", inner) f = Field("F") with pytest.raises( RecordFluxError, match=r"^<stdin>:90:10: model: error: invalid array element type" ' "P.I" for aggregate comparison$', ): Message( "P.M", [ Link(INITIAL, f, length=Number(18)), Link( f, FINAL, condition=Equal( Variable("F"), Aggregate(Number(1), Number(2), Number(64)), Location((90, 10)), ), ), ], {Field("F"): array_type}, )
def test_exclusive_with_length_invalid() -> None: f1 = Field(ID("F1", Location((98, 10)))) structure = [ Link(INITIAL, f1, length=Number(32)), Link(f1, FINAL, condition=Equal(Length("F1"), Number(32), Location((10, 2)))), Link(f1, Field("F2"), condition=Equal(Length("F1"), Number(32), Location((12, 4)))), Link(Field("F2"), FINAL), ] types = { Field("F1"): Opaque(), Field("F2"): RANGE_INTEGER, } assert_message_model_error( structure, types, r"^" r'<stdin>:98:10: model: error: conflicting conditions for field "F1"\n' r"<stdin>:10:2: model: info: condition 0 [(]F1 -> Final[)]: F1\'Length = 32\n" r"<stdin>:12:4: model: info: condition 1 [(]F1 -> F2[)]: F1\'Length = 32" r"$", )
def test_incongruent_overlay() -> None: structure = [ Link(INITIAL, Field("F1")), Link(Field("F1"), Field("F2")), Link(Field("F2"), Field("F3"), first=First("F1")), Link(Field("F3"), Field("F4")), Link(Field("F4"), FINAL), ] u8 = ModularInteger("P.U8", Number(256)) u16 = ModularInteger("P.U16", Number(65536)) types = { Field("F1"): u8, Field("F2"): u8, Field("F3"): u16, Field("F4"): u16, } assert_message_model_error( structure, types, r"^" r'model: error: field "F3" not congruent with overlaid field "F1"\n' r'model: info: unsatisfied "F1\'First = Message\'First"\n' r'model: info: unsatisfied "F1\'Last = [(][(]Message\'First [+] 8[)][)] - 1"\n' r'model: info: unsatisfied "[(][(]F1\'First [+] 16[)][)] - 1 = F1\'Last"' r"$", )
def test_aggregate_equal_array_invalid_length() -> None: magic = Field(ID("Magic", Location((3, 5)))) structure = [ Link(INITIAL, magic, length=Number(40, location=Location((19, 17)))), Link( magic, FINAL, condition=NotEqual(Variable("Magic"), Aggregate(Number(1), Number(2)), Location((17, 3))), ), ] types = { Field("Magic"): Array( "P.Arr", ModularInteger("P.Modular", Number(128), location=Location((66, 3)))), } assert_message_model_error( structure, types, r"^" r'<stdin>:17:3: model: error: contradicting condition in "P.M"\n' r'<stdin>:3:5: model: info: on path: "Magic"\n' r'<stdin>:17:3: model: info: unsatisfied "2 [*] Modular\'Length = Magic\'Length"\n' r'<stdin>:66:3: model: info: unsatisfied "Modular\'Length = 7"\n' r'<stdin>:19:17: model: info: unsatisfied "Magic\'Length = 40"', )
def test_no_contradiction_multi() -> None: structure = [ Link(INITIAL, Field("F0")), Link(Field("F0"), Field("F1"), condition=Equal(Variable("F0"), Number(1))), Link(Field("F0"), Field("F2"), condition=Equal(Variable("F0"), Number(2))), Link(Field("F1"), Field("F3")), Link(Field("F2"), Field("F3")), Link(Field("F3"), Field("F4"), condition=Equal(Variable("F0"), Number(1))), Link(Field("F3"), Field("F5"), condition=Equal(Variable("F0"), Number(2))), Link(Field("F4"), FINAL), Link(Field("F5"), FINAL), ] types = { Field("F0"): RANGE_INTEGER, Field("F1"): RANGE_INTEGER, Field("F2"): RANGE_INTEGER, Field("F3"): RANGE_INTEGER, Field("F4"): RANGE_INTEGER, Field("F5"): RANGE_INTEGER, } Message("P.M", structure, types)
def test_field_coverage_2(self) -> None: foo_type = ModularInteger("P.Foo", Pow(Number(2), Number(32))) structure = [ Link(INITIAL, Field("F1")), Link(Field("F1"), Field("F2")), Link(Field("F2"), Field("F4"), Greater(Variable("F1"), Number(100))), Link( Field("F2"), Field("F3"), LessEqual(Variable("F1"), Number(100)), first=Add(Last("F2"), Number(64)), ), Link(Field("F3"), Field("F4")), Link(Field("F4"), FINAL), ] types = { Field("F1"): foo_type, Field("F2"): foo_type, Field("F3"): foo_type, Field("F4"): foo_type, } with mock.patch("rflx.model.Message._Message__verify_conditions", lambda x: None): with self.assertRaisesRegex( ModelError, "^path F1 -> F2 -> F3 -> F4 does not cover whole message"): Message("P.M", structure, types)
def test_slice_simplified() -> None: assert_equal( Slice( Variable("Buffer"), First("Buffer"), Add(Last("Buffer"), Add(Number(21), Number(21))), ).simplified(), Slice(Variable("Buffer"), First("Buffer"), Add(Last("Buffer"), Number(42))), )
def test_named_aggregate_substituted() -> None: assert_equal( NamedAggregate(("First", First("X"))).substituted( lambda x: Number(42) if x == NamedAggregate(("First", First("X"))) else x ), Number(42), ) assert_equal( NamedAggregate(("First", First("X"))).substituted( lambda x: Number(42) if x == First("X") else x ), NamedAggregate(("First", Number(42))), ) assert_equal( NamedAggregate(("First", First("X"))).substituted( lambda x: Variable(f"P_{x}") if isinstance(x, Variable) else ( NamedAggregate(*[*x.elements, (ID("Last"), Last("Y"))]) if isinstance(x, NamedAggregate) else x ) ), NamedAggregate(("First", First("P_X")), ("Last", Last("P_Y"))), )
def test_message_type_message() -> None: simple_structure = [ Link(INITIAL, Field("Bar")), Link(Field("Bar"), Field("Baz")), Link(Field("Baz"), FINAL), ] simple_types = { Field("Bar"): ModularInteger("Message_Type.T", Number(256)), Field("Baz"): ModularInteger("Message_Type.T", Number(256)), } simple_message = Message("Message_Type.Simple_PDU", simple_structure, simple_types) structure = [ Link(INITIAL, Field("Foo")), Link(Field("Foo"), Field("Bar"), LessEqual(Variable("Foo"), Number(30, 16))), Link(Field("Foo"), Field("Baz"), Greater(Variable("Foo"), Number(30, 16))), Link(Field("Bar"), Field("Baz")), Link(Field("Baz"), FINAL), ] types = { **simple_types, **{Field("Foo"): ModularInteger("Message_Type.T", Number(256))}, } message = Message("Message_Type.PDU", structure, types) empty_message = Message("Message_Type.Empty_PDU", [], {}) assert_messages_files( [f"{TESTDIR}/message_type.rflx"], [message, simple_message, empty_message] )
def test_field_coverage_2(monkeypatch: Any) -> None: structure = [ Link(INITIAL, Field("F1")), Link(Field("F1"), Field("F2")), Link(Field("F2"), Field("F4"), Greater(Variable("F1"), Number(100))), Link( Field("F2"), Field("F3"), LessEqual(Variable("F1"), Number(100)), first=Add(Last("F2"), Number(64)), ), Link(Field("F3"), Field("F4")), Link(Field("F4"), FINAL), ] types = { Field("F1"): MODULAR_INTEGER, Field("F2"): MODULAR_INTEGER, Field("F3"): MODULAR_INTEGER, Field("F4"): MODULAR_INTEGER, } monkeypatch.setattr(Message, "_AbstractMessage__verify_conditions", lambda x: None) assert_message_model_error( structure, types, r"^" r"model: error: path does not cover whole message\n" r'model: info: on path: "F1"\n' r'model: info: on path: "F2"\n' r'model: info: on path: "F3"\n' r'model: info: on path: "F4"' r"$", )
def test_sub(self) -> None: self.assertEqual( Sub(Number(6), Number(4)).z3expr(), z3.IntVal(6) - z3.IntVal(4)) self.assertEqual( Sub(Number(12), Number(20)).z3expr(), z3.IntVal(12) - z3.IntVal(20))
def test_no_path_to_final_transitive() -> None: structure = [ Link(INITIAL, Field("F1")), Link(Field("F1"), Field("F2")), Link(Field("F2"), Field("F3"), Greater(Variable("F1"), Number(100))), Link(Field("F3"), FINAL), Link(Field("F2"), Field("F4"), LessEqual(Variable("F1"), Number(100))), Link(Field("F4"), Field("F5")), Link(Field("F5"), Field("F6")), ] types = { Field("F1"): MODULAR_INTEGER, Field("F2"): MODULAR_INTEGER, Field("F3"): MODULAR_INTEGER, Field("F4"): MODULAR_INTEGER, Field("F5"): MODULAR_INTEGER, Field("F6"): MODULAR_INTEGER, } assert_message_model_error( structure, types, r"^" r'model: error: no path to FINAL for field "F4" in "P.M"\n' r'model: error: no path to FINAL for field "F5" in "P.M"\n' r'model: error: no path to FINAL for field "F6" in "P.M"' r"$", )
def test_array_aggregate_out_of_range() -> None: array_type = Array("P.Array", ModularInteger("P.Element", Number(64))) f = Field("F") with pytest.raises( RecordFluxError, match= r"^<stdin>:44:3: model: error: aggregate element out of range 0 .. 63", ): Message( "P.M", [ Link(INITIAL, f, length=Number(18)), Link( f, FINAL, condition=Equal( Variable("F"), Aggregate(Number(1), Number(2), Number(64, location=Location((44, 3)))), ), ), ], {Field("F"): array_type}, )
def test_exclusive_conflict() -> None: f1 = Field(ID("F1", Location((8, 4)))) structure = [ Link(INITIAL, f1), Link(f1, FINAL, condition=Greater(Variable("F1"), Number(50), Location((10, 5)))), Link(f1, Field("F2"), condition=Less(Variable("F1"), Number(80), Location((11, 7)))), Link(Field("F2"), FINAL), ] types = { Field("F1"): RANGE_INTEGER, Field("F2"): RANGE_INTEGER, } assert_message_model_error( structure, types, r"^" r'<stdin>:8:4: model: error: conflicting conditions for field "F1"\n' r"<stdin>:10:5: model: info: condition 0 [(]F1 -> Final[)]: F1 > 50\n" r"<stdin>:11:7: model: info: condition 1 [(]F1 -> F2[)]: F1 < 80" r"$", )
def test_opaque_not_byte_aligned_dynamic() -> None: with pytest.raises( RecordFluxError, match= r'^<stdin>:44:3: model: error: opaque field "O2" not aligned to' r" 8 bit boundary [(]L1 -> O1 -> L2 -> O2[)]", ): o2 = Field(ID("O2", location=Location((44, 3)))) Message( "P.M", [ Link(INITIAL, Field("L1")), Link( Field("L1"), Field("O1"), length=Variable("L1"), condition=Equal(Mod(Variable("L1"), Number(8)), Number(0)), ), Link(Field("O1"), Field("L2")), Link(Field("L2"), o2, length=Number(128)), Link(o2, FINAL), ], { Field("L1"): MODULAR_INTEGER, Field("L2"): ModularInteger("P.T", Number(4)), Field("O1"): Opaque(), o2: Opaque(), }, )
def test_conditionally_unreachable_field_outgoing_multi() -> None: f2 = Field(ID("F2", Location((90, 12)))) structure = [ Link(INITIAL, Field("F1")), Link(Field("F1"), f2, LessEqual(Variable("F1"), Number(32), Location((66, 3)))), Link(Field("F1"), Field("F3"), Greater(Variable("F1"), Number(32))), Link( f2, Field("F3"), And( Greater(Variable("F1"), Number(32)), LessEqual(Variable("F1"), Number(48)), location=Location((22, 34)), ), ), Link(f2, FINAL, Greater(Variable("F1"), Number(48))), Link(Field("F3"), FINAL), ] types = { Field("F1"): MODULAR_INTEGER, Field("F2"): MODULAR_INTEGER, Field("F3"): MODULAR_INTEGER, } assert_message_model_error( structure, types, r"^" r'<stdin>:90:12: model: error: unreachable field "F2" in "P.M"\n' r"<stdin>:90:12: model: info: path 0 [(]F1 -> F2[)]:\n" r'<stdin>:66:3: model: info: unsatisfied "F1 <= 32"\n' r'<stdin>:90:12: model: info: unsatisfied "[(]F1 > 32 and F1 <= 48[)] or F1 > 48"', )
def test_enumeration_invalid_literal_value() -> None: assert_type_error( Enumeration("P.T", [("A", Number(2**63))], Number(64), False, Location((10, 5))), r'^<stdin>:10:5: model: error: enumeration value of "T"' r" outside of permitted range \(0 .. 2\*\*63 - 1\)$", )
def test_write_specification_files_missing_deps(tmp_path: Path) -> None: s = ModularInteger("P::S", Number(65536)) t = ModularInteger("P::T", Number(256)) v = mty.Sequence("P::V", element_type=t) m = Message("P::M", [Link(INITIAL, Field("Foo")), Link(Field("Foo"), FINAL)], {Field("Foo"): t}) Model([s, v, m]).write_specification_files(tmp_path) expected_path = tmp_path / Path("p.rflx") assert list(tmp_path.glob("*.rflx")) == [expected_path] assert expected_path.read_text() == textwrap.dedent( """\ package P is type S is mod 65536; type T is mod 256; type V is sequence of P::T; type M is message Foo : P::T; end message; end P;""" )
def test_sub_simplified(self) -> None: self.assertEqual(Sub(Number(1), Variable("X")).simplified(), Add(Variable("X"), Number(-1))) self.assertEqual(Sub(Variable("X"), Number(1)).simplified(), Add(Variable("X"), Number(-1))) self.assertEqual(Sub(Number(6), Number(2)).simplified(), Number(4)) self.assertEqual( Sub(Variable("X"), Variable("Y")).simplified(), Add(Variable("X"), Variable("Y", True)) )
def test_unexpected_mathematical_operator() -> None: with pytest.raises(ParseFatalException, match=r"^unexpected mathematical operator"): grammar.parse_mathematical_expression( "", 0, [[Number(1, location=Location((1, 1))), "//", Number(1, location=Location((1, 8)))]], )
def test_slice_simplified(self) -> None: self.assertEqual( Slice( "Buffer", First("Buffer"), Add(Last("Buffer"), Add(Number(21), Number(21))) ).simplified(), Slice("Buffer", First("Buffer"), Add(Last("Buffer"), Number(42))), )
def test_array_type_spec() -> None: spec = { "Array_Type": Specification( ContextSpec([]), PackageSpec( "Array_Type", [ ModularInteger("__PACKAGE__.Byte", Number(256)), ArraySpec("__PACKAGE__.Bytes", ReferenceSpec("__PACKAGE__.Byte")), MessageSpec( "__PACKAGE__.Foo", [ Component( "Length", "Byte", [Then("Bytes", UNDEFINED, Mul(Variable("Length"), Number(8)))], ), Component("Bytes", "Bytes"), ], ), ArraySpec("__PACKAGE__.Bar", ReferenceSpec("__PACKAGE__.Foo")), ], ), ) } assert_specifications_files([f"{TESTDIR}/array_type.rflx"], spec)
def test_message_incoming(self) -> None: self.assertEqual(ETHERNET_FRAME.incoming(INITIAL), []) self.assertEqual( ETHERNET_FRAME.incoming(Field("Type_Length")), [ Link( Field("Type_Length_TPID"), Field("Type_Length"), NotEqual(Variable("Type_Length_TPID"), Number(0x8100, 16)), first=First("Type_Length_TPID"), ), Link(Field("TCI"), Field("Type_Length")), ], ) self.assertEqual( ETHERNET_FRAME.incoming(FINAL), [ Link( Field("Payload"), FINAL, And( GreaterEqual(Div(Length("Payload"), Number(8)), Number(46)), LessEqual(Div(Length("Payload"), Number(8)), Number(1500)), ), ) ], )
def test_number_ne() -> None: # pylint: disable=unneeded-not assert not Number(1) != Number(1) assert not Number(1, 10) != Number(1, 16) assert not Number(42, 16) != Number(42, 10) assert Number(1) != Number(2) assert Number(1, 16) != Number(2, 16)