-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #88 from dlech/protocol-tests
Protocol tests
- Loading branch information
Showing
7 changed files
with
374 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,6 @@ cache: pip | |
|
||
python: | ||
- 2.7 | ||
- 3.4 | ||
- 3.5 | ||
- 3.6 | ||
- 3.7 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,360 @@ | ||
import struct | ||
|
||
from twisted.trial import unittest | ||
|
||
from txdbus import protocol | ||
|
||
|
||
class TestTransport: | ||
def __init__(self): | ||
self.data = b'' | ||
self.fds = [] | ||
|
||
def sendFileDescriptor(self, fd): | ||
# twisted queues fds to be sent later | ||
self.fds.append(fd) | ||
|
||
def write(self, data): | ||
self.data += data | ||
return len(data) | ||
|
||
|
||
class TestProtocol(protocol.BasicDBusProtocol): | ||
def __init__(self): | ||
self._receivedFDs = [] | ||
self._toBeSentFDs = [] | ||
self.transport = TestTransport() | ||
self.mcalls = [] | ||
self.mrets = [] | ||
self.merrs = [] | ||
self.msigs = [] | ||
|
||
def methodCallReceived(self, mcall): | ||
self.mcalls.append(mcall) | ||
|
||
def methodReturnReceived(self, mret): | ||
self.mrets.append(mret) | ||
|
||
def errorReceived(self, merr): | ||
self.merrs.append(merr) | ||
|
||
def signalReceived(self, msig): | ||
self.msigs.append(msig) | ||
|
||
|
||
class Endian: | ||
LITTLE = ord('l') | ||
BIG = ord('B') | ||
|
||
|
||
class MsgType: | ||
INVALID = 0 | ||
METHOD_CALL = 1 | ||
METHOD_RETURN = 2 | ||
ERROR = 3 | ||
SIGNAL = 4 | ||
|
||
|
||
class Flags: | ||
NONE = 0x0 | ||
NO_REPLY_EXPECTED = 0x1 | ||
NO_AUTO_START = 0x2 | ||
ALLOW_INTERACTIVE_AUTHORIZATION = 0x4 | ||
|
||
|
||
class Version: | ||
ONE = 1 | ||
|
||
|
||
class DataType: | ||
INVALID = 0 | ||
BYTE = ord('y') | ||
BOOLEAN = ord('b') | ||
INT16 = ord('n') | ||
UINT16 = ord('q') | ||
INT32 = ord('i') | ||
UINT32 = ord('u') | ||
INT64 = ord('x') | ||
UINT64 = ord('t') | ||
DOUBLE = ord('d') | ||
STRING = ord('s') | ||
OBJECT_PATH = ord('o') | ||
SIGNATURE = ord('g') | ||
ARRAY = ord('a') | ||
STRUCT_BEGIN = ord('(') | ||
STRUCT_END = ord(')') | ||
VARIANT = ord('v') | ||
DICT_ENTRY = ord('e') | ||
UNIX_FD = ord('h') | ||
|
||
|
||
class HeaderField: | ||
INVALID = 0 | ||
PATH = 1 | ||
INTERFACE = 2 | ||
MEMBER = 3 | ||
ERROR_NAME = 4 | ||
REPLY_SERIAL = 5 | ||
DESTINATION = 6 | ||
SENDER = 7 | ||
SIGNATURE = 8 | ||
UNIX_FDS = 9 | ||
|
||
|
||
def encode_signature(*data_types): | ||
data = bytearray(data_types) | ||
data.append(0) | ||
data.insert(0, len(data_types)) | ||
return data | ||
|
||
|
||
def align(align, offset): | ||
count = offset % align | ||
if not count: | ||
return b'' | ||
return b'\x00' * (align - count) | ||
|
||
|
||
def create_basic_method(path, member): | ||
"""Creates raw D-Bus message` with `path` for method `member having no | ||
parameters | ||
""" | ||
serial = 1 | ||
path = path.encode() | ||
member = member.encode() | ||
|
||
headers = bytearray() | ||
headers.append(HeaderField.PATH) | ||
headers += encode_signature(DataType.OBJECT_PATH) | ||
headers += align(4, len(headers)) | ||
headers += struct.pack('<I', len(path)) | ||
headers += path | ||
headers.append(0) | ||
headers += align(8, len(headers)) | ||
|
||
headers.append(HeaderField.MEMBER) | ||
headers += encode_signature(DataType.STRING) | ||
headers += align(4, len(headers)) | ||
headers += struct.pack('<I', len(member)) | ||
headers += member | ||
headers.append(0) | ||
|
||
body = bytearray() | ||
|
||
data = bytearray() | ||
data.append(Endian.LITTLE) | ||
data.append(MsgType.METHOD_CALL) | ||
data.append(Flags.NONE) | ||
data.append(Version.ONE) | ||
data += struct.pack('<I', len(body)) | ||
data += struct.pack('<I', serial) | ||
data += struct.pack('<I', len(headers)) | ||
data += headers | ||
data += align(8, len(data)) | ||
data += body | ||
|
||
return bytes(data) | ||
|
||
|
||
def create_one_unix_fd_method(path, member, fd_index): | ||
"""Creates raw D-Bus message` with `path` for method `member having one | ||
UNIX file descriptor input parameter | ||
""" | ||
|
||
serial = 1 | ||
path = path.encode() | ||
member = member.encode() | ||
signature = bytearray([DataType.UNIX_FD]) | ||
num_fds = 1 | ||
|
||
headers = bytearray() | ||
headers.append(HeaderField.PATH) | ||
headers += encode_signature(DataType.OBJECT_PATH) | ||
headers += align(4, len(headers)) | ||
headers += struct.pack('<I', len(path)) | ||
headers += path | ||
headers.append(0) | ||
headers += align(8, len(headers)) | ||
|
||
headers.append(HeaderField.MEMBER) | ||
headers += encode_signature(DataType.STRING) | ||
headers += align(4, len(headers)) | ||
headers += struct.pack('<I', len(member)) | ||
headers += member | ||
headers.append(0) | ||
headers += align(8, len(headers)) | ||
|
||
headers.append(HeaderField.SIGNATURE) | ||
headers += encode_signature(DataType.SIGNATURE) | ||
headers += struct.pack('B', len(signature)) | ||
headers += signature | ||
headers.append(0) | ||
headers += align(8, len(headers)) | ||
|
||
headers.append(HeaderField.UNIX_FDS) | ||
headers += encode_signature(DataType.UINT32) | ||
headers += align(4, len(headers)) | ||
headers += struct.pack('<I', num_fds) | ||
|
||
body = bytearray() | ||
body += struct.pack('<I', fd_index) | ||
|
||
data = bytearray() | ||
data.append(Endian.LITTLE) | ||
data.append(MsgType.METHOD_CALL) | ||
data.append(Flags.NONE) | ||
data.append(Version.ONE) | ||
data += struct.pack('<I', len(body)) | ||
data += struct.pack('<I', serial) | ||
data += struct.pack('<I', len(headers)) | ||
data += headers | ||
data += align(8, len(data)) | ||
data += body | ||
|
||
return bytes(data) | ||
|
||
|
||
class ProtocolTester(unittest.TestCase): | ||
def test_dataReceived_simple_method_call(self): | ||
p = TestProtocol() | ||
p._authenticated = True | ||
|
||
path = '/test/path' | ||
member = 'testMethod' | ||
data = create_basic_method(path, member) | ||
|
||
self.assertIsNone(p.dataReceived(data)) | ||
self.assertEqual(len(p.mcalls), 1) | ||
self.assertEqual(p.mcalls[0].path, path) | ||
self.assertEqual(p.mcalls[0].member, member) | ||
self.assertIsNone(p.mcalls[0].body) | ||
self.assertEqual(len(p.mrets), 0) | ||
self.assertEqual(len(p.merrs), 0) | ||
self.assertEqual(len(p.msigs), 0) | ||
|
||
def test_dataReceived_method_call_with_unix_fd(self): | ||
p = TestProtocol() | ||
p._authenticated = True | ||
|
||
path = '/test/path' | ||
member = 'testMethod' | ||
fd = 99 | ||
data = create_one_unix_fd_method(path, member, 0) | ||
|
||
p.fileDescriptorReceived(fd) | ||
self.assertIsNone(p.dataReceived(data)) | ||
self.assertEqual(len(p.mcalls), 1) | ||
self.assertEqual(p.mcalls[0].path, path) | ||
self.assertEqual(p.mcalls[0].member, member) | ||
self.assertEqual(p.mcalls[0].body, [fd]) | ||
self.assertEqual(len(p.mrets), 0) | ||
self.assertEqual(len(p.merrs), 0) | ||
self.assertEqual(len(p.msigs), 0) | ||
|
||
def test_dataReceived_method_call_with_unix_fd_race(self): | ||
p = TestProtocol() | ||
p._authenticated = True | ||
|
||
# message 1 | ||
path1 = '/test/path1' | ||
member1 = 'testMethod1' | ||
fd1 = 99 | ||
data1 = create_one_unix_fd_method(path1, member1, 0) | ||
|
||
# message 2 | ||
path2 = '/test/path2' | ||
member2 = 'testMethod2' | ||
data2 = create_basic_method(path2, member2) | ||
|
||
# possible race condition, file descriptor for message 1 is received | ||
p.fileDescriptorReceived(fd1) | ||
# then unrelated message 2 is received | ||
self.assertIsNone(p.dataReceived(data2)) | ||
# then message 1, which is expecting file descriptor, is received | ||
self.assertIsNone(p.dataReceived(data1)) | ||
|
||
self.assertEqual(len(p.mcalls), 2) | ||
self.assertEqual(p.mcalls[0].path, path2) | ||
self.assertEqual(p.mcalls[0].member, member2) | ||
self.assertIsNone(p.mcalls[0].body) | ||
self.assertEqual(p.mcalls[1].path, path1) | ||
self.assertEqual(p.mcalls[1].member, member1) | ||
self.assertEqual(p.mcalls[1].body, [fd1]) | ||
self.assertEqual(len(p.mrets), 0) | ||
self.assertEqual(len(p.merrs), 0) | ||
self.assertEqual(len(p.msigs), 0) | ||
|
||
def test_dataReceived_method_call_with_unix_fd_race2(self): | ||
p = TestProtocol() | ||
p._authenticated = True | ||
|
||
# message 1 | ||
path1 = '/test/path1' | ||
member1 = 'testMethod1' | ||
fd1 = 99 | ||
data1 = create_one_unix_fd_method(path1, member1, 0) | ||
|
||
# message 2 | ||
path2 = '/test/path2' | ||
member2 = 'testMethod2' | ||
fd2 = 88 | ||
data2 = create_one_unix_fd_method(path2, member2, 0) | ||
|
||
# possible race condition, file descriptor for message 1 is received | ||
p.fileDescriptorReceived(fd1) | ||
# then file descriptor for message 2 is received | ||
p.fileDescriptorReceived(fd2) | ||
# then message 1 is received | ||
self.assertIsNone(p.dataReceived(data1)) | ||
# then message 2 is received | ||
self.assertIsNone(p.dataReceived(data2)) | ||
|
||
self.assertEqual(len(p.mcalls), 2) | ||
self.assertEqual(p.mcalls[0].path, path1) | ||
self.assertEqual(p.mcalls[0].member, member1) | ||
self.assertEqual(p.mcalls[0].body, [fd1]) | ||
self.assertEqual(p.mcalls[1].path, path2) | ||
self.assertEqual(p.mcalls[1].member, member2) | ||
self.assertEqual(p.mcalls[1].body, [fd2]) | ||
self.assertEqual(len(p.mrets), 0) | ||
self.assertEqual(len(p.merrs), 0) | ||
self.assertEqual(len(p.msigs), 0) | ||
|
||
def test_dataReceived_method_call_with_unix_fd_race3(self): | ||
raise unittest.SkipTest('It is not possible to fix this in txdbus - ' | ||
'it must be fixed in twisted.') | ||
|
||
p = TestProtocol() | ||
p._authenticated = True | ||
|
||
# message 1 | ||
path1 = '/test/path1' | ||
member1 = 'testMethod1' | ||
fd1 = 99 | ||
data1 = create_one_unix_fd_method(path1, member1, 0) | ||
|
||
# message 2 | ||
path2 = '/test/path2' | ||
member2 = 'testMethod2' | ||
fd2 = 88 | ||
data2 = create_one_unix_fd_method(path2, member2, 0) | ||
|
||
# possible race condition, file descriptor for message 1 is received | ||
p.fileDescriptorReceived(fd1) | ||
# then file descriptor for message 2 is received | ||
p.fileDescriptorReceived(fd2) | ||
# then message 2 is received | ||
self.assertIsNone(p.dataReceived(data2)) | ||
# then message 1 is received | ||
self.assertIsNone(p.dataReceived(data1)) | ||
|
||
self.assertEqual(len(p.mcalls), 2) | ||
self.assertEqual(p.mcalls[0].path, path2) | ||
self.assertEqual(p.mcalls[0].member, member2) | ||
self.assertEqual(p.mcalls[0].body, [fd2]) | ||
self.assertEqual(p.mcalls[1].path, path1) | ||
self.assertEqual(p.mcalls[1].member, member1) | ||
self.assertEqual(p.mcalls[1].body, [fd1]) | ||
self.assertEqual(len(p.mrets), 0) | ||
self.assertEqual(len(p.merrs), 0) | ||
self.assertEqual(len(p.msigs), 0) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.