Init Commit: Moved bproto to seperate repo
This commit is contained in:
38
test/compiler/ast/test_ast_message.py
Normal file
38
test/compiler/ast/test_ast_message.py
Normal file
@@ -0,0 +1,38 @@
|
||||
import sys
|
||||
import pytest
|
||||
from typing import Any
|
||||
sys.path.append('src/')
|
||||
|
||||
from protocol_components.dtypes import BprotoFieldBaseType
|
||||
from parser.parser import create_ast_parser, bproto_ErrorListener
|
||||
from parser.ast_visitor import BprotoASTVisitor
|
||||
|
||||
|
||||
@pytest.mark.parametrize("source_text,expected_output", [
|
||||
("uint64\n", (BprotoFieldBaseType.UINT64, 1, None)),
|
||||
("float32\n", (BprotoFieldBaseType.FLOAT32, 1, None)),
|
||||
("float32[8]\n", (BprotoFieldBaseType.FLOAT32, 8, None)),
|
||||
])
|
||||
def test_ast_field_dtype_defintion(
|
||||
source_text: str,
|
||||
expected_output: tuple[BprotoFieldBaseType, int, Any]):
|
||||
|
||||
err_listner = bproto_ErrorListener()
|
||||
parser = create_ast_parser(source_text, err_listner)
|
||||
ast = parser.dtype()
|
||||
vinterp = BprotoASTVisitor()
|
||||
|
||||
assert len(err_listner.syntax_error_list) == 0
|
||||
|
||||
res = vinterp.visit(ast)
|
||||
assert isinstance(res, tuple)
|
||||
assert len(res) == 3
|
||||
|
||||
# Correct dtype
|
||||
assert res[0] == expected_output[0].value
|
||||
|
||||
# Correct array size
|
||||
assert res[1] == expected_output[1]
|
||||
|
||||
# Correct refrerence; Should normally be None be Non
|
||||
assert res[2] == expected_output[2]
|
||||
23
test/compiler/ast/test_ast_tag_line.py
Normal file
23
test/compiler/ast/test_ast_tag_line.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import sys
|
||||
sys.path.append('src/')
|
||||
|
||||
from parser.parser import create_ast_parser, bproto_ErrorListener
|
||||
from parser.ast_visitor import BprotoASTVisitor
|
||||
|
||||
|
||||
def test_ast_tag_line():
|
||||
source = "protocol HCP version 25\n"
|
||||
|
||||
err_listner = bproto_ErrorListener()
|
||||
parser = create_ast_parser(source, err_listner)
|
||||
ast = parser.protocol_header()
|
||||
vinterp = BprotoASTVisitor()
|
||||
|
||||
assert len(err_listner.syntax_error_list) == 0
|
||||
|
||||
res = vinterp.visit(ast)
|
||||
assert isinstance(res, tuple)
|
||||
assert len(res) == 2
|
||||
name, version = res
|
||||
assert name == "HCP"
|
||||
assert version == 25
|
||||
26
test/compiler/backend/test_comiler_c.py
Normal file
26
test/compiler/backend/test_comiler_c.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import sys
|
||||
from compiler import BprotoCompiler
|
||||
sys.path.append('src/')
|
||||
|
||||
from errors import BprotoCompilerError
|
||||
from backend.rendering.cBackend import CBackendRenderer
|
||||
from backend.fsOutput import BackendFileSystemOutput
|
||||
|
||||
|
||||
def test_compiler_c_smoke_test():
|
||||
# Load Refernces
|
||||
with open("test/test_protocol.bproto") as f:
|
||||
source_text = f.read()
|
||||
|
||||
# Comile Stuff
|
||||
compiler = BprotoCompiler([
|
||||
CBackendRenderer(".")
|
||||
], "template")
|
||||
|
||||
try:
|
||||
output: BackendFileSystemOutput = compiler.compile(source_text, ".")
|
||||
except BprotoCompilerError:
|
||||
assert False, "This should run througth"
|
||||
|
||||
output.saveToDisk("test/backend_output/c")
|
||||
output.toString()
|
||||
59
test/compiler/backend/test_compiler_python.py
Normal file
59
test/compiler/backend/test_compiler_python.py
Normal file
@@ -0,0 +1,59 @@
|
||||
import sys
|
||||
import pytest
|
||||
from compiler import BprotoCompiler
|
||||
sys.path.append('src/')
|
||||
|
||||
from errors import BprotoCompilerError
|
||||
from backend.rendering.pythonBackend import PythonBackendRenderer
|
||||
from backend.fsOutput import BackendFileSystemOutput
|
||||
|
||||
|
||||
@pytest.mark.skip()
|
||||
def test_compiler_python_against_ref():
|
||||
# Load Refernces
|
||||
with open("test/test_protocol.bproto") as f:
|
||||
source_text = f.read()
|
||||
|
||||
# Comile Stuff
|
||||
compiler = BprotoCompiler([
|
||||
PythonBackendRenderer(".")
|
||||
], "template")
|
||||
|
||||
try:
|
||||
output: BackendFileSystemOutput = compiler.compile(source_text, ".")
|
||||
except BprotoCompilerError:
|
||||
assert False, "This should run througth"
|
||||
|
||||
output.saveToDisk("test/backend_output/python")
|
||||
string_output = output.toString()
|
||||
|
||||
with open("test/reference/python/HCP_protocol_enum.py") as f:
|
||||
assert f.read() == string_output.get("HCP_protocol_enum.py")
|
||||
|
||||
with open("test/reference/python/HCP_protocol_bitfield.py") as f:
|
||||
assert f.read() == string_output.get("HCP_protocol_bitfield.py")
|
||||
|
||||
with open("test/reference/python/HCP_protocol_message_ids.py") as f:
|
||||
assert f.read() == string_output.get("HCP_protocol_message_ids.py")
|
||||
|
||||
with open("test/reference/python/HCP_protocol_packets.py") as f:
|
||||
assert f.read() == string_output.get("HCP_protocol_packets.py")
|
||||
|
||||
|
||||
def test_compiler_python_smoke_test():
|
||||
# Load Refernces
|
||||
with open("test/test_protocol.bproto") as f:
|
||||
source_text = f.read()
|
||||
|
||||
# Comile Stuff
|
||||
compiler = BprotoCompiler([
|
||||
PythonBackendRenderer(".")
|
||||
], "template")
|
||||
|
||||
try:
|
||||
output: BackendFileSystemOutput = compiler.compile(source_text, ".")
|
||||
except BprotoCompilerError:
|
||||
assert False, "This should run througth"
|
||||
|
||||
output.saveToDisk("test/backend_output/python")
|
||||
output.toString()
|
||||
32
test/compiler/backend/test_compiler_txt.py
Normal file
32
test/compiler/backend/test_compiler_txt.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import sys
|
||||
from compiler import BprotoCompiler
|
||||
sys.path.append('src/')
|
||||
|
||||
from errors import BprotoCompilerError
|
||||
from backend.rendering.txtBackend import TxtBackendRenderer
|
||||
from backend.fsOutput import BackendFileSystemOutput
|
||||
|
||||
|
||||
def test_compiler_txt_against_ref():
|
||||
with open("test/test_protocol.bproto") as f:
|
||||
source_text = f.read()
|
||||
|
||||
# Comile Stuff
|
||||
compiler = BprotoCompiler([
|
||||
TxtBackendRenderer(".")
|
||||
], "template")
|
||||
|
||||
try:
|
||||
output: BackendFileSystemOutput = compiler.compile(source_text, ".")
|
||||
except BprotoCompilerError:
|
||||
assert False, "This should run througth"
|
||||
|
||||
string_output = output.toString()
|
||||
output.saveToDisk("test/backend_output/txt")
|
||||
|
||||
# Load Refernces
|
||||
with open("test/reference/txt/protocolSummary.txt") as f:
|
||||
expected_output = f.read()
|
||||
|
||||
assert isinstance(string_output.get("protocolSummary.txt"), str)
|
||||
assert string_output.get("protocolSummary.txt") == expected_output
|
||||
168
test/compiler/backend/test_fsOutput.py
Normal file
168
test/compiler/backend/test_fsOutput.py
Normal file
@@ -0,0 +1,168 @@
|
||||
import sys
|
||||
import os
|
||||
import pytest
|
||||
import shutil
|
||||
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||
sys.path.append('src/')
|
||||
|
||||
from backend.fsOutput import BackendFSOutFile, BackendFSOutStaticConent, BackendFSOutFolder, BackendFSOutputJinjaFile
|
||||
|
||||
TEST_FOLDER = "/tmp/protocolGenerator_test_data"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def filesystem_folder():
|
||||
yield TEST_FOLDER
|
||||
shutil.rmtree(TEST_FOLDER)
|
||||
|
||||
print("Cleanup done")
|
||||
|
||||
|
||||
def test_fsOutput_saveToDisk(filesystem_folder: str):
|
||||
jinja_env = Environment(
|
||||
loader=FileSystemLoader("test/data/templates/"),
|
||||
autoescape=select_autoescape()
|
||||
)
|
||||
|
||||
# Setup
|
||||
root_folder = BackendFSOutFolder("root", [
|
||||
BackendFSOutFile("file1", "some content"),
|
||||
BackendFSOutFile("file2", "some other content"),
|
||||
BackendFSOutFolder("subfolder", [
|
||||
BackendFSOutFile("file3", "some more content #2"),
|
||||
BackendFSOutFile("file4", "even more content"),
|
||||
]),
|
||||
BackendFSOutStaticConent("test/data/static_folder/static_content2.txt", "static_content2.txt"),
|
||||
BackendFSOutStaticConent("test/data/static_content1.txt", "static_content1.txt"),
|
||||
BackendFSOutputJinjaFile(jinja_env, "template.jinja2", "template.txt", {"name": "World"})
|
||||
])
|
||||
|
||||
root_folder.saveToDisk(filesystem_folder)
|
||||
|
||||
# Test Structure
|
||||
assert os.path.isdir(filesystem_folder)
|
||||
assert os.path.isfile(filesystem_folder + "/root/file1")
|
||||
assert os.path.isfile(filesystem_folder + "/root/file2")
|
||||
assert os.path.isdir(filesystem_folder + "/root/subfolder")
|
||||
assert os.path.isfile(filesystem_folder + "/root/subfolder/file3")
|
||||
assert os.path.isfile(filesystem_folder + "/root/subfolder/file4")
|
||||
assert os.path.isfile(filesystem_folder + "/root/static_content1.txt")
|
||||
assert os.path.isfile(filesystem_folder + "/root/static_content2.txt")
|
||||
|
||||
assert os.path.isfile(filesystem_folder + "/root/template.txt")
|
||||
|
||||
# Test Content
|
||||
|
||||
with open(filesystem_folder + "/root/file1", "r") as f:
|
||||
assert f.read() == "some content"
|
||||
|
||||
with open(filesystem_folder + "/root/file2", "r") as f:
|
||||
assert f.read() == "some other content"
|
||||
|
||||
with open(filesystem_folder + "/root/subfolder/file3", "r") as f:
|
||||
assert f.read() == "some more content #2"
|
||||
|
||||
with open(filesystem_folder + "/root/subfolder/file4", "r") as f:
|
||||
assert f.read() == "even more content"
|
||||
|
||||
with open(filesystem_folder + "/root/static_content1.txt", "r") as f:
|
||||
assert f.read() == "test1"
|
||||
|
||||
with open(filesystem_folder + "/root/static_content2.txt", "r") as f:
|
||||
assert f.read() == "test2"
|
||||
|
||||
with open(filesystem_folder + "/root/template.txt", "r") as f:
|
||||
assert f.read() == "Hello World!"
|
||||
|
||||
|
||||
def test_fsOutput_treeString():
|
||||
jinja_env = Environment(
|
||||
loader=FileSystemLoader("test/data/templates/"),
|
||||
autoescape=select_autoescape()
|
||||
)
|
||||
|
||||
root_folder = BackendFSOutFolder("root", [
|
||||
BackendFSOutFile("file1", "some content"),
|
||||
BackendFSOutFile("file2", "some other content"),
|
||||
BackendFSOutFolder("subfolder", [
|
||||
BackendFSOutFile("file3", "some more content #2"),
|
||||
BackendFSOutFile("file4", "even more content"),
|
||||
BackendFSOutputJinjaFile(jinja_env, "template.jinja", "generate", {"name": "Jinja2"})
|
||||
]),
|
||||
BackendFSOutStaticConent("test/data/static_folder", "data"),
|
||||
BackendFSOutputJinjaFile(jinja_env, "template.jinja", None, {"name": "World"})
|
||||
])
|
||||
|
||||
string = root_folder.assemble_file_tree_string()
|
||||
assert string == """└── root
|
||||
├── [file1]
|
||||
├── [file2]
|
||||
├── subfolder
|
||||
│ ├── [file3]
|
||||
│ ├── [file4]
|
||||
│ └── Template [generate]
|
||||
├── Static [data]
|
||||
└── Template [template]
|
||||
"""
|
||||
|
||||
|
||||
def test_fsOutput_toString():
|
||||
jinja_env = Environment(
|
||||
loader=FileSystemLoader("test/data/templates/"),
|
||||
autoescape=select_autoescape()
|
||||
)
|
||||
|
||||
root_folder = BackendFSOutFolder("root", [
|
||||
BackendFSOutFile("file1", "some content"),
|
||||
BackendFSOutFile("file2", "some other content"),
|
||||
BackendFSOutFolder("subfolder", [
|
||||
BackendFSOutFile("file3", "some more content #2"),
|
||||
BackendFSOutFile("file4", "even more content"),
|
||||
BackendFSOutputJinjaFile(jinja_env, "template.jinja2", "generate", {"name": "Jinja2"})
|
||||
]),
|
||||
BackendFSOutStaticConent("test/data/static_folder", "data/static_folder"),
|
||||
BackendFSOutStaticConent("test/data/static_content1.txt", "data"),
|
||||
BackendFSOutputJinjaFile(jinja_env, "template.jinja2", None, {"name": "World"})
|
||||
])
|
||||
|
||||
res = root_folder.toString()
|
||||
|
||||
assert res == {
|
||||
"root/file1": "some content",
|
||||
"root/file2": "some other content",
|
||||
"root/subfolder/file3": "some more content #2",
|
||||
"root/subfolder/file4": "even more content",
|
||||
"root/subfolder/generate": "Hello Jinja2!",
|
||||
"root/data/static_content1.txt": "test1",
|
||||
"root/data/static_folder/static_content2.txt": "test2",
|
||||
"root/template": "Hello World!"
|
||||
}
|
||||
|
||||
|
||||
def test_fsOutput_pathnormalization(filesystem_folder: str):
|
||||
|
||||
root_folder = BackendFSOutFolder(".", [
|
||||
BackendFSOutFolder(".", [
|
||||
BackendFSOutFile("file2", "some other content"),
|
||||
]),
|
||||
BackendFSOutFile("file1", "some content"),
|
||||
])
|
||||
|
||||
# Check if this works for toString
|
||||
res = root_folder.toString()
|
||||
assert res == {
|
||||
"file2": "some other content",
|
||||
"file1": "some content"
|
||||
}
|
||||
|
||||
# Check if this works for saveToDisk
|
||||
root_folder.saveToDisk(filesystem_folder)
|
||||
|
||||
assert os.path.isfile(filesystem_folder + "/file1")
|
||||
assert os.path.isfile(filesystem_folder + "/file2")
|
||||
|
||||
with open(filesystem_folder + "/file1", "r") as f:
|
||||
assert f.read() == "some content"
|
||||
|
||||
with open(filesystem_folder + "/file2", "r") as f:
|
||||
assert f.read() == "some other content"
|
||||
49
test/compiler/name_conversion/test_name_bproto_style.py
Normal file
49
test/compiler/name_conversion/test_name_bproto_style.py
Normal file
@@ -0,0 +1,49 @@
|
||||
import sys
|
||||
sys.path.append('src/')
|
||||
|
||||
from nameHandling.base import ComponentName, NameStyleBproto
|
||||
|
||||
|
||||
def test_component_name():
|
||||
assert ComponentName(["test"]) != 1234
|
||||
assert ComponentName(["test"]) == ComponentName(["test"])
|
||||
assert ComponentName(["test"]) != ComponentName(["test", "test"])
|
||||
assert hash(ComponentName(["test"])) == hash(ComponentName(["test"]))
|
||||
assert hash(ComponentName(["test"])) != hash(ComponentName(["test", "test"]))
|
||||
assert repr(ComponentName(["test"])) == "ComponentName(Test)"
|
||||
assert repr(ComponentName(["test", "test"])) == "ComponentName(Test-Test)"
|
||||
|
||||
|
||||
def test_ComponentNameStyleBproto():
|
||||
assert NameStyleBproto.fromStr("test") == ComponentName(["test"])
|
||||
assert NameStyleBproto.fromStr("ThisIsATest") == ComponentName(["this", "is", "a", "test"])
|
||||
assert NameStyleBproto.fromStr("thisIsATest") == ComponentName(["this", "is", "a", "test"])
|
||||
assert NameStyleBproto.fromStr("this-is-a-test") == ComponentName(["this", "is", "a", "test"])
|
||||
assert NameStyleBproto.fromStr("this-Is-A-test") == ComponentName(["this", "is", "a", "test"])
|
||||
assert NameStyleBproto.fromStr("this_is_a_test") == ComponentName(["this", "is", "a", "test"])
|
||||
assert NameStyleBproto.fromStr("this_Is_a_Test") == ComponentName(["this", "is", "a", "test"])
|
||||
assert NameStyleBproto.fromStr("this-Is_ATest") == ComponentName(["this", "is", "a", "test"])
|
||||
assert NameStyleBproto.fromStr("ThisIs-A_test") == ComponentName(["this", "is", "a", "test"])
|
||||
|
||||
|
||||
def test_ComponentNameStyleBproto_context_enum():
|
||||
assert NameStyleBproto.fromStr("test", "enum") == ComponentName(["test"])
|
||||
assert NameStyleBproto.fromStr("ThisIsATest", "enum") == ComponentName(["thisisatest"])
|
||||
assert NameStyleBproto.fromStr("TESTTEST", "enum") == ComponentName(["testtest"])
|
||||
assert NameStyleBproto.fromStr("TEST_TEST", "enum") == ComponentName(["test", "test"])
|
||||
|
||||
|
||||
def test_ComponentNameStyleBproto_comparison():
|
||||
c = NameStyleBproto.fromStr("ThisIsATest")
|
||||
assert c == "this-is-a-test"
|
||||
assert c == "this_is_a_test"
|
||||
assert c == "thisIsATest"
|
||||
assert c == "ThisIsATest"
|
||||
assert c == "This_Is_A_Test"
|
||||
|
||||
|
||||
def test_ComponentNameStyleBproto_from_Component():
|
||||
c = ComponentName(["this", "is", "a", "test"])
|
||||
c2 = NameStyleBproto.fromStr(c)
|
||||
|
||||
assert c == c2
|
||||
40
test/compiler/name_conversion/test_name_brand_applier.py
Normal file
40
test/compiler/name_conversion/test_name_brand_applier.py
Normal file
@@ -0,0 +1,40 @@
|
||||
import sys
|
||||
sys.path.append('src/')
|
||||
|
||||
from nameHandling.brand_applier import BrandApplier
|
||||
from protocol_components.message import Message
|
||||
from nameHandling.base import ComponentName
|
||||
|
||||
|
||||
def test_name_applier():
|
||||
protocolNameApplier = BrandApplier("brand")
|
||||
|
||||
name = ComponentName(["test"])
|
||||
name = protocolNameApplier.apply(name)
|
||||
assert name == ComponentName(["brand", "test"])
|
||||
|
||||
name = ComponentName(["test", "test"])
|
||||
name = protocolNameApplier.apply(name)
|
||||
assert name == ComponentName(["brand", "test", "test"])
|
||||
|
||||
msg = Message(ComponentName(["this", "is", "a", "test"]), 0)
|
||||
msg: Message = protocolNameApplier.apply(msg)
|
||||
assert msg.name == ComponentName(["brand", "this", "is", "a", "test"])
|
||||
|
||||
protocolNameApplier = BrandApplier(ComponentName(["happy", "brand"]))
|
||||
|
||||
msg = Message(ComponentName(["this", "is", "a", "test"]), 0)
|
||||
msg: Message = protocolNameApplier.apply(msg)
|
||||
assert msg.name == ComponentName(["happy", "brand", "this", "is", "a", "test"])
|
||||
|
||||
try:
|
||||
protocolNameApplier.apply(None)
|
||||
assert False
|
||||
except Exception as e:
|
||||
assert isinstance(e, TypeError)
|
||||
|
||||
try:
|
||||
protocolNameApplier = BrandApplier(None)
|
||||
assert False
|
||||
except Exception as e:
|
||||
assert isinstance(e, TypeError)
|
||||
73
test/compiler/name_conversion/test_name_c_style.py
Normal file
73
test/compiler/name_conversion/test_name_c_style.py
Normal file
@@ -0,0 +1,73 @@
|
||||
import sys
|
||||
sys.path.append('src/')
|
||||
|
||||
from nameHandling.base import ComponentName
|
||||
from protocol_components.protocolFactory import FactoryProtocolDefition
|
||||
from protocol_components.enumeration import FactoryEnumeration, Enumeration
|
||||
from protocol_components.bitfields import FactoryBitfield, Bitfield
|
||||
from protocol_components.dtypes import BprotoFieldBaseType
|
||||
from protocol_components.field import FactoryField
|
||||
from protocol_components.message import FactoryMessage, Message
|
||||
from nameHandling.style.cNameStyle import NameStyleC
|
||||
|
||||
|
||||
def test_style_c_fromStr():
|
||||
try:
|
||||
NameStyleC.fromStr("")
|
||||
assert False
|
||||
except Exception as e:
|
||||
assert isinstance(e, NotImplementedError)
|
||||
|
||||
|
||||
def test_style_c_toStr_enum():
|
||||
assert NameStyleC.toStr(ComponentName(["my", "custom", "Values"]), "enum_name") == "MyCustomValues"
|
||||
assert NameStyleC.toStr(ComponentName(["my", "custom", "enum", "value"]), "enum_item") == "MY_CUSTOM_ENUM_VALUE"
|
||||
assert NameStyleC.toStr(ComponentName(["my", "custom", "class"]), "struct_name") == "MyCustomClass"
|
||||
assert NameStyleC.toStr(ComponentName(["my", "custom", "class", "member"]), "struct_member") == "myCustomClassMember"
|
||||
assert NameStyleC.toStr(ComponentName(["my", "custom", "class"]), "bitfield_name") == "MyCustomClass"
|
||||
assert NameStyleC.toStr(ComponentName(["my", "custom", "class", "member"]), "bitfield_member") == "myCustomClassMember"
|
||||
|
||||
|
||||
def test_style_c_preprocessor():
|
||||
protocol_factory = FactoryProtocolDefition()
|
||||
|
||||
enum_factory = FactoryEnumeration()
|
||||
enum_factory.add_value("first-Value", 1)
|
||||
enum_factory.add_value("second-Important-Value", 2)
|
||||
protocol_factory.add_enum(enum_factory.assemble("mode"))
|
||||
|
||||
bitfield_factory = FactoryBitfield()
|
||||
bitfield_factory.add_bit("FirstBit", 0)
|
||||
bitfield_factory.add_bit("SecondBit", 1)
|
||||
protocol_factory.add_bitfield(bitfield_factory.assemble("bitfield1"))
|
||||
|
||||
message_factory = FactoryMessage()
|
||||
message_factory.add_field(FactoryField().assemble("x", 1, BprotoFieldBaseType.FLOAT32, 2, None))
|
||||
message_factory.add_field(FactoryField().assemble("y", 2, BprotoFieldBaseType.FLOAT32, 2, None))
|
||||
protocol_factory.add_message(message_factory.assemble("cordUpdate", 1))
|
||||
|
||||
protocol = protocol_factory.assemble("ABCP", 5)
|
||||
protocol = NameStyleC.preprocess(protocol)
|
||||
|
||||
enum: Enumeration = list(protocol.enums.values())[0]
|
||||
bitfield: Bitfield = list(protocol.bitfields.values())[0]
|
||||
message: Message = list(protocol.messages.values())[0]
|
||||
|
||||
assert isinstance(enum, Enumeration)
|
||||
assert isinstance(bitfield, Bitfield)
|
||||
assert isinstance(message, Message)
|
||||
|
||||
assert NameStyleC.toStr(enum.name, "enum_name") == "Abcp_enum_Mode"
|
||||
assert NameStyleC.toStr(bitfield.name, "bitfield_name") == "Abcp_bitfield_Bitfield1"
|
||||
assert NameStyleC.toStr(message.name, "struct_name") == "Abcp_message_CordUpdate"
|
||||
|
||||
assert enum.values == {
|
||||
ComponentName(["abcp", "_enum_", "mode", "value", "first", "value"]): 1,
|
||||
ComponentName(["abcp", "_enum_", "mode", "value", "second", "important", "value"]): 2
|
||||
}
|
||||
|
||||
assert bitfield.bits == {
|
||||
ComponentName(["first", "bit"]): 0,
|
||||
ComponentName(["second", "bit"]): 1
|
||||
}
|
||||
|
||||
59
test/compiler/name_conversion/test_name_python_style.py
Normal file
59
test/compiler/name_conversion/test_name_python_style.py
Normal file
@@ -0,0 +1,59 @@
|
||||
import sys
|
||||
sys.path.append('src/')
|
||||
|
||||
from nameHandling.base import ComponentName
|
||||
from protocol_components.protocolFactory import FactoryProtocolDefition
|
||||
from protocol_components.enumeration import FactoryEnumeration, Enumeration
|
||||
from protocol_components.bitfields import FactoryBitfield, Bitfield
|
||||
from protocol_components.dtypes import BprotoFieldBaseType
|
||||
from protocol_components.field import FactoryField
|
||||
from protocol_components.message import FactoryMessage, Message
|
||||
from nameHandling.style.pythonNameStyle import NameStylePython
|
||||
|
||||
|
||||
def test_style_python_fromStr():
|
||||
try:
|
||||
NameStylePython.fromStr("")
|
||||
assert False
|
||||
except Exception as e:
|
||||
assert isinstance(e, NotImplementedError)
|
||||
|
||||
|
||||
def test_style_python_toStr_enum():
|
||||
assert NameStylePython.toStr(ComponentName(["my", "custom", "enum"]), "enum_name") == "MyCustomEnum"
|
||||
assert NameStylePython.toStr(ComponentName(["my", "custom", "enum", "value"]), "enum_item") == "MY_CUSTOM_ENUM_VALUE"
|
||||
assert NameStylePython.toStr(ComponentName(["my", "custom", "class"]), "class_name") == "MyCustomClass"
|
||||
assert NameStylePython.toStr(ComponentName(["my", "custom", "class", "member"]), "class_member") == "my_custom_class_member"
|
||||
|
||||
|
||||
def test_style_python_preprocessor():
|
||||
protocol_factory = FactoryProtocolDefition()
|
||||
|
||||
enum_factory = FactoryEnumeration()
|
||||
enum_factory.add_value("first-Value", 1)
|
||||
enum_factory.add_value("second-Important-Value", 2)
|
||||
protocol_factory.add_enum(enum_factory.assemble("mode"))
|
||||
|
||||
bitfield_factory = FactoryBitfield()
|
||||
bitfield_factory.add_bit("FirstBit", 0)
|
||||
bitfield_factory.add_bit("SecondBit", 1)
|
||||
protocol_factory.add_bitfield(bitfield_factory.assemble("bitfield1"))
|
||||
|
||||
message_factory = FactoryMessage()
|
||||
message_factory.add_field(FactoryField().assemble("x", 1, BprotoFieldBaseType.FLOAT32, 2, None))
|
||||
message_factory.add_field(FactoryField().assemble("y", 2, BprotoFieldBaseType.FLOAT32, 2, None))
|
||||
protocol_factory.add_message(message_factory.assemble("cordUpdate", 1))
|
||||
|
||||
protocol = protocol_factory.assemble("HCPT", 5)
|
||||
protocol = NameStylePython.preprocess(protocol)
|
||||
|
||||
enum: Enumeration = list(protocol.enums.values())[0]
|
||||
bitfield: Bitfield = list(protocol.bitfields.values())[0]
|
||||
message: Message = list(protocol.messages.values())[0]
|
||||
|
||||
assert isinstance(enum, Enumeration)
|
||||
assert isinstance(bitfield, Bitfield)
|
||||
|
||||
assert NameStylePython.toStr(enum.name, "enum_name") == "HcptEnumMode"
|
||||
assert NameStylePython.toStr(bitfield.name, "class_name") == "HcptBitfieldBitfield1"
|
||||
assert NameStylePython.toStr(message.name, "class_name") == "HcptMessageCordUpdate"
|
||||
80
test/compiler/test_bitfields.py
Normal file
80
test/compiler/test_bitfields.py
Normal file
@@ -0,0 +1,80 @@
|
||||
import sys
|
||||
sys.path.append('src/')
|
||||
|
||||
from nameHandling.base import ComponentName
|
||||
from protocol_components.bitfields import FactoryBitfield, Bitfield
|
||||
from errors import BprotoDuplicateNameError, BprotoDuplicateBitfieldPositionError, BprotoCompilerError
|
||||
from nameHandling.base import NameStyleBproto
|
||||
|
||||
|
||||
def test_bitfield_factory():
|
||||
fatory = FactoryBitfield()
|
||||
fatory.add_bit("BIT #1", 0)
|
||||
fatory.add_bit("BIT #4", None)
|
||||
fatory.add_bit("BIT #2", 1)
|
||||
fatory.add_bit("BIT #5", 4)
|
||||
fatory.add_bit("BIT #6", None)
|
||||
fatory.add_bit("BIT #3", 2)
|
||||
fatory.add_bit("BIT #7", 6)
|
||||
|
||||
bitfield: Bitfield = fatory.assemble("TestBitfield")
|
||||
|
||||
assert bitfield.name == "TestBitfield"
|
||||
assert isinstance(bitfield.name, ComponentName)
|
||||
assert isinstance(bitfield.get_name(), ComponentName)
|
||||
assert isinstance(bitfield.get_identifier(), ComponentName)
|
||||
|
||||
assert bitfield.bits == {
|
||||
NameStyleBproto.fromStr("BIT #1"): 0,
|
||||
NameStyleBproto.fromStr("BIT #2"): 1,
|
||||
NameStyleBproto.fromStr("BIT #3"): 2,
|
||||
NameStyleBproto.fromStr("BIT #4"): 3,
|
||||
NameStyleBproto.fromStr("BIT #5"): 4,
|
||||
NameStyleBproto.fromStr("BIT #6"): 5,
|
||||
NameStyleBproto.fromStr("BIT #7"): 6,
|
||||
}
|
||||
assert isinstance(list(bitfield.bits.keys())[0], ComponentName)
|
||||
|
||||
|
||||
def test_bitfield_factory_failure_cases_duplicate_name():
|
||||
factory = FactoryBitfield()
|
||||
|
||||
factory.add_bit("BIT #1", 0)
|
||||
factory.add_bit("BIT #1", 1)
|
||||
factory.add_bit("BIT #2", 2)
|
||||
|
||||
try:
|
||||
factory.assemble("TestBitfield")
|
||||
assert False
|
||||
except BprotoCompilerError as e:
|
||||
assert isinstance(e, BprotoDuplicateNameError)
|
||||
|
||||
|
||||
def test_bitfield_factory_failure_cases_duplicate_position():
|
||||
factory = FactoryBitfield()
|
||||
|
||||
factory.add_bit("BIT #1", 0)
|
||||
factory.add_bit("BIT #2", 1)
|
||||
factory.add_bit("BIT #3", 1)
|
||||
|
||||
try:
|
||||
factory.assemble("TestBitfield")
|
||||
assert False
|
||||
except BprotoCompilerError as e:
|
||||
assert isinstance(e, BprotoDuplicateBitfieldPositionError)
|
||||
|
||||
|
||||
def test_bitfield_size_calculation():
|
||||
bitfield = Bitfield("TestBitfield")
|
||||
|
||||
bitfield.length = 7
|
||||
assert bitfield.get_size_bytes() == 1
|
||||
|
||||
bitfield.length = 0
|
||||
assert bitfield.get_size_bytes() == 1
|
||||
|
||||
bitfield.length = 8
|
||||
assert bitfield.get_size_bytes() == 1
|
||||
|
||||
bitfield.length = 9
|
||||
assert bitfield.get_size_bytes() == 2
|
||||
73
test/compiler/test_deep_copy.py
Normal file
73
test/compiler/test_deep_copy.py
Normal file
@@ -0,0 +1,73 @@
|
||||
from nameHandling.base import ComponentName, NameStyleBproto
|
||||
from nameHandling.style.pythonNameStyle import NameStylePython
|
||||
from protocol_components.enumeration import FactoryEnumeration
|
||||
from copy import deepcopy
|
||||
|
||||
|
||||
def test_deepcopy_component_name():
|
||||
name = ComponentName(["This", "Is", "Some", "text"], NameStylePython, source_string="ThisIsSomeText")
|
||||
|
||||
new_name = deepcopy(name)
|
||||
|
||||
name.name_parts = ["Some", "Other", "Stuff"]
|
||||
name.source_string = "SomeOtherStuff"
|
||||
name.source_style = NameStyleBproto
|
||||
|
||||
# Verify Values did not change
|
||||
assert name.name_parts != new_name.name_parts
|
||||
assert name.source_string != new_name.source_string
|
||||
assert name.source_style != new_name.source_style
|
||||
|
||||
|
||||
def test_deepcopy_enum():
|
||||
old_enum_factory = FactoryEnumeration()
|
||||
old_enum_factory.add_value("Value1", 1)
|
||||
old_enum_factory.add_value("Value2", 2)
|
||||
old_enum_factory.add_value("Value3", 3)
|
||||
old_enum = old_enum_factory.assemble("OldEnum")
|
||||
|
||||
new_enum = deepcopy(old_enum)
|
||||
|
||||
# Check ids
|
||||
assert id(new_enum) != id(old_enum)
|
||||
assert id(new_enum.name) != id(old_enum.name)
|
||||
|
||||
assert id(old_enum.values) != id(new_enum.values)
|
||||
|
||||
old_keys = list(old_enum.values.keys())
|
||||
new_keys = list(new_enum.values.keys())
|
||||
assert id(old_keys[0]) != id(new_keys[0])
|
||||
assert id(old_keys[1]) != id(new_keys[1])
|
||||
assert id(old_keys[2]) != id(new_keys[2])
|
||||
|
||||
|
||||
def test_deepcopy_component_names_memo():
|
||||
name1 = NameStyleBproto.fromStr("Value1")
|
||||
name2 = NameStyleBproto.fromStr("Value2")
|
||||
|
||||
assert id(name1) != id(name2)
|
||||
|
||||
name1_copy = deepcopy(name1)
|
||||
assert isinstance(name1_copy, ComponentName)
|
||||
|
||||
assert name1_copy == name1
|
||||
assert id(name1_copy) != id(name1)
|
||||
|
||||
name1_copy2 = deepcopy(name1, {
|
||||
id(name1): name1_copy,
|
||||
id(name1_copy): name1_copy
|
||||
})
|
||||
|
||||
name1_copy3 = deepcopy(name1_copy, {
|
||||
id(name1): name1_copy,
|
||||
id(name1_copy): name1_copy
|
||||
})
|
||||
|
||||
assert isinstance(name1_copy2, ComponentName)
|
||||
assert isinstance(name1_copy3, ComponentName)
|
||||
|
||||
assert id(name1_copy) == id(name1_copy2)
|
||||
assert id(name1_copy2) != id(name1)
|
||||
|
||||
assert id(name1_copy) == id(name1_copy3)
|
||||
assert id(name1_copy3) != id(name1)
|
||||
228
test/compiler/test_emums.py
Normal file
228
test/compiler/test_emums.py
Normal file
@@ -0,0 +1,228 @@
|
||||
import sys
|
||||
sys.path.append('src/')
|
||||
|
||||
from protocol_components.dtypes import BprotoFieldBaseType
|
||||
from protocol_components.enumeration import FactoryEnumeration, Enumeration
|
||||
from errors import BprotoDuplicateNameError, BprotoDuplicateEnumValueError, BprotoCompilerError, BprotoEnumBitsizeTooLargeError
|
||||
from nameHandling.base import NameStyleBproto, ComponentName
|
||||
|
||||
|
||||
def test_enum_factory():
|
||||
|
||||
factory = FactoryEnumeration()
|
||||
|
||||
factory.add_value("X", 0)
|
||||
factory.add_value("A", 1)
|
||||
factory.add_value("C", 3)
|
||||
factory.add_value("B", 2)
|
||||
factory.add_value("D", None)
|
||||
factory.add_value("E", 5)
|
||||
factory.add_value("F", None)
|
||||
|
||||
enum = factory.assemble("TestEnum")
|
||||
|
||||
assert enum.name == "TestEnum"
|
||||
assert enum.values == {
|
||||
NameStyleBproto.fromStr("X"): 0,
|
||||
NameStyleBproto.fromStr("A"): 1,
|
||||
NameStyleBproto.fromStr("B"): 2,
|
||||
NameStyleBproto.fromStr("C"): 3,
|
||||
NameStyleBproto.fromStr("D"): 4,
|
||||
NameStyleBproto.fromStr("E"): 5,
|
||||
NameStyleBproto.fromStr("F"): 6
|
||||
}
|
||||
|
||||
assert isinstance(enum.name, ComponentName)
|
||||
assert isinstance(enum.get_name(), ComponentName)
|
||||
assert isinstance(enum.get_identifier(), ComponentName)
|
||||
assert isinstance(list(enum.values.keys())[0], ComponentName)
|
||||
|
||||
|
||||
def test_enum_factory_failure_cases_dubplicate_value():
|
||||
|
||||
factory = FactoryEnumeration()
|
||||
|
||||
factory.add_value("X", 0)
|
||||
factory.add_value("Y", 0)
|
||||
factory.add_value("Z", 1)
|
||||
|
||||
try:
|
||||
factory.assemble("TestEnum")
|
||||
assert False
|
||||
except BprotoCompilerError as e:
|
||||
assert isinstance(e, BprotoDuplicateEnumValueError)
|
||||
|
||||
|
||||
def test_enum_factory_failure_cases_duplicate_name():
|
||||
factory = FactoryEnumeration()
|
||||
|
||||
factory.add_value("X", 0)
|
||||
factory.add_value("Y", 1)
|
||||
factory.add_value("Y", 2)
|
||||
|
||||
try:
|
||||
factory.assemble("TestEnum")
|
||||
assert False
|
||||
except BprotoCompilerError as e:
|
||||
assert isinstance(e, BprotoDuplicateNameError)
|
||||
|
||||
|
||||
def test_enum_factory_failure_cases_duplicate_name_and_value():
|
||||
factory = FactoryEnumeration()
|
||||
|
||||
factory.add_value("X", 0)
|
||||
factory.add_value("X", 0)
|
||||
|
||||
try:
|
||||
factory.assemble("TestEnum")
|
||||
assert False
|
||||
except BprotoCompilerError as e:
|
||||
assert isinstance(e, BprotoDuplicateNameError) or isinstance(e, BprotoDuplicateEnumValueError)
|
||||
|
||||
|
||||
def test_enum_sorted():
|
||||
factory = FactoryEnumeration()
|
||||
|
||||
factory.add_value("X", 0)
|
||||
factory.add_value("D", None)
|
||||
factory.add_value("C", 3)
|
||||
factory.add_value("E", 5)
|
||||
factory.add_value("F", None)
|
||||
factory.add_value("B", 2)
|
||||
factory.add_value("A", 1)
|
||||
|
||||
enum = factory.assemble("TestEnum")
|
||||
|
||||
assert list(enum.values.values()) == list(range(7))
|
||||
|
||||
|
||||
def test_enum_size_calc():
|
||||
assert FactoryEnumeration.calculate_bitsize(list(range(17))) == 5
|
||||
assert FactoryEnumeration.calculate_bitsize(list(range(16))) == 4
|
||||
assert FactoryEnumeration.calculate_bitsize(list(range(9))) == 4
|
||||
assert FactoryEnumeration.calculate_bitsize(list(range(8))) == 3
|
||||
assert FactoryEnumeration.calculate_bitsize(list(range(7))) == 3
|
||||
|
||||
assert FactoryEnumeration.calculate_bitsize([1, 2, 31]) == 5
|
||||
assert FactoryEnumeration.calculate_bitsize([1, 32, 2]) == 6
|
||||
assert FactoryEnumeration.calculate_bitsize([127, 0, 2]) == 7
|
||||
|
||||
assert FactoryEnumeration.calculate_bitsize([]) == 1
|
||||
|
||||
|
||||
def test_enum_auxiliary_data_typemap():
|
||||
|
||||
e = Enumeration("TestEnum", 8)
|
||||
assert e.map_auxiliary_datatypes() == BprotoFieldBaseType.UINT8
|
||||
|
||||
e = Enumeration("TestEnum", 16)
|
||||
assert e.map_auxiliary_datatypes() == BprotoFieldBaseType.UINT16
|
||||
|
||||
e = Enumeration("TestEnum", 24)
|
||||
assert e.map_auxiliary_datatypes() == BprotoFieldBaseType.UINT32
|
||||
|
||||
e = Enumeration("TestEnum", 32)
|
||||
assert e.map_auxiliary_datatypes() == BprotoFieldBaseType.UINT32
|
||||
|
||||
e = Enumeration("TestEnum", 64)
|
||||
assert e.map_auxiliary_datatypes() == BprotoFieldBaseType.UINT64
|
||||
|
||||
e = Enumeration("TestEnum", 65)
|
||||
try:
|
||||
e.map_auxiliary_datatypes()
|
||||
assert False
|
||||
except Exception as err:
|
||||
assert isinstance(err, BprotoEnumBitsizeTooLargeError)
|
||||
|
||||
|
||||
def test_enum_size_calculation():
|
||||
# Uint8
|
||||
factory = FactoryEnumeration()
|
||||
for i in range(3):
|
||||
factory.add_value(f"#{i}", i)
|
||||
e = factory.assemble("TestEnum")
|
||||
assert e.get_size_bits() == 8
|
||||
assert e.get_size_bytes() == 1
|
||||
|
||||
factory = FactoryEnumeration()
|
||||
for i in range(2**8):
|
||||
factory.add_value(f"#{i}", i)
|
||||
e = factory.assemble("TestEnum")
|
||||
assert e.get_size_bits() == 8
|
||||
assert e.get_size_bytes() == 1
|
||||
|
||||
# Uint16
|
||||
factory = FactoryEnumeration()
|
||||
for i in range(2**8 + 1):
|
||||
factory.add_value(f"#{i}", i)
|
||||
e = factory.assemble("TestEnum")
|
||||
assert e.get_size_bits() == 16
|
||||
assert e.get_size_bytes() == 2
|
||||
|
||||
factory = FactoryEnumeration()
|
||||
for i in range(2**16):
|
||||
factory.add_value(f"#{i}", i)
|
||||
e = factory.assemble("TestEnum")
|
||||
assert e.get_size_bits() == 16
|
||||
assert e.get_size_bytes() == 2
|
||||
|
||||
# Uint32
|
||||
factory = FactoryEnumeration()
|
||||
for i in range(17):
|
||||
factory.add_value(f"#{i}", i)
|
||||
factory.add_value(f"#{2**16}", 2**16)
|
||||
# We start couinting from 0 -> 2**16 does not fit in 16 bits
|
||||
|
||||
e = factory.assemble("TestEnum")
|
||||
assert e.get_size_bits() == 32
|
||||
assert e.get_size_bytes() == 4
|
||||
|
||||
factory = FactoryEnumeration()
|
||||
for i in range(17):
|
||||
factory.add_value(f"#{i}", i)
|
||||
factory.add_value(f"#{2**32 - 1}", 2**32 - 1)
|
||||
# We start couinting from 0 2**32 - 1 does barely fit in 32 bits
|
||||
|
||||
e = factory.assemble("TestEnum")
|
||||
assert e.get_size_bits() == 32
|
||||
assert e.get_size_bytes() == 4
|
||||
|
||||
# Uint64
|
||||
factory = FactoryEnumeration()
|
||||
for i in range(17):
|
||||
factory.add_value(f"#{i}", i)
|
||||
factory.add_value(f"#{2**32}", 2**32)
|
||||
# We start couinting from 0 -> 2**32 does not fit in 32 bits
|
||||
|
||||
e = factory.assemble("TestEnum")
|
||||
assert e.get_size_bits() == 64
|
||||
assert e.get_size_bytes() == 8
|
||||
|
||||
factory = FactoryEnumeration()
|
||||
for i in range(17):
|
||||
factory.add_value(f"#{i}", i)
|
||||
factory.add_value(f"#{2**64 - 1}", 2**64 - 1)
|
||||
# We start couinting from 0 2**64 - 1 does barely fit in 64 bits
|
||||
|
||||
e = factory.assemble("TestEnum")
|
||||
assert e.get_size_bits() == 64
|
||||
assert e.get_size_bytes() == 8
|
||||
|
||||
# Too large
|
||||
factory = FactoryEnumeration()
|
||||
for i in range(17):
|
||||
factory.add_value(f"#{i}", i)
|
||||
factory.add_value(f"#{2**64}", 2**64)
|
||||
|
||||
# This can cause rounding errors
|
||||
# log2(2**64) ~ log2(2**64 - 1) ~ 64
|
||||
# This is btw not a problem for 2**32
|
||||
# stupid floating point errors
|
||||
|
||||
# We start couinting from 0 -> 2**64 does not fit in 64 bits
|
||||
|
||||
try:
|
||||
e = factory.assemble("TestEnum")
|
||||
assert False
|
||||
except Exception as err:
|
||||
assert isinstance(err, BprotoEnumBitsizeTooLargeError)
|
||||
98
test/compiler/test_field.py
Normal file
98
test/compiler/test_field.py
Normal file
@@ -0,0 +1,98 @@
|
||||
import sys
|
||||
sys.path.append('src/')
|
||||
|
||||
from protocol_components.dtypes import BprotoFieldBaseType, BprotoArrayPolicyViolationError, BprotoUnknownDataTypeError
|
||||
from protocol_components.field import Field, FactoryField, FieldBitfield, FieldBitfieldRef
|
||||
from protocol_components.bitfields import Bitfield
|
||||
from nameHandling.base import ComponentName
|
||||
|
||||
|
||||
def test_field_object():
|
||||
field = Field("field1", 3, BprotoFieldBaseType.UINT64, 4)
|
||||
|
||||
assert field.name == "field1"
|
||||
assert field.pos == 3
|
||||
assert field.type == BprotoFieldBaseType.UINT64
|
||||
assert field.array_size == 4
|
||||
|
||||
assert field.get_identifier() == "field1"
|
||||
assert field.get_name() == "field1"
|
||||
assert field.get_type_name() == "field"
|
||||
assert field.get_size_bytes() == 8 * 4
|
||||
assert field.get_size_bits() == 64 * 4
|
||||
|
||||
|
||||
def test_field_factory():
|
||||
factory = FactoryField()
|
||||
|
||||
field: Field = factory.assemble("field1", 3, BprotoFieldBaseType.UINT64.value, 4, None)
|
||||
|
||||
assert isinstance(field, Field)
|
||||
assert field.name == "field1"
|
||||
assert field.pos == 3
|
||||
assert field.type == BprotoFieldBaseType.UINT64
|
||||
assert field.array_size == 4
|
||||
|
||||
assert isinstance(field.name, ComponentName)
|
||||
assert isinstance(field.get_identifier(), ComponentName)
|
||||
assert isinstance(field.get_name(), ComponentName)
|
||||
assert isinstance(field.get_type_name(), str)
|
||||
|
||||
|
||||
def test_field_factory_errors_invalid_datatype():
|
||||
factory = FactoryField()
|
||||
try:
|
||||
factory.assemble(
|
||||
"field1", 3, "junkDataType", 4, None
|
||||
)
|
||||
assert False
|
||||
except BprotoUnknownDataTypeError:
|
||||
assert True
|
||||
except Exception as e:
|
||||
assert False, f"Unexpected exception: {e}"
|
||||
|
||||
|
||||
def test_field_factory_errors_invalid_array_policy():
|
||||
factory = FactoryField()
|
||||
try:
|
||||
factory.assemble(
|
||||
"field1", 1, BprotoFieldBaseType.BITFIELD, 4, None
|
||||
)
|
||||
assert False
|
||||
except BprotoArrayPolicyViolationError:
|
||||
assert True
|
||||
except Exception as e:
|
||||
assert False, f"Unexpected exception: {e}"
|
||||
|
||||
|
||||
def test_field_factory_bitfield():
|
||||
factory = FactoryField()
|
||||
|
||||
field: FieldBitfield = factory.assemble(
|
||||
"field1", 3, BprotoFieldBaseType.BITFIELD, 1,
|
||||
Bitfield("test")
|
||||
)
|
||||
|
||||
assert isinstance(field, FieldBitfield)
|
||||
assert field.name == "field1"
|
||||
assert field.pos == 3
|
||||
assert field.type == BprotoFieldBaseType.BITFIELD
|
||||
assert field.array_size == 1
|
||||
assert isinstance(field.bitfield, Bitfield)
|
||||
assert field.bitfield.get_name() == "test"
|
||||
|
||||
|
||||
def test_field_factory_bitfield_ref():
|
||||
factory = FactoryField()
|
||||
|
||||
field: FieldBitfield = factory.assemble(
|
||||
"field1", 3, BprotoFieldBaseType.BITFIELD, 1,
|
||||
"test"
|
||||
)
|
||||
|
||||
assert isinstance(field, FieldBitfieldRef)
|
||||
assert field.name == "field1"
|
||||
assert field.pos == 3
|
||||
assert field.type == BprotoFieldBaseType.BITFIELD
|
||||
assert field.array_size == 1
|
||||
assert field.ref == "test"
|
||||
71
test/compiler/test_message.py
Normal file
71
test/compiler/test_message.py
Normal file
@@ -0,0 +1,71 @@
|
||||
import sys
|
||||
sys.path.append('src/')
|
||||
|
||||
from nameHandling.base import ComponentName, NameStyleBproto
|
||||
from protocol_components.message import FactoryMessage, Message
|
||||
from protocol_components.field import FactoryField
|
||||
from errors import BprotoDuplicateNameError, BprotoMessageIDAlreadyUsed
|
||||
|
||||
|
||||
def test_message_factory():
|
||||
factory = FactoryMessage()
|
||||
field1 = FactoryField().assemble("field1", 0, "uint64", 4, None)
|
||||
field2 = FactoryField().assemble("field2", 1, "uint64", 4, None)
|
||||
field3 = FactoryField().assemble("field3", 2, "uint64", 4, None)
|
||||
field4 = FactoryField().assemble("field4", 3, "uint64", 4, None)
|
||||
|
||||
factory.add_field(field3)
|
||||
factory.add_field(field2)
|
||||
factory.add_field(field4)
|
||||
factory.add_field(field1)
|
||||
|
||||
message: Message = factory.assemble("TestMessage", 42)
|
||||
|
||||
assert message.name == "TestMessage"
|
||||
assert isinstance(message.name, ComponentName)
|
||||
assert isinstance(message.get_name(), ComponentName)
|
||||
|
||||
assert message.message_index_number == 42
|
||||
assert message.get_identifier() == 42
|
||||
|
||||
assert message.fields == {
|
||||
NameStyleBproto.fromStr("field1"): field1,
|
||||
NameStyleBproto.fromStr("field2"): field2,
|
||||
NameStyleBproto.fromStr("field3"): field3,
|
||||
NameStyleBproto.fromStr("field4"): field4
|
||||
}
|
||||
|
||||
assert isinstance(list(message.fields.keys())[0], ComponentName)
|
||||
|
||||
assert message.get_size_bytes() == 8 * 4 * 4
|
||||
assert message.get_size_bits() == 64 * 4 * 4
|
||||
|
||||
|
||||
def test_message_factory_error_duplicate_name():
|
||||
field1 = FactoryField().assemble("field1", 0, "uint64", 4, None)
|
||||
field2 = FactoryField().assemble("field1", 1, "uint64", 4, None)
|
||||
|
||||
factory = FactoryMessage()
|
||||
factory.add_field(field1)
|
||||
factory.add_field(field2)
|
||||
|
||||
try:
|
||||
factory.assemble("TestMessage", 42)
|
||||
assert False
|
||||
except Exception as e:
|
||||
assert isinstance(e, BprotoDuplicateNameError)
|
||||
|
||||
|
||||
def test_message_factory_error_duplicate_index():
|
||||
field1 = FactoryField().assemble("field1", 0, "uint64", 4, None)
|
||||
field2 = FactoryField().assemble("field2", 0, "uint64", 4, None)
|
||||
|
||||
factory = FactoryMessage()
|
||||
factory.add_field(field1)
|
||||
factory.add_field(field2)
|
||||
|
||||
try:
|
||||
factory.assemble("TestMessage", 42)
|
||||
assert False
|
||||
except Exception as e:
|
||||
assert isinstance(e, BprotoMessageIDAlreadyUsed)
|
||||
181
test/compiler/test_protocol.py
Normal file
181
test/compiler/test_protocol.py
Normal file
@@ -0,0 +1,181 @@
|
||||
import sys
|
||||
sys.path.append('src/')
|
||||
|
||||
from protocol_components.dtypes import BprotoFieldBaseType
|
||||
from nameHandling.base import ComponentName, NameStyleBproto
|
||||
from protocol_components.message import FactoryMessage
|
||||
from protocol_components.enumeration import FactoryEnumeration, Enumeration
|
||||
from protocol_components.bitfields import FactoryBitfield, Bitfield
|
||||
from protocol_components.protocolFactory import FactoryProtocolDefition
|
||||
from protocol_components.field import FactoryField, Field, FieldEnum, FieldBitfield, FieldEnumRef, FieldBitfieldRef
|
||||
from errors import BprotoUnresolvedReferenceError, BprotoAlreadyDefinedError
|
||||
|
||||
from nameHandling.resolver import ProtocolReferenceResolver
|
||||
|
||||
|
||||
def test_protocol_factory():
|
||||
factory = FactoryProtocolDefition()
|
||||
factory.add_message(FactoryMessage().assemble("Message1", 42))
|
||||
factory.add_message(FactoryMessage().assemble("Message2", 43))
|
||||
|
||||
message_with_fields_factory = FactoryMessage()
|
||||
message_with_fields_factory.add_field(FactoryField().assemble("field1", 0, "uint64", 1, None))
|
||||
message_with_fields_factory.add_field(FactoryField().assemble("enum_ref", 1, BprotoFieldBaseType.ENUM, 1, "Enum1"))
|
||||
message_with_fields_factory.add_field(FactoryField().assemble("bitfield_ref", 2, BprotoFieldBaseType.BITFIELD, 1, "Bitfield2"))
|
||||
|
||||
factory.add_message(
|
||||
message_with_fields_factory.assemble("Message3", 44)
|
||||
)
|
||||
|
||||
factory.add_enum(FactoryEnumeration().assemble("Enum1"))
|
||||
factory.add_enum(FactoryEnumeration().assemble("Enum2"))
|
||||
factory.add_enum(FactoryEnumeration().assemble("Enum3"))
|
||||
|
||||
factory.add_bitfield(FactoryBitfield().assemble("Bitfield1"))
|
||||
factory.add_bitfield(FactoryBitfield().assemble("Bitfield2"))
|
||||
factory.add_bitfield(FactoryBitfield().assemble("Bitfield3"))
|
||||
|
||||
protocol = factory.assemble("TESTPROTOCOL", 1)
|
||||
|
||||
assert protocol.name.name_parts == ["testprotocol"]
|
||||
assert isinstance(protocol.name, ComponentName)
|
||||
assert protocol.version == 1
|
||||
|
||||
assert protocol.messages == {
|
||||
NameStyleBproto.fromStr("Message1"): factory.messages.get(NameStyleBproto.fromStr("Message1")),
|
||||
NameStyleBproto.fromStr("Message2"): factory.messages.get(NameStyleBproto.fromStr("Message2")),
|
||||
NameStyleBproto.fromStr("Message3"): factory.messages.get(NameStyleBproto.fromStr("Message3"))
|
||||
}
|
||||
|
||||
assert protocol.enums == {
|
||||
NameStyleBproto.fromStr("Enum1"): factory.enums.get(NameStyleBproto.fromStr("Enum1")),
|
||||
NameStyleBproto.fromStr("Enum2"): factory.enums.get(NameStyleBproto.fromStr("Enum2")),
|
||||
NameStyleBproto.fromStr("Enum3"): factory.enums.get(NameStyleBproto.fromStr("Enum3"))
|
||||
}
|
||||
|
||||
assert protocol.bitfields == {
|
||||
NameStyleBproto.fromStr("Bitfield1"): factory.bitfields.get(NameStyleBproto.fromStr("Bitfield1")),
|
||||
NameStyleBproto.fromStr("Bitfield2"): factory.bitfields.get(NameStyleBproto.fromStr("Bitfield2")),
|
||||
NameStyleBproto.fromStr("Bitfield3"): factory.bitfields.get(NameStyleBproto.fromStr("Bitfield3"))
|
||||
}
|
||||
|
||||
# Check if references are resolved works
|
||||
assert isinstance(protocol.messages.get(NameStyleBproto.fromStr("Message3")).fields.get(NameStyleBproto.fromStr("enum_ref")), FieldEnum)
|
||||
assert isinstance(protocol.messages.get(NameStyleBproto.fromStr("Message3")).fields.get(NameStyleBproto.fromStr("bitfield_ref")), FieldBitfield)
|
||||
|
||||
|
||||
def test_protocol_resolve_refernces():
|
||||
enum1 = FactoryEnumeration().assemble("Enum1")
|
||||
bitfield1 = FactoryBitfield().assemble("Bitfield1")
|
||||
|
||||
message1Factory = FactoryMessage()
|
||||
message1Factory.add_field(FactoryField().assemble("field1", 0, "uint64", 1, None))
|
||||
message1Factory.add_field(FactoryField().assemble("enum_field", 1, BprotoFieldBaseType.ENUM, 1, enum1))
|
||||
message1Factory.add_field(FactoryField().assemble("enum_ref_field", 2, BprotoFieldBaseType.ENUM, 1, "Enum1"))
|
||||
message1Factory.add_field(FactoryField().assemble("bitfield_field", 3, BprotoFieldBaseType.BITFIELD, 1, bitfield1))
|
||||
message1Factory.add_field(FactoryField().assemble("bitfield_ref_field", 4, BprotoFieldBaseType.BITFIELD, 1, "Bitfield1"))
|
||||
message1 = message1Factory.assemble("Message1", 42)
|
||||
|
||||
# Check if message is correctly assembled
|
||||
assert isinstance(message1.fields.get(NameStyleBproto.fromStr("field1")), Field)
|
||||
assert message1.fields.get(NameStyleBproto.fromStr("field1")).type == BprotoFieldBaseType.UINT64
|
||||
assert message1.fields.get(NameStyleBproto.fromStr("field1")).array_size == 1
|
||||
assert message1.fields.get(NameStyleBproto.fromStr("field1")).ref is None
|
||||
assert isinstance(message1.fields.get(NameStyleBproto.fromStr("field1")).ref, type(None))
|
||||
|
||||
assert isinstance(message1.fields.get(NameStyleBproto.fromStr("enum_field")), FieldEnum)
|
||||
assert message1.fields.get(NameStyleBproto.fromStr("enum_field")).type == BprotoFieldBaseType.ENUM
|
||||
assert isinstance(message1.fields.get(NameStyleBproto.fromStr("enum_field")).enum, Enumeration)
|
||||
assert message1.fields.get(NameStyleBproto.fromStr("enum_field")).enum == enum1
|
||||
|
||||
assert isinstance(message1.fields.get(NameStyleBproto.fromStr("enum_ref_field")), FieldEnumRef)
|
||||
assert message1.fields.get(NameStyleBproto.fromStr("enum_ref_field")).type == BprotoFieldBaseType.ENUM
|
||||
assert message1.fields.get(NameStyleBproto.fromStr("enum_ref_field")).ref == "Enum1"
|
||||
|
||||
assert isinstance(message1.fields.get(NameStyleBproto.fromStr("bitfield_field")), FieldBitfield)
|
||||
assert message1.fields.get(NameStyleBproto.fromStr("bitfield_field")).type == BprotoFieldBaseType.BITFIELD
|
||||
assert isinstance(message1.fields.get(NameStyleBproto.fromStr("bitfield_field")).bitfield, Bitfield)
|
||||
assert message1.fields.get(NameStyleBproto.fromStr("bitfield_field")).bitfield == bitfield1
|
||||
|
||||
assert isinstance(message1.fields.get(NameStyleBproto.fromStr("bitfield_ref_field")), FieldBitfieldRef)
|
||||
assert message1.fields.get(NameStyleBproto.fromStr("bitfield_ref_field")).type == BprotoFieldBaseType.BITFIELD
|
||||
assert message1.fields.get(NameStyleBproto.fromStr("bitfield_ref_field")).ref == "Bitfield1"
|
||||
|
||||
factory = FactoryProtocolDefition()
|
||||
factory.add_message(message1)
|
||||
factory.add_enum(enum1)
|
||||
factory.add_bitfield(bitfield1)
|
||||
protocol = factory.assemble("TestProtocol", 1)
|
||||
|
||||
# Check if references are resolved works
|
||||
|
||||
resolver = ProtocolReferenceResolver(protocol)
|
||||
resolver.resolve_refrence_protocol()
|
||||
|
||||
assert protocol.messages.get(NameStyleBproto.fromStr("Message1")).fields.get(NameStyleBproto.fromStr("enum_field")).type == BprotoFieldBaseType.ENUM
|
||||
assert isinstance(protocol.messages.get(NameStyleBproto.fromStr("Message1")).fields.get(NameStyleBproto.fromStr("enum_field")), FieldEnum)
|
||||
assert protocol.messages.get(NameStyleBproto.fromStr("Message1")).fields.get(NameStyleBproto.fromStr("enum_field")).enum == enum1
|
||||
|
||||
assert protocol.messages.get(NameStyleBproto.fromStr("Message1")).fields.get(NameStyleBproto.fromStr("enum_ref_field")).type == BprotoFieldBaseType.ENUM
|
||||
assert isinstance(protocol.messages.get(NameStyleBproto.fromStr("Message1")).fields.get(NameStyleBproto.fromStr("enum_ref_field")), FieldEnum)
|
||||
assert protocol.messages.get(NameStyleBproto.fromStr("Message1")).fields.get(NameStyleBproto.fromStr("enum_ref_field")).enum == enum1
|
||||
|
||||
assert protocol.messages.get(NameStyleBproto.fromStr("Message1")).fields.get(NameStyleBproto.fromStr("bitfield_field")).type == BprotoFieldBaseType.BITFIELD
|
||||
assert isinstance(protocol.messages.get(NameStyleBproto.fromStr("Message1")).fields.get(NameStyleBproto.fromStr("bitfield_field")), FieldBitfield)
|
||||
assert protocol.messages.get(NameStyleBproto.fromStr("Message1")).fields.get(NameStyleBproto.fromStr("bitfield_field")).bitfield == bitfield1
|
||||
|
||||
assert protocol.messages.get(NameStyleBproto.fromStr("Message1")).fields.get(NameStyleBproto.fromStr("bitfield_ref_field")).type == BprotoFieldBaseType.BITFIELD
|
||||
assert isinstance(protocol.messages.get(NameStyleBproto.fromStr("Message1")).fields.get(NameStyleBproto.fromStr("bitfield_ref_field")), FieldBitfield)
|
||||
assert protocol.messages.get(NameStyleBproto.fromStr("Message1")).fields.get(NameStyleBproto.fromStr("bitfield_ref_field")).bitfield == bitfield1
|
||||
|
||||
|
||||
def test_protocol_resolve_refernces_unknown_refernces():
|
||||
message1Factory = FactoryMessage()
|
||||
message1Factory.add_field(FactoryField().assemble("field1", 0, "uint64", 1, None))
|
||||
message1Factory.add_field(FactoryField().assemble("enum_ref_field", 1, BprotoFieldBaseType.ENUM, 1, "Enum1"))
|
||||
message1Factory.add_field(FactoryField().assemble("bitfield_ref_field", 2, BprotoFieldBaseType.BITFIELD, 1, "Bitfield1",))
|
||||
message1 = message1Factory.assemble("Message1", 42)
|
||||
|
||||
factory = FactoryProtocolDefition()
|
||||
factory.add_message(message1)
|
||||
|
||||
try:
|
||||
factory.assemble("TestProtocol", 1)
|
||||
assert False
|
||||
except Exception as e:
|
||||
assert isinstance(e, BprotoUnresolvedReferenceError)
|
||||
|
||||
try:
|
||||
factory.assemble("TestProtocol", 1)
|
||||
assert False
|
||||
except Exception as e:
|
||||
assert isinstance(e, BprotoUnresolvedReferenceError)
|
||||
|
||||
|
||||
def test_protocol_factory_already_define_error():
|
||||
factory = FactoryProtocolDefition()
|
||||
factory.add_message(FactoryMessage().assemble("Message1", 42))
|
||||
try:
|
||||
factory.add_message(FactoryMessage().assemble("Message1", 42))
|
||||
factory.assemble("A", 1)
|
||||
assert False
|
||||
except Exception as e:
|
||||
assert isinstance(e, BprotoAlreadyDefinedError)
|
||||
|
||||
factory = FactoryProtocolDefition()
|
||||
factory.add_enum(FactoryEnumeration().assemble("Enum1"))
|
||||
try:
|
||||
factory.assemble("A", 1)
|
||||
factory.add_enum(FactoryEnumeration().assemble("Enum1"))
|
||||
assert False
|
||||
except Exception as e:
|
||||
assert isinstance(e, BprotoAlreadyDefinedError)
|
||||
|
||||
factory = FactoryProtocolDefition()
|
||||
factory.add_bitfield(FactoryBitfield().assemble("Bitfield1"))
|
||||
try:
|
||||
factory.add_bitfield(FactoryBitfield().assemble("Bitfield1"))
|
||||
factory.assemble("A", 1)
|
||||
assert False
|
||||
except Exception as e:
|
||||
assert isinstance(e, BprotoAlreadyDefinedError)
|
||||
1
test/data/static_content1.txt
Normal file
1
test/data/static_content1.txt
Normal file
@@ -0,0 +1 @@
|
||||
test1
|
||||
1
test/data/static_folder/static_content2.txt
Normal file
1
test/data/static_folder/static_content2.txt
Normal file
@@ -0,0 +1 @@
|
||||
test2
|
||||
1
test/data/templates/template.jinja2
Normal file
1
test/data/templates/template.jinja2
Normal file
@@ -0,0 +1 @@
|
||||
Hello {{ name }}!
|
||||
48
test/output/c/CMakeLists.txt
Normal file
48
test/output/c/CMakeLists.txt
Normal file
@@ -0,0 +1,48 @@
|
||||
cmake_minimum_required(VERSION 3.25)
|
||||
project(bproto_test)
|
||||
enable_testing()
|
||||
|
||||
include("../../backend_output/c/HCP.cmake")
|
||||
|
||||
# include CTest support
|
||||
include(CTest)
|
||||
set(CMAKE_BUILD_TYPE "Debug")
|
||||
|
||||
# Adding Unity test framework
|
||||
add_library(unity STATIC Unity/src/unity.c)
|
||||
target_include_directories(unity PUBLIC Unity/src)
|
||||
|
||||
# Normal test target
|
||||
add_executable(test_runner
|
||||
src/main.c
|
||||
src/test_messages.c
|
||||
)
|
||||
|
||||
target_link_libraries(test_runner PUBLIC
|
||||
bproto
|
||||
unity
|
||||
gcov
|
||||
)
|
||||
|
||||
target_include_directories(test_runner PUBLIC
|
||||
${CMAKE_CURRENT_LIST_DIR}/src
|
||||
)
|
||||
|
||||
target_compile_options(test_runner PRIVATE -fprofile-arcs -ftest-coverage -fPIC -O0 --coverage)
|
||||
target_compile_options(bproto PRIVATE -fprofile-arcs -ftest-coverage -fPIC -O0 --coverage)
|
||||
target_link_options(test_runner PRIVATE -fprofile-arcs -ftest-coverage -fPIC --coverage)
|
||||
target_link_options(bproto PRIVATE -fprofile-arcs -ftest-coverage -fPIC --coverage)
|
||||
|
||||
# Python Integration test build
|
||||
add_executable(test_integration_python
|
||||
src/test_python_integration.c
|
||||
)
|
||||
|
||||
target_link_libraries(test_integration_python PUBLIC
|
||||
bproto
|
||||
gcov
|
||||
)
|
||||
|
||||
target_include_directories(test_integration_python PUBLIC
|
||||
${CMAKE_CURRENT_LIST_DIR}/src
|
||||
)
|
||||
1
test/output/c/src/Unity
Submodule
1
test/output/c/src/Unity
Submodule
Submodule test/output/c/src/Unity added at 23e8edbd64
17
test/output/c/src/main.c
Normal file
17
test/output/c/src/main.c
Normal file
@@ -0,0 +1,17 @@
|
||||
#include "unity.h"
|
||||
#include "suit.h"
|
||||
|
||||
void setUp(void) {};
|
||||
void tearDown(void) {};
|
||||
|
||||
int main() {
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_encdec_generic_Error);
|
||||
RUN_TEST(test_encdec_generic_MotionUpdate);
|
||||
|
||||
RUN_TEST(test_encdec_generic_Error_invalid_id);
|
||||
RUN_TEST(test_encdec_generic_Error_invalid_size);
|
||||
RUN_TEST(test_encdec_generic_Error_large_size);
|
||||
RUN_TEST(test_encdec_generic_Error_invalid_crc);
|
||||
return UNITY_END();
|
||||
}
|
||||
9
test/output/c/src/suit.h
Normal file
9
test/output/c/src/suit.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
// Enc/Dec with generic data buffer
|
||||
void test_encdec_generic_MotionUpdate();
|
||||
void test_encdec_generic_Error();
|
||||
void test_encdec_generic_Error_invalid_id();
|
||||
void test_encdec_generic_Error_invalid_size();
|
||||
void test_encdec_generic_Error_large_size();
|
||||
void test_encdec_generic_Error_invalid_crc();
|
||||
224
test/output/c/src/test_messages.c
Normal file
224
test/output/c/src/test_messages.c
Normal file
@@ -0,0 +1,224 @@
|
||||
#include "suit.h"
|
||||
#include <stdint.h>
|
||||
#include "unity.h"
|
||||
#include "HCP_message.h"
|
||||
|
||||
void test_encdec_generic_Error() {
|
||||
uint8_t buffer_msg_data[MAX_MESSAGE_DATA_SIZE];
|
||||
|
||||
Hcp_message_MotionUpdate msg = {
|
||||
.speed = {3, 6, 9},
|
||||
.stearing = 0.5,
|
||||
.name = "This is a test",
|
||||
.heading = 'B',
|
||||
.enable = {true, false, true, false},
|
||||
.enables = {
|
||||
.a = false, .b = true, .c = false
|
||||
},
|
||||
.dangerLvl = HCP_ENUM_INLINE_MOTION_UPDATE_DANGER_LVL_VALUE_MELTDOWN
|
||||
};
|
||||
|
||||
size_t length = toBytes_Hcp_message_MotionUpdate(&msg, buffer_msg_data);
|
||||
|
||||
TEST_ASSERT_EQUAL(length, 58);
|
||||
TEST_ASSERT_EQUAL(buffer_msg_data[0], 0x01);
|
||||
|
||||
Hcp_message_MotionUpdate rx_msg;
|
||||
size_t parsed_length = 0;
|
||||
HCP_MSG_ID msg_id = 0xff;
|
||||
HCP_PARSER_RESULT result = fromBytes(&rx_msg, buffer_msg_data, length, &parsed_length, &msg_id);
|
||||
|
||||
TEST_ASSERT_EQUAL(result, HCP_PARSER_RESULT_OK);
|
||||
TEST_ASSERT_EQUAL(parsed_length, 58);
|
||||
TEST_ASSERT_EQUAL(msg_id, HCP_MESSAGE_MOTION_UPDATE_ID);
|
||||
|
||||
TEST_ASSERT_EQUAL(msg.stearing, rx_msg.stearing);
|
||||
TEST_ASSERT_EQUAL_STRING(msg.name, rx_msg.name);
|
||||
TEST_ASSERT_EQUAL(msg.heading, rx_msg.heading);
|
||||
TEST_ASSERT_EQUAL(msg.dangerLvl, rx_msg.dangerLvl);
|
||||
|
||||
TEST_ASSERT_EQUAL(msg.speed[0], rx_msg.speed[0]);
|
||||
TEST_ASSERT_EQUAL(msg.speed[1], rx_msg.speed[1]);
|
||||
TEST_ASSERT_EQUAL(msg.speed[2], rx_msg.speed[2]);
|
||||
|
||||
TEST_ASSERT_EQUAL(msg.enable[0], rx_msg.enable[0]);
|
||||
TEST_ASSERT_EQUAL(msg.enable[1], rx_msg.enable[1]);
|
||||
TEST_ASSERT_EQUAL(msg.enable[2], rx_msg.enable[2]);
|
||||
TEST_ASSERT_EQUAL(msg.enable[3], rx_msg.enable[3]);
|
||||
|
||||
TEST_ASSERT_EQUAL(msg.enables.a, rx_msg.enables.a);
|
||||
TEST_ASSERT_EQUAL(msg.enables.b, rx_msg.enables.b);
|
||||
TEST_ASSERT_EQUAL(msg.enables.c, rx_msg.enables.c);
|
||||
}
|
||||
|
||||
void test_encdec_generic_MotionUpdate() {
|
||||
uint8_t buffer_msg_data[MAX_MESSAGE_DATA_SIZE];
|
||||
|
||||
Hcp_message_Error msg = {
|
||||
.enables = {
|
||||
.aa = true,
|
||||
.asd = false,
|
||||
.b = true,
|
||||
.c = false,
|
||||
.test = true
|
||||
},
|
||||
.recoveryStatus = HCP_ENUM_INLINE_ERROR_RECOVERY_STATUS_VALUE_NO
|
||||
};
|
||||
|
||||
size_t length = toBytes_Hcp_message_Error(&msg, buffer_msg_data);
|
||||
|
||||
TEST_ASSERT_EQUAL(length, 5);
|
||||
TEST_ASSERT_EQUAL(buffer_msg_data[0], 0x02);
|
||||
|
||||
Hcp_message_Error rx_msg;
|
||||
size_t parsed_length = 0;
|
||||
HCP_MSG_ID msg_id = 0xff;
|
||||
HCP_PARSER_RESULT result = fromBytes(&rx_msg, buffer_msg_data, length, &parsed_length, &msg_id);
|
||||
|
||||
TEST_ASSERT_EQUAL(result, HCP_PARSER_RESULT_OK);
|
||||
TEST_ASSERT_EQUAL(parsed_length, 5);
|
||||
TEST_ASSERT_EQUAL(msg_id, HCP_MESSAGE_ERROR_ID);
|
||||
|
||||
TEST_ASSERT_EQUAL(rx_msg.enables.aa, msg.enables.aa);
|
||||
TEST_ASSERT_EQUAL(rx_msg.enables.asd, msg.enables.asd);
|
||||
TEST_ASSERT_EQUAL(rx_msg.enables.b, msg.enables.b);
|
||||
TEST_ASSERT_EQUAL(rx_msg.enables.c, msg.enables.c);
|
||||
TEST_ASSERT_EQUAL(rx_msg.enables.test, msg.enables.test);
|
||||
|
||||
TEST_ASSERT_EQUAL(rx_msg.recoveryStatus, msg.recoveryStatus);
|
||||
}
|
||||
|
||||
void test_encdec_generic_Error_invalid_id() {
|
||||
uint8_t buffer_msg_data[MAX_MESSAGE_DATA_SIZE];
|
||||
|
||||
Hcp_message_Error msg = {
|
||||
.enables = {
|
||||
.aa = true,
|
||||
.asd = false,
|
||||
.b = true,
|
||||
.c = false,
|
||||
.test = true
|
||||
},
|
||||
.recoveryStatus = HCP_ENUM_INLINE_ERROR_RECOVERY_STATUS_VALUE_NO
|
||||
};
|
||||
|
||||
size_t length = toBytes_Hcp_message_Error(&msg, buffer_msg_data);
|
||||
|
||||
TEST_ASSERT_EQUAL(length, 5);
|
||||
TEST_ASSERT_EQUAL(buffer_msg_data[0], 0x02);
|
||||
|
||||
// Mofiy data
|
||||
buffer_msg_data[0] = 0xf2;
|
||||
|
||||
Hcp_message_Error rx_msg;
|
||||
size_t parsed_length = 0;
|
||||
HCP_MSG_ID msg_id = 0xff;
|
||||
HCP_PARSER_RESULT result = fromBytes(&rx_msg, buffer_msg_data, length, &parsed_length, &msg_id);
|
||||
|
||||
TEST_ASSERT_EQUAL(parsed_length, 0);
|
||||
TEST_ASSERT_EQUAL(result, HCP_PARSER_RESULT_ERROR_INVALID_ID);
|
||||
TEST_ASSERT_EQUAL(msg_id, 0xff);
|
||||
}
|
||||
|
||||
void test_encdec_generic_Error_invalid_size() {
|
||||
uint8_t buffer_msg_data[MAX_MESSAGE_DATA_SIZE];
|
||||
|
||||
Hcp_message_Error msg = {
|
||||
.enables = {
|
||||
.aa = true,
|
||||
.asd = false,
|
||||
.b = true,
|
||||
.c = false,
|
||||
.test = true
|
||||
},
|
||||
.recoveryStatus = HCP_ENUM_INLINE_ERROR_RECOVERY_STATUS_VALUE_NO
|
||||
};
|
||||
|
||||
size_t length = toBytes_Hcp_message_Error(&msg, buffer_msg_data);
|
||||
|
||||
TEST_ASSERT_EQUAL(length, 5);
|
||||
TEST_ASSERT_EQUAL(buffer_msg_data[0], 0x02);
|
||||
|
||||
// Mofiy data
|
||||
length = 2;
|
||||
|
||||
Hcp_message_Error rx_msg;
|
||||
size_t parsed_length = 0;
|
||||
HCP_MSG_ID msg_id = 0xff;
|
||||
HCP_PARSER_RESULT result = fromBytes(&rx_msg, buffer_msg_data, length, &parsed_length, &msg_id);
|
||||
|
||||
TEST_ASSERT_EQUAL(parsed_length, 0);
|
||||
TEST_ASSERT_EQUAL(result, HCP_PARSER_RESULT_ERROR_INVALID_SIZE);
|
||||
TEST_ASSERT_EQUAL(msg_id, 0xff);
|
||||
}
|
||||
|
||||
void test_encdec_generic_Error_large_size() {
|
||||
uint8_t buffer_msg_data[MAX_MESSAGE_DATA_SIZE];
|
||||
|
||||
Hcp_message_Error msg = {
|
||||
.enables = {
|
||||
.aa = true,
|
||||
.asd = false,
|
||||
.b = true,
|
||||
.c = false,
|
||||
.test = true
|
||||
},
|
||||
.recoveryStatus = HCP_ENUM_INLINE_ERROR_RECOVERY_STATUS_VALUE_NO
|
||||
};
|
||||
|
||||
size_t length = toBytes_Hcp_message_Error(&msg, buffer_msg_data);
|
||||
|
||||
TEST_ASSERT_EQUAL(length, 5);
|
||||
TEST_ASSERT_EQUAL(buffer_msg_data[0], 0x02);
|
||||
|
||||
length += 5;
|
||||
|
||||
Hcp_message_Error rx_msg;
|
||||
size_t parsed_length = 0;
|
||||
HCP_MSG_ID msg_id = 0xff;
|
||||
HCP_PARSER_RESULT result = fromBytes(&rx_msg, buffer_msg_data, length, &parsed_length, &msg_id);
|
||||
|
||||
TEST_ASSERT_EQUAL(result, HCP_PARSER_RESULT_OK);
|
||||
TEST_ASSERT_EQUAL(parsed_length, 5);
|
||||
TEST_ASSERT_EQUAL(msg_id, HCP_MESSAGE_ERROR_ID);
|
||||
|
||||
TEST_ASSERT_EQUAL(rx_msg.enables.aa, msg.enables.aa);
|
||||
TEST_ASSERT_EQUAL(rx_msg.enables.asd, msg.enables.asd);
|
||||
TEST_ASSERT_EQUAL(rx_msg.enables.b, msg.enables.b);
|
||||
TEST_ASSERT_EQUAL(rx_msg.enables.c, msg.enables.c);
|
||||
TEST_ASSERT_EQUAL(rx_msg.enables.test, msg.enables.test);
|
||||
|
||||
TEST_ASSERT_EQUAL(rx_msg.recoveryStatus, msg.recoveryStatus);
|
||||
}
|
||||
|
||||
void test_encdec_generic_Error_invalid_crc() {
|
||||
uint8_t buffer_msg_data[MAX_MESSAGE_DATA_SIZE];
|
||||
|
||||
Hcp_message_Error msg = {
|
||||
.enables = {
|
||||
.aa = true,
|
||||
.asd = false,
|
||||
.b = true,
|
||||
.c = false,
|
||||
.test = true
|
||||
},
|
||||
.recoveryStatus = HCP_ENUM_INLINE_ERROR_RECOVERY_STATUS_VALUE_NO
|
||||
};
|
||||
|
||||
size_t length = toBytes_Hcp_message_Error(&msg, buffer_msg_data);
|
||||
|
||||
TEST_ASSERT_EQUAL(length, 5);
|
||||
TEST_ASSERT_EQUAL(buffer_msg_data[0], 0x02);
|
||||
|
||||
// Mofiy data
|
||||
buffer_msg_data[3] = 0xff;
|
||||
|
||||
Hcp_message_Error rx_msg;
|
||||
size_t parsed_length = 0;
|
||||
HCP_MSG_ID msg_id = 0xff;
|
||||
HCP_PARSER_RESULT result = fromBytes(&rx_msg, buffer_msg_data, length, &parsed_length, &msg_id);
|
||||
|
||||
TEST_ASSERT_EQUAL(parsed_length, 0);
|
||||
TEST_ASSERT_EQUAL(result, HCP_PARSER_RESULT_ERROR_INVALID_CRC);
|
||||
TEST_ASSERT_EQUAL(msg_id, 0xff);
|
||||
}
|
||||
84
test/output/c/src/test_python_integration.c
Normal file
84
test/output/c/src/test_python_integration.c
Normal file
@@ -0,0 +1,84 @@
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "HCP_message.h"
|
||||
#include "HCP_enum.h"
|
||||
|
||||
int main() {
|
||||
char hex_string[1024];
|
||||
uint8_t data[sizeof(hex_string)/2];
|
||||
|
||||
size_t bytesRead = read(STDIN_FILENO, hex_string, sizeof(hex_string));
|
||||
|
||||
for(int i=0; i<(bytesRead/2); i+=1) {
|
||||
sscanf(&hex_string[i*2], "%02hhx", &data[i]);
|
||||
}
|
||||
|
||||
// Decode the package
|
||||
char package_buffer[MAX_MESSAGE_STRUCT_SIZE];
|
||||
|
||||
size_t parsed_size = 0;
|
||||
HCP_MSG_ID msg_id;
|
||||
HCP_PARSER_RESULT result = fromBytes(
|
||||
(void *)(package_buffer), data, bytesRead/2, &parsed_size, &msg_id
|
||||
);
|
||||
|
||||
// Early exit if the decode failed
|
||||
if(result != HCP_PARSER_RESULT_OK) {
|
||||
if(result == HCP_PARSER_RESULT_ERROR_INVALID_ID) {
|
||||
printf("ID\n");
|
||||
return -1;
|
||||
} else if(result == HCP_PARSER_RESULT_ERROR_INVALID_CRC) {
|
||||
printf("CRC\n");
|
||||
return -1;
|
||||
} else if(result == HCP_PARSER_RESULT_ERROR_INVALID_SIZE) {
|
||||
printf("SIZE\n");
|
||||
return -1;
|
||||
} else {
|
||||
printf("???\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Modify and encode the package
|
||||
char send_buffer[MAX_MESSAGE_DATA_SIZE];
|
||||
size_t send_size;
|
||||
|
||||
switch (msg_id) {
|
||||
case HCP_MESSAGE_MOTION_UPDATE_ID: {
|
||||
Hcp_message_MotionUpdate *msg = (struct Hcp_message_MotionUpdate *)package_buffer;
|
||||
msg->speed[0] += 1;
|
||||
msg->speed[1] += 2;
|
||||
msg->speed[2] += 3;
|
||||
msg->stearing *= 0.5;
|
||||
msg->name[0] = 'A';
|
||||
msg->enables.a = !msg->enables.a;
|
||||
msg->enables.b = !msg->enables.b;
|
||||
msg->enables.c = !msg->enables.c;
|
||||
msg->dangerLvl = HCP_ENUM_INLINE_MOTION_UPDATE_DANGER_LVL_VALUE_MELTDOWN;
|
||||
send_size = toBytes_Hcp_message_MotionUpdate(msg, send_buffer);
|
||||
break;
|
||||
}
|
||||
case HCP_MESSAGE_ERROR_ID: {
|
||||
Hcp_message_Error *msg = (struct Hcp_message_Error *)package_buffer;
|
||||
msg->enables.b = !msg->enables.b;
|
||||
msg->enables.c = !msg->enables.c;
|
||||
send_size = toBytes_Hcp_message_Error(msg, send_buffer);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Print the encoded package
|
||||
printf("OK\n");
|
||||
for(int i=0; i<send_size; i++) {
|
||||
uint8_t byte = send_buffer[i];
|
||||
printf("%02x", byte);
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
162
test/output/test_c_integration.py
Normal file
162
test/output/test_c_integration.py
Normal file
@@ -0,0 +1,162 @@
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
sys.path.append('src/')
|
||||
sys.path.append('test/')
|
||||
sys.path.append('test/backend_output/python')
|
||||
|
||||
|
||||
def send_c_binary(data: bytes) -> tuple[int, str, bytes]:
|
||||
proc = subprocess.Popen(
|
||||
["build/test_integration_python"],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE
|
||||
)
|
||||
stdout, _ = proc.communicate(data.hex().encode() + b"\n")
|
||||
return_code = proc.wait()
|
||||
|
||||
lines = stdout.splitlines()
|
||||
|
||||
if return_code != 0:
|
||||
return (return_code, lines[0].decode("ascii"), b"")
|
||||
else:
|
||||
return (
|
||||
return_code,
|
||||
lines[0].decode("ascii"),
|
||||
bytes.fromhex(lines[1].decode())
|
||||
)
|
||||
|
||||
|
||||
def test_integration_c_encdec_motion_update():
|
||||
from backend_output.python.HCP_protocol_packets import HcpMessageMotionUpdate
|
||||
from backend_output.python.HCP_protocol_bitfield import HcpBitfieldEnables
|
||||
from backend_output.python.HCP_protocol_enum import HcpEnumInlineMotionUpdateDangerLvl
|
||||
|
||||
pkg = HcpMessageMotionUpdate(
|
||||
speed=[1, 2, 3],
|
||||
stearing=-0.1,
|
||||
name="This is a test",
|
||||
enable=[True, False, True, False],
|
||||
heading="S",
|
||||
enables=HcpBitfieldEnables(
|
||||
a=True, b=False, c=True
|
||||
),
|
||||
danger_lvl=HcpEnumInlineMotionUpdateDangerLvl.WARNING
|
||||
)
|
||||
|
||||
# Send to Integration Test Binary
|
||||
ret_code, _, data_incoming = send_c_binary(pkg.to_bytes())
|
||||
|
||||
assert ret_code == 0
|
||||
|
||||
# Decode the incoming data.
|
||||
pkg_incoming = HcpMessageMotionUpdate().from_bytes(data_incoming)
|
||||
|
||||
# Check that the incoming data is correctly modified.
|
||||
assert pkg.speed[0] + 1 == pkg_incoming.speed[0]
|
||||
assert pkg.speed[1] + 2 == pkg_incoming.speed[1]
|
||||
assert pkg.speed[2] + 3 == pkg_incoming.speed[2]
|
||||
|
||||
assert round(pkg.stearing * 0.5 * 1000) == round(pkg_incoming.stearing * 1000)
|
||||
|
||||
assert pkg_incoming.name[0] == "A"
|
||||
assert pkg.name[1:] == pkg_incoming.name[1:]
|
||||
|
||||
assert pkg.enables.a != pkg_incoming.enables.a
|
||||
assert pkg.enables.b != pkg_incoming.enables.b
|
||||
assert pkg.enables.c != pkg_incoming.enables.c
|
||||
|
||||
assert pkg.enable == pkg_incoming.enable
|
||||
|
||||
assert pkg_incoming.danger_lvl.value == HcpEnumInlineMotionUpdateDangerLvl.MELTDOWN.value
|
||||
|
||||
|
||||
def test_integration_c_encdec_error():
|
||||
from backend_output.python.HCP_protocol_packets import HcpMessageError
|
||||
from backend_output.python.HCP_protocol_bitfield import HcpBitfieldInlineErrorEnables
|
||||
from backend_output.python.HCP_protocol_enum import HcpEnumInlineErrorRecoveryStatus
|
||||
|
||||
pkg = HcpMessageError(
|
||||
recovery_status=HcpEnumInlineErrorRecoveryStatus.NO,
|
||||
enables=HcpBitfieldInlineErrorEnables(
|
||||
test=True, asd=False, b=False, c=True, aa=True
|
||||
),
|
||||
)
|
||||
|
||||
# Send to Integration Test Binary
|
||||
ret_code, _, data_incoming = send_c_binary(pkg.to_bytes())
|
||||
|
||||
assert ret_code == 0
|
||||
|
||||
# Decode the incoming data.
|
||||
pkg_incoming = HcpMessageError().from_bytes(data_incoming)
|
||||
|
||||
# Check that the incoming data is correctly modified.
|
||||
assert pkg_incoming.recovery_status.value == pkg.recovery_status.value
|
||||
assert pkg_incoming.enables.test == pkg.enables.test
|
||||
assert pkg_incoming.enables.asd == pkg.enables.asd
|
||||
assert pkg_incoming.enables.b != pkg.enables.b
|
||||
assert pkg_incoming.enables.c != pkg.enables.c
|
||||
assert pkg_incoming.enables.aa == pkg.enables.aa
|
||||
|
||||
|
||||
def test_integration_c_encdec_failure_size():
|
||||
from backend_output.python.HCP_protocol_packets import HcpMessageError
|
||||
from backend_output.python.HCP_protocol_bitfield import HcpBitfieldInlineErrorEnables
|
||||
from backend_output.python.HCP_protocol_enum import HcpEnumInlineErrorRecoveryStatus
|
||||
|
||||
pkg = HcpMessageError(
|
||||
recovery_status=HcpEnumInlineErrorRecoveryStatus.NO,
|
||||
enables=HcpBitfieldInlineErrorEnables(
|
||||
test=True, asd=False, b=False, c=True, aa=True
|
||||
),
|
||||
)
|
||||
|
||||
# Send to Integration Test Binary
|
||||
data = pkg.to_bytes()
|
||||
ret_code, msg, _ = send_c_binary(data[:4])
|
||||
|
||||
assert ret_code != 0
|
||||
assert msg == "SIZE"
|
||||
|
||||
|
||||
def test_integration_c_encdec_failure_crc():
|
||||
from backend_output.python.HCP_protocol_packets import HcpMessageError
|
||||
from backend_output.python.HCP_protocol_bitfield import HcpBitfieldInlineErrorEnables
|
||||
from backend_output.python.HCP_protocol_enum import HcpEnumInlineErrorRecoveryStatus
|
||||
|
||||
pkg = HcpMessageError(
|
||||
recovery_status=HcpEnumInlineErrorRecoveryStatus.NO,
|
||||
enables=HcpBitfieldInlineErrorEnables(
|
||||
test=True, asd=False, b=False, c=True, aa=True
|
||||
),
|
||||
)
|
||||
|
||||
# Send to Integration Test Binary
|
||||
data = pkg.to_bytes()
|
||||
data = data[:2] + b"\xaa" + data[3:]
|
||||
ret_code, msg, _ = send_c_binary(data)
|
||||
|
||||
assert ret_code != 0
|
||||
assert msg == "CRC"
|
||||
|
||||
|
||||
def test_integration_c_encdec_failure_id():
|
||||
from backend_output.python.HCP_protocol_packets import HcpMessageError
|
||||
from backend_output.python.HCP_protocol_bitfield import HcpBitfieldInlineErrorEnables
|
||||
from backend_output.python.HCP_protocol_enum import HcpEnumInlineErrorRecoveryStatus
|
||||
|
||||
pkg = HcpMessageError(
|
||||
recovery_status=HcpEnumInlineErrorRecoveryStatus.NO,
|
||||
enables=HcpBitfieldInlineErrorEnables(
|
||||
test=True, asd=False, b=False, c=True, aa=True
|
||||
),
|
||||
)
|
||||
|
||||
# Send to Integration Test Binary
|
||||
data = pkg.to_bytes()
|
||||
data = b"\xff" + data[1:]
|
||||
ret_code, msg, _ = send_c_binary(data)
|
||||
|
||||
assert ret_code != 0
|
||||
assert msg == "ID"
|
||||
63
test/output/test_crc.py
Normal file
63
test/output/test_crc.py
Normal file
@@ -0,0 +1,63 @@
|
||||
|
||||
def crc16(data: bytearray, offset: int, length: int):
|
||||
if data is None or offset < 0 or offset > len(data) - 1 and offset + length > len(data):
|
||||
return 0
|
||||
crc = 0xFFFF
|
||||
for i in range(0, length):
|
||||
crc ^= data[offset + i] << 8
|
||||
for j in range(0, 8):
|
||||
if (crc & 0x8000) > 0:
|
||||
crc = (crc << 1) ^ 0x1021
|
||||
else:
|
||||
crc = crc << 1
|
||||
return crc & 0xFFFF
|
||||
|
||||
|
||||
def test_crc_sum():
|
||||
# ignore: F401
|
||||
from backend_output.python.bproto_base import crc16_calc_inital, calc_crc_sum
|
||||
|
||||
protocol_hash = bytes.fromhex("4a07789a41ef64c38a7edb78574497d4")
|
||||
protocol_data = bytes.fromhex("c9850ff561ea6d27f3171e1c680a4607ea8fa5278ee96de5c8e0ade101bdeb4332d07cb2269a47c910ca2cfe95de9ceda0bb9ee05b0909dcaf68f6a605df7dd4")
|
||||
|
||||
inital = crc16_calc_inital(protocol_hash)
|
||||
checksum = calc_crc_sum(protocol_data, inital)
|
||||
ref_data = protocol_hash + protocol_data
|
||||
assert crc16(ref_data, 0, len(ref_data)) == checksum
|
||||
|
||||
protocol_hash = bytes.fromhex("4a07789a41ef64c38a7edb78574497d4")
|
||||
protocol_data = bytes.fromhex("8898ebbd28e82663b71a7bbf8bc5ec72872ca057a77e31c1c11deff5004e8985767acb75da9b2d1dea643b08ed0458477e4a")
|
||||
|
||||
inital = crc16_calc_inital(protocol_hash)
|
||||
checksum = calc_crc_sum(protocol_data, inital)
|
||||
ref_data = protocol_hash + protocol_data
|
||||
assert crc16(ref_data, 0, len(ref_data)) == checksum
|
||||
|
||||
protocol_hash = bytes.fromhex("4a07789a41ef64c38a7edb78574497d4")
|
||||
protocol_data = bytes.fromhex("ca321a923e202614482cb392d858175ffe9f117d38")
|
||||
|
||||
inital = crc16_calc_inital(protocol_hash)
|
||||
checksum = calc_crc_sum(protocol_data, inital)
|
||||
ref_data = protocol_hash + protocol_data
|
||||
assert crc16(ref_data, 0, len(ref_data)) == checksum
|
||||
|
||||
|
||||
def test_crc_sum_change():
|
||||
# ignore: F401
|
||||
from backend_output.python.bproto_base import crc16_calc_inital, calc_crc_sum
|
||||
|
||||
protocol_hash = bytes.fromhex("4a07789a41ef64c38a7edb78574497d4")
|
||||
protocol_data = bytes.fromhex("ca321a923e202614482cb392d858175ffe9f117d38")
|
||||
|
||||
inital = crc16_calc_inital(protocol_hash)
|
||||
checksum = calc_crc_sum(protocol_data + b"\xaa", inital)
|
||||
ref_data = protocol_hash + protocol_data
|
||||
assert crc16(ref_data, 0, len(ref_data)) != checksum
|
||||
|
||||
protocol_hash = bytes.fromhex("4a07789a41ef64c38a7edb78574497d4")
|
||||
protocol_data = bytes.fromhex("ca321a923e202614482cb392d858175ffe9f117d38")
|
||||
|
||||
inital = crc16_calc_inital(protocol_hash[-1:] + b"\xaa")
|
||||
checksum = calc_crc_sum(protocol_data, inital)
|
||||
ref_data = protocol_hash + protocol_data
|
||||
assert crc16(ref_data, 0, len(ref_data)) != checksum
|
||||
197
test/output/test_encdec.py
Normal file
197
test/output/test_encdec.py
Normal file
@@ -0,0 +1,197 @@
|
||||
import sys
|
||||
sys.path.append('src/')
|
||||
sys.path.append('test/')
|
||||
sys.path.append('test/backend_output/python')
|
||||
|
||||
|
||||
def test_HCP_encdec_MessageMotionUpdate():
|
||||
# ignore: F401
|
||||
from backend_output.python.HCP_protocol_packets import HcpMessageMotionUpdate
|
||||
from backend_output.python.HCP_protocol_bitfield import HcpBitfieldEnables
|
||||
from backend_output.python.HCP_protocol_enum import HcpEnumInlineMotionUpdateDangerLvl
|
||||
|
||||
src_msg = HcpMessageMotionUpdate(
|
||||
speed=[2, 3, 4],
|
||||
stearing=0.3,
|
||||
name="Hello HCP!",
|
||||
enable=[True, False, True, False],
|
||||
heading="N",
|
||||
enables=HcpBitfieldEnables(
|
||||
a=True, b=False, c=True
|
||||
),
|
||||
danger_lvl=HcpEnumInlineMotionUpdateDangerLvl.ERROR
|
||||
)
|
||||
|
||||
data = src_msg.to_bytes()
|
||||
|
||||
isinstance(data, bytes)
|
||||
|
||||
dest_msg = HcpMessageMotionUpdate().from_bytes(data)
|
||||
|
||||
assert isinstance(dest_msg.speed, list)
|
||||
assert isinstance(dest_msg.speed[0], int)
|
||||
assert src_msg.speed == dest_msg.speed
|
||||
|
||||
assert isinstance(dest_msg.stearing, float)
|
||||
assert round(src_msg.stearing * 1000) == round(dest_msg.stearing * 1000)
|
||||
|
||||
assert isinstance(dest_msg.name, str)
|
||||
assert src_msg.name == dest_msg.name
|
||||
|
||||
assert isinstance(dest_msg.enable, list)
|
||||
assert isinstance(dest_msg.enable[0], bool)
|
||||
assert src_msg.enable == dest_msg.enable
|
||||
|
||||
assert isinstance(dest_msg.heading, str)
|
||||
assert src_msg.heading == dest_msg.heading
|
||||
|
||||
assert isinstance(dest_msg.enables.a, bool)
|
||||
assert isinstance(dest_msg.enables.b, bool)
|
||||
assert isinstance(dest_msg.enables.c, bool)
|
||||
assert src_msg.enables.a == dest_msg.enables.a
|
||||
assert src_msg.enables.b == dest_msg.enables.b
|
||||
assert src_msg.enables.c == dest_msg.enables.c
|
||||
|
||||
assert isinstance(src_msg.danger_lvl, HcpEnumInlineMotionUpdateDangerLvl)
|
||||
assert src_msg.danger_lvl.value == dest_msg.danger_lvl.value
|
||||
|
||||
|
||||
def test_HCP_encdec_MessageMotionUpdate_failure():
|
||||
# ignore: F401
|
||||
from backend_output.python.HCP_protocol_packets import HcpMessageMotionUpdate
|
||||
from backend_output.python.HCP_protocol_bitfield import HcpBitfieldEnables
|
||||
from backend_output.python.HCP_protocol_enum import HcpEnumInlineMotionUpdateDangerLvl
|
||||
from backend_output.python.bproto_error import bproto_PackageErrorInvalidMessageID, bproto_PackageErrorInvalidSize
|
||||
|
||||
src_msg = HcpMessageMotionUpdate(
|
||||
speed=[2, 3, 4],
|
||||
stearing=0.3,
|
||||
name="Hello HCP!",
|
||||
enable=[True, False, True, False],
|
||||
heading="N",
|
||||
enables=HcpBitfieldEnables(
|
||||
a=True, b=False, c=True
|
||||
),
|
||||
danger_lvl=HcpEnumInlineMotionUpdateDangerLvl.ERROR
|
||||
)
|
||||
|
||||
data = src_msg.to_bytes()
|
||||
|
||||
try:
|
||||
HcpMessageMotionUpdate().from_bytes(b"\xaa" + data[1:])
|
||||
except Exception as e:
|
||||
isinstance(e, bproto_PackageErrorInvalidMessageID)
|
||||
|
||||
data = src_msg.to_bytes()
|
||||
|
||||
try:
|
||||
HcpMessageMotionUpdate().from_bytes(data[:10])
|
||||
except Exception as e:
|
||||
isinstance(e, bproto_PackageErrorInvalidSize)
|
||||
|
||||
|
||||
def test_HCP_encdec_HcpMessageError():
|
||||
# ignore: F401
|
||||
from backend_output.python.HCP_protocol_packets import HcpMessageError
|
||||
from backend_output.python.HCP_protocol_bitfield import HcpBitfieldInlineErrorEnables
|
||||
from backend_output.python.HCP_protocol_enum import HcpEnumInlineErrorRecoveryStatus
|
||||
|
||||
src_msg = HcpMessageError(
|
||||
recovery_status=HcpEnumInlineErrorRecoveryStatus.YES,
|
||||
enables=HcpBitfieldInlineErrorEnables(
|
||||
test=True, asd=False, b=True, c=False, aa=True
|
||||
)
|
||||
)
|
||||
|
||||
data = src_msg.to_bytes()
|
||||
|
||||
isinstance(data, bytes)
|
||||
|
||||
dest_msg = HcpMessageError().from_bytes(data)
|
||||
|
||||
assert src_msg.recovery_status.value == dest_msg.recovery_status.value
|
||||
|
||||
assert src_msg.enables.test == dest_msg.enables.test
|
||||
assert src_msg.enables.asd == dest_msg.enables.asd
|
||||
assert src_msg.enables.b == dest_msg.enables.b
|
||||
assert src_msg.enables.c == dest_msg.enables.c
|
||||
assert src_msg.enables.aa == dest_msg.enables.aa
|
||||
|
||||
|
||||
def test_HCP_encdec_HcpMessageError_failure():
|
||||
# ignore: F401
|
||||
from backend_output.python.HCP_protocol_packets import HcpMessageError
|
||||
from backend_output.python.HCP_protocol_bitfield import HcpBitfieldInlineErrorEnables
|
||||
from backend_output.python.HCP_protocol_enum import HcpEnumInlineErrorRecoveryStatus
|
||||
from backend_output.python.bproto_error import bproto_PackageErrorInvalidMessageID, bproto_PackageErrorInvalidSize
|
||||
|
||||
src_msg = HcpMessageError(
|
||||
recovery_status=HcpEnumInlineErrorRecoveryStatus.YES,
|
||||
enables=HcpBitfieldInlineErrorEnables(
|
||||
test=True, asd=False, b=True, c=False, aa=True
|
||||
)
|
||||
)
|
||||
|
||||
data = src_msg.to_bytes()
|
||||
|
||||
try:
|
||||
HcpMessageError().from_bytes(b"\xaa" + data[1:])
|
||||
except Exception as e:
|
||||
isinstance(e, bproto_PackageErrorInvalidMessageID)
|
||||
|
||||
data = src_msg.to_bytes()
|
||||
|
||||
try:
|
||||
HcpMessageError().from_bytes(data[:3])
|
||||
except Exception as e:
|
||||
isinstance(e, bproto_PackageErrorInvalidSize)
|
||||
|
||||
|
||||
def test_generic_parse_package():
|
||||
# ignore: F401
|
||||
from backend_output.python.HCP_protocol_packets import HcpMessageError, HcpMessageMotionUpdate, parse_package
|
||||
from backend_output.python.HCP_protocol_bitfield import HcpBitfieldInlineErrorEnables, HcpBitfieldEnables
|
||||
from backend_output.python.HCP_protocol_enum import HcpEnumInlineErrorRecoveryStatus, HcpEnumInlineMotionUpdateDangerLvl
|
||||
from backend_output.python.bproto_error import bproto_PackageErrorInvalidMessageID, bproto_PackageErrorInvalidSize
|
||||
|
||||
src_msg1 = HcpMessageError(
|
||||
recovery_status=HcpEnumInlineErrorRecoveryStatus.YES,
|
||||
enables=HcpBitfieldInlineErrorEnables(
|
||||
test=True, asd=False, b=True, c=False, aa=True
|
||||
)
|
||||
)
|
||||
|
||||
src_msg2 = HcpMessageMotionUpdate(
|
||||
speed=[2, 3, 4],
|
||||
stearing=0.3,
|
||||
name="Hello HCP!",
|
||||
enable=[True, False, True, False],
|
||||
heading="N",
|
||||
enables=HcpBitfieldEnables(
|
||||
a=True, b=False, c=True
|
||||
),
|
||||
danger_lvl=HcpEnumInlineMotionUpdateDangerLvl.ERROR
|
||||
)
|
||||
|
||||
dest_msg1, parsed_size = parse_package(src_msg1.to_bytes())
|
||||
assert parsed_size == 5
|
||||
assert isinstance(dest_msg1, HcpMessageError)
|
||||
|
||||
dest_msg2, parsed_size = parse_package(src_msg2.to_bytes())
|
||||
assert parsed_size == 58
|
||||
assert isinstance(dest_msg2, HcpMessageMotionUpdate)
|
||||
|
||||
try:
|
||||
parse_package(b"\xaa" + src_msg2.to_bytes()[1:])
|
||||
except Exception as e:
|
||||
isinstance(e, bproto_PackageErrorInvalidMessageID)
|
||||
|
||||
try:
|
||||
parse_package(src_msg1.to_bytes()[:2])
|
||||
except Exception as e:
|
||||
isinstance(e, bproto_PackageErrorInvalidSize)
|
||||
|
||||
try:
|
||||
parse_package(src_msg2.to_bytes()[:2])
|
||||
except Exception as e:
|
||||
isinstance(e, bproto_PackageErrorInvalidSize)
|
||||
82
test/reference/python/HCP_protocol_bitfield.py
Normal file
82
test/reference/python/HCP_protocol_bitfield.py
Normal file
@@ -0,0 +1,82 @@
|
||||
from bproto_base import bproto_Bitfield
|
||||
|
||||
|
||||
class HcpBitfieldEnables(bproto_Bitfield):
|
||||
BYTE_SIZE = 1
|
||||
|
||||
def __init__(self,
|
||||
a=False,
|
||||
b=False,
|
||||
c=False,
|
||||
):
|
||||
self.a = a
|
||||
self.b = b
|
||||
self.c = c
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
res = 0
|
||||
res |= (1 << 0) if self.a else 0
|
||||
res |= (1 << 1) if self.b else 0
|
||||
res |= (1 << 3) if self.c else 0
|
||||
return int.to_bytes(res, self.BYTE_SIZE, 'little')
|
||||
|
||||
def from_bytes(self, data: bytes):
|
||||
bits = int.from_bytes(data[:self.BYTE_SIZE], 'little')
|
||||
self.a = (bits & (1 << 0)) != 0
|
||||
self.b = (bits & (1 << 1)) != 0
|
||||
self.c = (bits & (1 << 3)) != 0
|
||||
return self
|
||||
|
||||
def __repr__(self) -> str:
|
||||
value_str = ", ".join([
|
||||
"a=" + str(self.a),
|
||||
"b=" + str(self.b),
|
||||
"c=" + str(self.c),
|
||||
])
|
||||
return "HcpBitfieldEnables(" + value_str + ")"
|
||||
|
||||
|
||||
class HcpBitfieldInlineErrorEnables(bproto_Bitfield):
|
||||
BYTE_SIZE = 1
|
||||
|
||||
def __init__(self,
|
||||
test=False,
|
||||
asd=False,
|
||||
b=False,
|
||||
c=False,
|
||||
aa=False,
|
||||
):
|
||||
self.test = test
|
||||
self.asd = asd
|
||||
self.b = b
|
||||
self.c = c
|
||||
self.aa = aa
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
res = 0
|
||||
res |= (1 << 0) if self.test else 0
|
||||
res |= (1 << 1) if self.asd else 0
|
||||
res |= (1 << 2) if self.b else 0
|
||||
res |= (1 << 3) if self.c else 0
|
||||
res |= (1 << 7) if self.aa else 0
|
||||
return int.to_bytes(res, self.BYTE_SIZE, 'little')
|
||||
|
||||
def from_bytes(self, data: bytes):
|
||||
bits = int.from_bytes(data[:self.BYTE_SIZE], 'little')
|
||||
self.test = (bits & (1 << 0)) != 0
|
||||
self.asd = (bits & (1 << 1)) != 0
|
||||
self.b = (bits & (1 << 2)) != 0
|
||||
self.c = (bits & (1 << 3)) != 0
|
||||
self.aa = (bits & (1 << 7)) != 0
|
||||
return self
|
||||
|
||||
def __repr__(self) -> str:
|
||||
value_str = ", ".join([
|
||||
"test=" + str(self.test),
|
||||
"asd=" + str(self.asd),
|
||||
"b=" + str(self.b),
|
||||
"c=" + str(self.c),
|
||||
"aa=" + str(self.aa),
|
||||
])
|
||||
return "HcpBitfieldInlineErrorEnables(" + value_str + ")"
|
||||
|
||||
19
test/reference/python/HCP_protocol_enum.py
Normal file
19
test/reference/python/HCP_protocol_enum.py
Normal file
@@ -0,0 +1,19 @@
|
||||
import enum
|
||||
|
||||
|
||||
class HcpEnumErrorCodes(enum.Enum):
|
||||
MOTOR1_DISCONNECTED = 1
|
||||
MOTOR2_DISCONNECTED = 2
|
||||
MOTORS_DISCONNECTED = 3
|
||||
INPUT_BUFFER_OVERFLOW = 4
|
||||
|
||||
|
||||
class HcpEnumInlineMotionUpdateDangerLvl(enum.Enum):
|
||||
WARNING = 0
|
||||
ERROR = 1
|
||||
MELTDOWN = 2
|
||||
|
||||
|
||||
class HcpEnumInlineErrorRecoveryStatus(enum.Enum):
|
||||
YES = 0
|
||||
NO = 1
|
||||
12
test/reference/python/HCP_protocol_message_ids.py
Normal file
12
test/reference/python/HCP_protocol_message_ids.py
Normal file
@@ -0,0 +1,12 @@
|
||||
import enum
|
||||
|
||||
|
||||
class MessageIds_HCP(enum.Enum):
|
||||
MSG_ID_HCP_MESSAGE_MOTION_UPDATE = 1
|
||||
MSG_ID_HCP_MESSAGE_ERROR = 2
|
||||
|
||||
|
||||
MESSAGE_SIZE_MAP_HCP = {
|
||||
MessageIds_HCP.MSG_ID_HCP_MESSAGE_MOTION_UPDATE: 58,
|
||||
MessageIds_HCP.MSG_ID_HCP_MESSAGE_ERROR: 5,
|
||||
}
|
||||
134
test/reference/python/HCP_protocol_packets.py
Normal file
134
test/reference/python/HCP_protocol_packets.py
Normal file
@@ -0,0 +1,134 @@
|
||||
from bproto_base import bproto_Package, calc_crc_sum
|
||||
from bproto_error import bproto_Error, bproto_PackageErrorInvalidSize, bproto_PackageErrorInvalidMessageID, bproto_PackageErrorInvalidCRC
|
||||
|
||||
from HCP_protocol_enum import HcpEnumErrorCodes, HcpEnumInlineMotionUpdateDangerLvl, HcpEnumInlineErrorRecoveryStatus
|
||||
from HCP_protocol_bitfield import HcpBitfieldEnables, HcpBitfieldInlineErrorEnables
|
||||
from HCP_protocol_message_ids import MessageIds_HCP, MESSAGE_SIZE_MAP_HCP
|
||||
|
||||
from typing import Annotated, List
|
||||
import struct
|
||||
|
||||
|
||||
class HcpMessageMotionUpdate(bproto_Package):
|
||||
ID = MessageIds_HCP.MSG_ID_HCP_MESSAGE_MOTION_UPDATE
|
||||
SIZE = 58
|
||||
|
||||
speed: Annotated[List[int], 3]
|
||||
stearing: float
|
||||
name: str
|
||||
enable: Annotated[List[bool], 4]
|
||||
heading: str
|
||||
enables: HcpBitfieldEnables
|
||||
danger_lvl: HcpEnumInlineMotionUpdateDangerLvl
|
||||
|
||||
def __init__(self,
|
||||
speed: Annotated[List[int], 3] = 0,
|
||||
stearing: float = 0.0,
|
||||
name: str = '',
|
||||
enable: Annotated[List[bool], 4] = False,
|
||||
heading: str = '',
|
||||
enables: HcpBitfieldEnables = HcpBitfieldEnables(),
|
||||
danger_lvl: HcpEnumInlineMotionUpdateDangerLvl = HcpEnumInlineMotionUpdateDangerLvl.WARNING,
|
||||
):
|
||||
self.speed = speed
|
||||
self.stearing = stearing
|
||||
self.name = name
|
||||
self.enable = enable
|
||||
self.heading = heading
|
||||
self.enables = enables
|
||||
self.danger_lvl = danger_lvl
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
res = struct.pack(">B", self.ID.value)
|
||||
res += struct.pack('>iii', self.speed[0], self.speed[1], self.speed[2])
|
||||
res += struct.pack('>f', self.stearing)
|
||||
res += self.name.encode('ascii')[:32].ljust(32, b'\x00')
|
||||
res += struct.pack('>BBBB', self.enable[0], self.enable[1], self.enable[2], self.enable[3])
|
||||
res += self.heading.encode('ascii')[:1].ljust(1, b'\x00')
|
||||
res += self.enables.to_bytes()
|
||||
res += struct.pack('>B', self.danger_lvl.value)
|
||||
res += calc_crc_sum(res)
|
||||
return res
|
||||
|
||||
def from_bytes(self, data: bytes):
|
||||
# msg_id = struct.unpack('>B', data[:1])
|
||||
data = data[1:]
|
||||
self.speed = list(struct.unpack('>iii', data[:12]))
|
||||
data = data[12:]
|
||||
self.stearing = struct.unpack('>f', data[:4])[0]
|
||||
data = data[4:]
|
||||
self.name = data[:32].decode('ascii').strip('\x00')
|
||||
data = data[32:]
|
||||
self.enable = [bool(i) for i in struct.unpack('>BBBB', data[:4])]
|
||||
data = data[4:]
|
||||
self.heading = data[:1].decode('ascii').strip('\x00')
|
||||
data = data[1:]
|
||||
self.enables = HcpBitfieldEnables().from_bytes(data[:1])
|
||||
data = data[1:]
|
||||
self.danger_lvl = HcpEnumInlineMotionUpdateDangerLvl(struct.unpack('>B', data[:1])[0])
|
||||
data = data[1:]
|
||||
# crc = data[:2]
|
||||
return self
|
||||
|
||||
|
||||
class HcpMessageError(bproto_Package):
|
||||
ID = MessageIds_HCP.MSG_ID_HCP_MESSAGE_ERROR
|
||||
SIZE = 5
|
||||
|
||||
recovery_status: HcpEnumInlineErrorRecoveryStatus
|
||||
enables: HcpBitfieldInlineErrorEnables
|
||||
|
||||
def __init__(self,
|
||||
recovery_status: HcpEnumInlineErrorRecoveryStatus = HcpEnumInlineErrorRecoveryStatus.YES,
|
||||
enables: HcpBitfieldInlineErrorEnables = HcpBitfieldInlineErrorEnables(),
|
||||
):
|
||||
self.recovery_status = recovery_status
|
||||
self.enables = enables
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
res = struct.pack(">B", self.ID.value)
|
||||
res += struct.pack('>B', self.recovery_status.value)
|
||||
res += self.enables.to_bytes()
|
||||
res += calc_crc_sum(res)
|
||||
return res
|
||||
|
||||
def from_bytes(self, data: bytes):
|
||||
# msg_id = struct.unpack('>B', data[:1])
|
||||
data = data[1:]
|
||||
self.recovery_status = HcpEnumInlineErrorRecoveryStatus(struct.unpack('>B', data[:1])[0])
|
||||
data = data[1:]
|
||||
self.enables = HcpBitfieldInlineErrorEnables().from_bytes(data[:1])
|
||||
data = data[1:]
|
||||
# crc = data[:2]
|
||||
return self
|
||||
|
||||
|
||||
def parse_package(data: bytes) -> tuple[bproto_Package, int]:
|
||||
if len(data) < 1:
|
||||
raise bproto_PackageErrorInvalidSize("Package has no data")
|
||||
|
||||
msg_id = struct.unpack('>B', data[:1])
|
||||
|
||||
if msg_id not in MessageIds_HCP:
|
||||
raise bproto_PackageErrorInvalidMessageID(f"Message ID '{msg_id}' is invaild")
|
||||
|
||||
msg_id = MessageIds_HCP(msg_id)
|
||||
expected_size = MESSAGE_SIZE_MAP_HCP.get(msg_id) or 0
|
||||
|
||||
if expected_size > len(data):
|
||||
raise bproto_PackageErrorInvalidSize(f"Package is to short for '{msg_id}' ({expected_size} > {len(data)})")
|
||||
|
||||
pkg_data = data[:expected_size]
|
||||
|
||||
crc_data = pkg_data[-2:]
|
||||
|
||||
if calc_crc_sum(pkg_data[:-2]) != crc_data:
|
||||
raise bproto_PackageErrorInvalidCRC(f"CRC {crc_data.hex()} did not match calculated")
|
||||
|
||||
match(msg_id):
|
||||
case MessageIds_HCP.MSG_ID_HCP_MESSAGE_MOTION_UPDATE:
|
||||
return HcpMessageMotionUpdate().from_bytes(pkg_data), expected_size
|
||||
case MessageIds_HCP.MSG_ID_HCP_MESSAGE_ERROR:
|
||||
return HcpMessageError().from_bytes(pkg_data), expected_size
|
||||
case _:
|
||||
raise bproto_Error("Message ID could not be interpreted; this should not have happen; its a developer error! Create a issue")
|
||||
49
test/reference/txt/protocolSummary.txt
Normal file
49
test/reference/txt/protocolSummary.txt
Normal file
@@ -0,0 +1,49 @@
|
||||
Name: Hcp
|
||||
Version: 0
|
||||
|
||||
Enums:
|
||||
ErrorCodes:
|
||||
- MOTOR1_DISCONNECTED = 1
|
||||
- MOTOR2_DISCONNECTED = 2
|
||||
- MOTORS_DISCONNECTED = 3
|
||||
- INPUT_BUFFER_OVERFLOW = 4
|
||||
|
||||
InlineMotionUpdateDangerLvl:
|
||||
- WARNING = 0
|
||||
- ERROR = 1
|
||||
- MELTDOWN = 2
|
||||
|
||||
InlineErrorRecoveryStatus:
|
||||
- YES = 0
|
||||
- NO = 1
|
||||
|
||||
Bitfield:
|
||||
Enables:
|
||||
- a: 0
|
||||
- b: 1
|
||||
- c: 3
|
||||
|
||||
InlineErrorEnables:
|
||||
- test: 0
|
||||
- asd: 1
|
||||
- b: 2
|
||||
- c: 3
|
||||
- aa: 7
|
||||
|
||||
Messages:
|
||||
[1] MotionUpdate:
|
||||
Size: 55 bytes
|
||||
Fields:
|
||||
- (12) speed: int32[3]
|
||||
- (4) stearing: float32[1]
|
||||
- (32) name: string[32]
|
||||
- (4) enable: bool[4]
|
||||
- (1) heading: char[1]
|
||||
- (1) enables: bitfield[1] -> Enables
|
||||
- (1) dangerLvl: enum[1] -> InlineMotionUpdateDangerLvl
|
||||
|
||||
[2] Error:
|
||||
Size: 2 bytes
|
||||
Fields:
|
||||
- (1) recoveryStatus: enum[1] -> InlineErrorRecoveryStatus
|
||||
- (1) enables: bitfield[1] -> InlineErrorEnables
|
||||
4
test/runner.py
Normal file
4
test/runner.py
Normal file
@@ -0,0 +1,4 @@
|
||||
import pytest
|
||||
|
||||
if __name__ == '__main__':
|
||||
pytest.main()
|
||||
39
test/test_protocol.bproto
Normal file
39
test/test_protocol.bproto
Normal file
@@ -0,0 +1,39 @@
|
||||
protocol HCP version 0
|
||||
|
||||
enum error_codes {
|
||||
MOTOR1_disconnected = 1,
|
||||
MOTOR2_disconnected = 2,
|
||||
MOTORS_disconnected = 3,
|
||||
input_buffer_overflow = 4,
|
||||
}
|
||||
|
||||
bits enables {
|
||||
a, b, c: 3
|
||||
}
|
||||
|
||||
message [1] MotionUpdate {
|
||||
[0] speed : int32[3],
|
||||
[1] stearing : float32,
|
||||
[3] enable : bool[4],
|
||||
[4] heading : char,
|
||||
[2] name : string[32],
|
||||
[5] enables : bits enables,
|
||||
[6] dangerLvl: enum {
|
||||
WARNING,
|
||||
ERROR, MELTDOWN
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
message [2] Error {
|
||||
[0] recoveryStatus: enum {
|
||||
YES, NO
|
||||
},
|
||||
[1] enables: bits {
|
||||
test : 0,
|
||||
asd : 1,
|
||||
b,
|
||||
c,
|
||||
aa: 7
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user