diff options
Diffstat (limited to 'tests')
28 files changed, 1505 insertions, 705 deletions
diff --git a/tests/analysis/storage/storage.py b/tests/analysis/storage/storage.py deleted file mode 100644 index 612d500..0000000 --- a/tests/analysis/storage/storage.py +++ /dev/null @@ -1,81 +0,0 @@ - -from chrysacase import ChrysalideTestCase -from pychrysalide import core -from pychrysalide.analysis.contents import FileContent -from pychrysalide.analysis.storage import ObjectStorage -from pychrysalide.common import PackedBuffer -import os -import shutil -import tempfile - - -class TestObjectStorage(ChrysalideTestCase): - """TestCase for analysis.storage.""" - - @classmethod - def setUpClass(cls): - - super(TestObjectStorage, cls).setUpClass() - - cls._tmp_path = tempfile.mkdtemp() - - config = core.get_main_configuration() - param = config.search(core.MainParameterKeys.TMPDIR) - - cls._old_tmpdir = param.value - param.value = cls._tmp_path - - cls.log('Using temporary directory "%s"' % cls._tmp_path) - - - @classmethod - def tearDownClass(cls): - - super(TestObjectStorage, cls).tearDownClass() - - config = core.get_main_configuration() - param = config.search(core.MainParameterKeys.TMPDIR) - - param.value = cls._old_tmpdir - - # import os - # os.system('ls -laihR %s' % cls._tmp_path) - - cls.log('Delete directory "%s"' % cls._tmp_path) - - shutil.rmtree(cls._tmp_path) - - - def testFileContentStorage(self): - """Store and load file binary content.""" - - storage = ObjectStorage('my-storage-hash') - self.assertIsNotNone(storage) - - filename = os.path.join(self._tmp_path, 'test.bin') - - with open(filename, 'wb') as fd: - fd.write(b'ABC') - - cnt = FileContent(filename) - self.assertIsNotNone(cnt) - - ret = storage.store_object('contents', cnt) - self.assertEqual(ret, 0) - - pbuf = PackedBuffer() - - ret = storage.store(pbuf) - self.assertTrue(ret) - - self.assertTrue(pbuf.payload_length > 0) - - pbuf.rewind() - - storage2 = ObjectStorage.load(pbuf) - self.assertIsNotNone(storage2) - - cnt2 = storage2.load_object('contents', 0) - self.assertIsNotNone(cnt2) - - self.assertEqual(cnt.data, cnt2.data) diff --git a/tests/arch/immediate.py b/tests/arch/immediate.py deleted file mode 100644 index 74b8069..0000000 --- a/tests/arch/immediate.py +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/python3-dbg -# -*- coding: utf-8 -*- - - -import pychrysalide -from chrysacase import ChrysalideTestCase -from pychrysalide import arch -from pychrysalide.arch import ImmOperand - - - -class TestImmediate(ChrysalideTestCase): - """TestCase for arch.ImmOperand.""" - - - def validateValue(self, value, size, padding, strings): - """Check all kinds of things with a given immediate operand.""" - - display = [ - ImmOperand.IOD_BIN, ImmOperand.IOD_OCT, - ImmOperand.IOD_DEC, - ImmOperand.IOD_HEX - ] - - for d in display: - - op = ImmOperand(size, value) - - self.assertTrue(op.size == size) - self.assertTrue(op.value == value) - - op.padding = padding - op.display = d - - string = op.to_string() - self.assertEqual(string, strings[d]) - - - def testByteOne(self): - """Run sanity checks on immediate operand with value 1.""" - - strings = { - ImmOperand.IOD_BIN: 'b1', - ImmOperand.IOD_OCT: '01', - ImmOperand.IOD_DEC: '1', - ImmOperand.IOD_HEX: '0x1' - } - - self.validateValue(1, arch.MDS_8_BITS_UNSIGNED, False, strings) - - - def testByteOnePadded(self): - """Run sanity checks on immediate operand with padded value 1.""" - - strings = { - ImmOperand.IOD_BIN: 'b00000001', - ImmOperand.IOD_OCT: '01', - ImmOperand.IOD_DEC: '1', - ImmOperand.IOD_HEX: '0x01' - } - - self.validateValue(1, arch.MDS_8_BITS_UNSIGNED, True, strings) diff --git a/tests/arch/instruction.py b/tests/arch/instruction.py new file mode 100644 index 0000000..da4d8c1 --- /dev/null +++ b/tests/arch/instruction.py @@ -0,0 +1,52 @@ + +import pychrysalide +from chrysacase import ChrysalideTestCase +from pychrysalide.arch import ArchInstruction + + +class TestProcessor(ChrysalideTestCase): + """TestCase for arch.ArchProcessor.""" + + + def testAbstractClass(self): + """Forbid instruction class instance.""" + + with self.assertRaisesRegex(RuntimeError, 'pychrysalide.arch.ArchInstruction is an abstract class'): + ins = ArchInstruction() + + + def testInstructionBasicImplementation(self): + """Implement basic custom instructions.""" + + + class TodoInstruction(ArchInstruction): + + def __init__(self): + super().__init__(0x123) + + + ins = TodoInstruction() + + with self.assertRaisesRegex(NotImplementedError, 'unexpected NULL value as encoding'): + print(ins.encoding) + + with self.assertRaisesRegex(NotImplementedError, 'unexpected NULL value as keyword'): + print(ins.keyword) + + + class CustomInstruction(ArchInstruction): + + def __init__(self): + super().__init__(0x123) + + def _get_encoding(self): + return 'custom' + + def _get_keyword(self): + return 'kw' + + + ins = CustomInstruction() + + self.assertEqual('custom', ins.encoding) + self.assertEqual('kw', ins.keyword) diff --git a/tests/arch/operand.py b/tests/arch/operand.py new file mode 100644 index 0000000..e8df8b5 --- /dev/null +++ b/tests/arch/operand.py @@ -0,0 +1,72 @@ + +import pychrysalide + +from chrysacase import ChrysalideTestCase +from pychrysalide.arch import ArchOperand +from pychrysalide.glibext import HashableObject, StringBuilder + + +class TestOperand(ChrysalideTestCase): + """TestCase for arch.ArchOperand.""" + + + def testAbstractClass(self): + """Forbid operand class instance.""" + + with self.assertRaisesRegex(RuntimeError, 'pychrysalide.arch.ArchOperand is an abstract class'): + pc = ArchOperand() + + + def testStringBuilderMethodsOverriding(self): + """Override the StringBuilder interface provided by native implementations.""" + + class MyOperand(ArchOperand, StringBuilder): + + def __init__(self): + super().__init__(self) + + def _to_string(self, flags=0): + return 'my-op' + + op = MyOperand() + + self.assertEqual(op.to_string(), 'my-op') + self.assertEqual(str(op), 'my-op') + self.assertEqual(f'{op}', 'my-op') + + + def testHashableObjectMethods(self): + """Test the HashableObject methods implemantation for operands.""" + + # Pas d'implementation de particulière de HashableObject, + # c'est donc la définition d'implémentation d'ArchOperand + # qui s'applique. + + # Spécificité de l'implémentation GLib : hash 32 bits + + class DefaultHashableOperand(ArchOperand): + pass + + def_op = DefaultHashableOperand() + + h = hash(def_op) + + self.assertEqual(0, h & ~0xffffffff) + + + # Définition particulière de l'opérande, sur la base de + # l'implémentation parente. + + class CustomHashableOperand(ArchOperand, HashableObject): + + def _hash(self): + h = self.parent_hash() + h &= ~0xffff + return h + + cust_op = CustomHashableOperand() + + h = hash(cust_op) + + self.assertEqual(0, h & 0xffff) + self.assertEqual(0, h & ~0xffffffff) diff --git a/tests/arch/operands/immediate.py b/tests/arch/operands/immediate.py new file mode 100644 index 0000000..c3fcb84 --- /dev/null +++ b/tests/arch/operands/immediate.py @@ -0,0 +1,86 @@ + +import pychrysalide + +from chrysacase import ChrysalideTestCase +from pychrysalide import MemoryDataSize +from pychrysalide.arch.operands import ImmediateOperand +from pychrysalide.glibext import StringBuilder + + +class TestImmediate(ChrysalideTestCase): + """TestCase for arch.ImmediateOperand.""" + + + def testBasicImmediate(self): + """Check basic properties of immediate values.""" + + imm = ImmediateOperand(MemoryDataSize._32_BITS_UNSIGNED, 0x123) + + self.assertEqual(imm.size, MemoryDataSize._32_BITS_UNSIGNED) + self.assertEqual(imm.value, 0x123) + self.assertFalse(imm.is_negative) + + + def testStringBuilderForImmediatesOverriding(self): + """Override the StringBuilder interface for immediate values.""" + + class MyImmOperand(ImmediateOperand, StringBuilder): + + def __init__(self): + super().__init__(MemoryDataSize._32_BITS_UNSIGNED, 0x123) + + def _to_string(self, flags=0): + return 'NaN' + + op = MyImmOperand() + + self.assertEqual(op.to_string(), 'NaN') + self.assertEqual(str(op), 'NaN') + self.assertEqual(f'{op}', 'NaN') + + + def validateValue(self, value, size, padding, strings): + """Check all kinds of things with a given immediate operand.""" + + for d in strings.keys(): + + op = ImmediateOperand(size, value) + + self.assertTrue(op.size == size) + self.assertTrue(op.value == value) + + if padding: + op.set_flag(ImmediateOperand.ImmOperandFlag.ZERO_PADDING) + else: + op.unset_flag(ImmediateOperand.ImmOperandFlag.ZERO_PADDING_BY_DEFAULT) + + op.display = d + + string = op.to_string() + self.assertEqual(string, strings[d]) + + + def testByteOne(self): + """Run sanity checks on immediate operand with value 1.""" + + strings = { + ImmediateOperand.ImmOperandDisplay.BIN: 'b1', + ImmediateOperand.ImmOperandDisplay.OCT: '01', + ImmediateOperand.ImmOperandDisplay.DEC: '1', + ImmediateOperand.ImmOperandDisplay.HEX: '0x1' + } + + self.validateValue(1, pychrysalide.MemoryDataSize._8_BITS_UNSIGNED, False, strings) + + + def testByteOnePadded(self): + """Run sanity checks on immediate operand with padded value 1.""" + + strings = { + ImmediateOperand.ImmOperandDisplay.BIN: 'b00000001', + ImmediateOperand.ImmOperandDisplay.OCT: '01', + ImmediateOperand.ImmOperandDisplay.DEC: '1', + ImmediateOperand.ImmOperandDisplay.HEX: '0x01' + } + + self.validateValue(1, pychrysalide.MemoryDataSize._8_BITS_UNSIGNED, True, strings) diff --git a/tests/common/bitfield.py b/tests/common/bitfield.py index 75dfb6e..5d535fd 100644 --- a/tests/common/bitfield.py +++ b/tests/common/bitfield.py @@ -93,7 +93,7 @@ class TestBitFields(ChrysalideTestCase): bf_a = BitField(75, 0) bf_b = BitField(4, 1) - bf_b.reset(2, 1) + bf_b.reset(2) bf_a.or_at(bf_b, 63) @@ -130,14 +130,14 @@ class TestBitFields(ChrysalideTestCase): bf_t = BitField(75, 0) for i in range(75): - bf_t.set(i, 1) + bf_t.set(i) self.assertEqual(bf_t, bf_1) self.assertEqual(bf_t.popcount, bf_1.popcount) for i in range(75): - bf_t.reset(i, 1) + bf_t.reset(i) self.assertEqual(bf_t, bf_0) @@ -223,8 +223,8 @@ class TestBitFields(ChrysalideTestCase): """Check bitfield comparison.""" bf_a = BitField(9, 0) - bf_a.set(0, 1) - bf_a.set(5, 1) + bf_a.set(0) + bf_a.set(5) bf_b = BitField(9, 1) @@ -240,7 +240,7 @@ class TestBitFields(ChrysalideTestCase): bits = [ 0, 1, 50, 63, 64, 65, 111 ] for b in bits: - bf.set(b, 1) + bf.set(b) prev = None found = [] @@ -261,17 +261,17 @@ class TestBitFields(ChrysalideTestCase): def testRealCase00(self): - """Test bits in bitfields against other bitfields in a real case (#02).""" + """Test bits in bitfields against other bitfields in a real case (#00).""" bf = BitField(128, 0) for b in [ 0, 50, 54, 58, 66, 70, 98 ]: - bf.set(b, 1) + bf.set(b) mask = BitField(128, 0) for b in [ 0, 51 ]: - mask.set(b, 1) + mask.set(b) self.assertFalse(bf.test_zeros_with(0, mask)) @@ -284,7 +284,7 @@ class TestBitFields(ChrysalideTestCase): self.assertTrue(bf.test_zeros_with(0, mask)) for b in [ 0, 8, 9, 10 ]: - mask.set(b, 1) + mask.set(b) self.assertTrue(bf.test_zeros_with(0, mask)) @@ -305,7 +305,7 @@ class TestBitFields(ChrysalideTestCase): bits = [ 0, 50, 54, 58, 66, 70, 98 ] for b in bits: - mask.set(b, 1) + mask.set(b) bf.or_at(mask, 0) diff --git a/tests/common/leb128.py b/tests/common/leb128.py index db3013e..037af4d 100644 --- a/tests/common/leb128.py +++ b/tests/common/leb128.py @@ -1,7 +1,6 @@ from chrysacase import ChrysalideTestCase from pychrysalide.common import pack_uleb128, unpack_uleb128, pack_leb128, unpack_leb128 -from pychrysalide.common import PackedBuffer class TestLEB128Values(ChrysalideTestCase): @@ -16,34 +15,30 @@ class TestLEB128Values(ChrysalideTestCase): 128: b'\x80\x01', } - for value, encoding in cases.items(): - - pbuf = PackedBuffer() + # Lecture depuis des blocs individuels - status = pack_uleb128(value, pbuf) - self.assertTrue(status) + for value, encoding in cases.items(): - self.assertEqual(pbuf.payload_length, len(encoding)) + self.assertEqual(pack_uleb128(value), encoding) - pbuf.rewind() + v, r = unpack_uleb128(encoding) - got = pbuf.extract(len(encoding)) + self.assertEqual(value, v) + self.assertEqual(b'', r) - self.assertEqual(got, encoding) + # Lecture depuis un bloc commun - self.assertFalse(pbuf.more_data) + data = b''.join(cases.values()) - for value, encoding in cases.items(): + values = [] - pbuf = PackedBuffer() - pbuf.extend(encoding, False) + while len(data) > 0: - pbuf.rewind() + val, data = unpack_uleb128(data) - got = unpack_uleb128(pbuf) - self.assertIsNotNone(got) + values.append(val) - self.assertEqual(got, value) + self.assertEqual(values, [ k for k in cases.keys() ]) def testSignedLeb128Encoding(self): @@ -55,54 +50,39 @@ class TestLEB128Values(ChrysalideTestCase): -9001: b'\xd7\xb9\x7f', } - for value, encoding in cases.items(): + # Lecture depuis des blocs individuels - pbuf = PackedBuffer() + for value, encoding in cases.items(): - status = pack_leb128(value, pbuf) - self.assertTrue(status) + self.assertEqual(pack_leb128(value), encoding) - self.assertEqual(pbuf.payload_length, len(encoding)) + v, r = unpack_leb128(encoding) - pbuf.rewind() + self.assertEqual(value, v) + self.assertEqual(b'', r) - got = pbuf.extract(len(encoding)) + # Lecture depuis un bloc commun - self.assertEqual(got, encoding) + data = b''.join(cases.values()) - self.assertFalse(pbuf.more_data) + values = [] - for value, encoding in cases.items(): + while len(data) > 0: - pbuf = PackedBuffer() - pbuf.extend(encoding, False) + val, data = unpack_leb128(data) - pbuf.rewind() + values.append(val) - got = unpack_leb128(pbuf) - self.assertIsNotNone(got) - - self.assertEqual(got, value) + self.assertEqual(values, [ k for k in cases.keys() ]) def testTooBigLeb128Encodings(self): """Prevent overflow for LEB128 values.""" - pbuf = PackedBuffer() - pbuf.extend(b'\x80' * 10 + b'\x7f', False) - - pbuf.rewind() - - got = unpack_uleb128(pbuf) - - self.assertIsNone(got) - - pbuf = PackedBuffer() - pbuf.extend(b'\x80' * 10 + b'\x7f', False) - - pbuf.rewind() + v = unpack_uleb128(b'\x80' * 10 + b'\x7f') - got = unpack_leb128(pbuf) + self.assertIsNone(v) - self.assertIsNone(got) + v = unpack_leb128(b'\x80' * 10 + b'\x7f') + self.assertIsNone(v) diff --git a/tests/common/xdg.py b/tests/common/xdg.py new file mode 100644 index 0000000..df03c3c --- /dev/null +++ b/tests/common/xdg.py @@ -0,0 +1,71 @@ + +import os + +from chrysacase import ChrysalideTestCase +from pychrysalide.common import get_xdg_cache_dir, get_xdg_config_dir, get_xdg_data_dir, \ + get_xdg_state_dir, get_xdg_runtime_dir + + +class TestXDG(ChrysalideTestCase): + """TestCase for XDG directories.""" + + def testXDGCachePath(self): + """Retrieve the XDG cache directory.""" + + filename = get_xdg_cache_dir('test.txt', False) + + self.assertIsNotNone(filename) + self.assertTrue(filename.startswith(os.sep)) + self.assertTrue(filename.endswith('test.txt')) + + # Depends on current configuration + self.assertTrue('.cache' in filename) + + + def testXDGConfigPath(self): + """Retrieve the XDG config directory.""" + + filename = get_xdg_config_dir('test.txt', False) + + self.assertIsNotNone(filename) + self.assertTrue(filename.startswith(os.sep)) + self.assertTrue(filename.endswith('test.txt')) + + # Depends on current configuration + self.assertTrue('.config' in filename) + + + def testXDGDataPath(self): + """Retrieve the XDG data directory.""" + + filename = get_xdg_data_dir('test.txt', False) + + self.assertIsNotNone(filename) + self.assertTrue(filename.startswith(os.sep)) + self.assertTrue(filename.endswith('test.txt')) + + # Depends on current configuration + self.assertTrue(os.path.join('.local', 'share') in filename) + + + def testXDGStatePath(self): + """Retrieve the XDG state directory.""" + + filename = get_xdg_state_dir('test.txt', False) + + self.assertIsNotNone(filename) + self.assertTrue(filename.startswith(os.sep)) + self.assertTrue(filename.endswith('test.txt')) + + # Depends on current configuration + self.assertTrue(os.path.join('.local', 'state') in filename) + + + def testXDGRuntimePath(self): + """Retrieve the XDG runtime directory.""" + + filename = get_xdg_runtime_dir('test.txt') + + self.assertIsNotNone(filename) + self.assertTrue(filename.startswith(os.sep)) + self.assertTrue(filename.endswith('test.txt')) diff --git a/tests/constval.py b/tests/constval.py deleted file mode 100644 index eafb8d3..0000000 --- a/tests/constval.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/python3-dbg -# -*- coding: utf-8 -*- - - -from chrysacase import ChrysalideTestCase -from pychrysalide import PyConstvalObject -from pychrysalide.arch import ArchInstruction -import pickle - - -class TestConstVal(ChrysalideTestCase): - """TestCase for PyConstvalObject.""" - - - def testCreation(self): - """Validate PyConstvalObject creation from Python.""" - - cst = PyConstvalObject(123, 'XXX') - - self.assertEqual(cst, 123) - - self.assertEqual(str(cst), 'XXX') - - - def testString(self): - """Validate the PyConstvalObject implementation.""" - - self.assertEqual(ArchInstruction.ILT_JUMP, 1) - - self.assertEqual(str(ArchInstruction.ILT_JUMP), 'ILT_JUMP') - - - def testStorage(self): - """Ensure PyConstvalObject instances are storable.""" - - cst = ArchInstruction.ILT_JUMP - - data = pickle.dumps(cst) - - cst = pickle.loads(data) - - self.assertEqual(cst, ArchInstruction.ILT_JUMP) - - self.assertEqual(str(cst), 'ILT_JUMP') diff --git a/tests/format/executable.py b/tests/format/executable.py new file mode 100644 index 0000000..ec42ccd --- /dev/null +++ b/tests/format/executable.py @@ -0,0 +1,59 @@ + +from chrysacase import ChrysalideTestCase +from pychrysalide.analysis.contents import MemoryContent +from pychrysalide.arch import vmpa +from pychrysalide.format import ExecutableFormat + + +class TestExecutableFormat(ChrysalideTestCase): + """TestCase for format.ExecutableFormat.""" + + + def testMainAddresses(self): + """Provide several kinds of main addresses.""" + + data = b'\x00\x00\x00\xef' + cnt = MemoryContent(data) + + + class CustomFormatVmpa(ExecutableFormat): + + def _get_main_address(self): + return vmpa(246, 357) + + cf = CustomFormatVmpa(cnt) + + self.assertEqual(cf.main_address.phys, 246) + self.assertEqual(cf.main_address.virt, 357) + + + class CustomFormatInt(ExecutableFormat): + + def _get_main_address(self): + return 123 + + cf = CustomFormatInt(cnt) + + self.assertIsNone(cf.main_address.phys) + self.assertEqual(cf.main_address.virt, 123) + + + class CustomFormatNone(ExecutableFormat): + + def _get_main_address(self): + return None + + cf = CustomFormatNone(cnt) + + self.assertIsNone(cf.main_address) + + + class CustomFormatBad(ExecutableFormat): + + def _get_main_address(self): + return 'bad' + + cf = CustomFormatBad(cnt) + + with self.assertRaisesRegex(Exception, 'unable to define a value for the main address'): + ma = cf.main_address diff --git a/tests/format/flat.py b/tests/format/flat.py index 6924e42..f84f7a4 100644 --- a/tests/format/flat.py +++ b/tests/format/flat.py @@ -1,16 +1,11 @@ -#!/usr/bin/python3-dbg -# -*- coding: utf-8 -*- - - -# Tests minimalistes pour valider la gestion des erreurs relevées. - from chrysacase import ChrysalideTestCase -from pychrysalide.analysis import LoadedBinary +from pychrysalide import SourceEndian +#from pychrysalide.analysis import LoadedBinary from pychrysalide.analysis.contents import MemoryContent -from pychrysalide.arch import vmpa +#from pychrysalide.arch import vmpa from pychrysalide.format import FlatFormat -from pychrysalide.glibext import BinPortion +#from pychrysalide.glibext import BinPortion class TestFlatFormat(ChrysalideTestCase): @@ -24,18 +19,21 @@ class TestFlatFormat(ChrysalideTestCase): cnt = MemoryContent(data) - fmt = FlatFormat(cnt) - fmt.set_machine('armv7') + fmt = FlatFormat(cnt, 'armv7', SourceEndian.LITTLE) + + self.assertEqual(fmt.target_machine, 'armv7') + self.assertEqual(fmt.endianness, SourceEndian.LITTLE) + - base = vmpa(0, 0) + # base = vmpa(0, 0) - p = BinPortion(BinPortion.BPC_CODE, base, cnt.size) - p.rights = BinPortion.PAC_READ | BinPortion.PAC_EXEC + # p = BinPortion(BinPortion.BPC_CODE, base, cnt.size) + # p.rights = BinPortion.PAC_READ | BinPortion.PAC_EXEC - fmt.register_user_portion(p) + # fmt.register_user_portion(p) - binary = LoadedBinary(fmt) + # binary = LoadedBinary(fmt) - binary.analyze_and_wait() + # binary.analyze_and_wait() - self.assertTrue(list(binary.processor.instrs)[0].keyword == 'svc') + # self.assertTrue(list(binary.processor.instrs)[0].keyword == 'svc') diff --git a/tests/format/format.py b/tests/format/format.py deleted file mode 100644 index b6aad8f..0000000 --- a/tests/format/format.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/python3-dbg -# -*- coding: utf-8 -*- - - -# Tests minimalistes pour valider la gestion des erreurs relevées. - - -from chrysacase import ChrysalideTestCase -from pychrysalide.arch import vmpa, mrange -from pychrysalide.format import BinFormat -from pychrysalide.format import BinSymbol -import os -import sys - - -class SimpleFormat(BinFormat): - pass - - -class TestFormatErrors(ChrysalideTestCase): - """TestCase for format.BinFormat.""" - - - def create_fake_symbol(self, index): - saddr = vmpa(index * 0x10, vmpa.VMPA_NO_VIRTUAL) - srange = mrange(saddr, 0x3) - symbol = BinSymbol(BinSymbol.STP_ENTRY_POINT, srange) - return symbol - - - def testBasicSymbolOperations(self): - """Deal with the basic operations related to symbols in a binary format.""" - - sf = SimpleFormat() - - self.assertTrue(len(list(sf.symbols)) == 0) - - symbols = [ self.create_fake_symbol(i) for i in range(4) ] - s0, s1, s2, s3 = symbols - - for s in symbols: - sf.add_symbol(s) - - self.assertTrue(len(list(sf.symbols)) == len(symbols)) - - sf.remove_symbol(s2) - - self.assertTrue(list(sf.symbols) == [s0, s1, s3]) - - - def testBadParamsForAdding(self): - """Check if bad parameters fail for adding a new symbol.""" - - sf = SimpleFormat() - - with self.assertRaises(TypeError): - sf.add_symbol('s') - - - def testWrongRemoval(self): - """Try to remove a wrong symbol from a format.""" - - sf = SimpleFormat() - - s23 = self.create_fake_symbol(23) - sf.remove_symbol(s23) diff --git a/tests/format/known.py b/tests/format/known.py index 056238f..3a51f31 100644 --- a/tests/format/known.py +++ b/tests/format/known.py @@ -1,6 +1,3 @@ -#!/usr/bin/python3-dbg -# -*- coding: utf-8 -*- - from chrysacase import ChrysalideTestCase from pychrysalide.analysis.contents import MemoryContent @@ -11,8 +8,62 @@ class TestKnownFormat(ChrysalideTestCase): """TestCase for format.KnownFormat.""" + def testCustomInstance(self): + """Validate a full custom KnownFormat implementation.""" + + data = b'\x01\x02\x03' + cnt = MemoryContent(data) + + + class CustomFormat(KnownFormat): + + def _get_key(self): + return 'tiny' + + def _get_description(self): + return 'Small description' + + cf = CustomFormat(cnt) + + self.assertEqual(cf.key, 'tiny') + self.assertEqual(cf.description, 'Small description') + + + class EmptyCustomFormat(KnownFormat): + pass + + cf = EmptyCustomFormat(cnt) + + # NotImplementedError: method implementation is missing for '_get_key' + with self.assertRaisesRegex(NotImplementedError, "method implementation is missing for '_get_key'"): + k = cf.key + + # NotImplementedError: method implementation is missing for '_get_description' + with self.assertRaisesRegex(NotImplementedError, "method implementation is missing for '_get_description'"): + k = cf.description + + + class BadCustomFormat(KnownFormat): + + def _get_key(self): + return 123 + + def _get_description(self): + return 456 + + cf = BadCustomFormat(cnt) + + # ValueError: unexpected value type for known format key + with self.assertRaisesRegex(ValueError, 'unexpected value type for known format key'): + k = cf.key + + # ValueError: unexpected value type for known format description + with self.assertRaisesRegex(ValueError, 'unexpected value type for known format description'): + k = cf.description + + def testKnownFormatConstructor(self): - """Build Load a simple content for a flat format.""" + """Load a simple content for a known format.""" with self.assertRaisesRegex(RuntimeError, 'pychrysalide.format.KnownFormat is an abstract class'): fmt = KnownFormat() @@ -28,17 +79,3 @@ class TestKnownFormat(ChrysalideTestCase): with self.assertRaisesRegex(TypeError, 'unable to convert the provided argument to binary content'): fmt = MyKnownFormat2(123) - - class MyKnownFormatReady(KnownFormat): - _key = 'rdy' - def __init2__(self, cnt): - super(MyKnownFormatReady, self).__init2__(cnt) - - data = b'\x00\x00\x00\xef' - - cnt = MemoryContent(data) - fmt = MyKnownFormatReady(cnt) - - self.assertIsNotNone(fmt) - - self.assertEqual(fmt.key, 'rdy') diff --git a/tests/format/program.py b/tests/format/program.py new file mode 100644 index 0000000..7027cdf --- /dev/null +++ b/tests/format/program.py @@ -0,0 +1,89 @@ + +from chrysacase import ChrysalideTestCase +from pychrysalide import SourceEndian +from pychrysalide.analysis.contents import MemoryContent +#from pychrysalide.arch import vmpa, mrange +from pychrysalide.format import ProgramFormat +#from pychrysalide.format import BinSymbol + + +# class SimpleFormat(BinFormat): +# pass + + +class TestProgramFormat(ChrysalideTestCase): + """TestCase for format.ProgramFormat.""" + + + def testCustomInstance(self): + """Validate a full custom ProgramFormat implementation.""" + + data = b'\x00\x00\x00\xef' + cnt = MemoryContent(data) + + + class CustomFormat(ProgramFormat): + + def _get_endianness(self): + return SourceEndian.BIG + + cf = CustomFormat(cnt) + + self.assertEqual(cf.endianness, SourceEndian.BIG) + + + class EmptyCustomFormat(ProgramFormat): + pass + + cf = EmptyCustomFormat(cnt) + + self.assertEqual(cf.endianness, SourceEndian.LITTLE) + + + + + + + # def create_fake_symbol(self, index): + # saddr = vmpa(index * 0x10, vmpa.VMPA_NO_VIRTUAL) + # srange = mrange(saddr, 0x3) + # symbol = BinSymbol(BinSymbol.STP_ENTRY_POINT, srange) + # return symbol + + + # def testBasicSymbolOperations(self): + # """Deal with the basic operations related to symbols in a binary format.""" + + # sf = SimpleFormat() + + # self.assertTrue(len(list(sf.symbols)) == 0) + + # symbols = [ self.create_fake_symbol(i) for i in range(4) ] + # s0, s1, s2, s3 = symbols + + # for s in symbols: + # sf.add_symbol(s) + + # self.assertTrue(len(list(sf.symbols)) == len(symbols)) + + # sf.remove_symbol(s2) + + # self.assertTrue(list(sf.symbols) == [s0, s1, s3]) + + + # def testBadParamsForAdding(self): + # """Check if bad parameters fail for adding a new symbol.""" + + # sf = SimpleFormat() + + # with self.assertRaises(TypeError): + # sf.add_symbol('s') + + + # def testWrongRemoval(self): + # """Try to remove a wrong symbol from a format.""" + + # sf = SimpleFormat() + + # s23 = self.create_fake_symbol(23) + # sf.remove_symbol(s23) diff --git a/tests/glibext/comparable.py b/tests/glibext/comparable.py new file mode 100644 index 0000000..48291ca --- /dev/null +++ b/tests/glibext/comparable.py @@ -0,0 +1,132 @@ + +import gi + +from chrysacase import ChrysalideTestCase +from gi.repository import GObject +from pychrysalide.glibext import ComparableObject + + +class TestStringBuilder(ChrysalideTestCase): + """Test cases for pychrysalide.glibext.ComparableObject.""" + + + def testComparableObjectCreation(self): + """Create objects with ComparableObject interface.""" + + with self.assertRaisesRegex(NotImplementedError, 'ComparableObject can not be constructed'): + + co = ComparableObject() + + + class NewComparableObjectImplem(gi._gi.GObject, ComparableObject): + pass + + nco = NewComparableObjectImplem() + + self.assertIsNotNone(nco) + + + class NewComparableObjectImplem2(GObject.Object, ComparableObject): + pass + + nco2 = NewComparableObjectImplem() + + self.assertIsNotNone(nco2) + + + def testComparableObjectMethods(self): + """Test the ComparableObject methods.""" + + class BasicComparableObjectImplem(GObject.Object, ComparableObject): + + def __init__(self, val): + super().__init__() + self._val = val + + def _compare(self, other): + if self._val < other._val: + status = -1 + elif self._val > other._val: + status = 1 + else: + status = 0 + return status + + + a = BasicComparableObjectImplem(123) + b = BasicComparableObjectImplem(456) + + self.assertTrue(a <= b) + + # Sans l'action de inherit_interface_slots(), c'est pygobject_richcompare() qui est appelée, + # laquelle compare simplement les adresses des pointeurs + + c = BasicComparableObjectImplem(789) + d = BasicComparableObjectImplem(234) + + self.assertTrue(c > d) + + + def testComparableObjectExceptions(self): + """Raise exceptions from the ComparableObject interface as expected.""" + + + class OtherComparableObject(GObject.Object, ComparableObject): + pass + + other = OtherComparableObject() + + + class BadComparableObjectImplem(GObject.Object, ComparableObject): + pass + + obj = BadComparableObjectImplem() + + + with self.assertRaisesRegex(NotImplementedError, "method implementation is missing for '_compare'"): + + s = obj < other + + + class BadComparableObjectImplem2(GObject.Object, ComparableObject): + + def _compare(self, other): + return 'AAA' + + obj2 = BadComparableObjectImplem2() + + + with self.assertRaisesRegex(TypeError, 'comparison status has to be a signed integer'): + + s = obj2 < other + + + class BadComparableObjectImplem3a(GObject.Object, ComparableObject): + + def _compare(self, other): + return 123 + + class BadComparableObjectImplem3b(BadComparableObjectImplem3a, ComparableObject): + + def _compare(self, other): + return self.parent_compare() + + obj3 = BadComparableObjectImplem3b() + + + with self.assertRaisesRegex(RuntimeError, 'object parent is not a native type'): + + s = obj3 < other + + + class BadComparableObjectImplem4(GObject.Object, ComparableObject): + + def _compare(self, other): + raise Exception('error') + + obj4 = BadComparableObjectImplem4() + + + with self.assertRaisesRegex(Exception, 'error'): + + s = obj4 < other diff --git a/tests/glibext/configuration.py b/tests/glibext/configuration.py deleted file mode 100644 index 880b445..0000000 --- a/tests/glibext/configuration.py +++ /dev/null @@ -1,106 +0,0 @@ - -import gi -gi.require_version('Gdk', '3.0') -from gi.repository import Gdk - -from chrysacase import ChrysalideTestCase -from pychrysalide.glibext import ConfigParam, GenConfig - - -class TestConfiguration(ChrysalideTestCase): - """TestCase for configuration related items.*""" - - - def testCfgParamValues(self): - """Set and unset configuration parameter values.""" - - color = Gdk.RGBA() - color.parse('#3465A4') - - param = ConfigParam('config.color', ConfigParam.ConfigParamType.COLOR, color) - - self.assertEqual(param.value, color) - - param.make_empty() - - void = Gdk.RGBA(red=0, green=0, blue=0, alpha=0) - self.assertEqual(param.value, void) - - param.value = color - - self.assertEqual(param.value, color) - - - def testCfgParamStates(self): - """Validate all states of an evolving parameter.""" - - param = ConfigParam('config.int', ConfigParam.ConfigParamType.INTEGER) - - self.assertEqual(param.state, ConfigParam.ConfigParamState.EMPTY | ConfigParam.ConfigParamState.DEFAULT) - - param.make_empty() - - self.assertEqual(param.state, ConfigParam.ConfigParamState.EMPTY | ConfigParam.ConfigParamState.DEFAULT) - - param = ConfigParam('config.int', ConfigParam.ConfigParamType.INTEGER, 0x123) - - self.assertEqual(param.value, 0x123) - - self.assertEqual(param.state, ConfigParam.ConfigParamState.DEFAULT) - - param.make_empty() - - self.assertEqual(param.state, ConfigParam.ConfigParamState.EMPTY | ConfigParam.ConfigParamState.CHANGED) - - param.value = 0x1 - - self.assertEqual(param.state, ConfigParam.ConfigParamState.CHANGED) - - param.reset() - - self.assertEqual(param.state, ConfigParam.ConfigParamState.DEFAULT) - - - def testCfgParamDesc(self): - """Export types and states as strings when needed.""" - - param = ConfigParam('config.int', ConfigParam.ConfigParamType.INTEGER) - - self.assertTrue('|' in str(param.state)) - - self.assertTrue('.' in str(param.type)) - - - def testConfiguration(self): - """Feed and browse a basic configuration.""" - - config = GenConfig() - self.assertIsNotNone(config) - self.assertIsNone(config.filename) - - for i in range(5): - param = ConfigParam('config.int.%u' % i, ConfigParam.ConfigParamType.INTEGER, i) - config.add(param) - - chain = '' - - for p in config.params: - chain += '%u' % p.value - - self.assertTrue(chain == ''.join([ '%u' % i for i in range(5) ])) - - found = config.search('config.int.3') - self.assertTrue(found.value == 3) - - found = config.search('config.int.33') - self.assertIsNone(found) - - for p in config.params: - p.value *= 10 - - chain = '' - - for p in config.params: - chain += '%u' % p.value - - self.assertTrue(chain == ''.join([ '%u' % (i * 10) for i in range(5) ])) diff --git a/tests/glibext/hashable.py b/tests/glibext/hashable.py new file mode 100644 index 0000000..07f22e3 --- /dev/null +++ b/tests/glibext/hashable.py @@ -0,0 +1,190 @@ + +import gi + +from chrysacase import ChrysalideTestCase +from gi.repository import GObject +from pychrysalide.glibext import HashableObject + + +class TestStringBuilder(ChrysalideTestCase): + """Test cases for pychrysalide.glibext.HashableObject.""" + + + def testHashableObjectCreation(self): + """Create objects with HashableObject interface.""" + + with self.assertRaisesRegex(NotImplementedError, 'HashableObject can not be constructed'): + + ho = HashableObject() + + + class NewHashableObjectImplem(gi._gi.GObject, HashableObject): + pass + + nho = NewHashableObjectImplem() + + self.assertIsNotNone(nho) + + + class NewHashableObjectImplem2(GObject.Object, HashableObject): + pass + + nho2 = NewHashableObjectImplem() + + self.assertIsNotNone(nho2) + + + def testHashableObjectMethods(self): + """Test the HashableObject methods.""" + + class BasicHashableObjectImplem(gi._gi.GObject, HashableObject): + + def __init__(self, val): + super().__init__() + self._val = val + + def _hash(self): + return self._val + + value = 1234 + + ho = BasicHashableObjectImplem(value) + + self.assertEqual(hash(ho), value) + + + class BasicHashableObjectImplem2(GObject.Object, HashableObject): + + def __init__(self, val): + super().__init__() + self._val = val + + def _hash(self): + return self._val + + value = 5678 + + ho2 = BasicHashableObjectImplem2(value) + + self.assertEqual(hash(ho2), value) + + + def testCascadingHashableObjectImplementations(self): + """Request the hash from the object parent for a full computing.""" + + + class CascadingHashableObjectFullImplemA(GObject.Object, HashableObject): + + def _hash(self): + return 1 + + class CascadingHashableObjectFullImplemB(CascadingHashableObjectFullImplemA, HashableObject): + + def _hash(self): + return super()._hash() * 2 + + class CascadingHashableObjectFullImplemC(CascadingHashableObjectFullImplemB, HashableObject): + + def _hash(self): + return super()._hash() * 3 + + + obj_a = CascadingHashableObjectFullImplemA() + + obj_b = CascadingHashableObjectFullImplemB() + + obj_c = CascadingHashableObjectFullImplemC() + + + self.assertEqual(hash(obj_a), 1) + self.assertEqual(hash(obj_b), 2) + self.assertEqual(hash(obj_c), 6) + + + class CascadingHashableObjectPartialImplemA(GObject.Object, HashableObject): + + def _hash(self): + return 1 + + class CascadingHashableObjectPartialImplemB(CascadingHashableObjectPartialImplemA): + pass + + class CascadingHashableObjectPartialImplemC(CascadingHashableObjectPartialImplemB, HashableObject): + + def _hash(self): + return super()._hash() * 3 + + + obj_a = CascadingHashableObjectPartialImplemA() + + obj_b = CascadingHashableObjectPartialImplemB() + + obj_c = CascadingHashableObjectPartialImplemC() + + + self.assertEqual(hash(obj_a), 1) + self.assertEqual(hash(obj_b), 1) + self.assertEqual(hash(obj_c), 3) + + + def testHashableObjectExceptions(self): + """Raise exceptions from the HashableObject interface as expected.""" + + class BadHashableObjectImplem(GObject.Object, HashableObject): + pass + + obj = BadHashableObjectImplem() + + + with self.assertRaisesRegex(NotImplementedError, "method implementation is missing for '_hash'"): + + h = hash(obj) + + + with self.assertRaisesRegex(TypeError, 'object parent does not implement the HashableObject interface'): + + h = obj.parent_hash() + + + class BadHashableObjectImplem2(GObject.Object, HashableObject): + + def _hash(self): + return 'AAA' + + obj2 = BadHashableObjectImplem2() + + + with self.assertRaisesRegex(TypeError, 'computed hash value has to be an unsigned integer'): + + h = hash(obj2) + + + class BadHashableObjectImplem3a(GObject.Object, HashableObject): + + def _hash(self): + return 123 + + class BadHashableObjectImplem3b(BadHashableObjectImplem3a, HashableObject): + + def _hash(self): + return self.parent_hash() + + obj3 = BadHashableObjectImplem3b() + + + with self.assertRaisesRegex(RuntimeError, 'object parent is not a native type'): + + h = hash(obj3) + + + class BadHashableObjectImplem4(GObject.Object, HashableObject): + + def _hash(self): + raise Exception('error') + + obj4 = BadHashableObjectImplem4() + + + with self.assertRaisesRegex(Exception, 'error'): + + h = hash(obj4) diff --git a/tests/glibext/objhole.py b/tests/glibext/objhole.py new file mode 100644 index 0000000..6a7c2e8 --- /dev/null +++ b/tests/glibext/objhole.py @@ -0,0 +1,31 @@ + +from chrysacase import ChrysalideTestCase +from pychrysalide.glibext import ThickObject + + +class TestWorks(ChrysalideTestCase): + """TestCase for pychrysalide.glibext.BinaryPortion""" + + def testExtraAccess(self): + """Access to various definitions of the extra data for ThickObject.""" + + obj = ThickObject() + + self.assertEqual(obj.extra, 0) + + obj.extra = 0xffffffe0 + + self.assertEqual(obj.extra, 0xffffffe0) + + obj.extra = 0x00123000 + + self.assertEqual(obj.extra, 0x00123000) + + + def testRservedBits(self): + """Check space leaved as available by the GLib.""" + + obj = ThickObject() + + self.assertTrue(obj._GOBJECT_RESERVED_EXTRA_BITS > 0) + self.assertTrue(obj._GOBJECT_RESERVED_EXTRA_BITS < 32) diff --git a/tests/glibext/portion.py b/tests/glibext/portion.py new file mode 100644 index 0000000..3e91969 --- /dev/null +++ b/tests/glibext/portion.py @@ -0,0 +1,17 @@ + +from chrysacase import ChrysalideTestCase +from pychrysalide.arch import vmpa +from pychrysalide.glibext import BinaryPortion + + +class TestWorks(ChrysalideTestCase): + """TestCase for pychrysalide.glibext.BinaryPortion""" + + def testBasicBinaryPortion(self): + """Implement a basic binary portion.""" + + addr = vmpa(0, vmpa.VmpaSpecialValue.NO_VIRTUAL) + + p = BinaryPortion(addr, 10) + + self.assertEqual(p.range.addr, addr) diff --git a/tests/glibext/re.chrysalide.tests.secstorage.gschema.xml b/tests/glibext/re.chrysalide.tests.secstorage.gschema.xml new file mode 100644 index 0000000..6afa96b --- /dev/null +++ b/tests/glibext/re.chrysalide.tests.secstorage.gschema.xml @@ -0,0 +1,15 @@ +<schemalist> + + <schema id="re.chrysalide.tests.secstorage" path="/re/chrysalide/tests/secstorage/"> + + <key name="salt" type="ay"> + <default>[]</default> + </key> + + <key name="master" type="ay"> + <default>[]</default> + </key> + + </schema> + +</schemalist> diff --git a/tests/glibext/secstorage.py b/tests/glibext/secstorage.py new file mode 100644 index 0000000..5b8f680 --- /dev/null +++ b/tests/glibext/secstorage.py @@ -0,0 +1,153 @@ + +import gi +import os +import subprocess + +from chrysacase import ChrysalideTestCase +from gi.repository import Gio, GLib +from pychrysalide.glibext import SecretStorage + + +class TestSecretStorage(ChrysalideTestCase): + """TestCase for secret storage features.""" + + @classmethod + def setUpClass(cls): + + super(TestSecretStorage, cls).setUpClass() + + cls.log('Creating GSettings schema...') + + path = os.path.dirname(os.path.realpath(__file__)) + + subprocess.run([ 'glib-compile-schemas', path ]) + + + @classmethod + def tearDownClass(cls): + + super(TestSecretStorage, cls).tearDownClass() + + cls.log('Removing compiled GSettings schema...') + + path = os.path.dirname(os.path.realpath(__file__)) + + filename = os.path.join(path, 'gschemas.compiled') + + if os.path.exists(filename): + os.remove(filename) + + + def _get_settings(self, sid): + """Provide local GSettings instance.""" + + path = os.path.dirname(os.path.realpath(__file__)) + + source = Gio.SettingsSchemaSource.new_from_directory(path, None, True) + + schema = Gio.SettingsSchemaSource.lookup(source, sid, False) + + settings = Gio.Settings.new_full(schema, None, None) + + return settings + + + def testMasterKeyDefinition(self): + """Check for cryptographic parameters for secret storage.""" + + settings = self._get_settings('re.chrysalide.tests.secstorage') + + storage = SecretStorage(settings) + + settings.reset('master') + + self.assertEqual(len(settings.get_value('master').unpack()), 0) + + self.assertFalse(storage.has_key) + + settings.set_value('master', GLib.Variant('ay', b'ABC')) + + self.assertFalse(storage.has_key) + + settings.set_value('master', GLib.Variant('ay', b'A' * 23)) + + self.assertTrue(storage.has_key) + + + def testMasterKeyCreation(self): + """Create and update cryptographic parameters for secret storage.""" + + settings = self._get_settings('re.chrysalide.tests.secstorage') + + storage = SecretStorage(settings) + + settings.reset('salt') + settings.reset('master') + + self.assertFalse(storage.has_key) + + status = storage.set_password('') + + self.assertTrue(status); + + self.assertTrue(storage.has_key) + self.assertTrue(storage.is_locked) + + status = storage.unlock('') + + self.assertTrue(status) + + self.assertFalse(storage.is_locked) + + storage.lock() + + self.assertTrue(storage.is_locked) + + status = storage.unlock('XXX') + + self.assertFalse(status) + + self.assertTrue(storage.is_locked) + + + def testDataEncryption(self): + """Encrypt and decrypt data with the secret storage.""" + + settings = self._get_settings('re.chrysalide.tests.secstorage') + + storage = SecretStorage(settings) + + settings.reset('salt') + settings.reset('master') + + status = storage.set_password('<s3cUre>') + + self.assertTrue(status); + + status = storage.unlock('<s3cUre>') + + self.assertTrue(status) + + + original = b'ABC' + + encrypted = storage.encrypt_data(original) + + self.assertIsNotNone(encrypted) + + plain = storage.decrypt_data(encrypted) + + self.assertIsNotNone(plain) + self.assertEqual(original, plain) + + + original = b'A' * 136 + + encrypted = storage.encrypt_data(original) + + self.assertIsNotNone(encrypted) + + plain = storage.decrypt_data(encrypted) + + self.assertIsNotNone(plain) + self.assertEqual(original, plain) diff --git a/tests/glibext/singleton.py b/tests/glibext/singleton.py index 4588ae5..712aece 100644 --- a/tests/glibext/singleton.py +++ b/tests/glibext/singleton.py @@ -1,21 +1,24 @@ +import gi + from chrysacase import ChrysalideTestCase from gi.repository import GObject -from pychrysalide.glibext import SingletonCandidate, SingletonFactory +from pychrysalide.glibext import ComparableObject, HashableObject, SingletonCandidate, SingletonFactory class TestSingleton(ChrysalideTestCase): """Test cases for pychrysalide.glibext.SingletonFactory.""" - def testSingletonCreation(self): - """Create singleton objects.""" + def testSingletonCandidateCreation(self): + """Create objects with SingletonCandidate interface.""" with self.assertRaisesRegex(NotImplementedError, 'SingletonCandidate can not be constructed'): sc = SingletonCandidate() - class NewSingletonImplem(GObject.Object, SingletonCandidate): + + class NewSingletonImplem(gi._gi.GObject, HashableObject, ComparableObject, SingletonCandidate): pass nsi = NewSingletonImplem() @@ -23,121 +26,132 @@ class TestSingleton(ChrysalideTestCase): self.assertIsNotNone(nsi) - def testFactoryCreation(self): - """Create singleton factories.""" + class NewSingletonImplem2(GObject.Object, HashableObject, ComparableObject, SingletonCandidate): + pass + + nsi2 = NewSingletonImplem2() - sf = SingletonFactory() + self.assertIsNotNone(nsi2) - self.assertIsNotNone(sf) - class MyFactory(SingletonFactory): - pass + # def testFactoryCreation(self): + # """Create singleton factories.""" + + # sf = SingletonFactory() + + # self.assertIsNotNone(sf) + + # class MyFactory(SingletonFactory): + # pass + + # msf = MyFactory() + + # self.assertIsNotNone(msf) + - msf = MyFactory() - self.assertIsNotNone(msf) - def testSingletonMethods(self): - """Test the singleton methods.""" + # def testSingletonMethods(self): + # """Test the singleton methods.""" - class IntegerCacheImplem(GObject.Object, SingletonCandidate): + # class IntegerCacheImplem(GObject.Object, SingletonCandidate): - def __init__(self, val): - super().__init__() - self._val = val + # def __init__(self, val): + # super().__init__() + # self._val = val - def _list_inner_instances(self): - return () + # def _list_inner_instances(self): + # return () - def __hash__(self): - return hash('common-key') + # def __hash__(self): + # return hash('common-key') - def __eq__(self, other): - return self._val == other._val + # def __eq__(self, other): + # return self._val == other._val - val_0 = IntegerCacheImplem(0) - val_0_bis = IntegerCacheImplem(0) - val_1 = IntegerCacheImplem(1) + # val_0 = IntegerCacheImplem(0) + # val_0_bis = IntegerCacheImplem(0) + # val_1 = IntegerCacheImplem(1) - self.assertEqual(hash(val_0), hash(val_0_bis)) - self.assertEqual(hash(val_0), hash(val_1)) + # self.assertEqual(hash(val_0), hash(val_0_bis)) + # self.assertEqual(hash(val_0), hash(val_1)) - self.assertEqual(val_0.hash(), val_0_bis.hash()) - self.assertEqual(val_0.hash(), val_1.hash()) + # self.assertEqual(val_0.hash(), val_0_bis.hash()) + # self.assertEqual(val_0.hash(), val_1.hash()) - self.assertTrue(val_0 == val_0_bis) - self.assertFalse(val_0 == val_1) + # self.assertTrue(val_0 == val_0_bis) + # self.assertFalse(val_0 == val_1) - def testSingletonFootprint(self): - """Check for singleton memory footprint.""" + # def testSingletonFootprint(self): + # """Check for singleton memory footprint.""" - sf = SingletonFactory() + # sf = SingletonFactory() - class IntegerCacheImplem(GObject.Object, SingletonCandidate): + # class IntegerCacheImplem(GObject.Object, SingletonCandidate): - def __init__(self, val): - super().__init__() - self._val = val + # def __init__(self, val): + # super().__init__() + # self._val = val - def _list_inner_instances(self): - return () + # def _list_inner_instances(self): + # return () - def __hash__(self): - return hash('common-key') + # def __hash__(self): + # return hash('common-key') - def __eq__(self, other): - return self._val == other._val + # def __eq__(self, other): + # return self._val == other._val - def _set_read_only(self): - pass + # def _set_read_only(self): + # pass - val_0 = IntegerCacheImplem(0) - val_0_bis = IntegerCacheImplem(0) - val_1 = IntegerCacheImplem(1) + # val_0 = IntegerCacheImplem(0) + # val_0_bis = IntegerCacheImplem(0) + # val_1 = IntegerCacheImplem(1) - obj = sf.get_instance(val_0) + # obj = sf.get_instance(val_0) - self.assertTrue(obj is val_0) + # self.assertTrue(obj is val_0) - obj = sf.get_instance(val_0_bis) + # obj = sf.get_instance(val_0_bis) - self.assertTrue(obj is val_0) + # self.assertTrue(obj is val_0) - obj = sf.get_instance(val_1) + # obj = sf.get_instance(val_1) - self.assertTrue(obj is val_1) + # self.assertTrue(obj is val_1) - self.assertEqual(len(obj.inner_instances), 0) + # self.assertEqual(len(obj.inner_instances), 0) - class MasterCacheImplem(GObject.Object, SingletonCandidate): + # class MasterCacheImplem(GObject.Object, SingletonCandidate): - def __init__(self, children): - super().__init__() - self._children = children + # def __init__(self, children): + # super().__init__() + # self._children = children - def _list_inner_instances(self): - return self._children + # def _list_inner_instances(self): + # return self._children - def _update_inner_instances(self, instances): - self._children = instances + # def _update_inner_instances(self, instances): + # self._children = instances - def __hash__(self): - return hash('master-key') + # def __hash__(self): + # return hash('master-key') - def __eq__(self, other): - return False + # def __eq__(self, other): + # return False - def _set_read_only(self): - pass + # def _set_read_only(self): + # pass - master = MasterCacheImplem(( val_0_bis, val_1 )) + # master = MasterCacheImplem(( val_0_bis, val_1 )) - obj = sf.get_instance(master) + # obj = sf.get_instance(master) - self.assertTrue(obj.inner_instances[0] is val_0) + # self.assertTrue(obj.inner_instances[0] is val_0) - self.assertTrue(obj.inner_instances[1] is val_1) + # self.assertTrue(obj.inner_instances[1] is val_1) diff --git a/tests/glibext/storage.py b/tests/glibext/storage.py new file mode 100644 index 0000000..b60377a --- /dev/null +++ b/tests/glibext/storage.py @@ -0,0 +1,74 @@ + +from chrysacase import ChrysalideTestCase +from pychrysalide.glibext import ObjectStorage, SerializableObject +import gi +import os +import tempfile + + +class TestObjectStorage(ChrysalideTestCase): + """TestCase for analysis.storage.""" + + @classmethod + def setUpClass(cls): + + super(TestObjectStorage, cls).setUpClass() + + _, cls._tmp_filename = tempfile.mkstemp() + + cls.log('Using temporary filename "%s"' % cls._tmp_filename) + + + @classmethod + def tearDownClass(cls): + + super(TestObjectStorage, cls).tearDownClass() + + cls.log('Delete filename "%s"' % cls._tmp_filename) + + os.unlink(cls._tmp_filename) + + + def testGenericStorage(self): + """Store and load basic objects.""" + + + class SimpleObject(gi._gi.GObject, SerializableObject): + + def __init__(self, b=None): + super().__init__() + self._b = b + + def _load(self, storage, fd): + assert(self._b is None) + self._b = os.read(fd, 1)[0] + return True + + def _store(self, storage, fd): + os.write(fd, bytes([ self._b ])) + return True + + def __eq__(self, other): + return self._b == other._b + + + # Store + + storage = ObjectStorage('TestStorage', 0, 'my-storage-hash') + self.assertIsNotNone(storage) + + so = SimpleObject(0x23) + + pos = storage.store_object('simple', so) + self.assertIsNotNone(pos) + + status = storage.store(self._tmp_filename) + self.assertTrue(status) + + # Reload + + storage2 = ObjectStorage.load(self._tmp_filename) + + so2 = storage2.load_object('simple', pos) + + self.assertEqual(so, so2) diff --git a/tests/glibext/strbuilder.py b/tests/glibext/strbuilder.py new file mode 100644 index 0000000..72fd5c2 --- /dev/null +++ b/tests/glibext/strbuilder.py @@ -0,0 +1,114 @@ + +import gi + +from chrysacase import ChrysalideTestCase +from gi.repository import GObject +from pychrysalide.glibext import StringBuilder + + +class TestStringBuilder(ChrysalideTestCase): + """Test cases for pychrysalide.glibext.StringBuilder.""" + + + def ZZZtestStringBuilderCreation(self): + """Create objects with StringBuilder interface.""" + + with self.assertRaisesRegex(NotImplementedError, 'StringBuilder can not be constructed'): + + sb = StringBuilder() + + + class NewStringBuilderImplem(gi._gi.GObject, StringBuilder): + pass + + nsb = NewStringBuilderImplem() + + self.assertIsNotNone(nsb) + + + class NewStringBuilderImplem2(GObject.Object, StringBuilder): + pass + + nsb2 = NewStringBuilderImplem() + + self.assertIsNotNone(nsb2) + + + def ZZZtestStringBuilderMethods(self): + """Test the StringBuilder methods.""" + + class BasicStringBuilderImplem(GObject.Object, StringBuilder): + + def __init__(self, desc): + super().__init__() + self._desc = desc + + def _to_string(self, flags=0): + return self._desc + + desc = 'simple_desc' + + sb = BasicStringBuilderImplem(desc) + + self.assertEqual(sb.to_string(), desc) + self.assertEqual(str(sb), desc) + self.assertEqual(f'{sb}', desc) + + + def testStringBuilderExceptions(self): + """Raise exceptions from the StringBuilder interface as expected.""" + + + class BadStringBuilderImplem(GObject.Object, StringBuilder): + pass + + obj = BadStringBuilderImplem() + + + with self.assertRaisesRegex(NotImplementedError, "method implementation is missing for '_to_string'"): + + s = str(obj) + + + class BadStringBuilderImplem2(GObject.Object, StringBuilder): + + def _to_string(self, flags=0): + return 0xcafe + + obj2 = BadStringBuilderImplem2() + + + with self.assertRaisesRegex(TypeError, 'object description has to get provided as an UTF-8 string value'): + + s = str(obj2) + + + class BadStringBuilderImplem3a(GObject.Object, StringBuilder): + + def _to_string(self, flags=0): + return 'desc' + + class BadStringBuilderImplem3b(BadStringBuilderImplem3a, StringBuilder): + + def _to_string(self, flags=0): + return self.parent_to_string() + + obj3 = BadStringBuilderImplem3b() + + + with self.assertRaisesRegex(RuntimeError, 'object parent is not a native type'): + + s = str(obj3) + + + class BadStringBuilderImplem4(GObject.Object, StringBuilder): + + def _to_string(self, flags=0): + raise Exception('error') + + obj4 = BadStringBuilderImplem4() + + + with self.assertRaisesRegex(Exception, 'error'): + + s = str(obj4) diff --git a/tests/glibext/work.py b/tests/glibext/work.py new file mode 100644 index 0000000..808e25e --- /dev/null +++ b/tests/glibext/work.py @@ -0,0 +1,26 @@ + +from chrysacase import ChrysalideTestCase +from pychrysalide.glibext import GenericWork + + +class TestWorks(ChrysalideTestCase): + """TestCase for glibext.GenericWork""" + + def testBasicWorkImplementation(self): + """Implement a basic work.""" + + class BasicWork(GenericWork): + def __init__(self, lst): + super(BasicWork, self).__init__() + self._lst = lst + def _run(self): + self._lst.append('done') + + test = [] + + work = BasicWork(test) + + work.process() + + self.assertEqual(len(test), 1) + self.assertEqual(test[0], 'done') diff --git a/tests/glibext/workqueue.py b/tests/glibext/workqueue.py new file mode 100644 index 0000000..203970b --- /dev/null +++ b/tests/glibext/workqueue.py @@ -0,0 +1,49 @@ + +from chrysacase import ChrysalideTestCase +from pychrysalide.glibext import GenericWork, WorkQueue +from threading import Lock + + +class TestWorks(ChrysalideTestCase): + """TestCase for glibext.*Work*""" + + def testBasicWorkQueueBehaviour(self): + """Check the default basic behaviour of a work queue.""" + + queue = WorkQueue() + + ret = queue.is_empty(123) + self.assertTrue(ret) + + + def testWorkScheduling(self): + """Check scheduled works results.""" + + class SchedulableWork(GenericWork): + def __init__(self, lck, val): + super(SchedulableWork, self).__init__() + self._lck = lck + self._val = val + def _run(self): + self._lck.acquire() + self._val['integer'] += 1 + self._lck.release() + + lock = Lock() + value = { 'integer': 0 } + + queue = WorkQueue() + + gid = queue.define_group(4) + + count = 31 + + for i in range(count): + + work = SchedulableWork(lock, value) + queue.schedule(work, gid) + + while not(queue.wait_for_completion(gid)): + pass + + self.assertEqual(value['integer'], count) diff --git a/tests/plugins/plugin.py b/tests/plugins/plugin.py index 6409975..9015409 100644 --- a/tests/plugins/plugin.py +++ b/tests/plugins/plugin.py @@ -1,223 +1,96 @@ -#!/usr/bin/python3-dbg -# -*- coding: utf-8 -*- - from chrysacase import ChrysalideTestCase -from pychrysalide import PluginModule -import gc +from pychrysalide.plugins import PluginModule class TestPlugin(ChrysalideTestCase): """TestCase for GPluginModule.""" - def testGarbageCollecting(self): - """Ensure the garbarge collector is working for plugin modules.""" - + def testPluginInfoImplementations(self): + """Retrieve plugin basic information provided by __init__().""" - class MyPG_1(PluginModule): + class MyPlugin(PluginModule): def __init__(self): + super().__init__('custom-name', desc='custom-desc', url='custom-url', version='0.0.1') - interface = { - 'name' : 'some_name', - 'desc' : 'Provide some information about the useless plugin', - 'version' : '0.1', - 'actions' : ( ) - } + my = MyPlugin() - super(MyPG_1, self).__init__(**interface) + self.assertEqual(my.name, 'custom-name') + self.assertEqual(my.desc, 'custom-desc') + self.assertEqual(my.version, '0.0.1') + self.assertEqual(my.url, 'custom-url') - pg = MyPG_1() - self.assertIsNotNone(pg) + def testPluginMethodImplementations(self): + """Ensure required plugins abstract methods can be implemented.""" - - class MyPG_2(PluginModule): + class MyWrongPlugin(PluginModule): def __init__(self): + super().__init__('pg-name') - interface = { - 'name' : 'some_name', - 'desc' : 'Provide some information about the useless plugin', - 'version' : '0.1', - 'actions' : ( ) - } - - super(MyPG_2, self).__init__(**interface) + my = MyWrongPlugin() + with self.assertRaisesRegex(NotImplementedError, "method implementation is missing for '_get_filename'"): + print(my.filename) - pg = MyPG_2() - self.assertIsNotNone(pg) + with self.assertRaisesRegex(NotImplementedError, "method implementation is missing for '_get_modname'"): + print(my.modname) - class MyPG_3(PluginModule): + class MyPlugin(PluginModule): def __init__(self): + super().__init__('pg-name') - interface = { - 'name' : 'some_name', - 'desc' : 'Provide some information about the useless plugin', - 'version' : '0.1', - 'actions' : ( ) - } + def _get_filename(self): + return 'file-name' - super(MyPG_3, self).__init__(**interface) + def _get_modname(self): + return 'mod-name' + my = MyPlugin() - pg = MyPG_3() - self.assertIsNotNone(pg) + self.assertEqual(my.filename, 'file-name') + self.assertEqual(my.modname, 'mod-name') - class MyPG_4(PluginModule): - - def __init__(self): - - interface = { - 'name' : 'some_name', - 'desc' : 'Provide some information about the useless plugin', - 'version' : '0.1', - 'actions' : ( ) - } - super(MyPG_4, self).__init__(**interface) + def testPluginRequirementIncludePython(self): + """Ensure that plugin requirements include the Python bindings.""" - - pg = MyPG_4() - self.assertIsNotNone(pg) - - - class MyPG_5(PluginModule): + class MyPlugin(PluginModule): def __init__(self): + super().__init__('pg-name') - interface = { - 'name' : 'some_name', - 'desc' : 'Provide some information about the useless plugin', - 'version' : '0.1', - 'actions' : ( ) - } - - super(MyPG_5, self).__init__(**interface) - - - pg = MyPG_5() - self.assertIsNotNone(pg) + my = MyPlugin() + self.assertEqual(len(my.requirements), 1) + self.assertEqual(my.requirements[0], 'PyChrysalide') - gc.collect() + def testPluginRequirementUniqueness(self): + """Ensure that plugin requirements are unique.""" - def testCreation(self): - """Validate PluginModule creation from Python.""" - - - class MyPG_0(PluginModule): - pass - - - # TypeError: Required argument 'name' (pos 1) not found - with self.assertRaises(TypeError): - pg = MyPG_0() - - - class MyPG_1(PluginModule): + class MyPlugin(PluginModule): def __init__(self): + super().__init__('pg-name', required=[ 'PyChrysalide' ]) - interface = { - 'name' : 'some_name', - 'desc' : 'Provide some information about the useless plugin', - 'version' : '0.1', - 'actions' : ( ) - } - - super(MyPG_1, self).__init__(**interface) + my = MyPlugin() + self.assertEqual(my.requirements.count('PyChrysalide'), 1) - pg = MyPG_1() - self.assertIsNotNone(pg) - - class MyPG_2(PluginModule): + class MyPlugin2(PluginModule): def __init__(self): + super().__init__('pg-name2', required=[ 'AA', 'BB', 'AA' ]) - interface = { - 'name' : 'some_name', - 'desc' : 'Provide some information about the useless plugin', - 'version' : '0.1', - 'actions' : ( 'ABC', ) - } - - super(MyPG_2, self).__init__(**interface) - - - # TypeError: Invalid type for plugin action. - with self.assertRaises(TypeError): - pg = MyPG_2() - - - class MyPG_3(PluginModule): - - def __init__(self): - - interface = { - 'name' : 'some_name', - 'desc' : 'Provide some information about the useless plugin', - 'version' : '0.1', - 'actions' : ( PluginModule.PGA_CONTENT_EXPLORER, ) - } - - super(MyPG_3, self).__init__(**interface) - - - # TypeError: missing features for the declared actions. - with self.assertRaises(TypeError): - pg = MyPG_3() - - - class MyPG_4(PluginModule): - - def __init__(self): - - interface = { - 'name' : 'some_name4', - 'desc' : 'Provide some information about the useless plugin', - 'version' : '0.1', - 'actions' : ( PluginModule.PGA_CONTENT_EXPLORER, ) - } - - super(MyPG_4, self).__init__(**interface) - - def handle_binary_content(self, action, content, wid, status): - pass - - - pg = MyPG_4() - self.assertIsNotNone(pg) - - - def testDoubleUsage(self): - """Validate PluginModule double usage in Python.""" - - - class MyPG(PluginModule): - - def __init__(self): - - interface = { - 'name' : 'some_name', - 'desc' : 'Provide some information about the useless plugin', - 'version' : '0.1', - 'actions' : ( ) - } - - super(MyPG, self).__init__(**interface) - - - pg1 = MyPG() - self.assertIsNotNone(pg1) + my = MyPlugin2() - pg2 = MyPG() - self.assertIsNotNone(pg2) + self.assertEqual(my.requirements.count('AA'), 1) + self.assertEqual(my.requirements.count('PyChrysalide'), 1) diff --git a/tests/plugins/python.py b/tests/plugins/python.py new file mode 100644 index 0000000..1a3dd97 --- /dev/null +++ b/tests/plugins/python.py @@ -0,0 +1,27 @@ + +from chrysacase import ChrysalideTestCase +from pychrysalide.plugins import PythonPlugin + + +class TestPlugin(ChrysalideTestCase): + """TestCase for GPythonPlugin.""" + + + def testPluginInfoImplementations(self): + """Retrieve plugin basic information provided by __init__().""" + + class MyPlugin(PythonPlugin): + """Custom description.""" + + def __init__(self): + super().__init__(__file__, '0.0.1', 'custom-url') + + my = MyPlugin() + + self.assertEqual(my.name, 'MyPlugin') + self.assertEqual(my.desc, 'Custom description.') + self.assertEqual(my.version, '0.0.1') + self.assertEqual(my.url, 'custom-url') + + self.assertEqual(my.filename, __file__) + self.assertEqual(my.modname, 'python') |