summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/analysis/storage/storage.py81
-rw-r--r--tests/arch/immediate.py62
-rw-r--r--tests/arch/instruction.py52
-rw-r--r--tests/arch/operand.py72
-rw-r--r--tests/arch/operands/immediate.py86
-rw-r--r--tests/common/bitfield.py22
-rw-r--r--tests/common/leb128.py80
-rw-r--r--tests/common/xdg.py71
-rw-r--r--tests/constval.py44
-rw-r--r--tests/format/executable.py59
-rw-r--r--tests/format/flat.py34
-rw-r--r--tests/format/format.py66
-rw-r--r--tests/format/known.py73
-rw-r--r--tests/format/program.py89
-rw-r--r--tests/glibext/comparable.py132
-rw-r--r--tests/glibext/configuration.py106
-rw-r--r--tests/glibext/hashable.py190
-rw-r--r--tests/glibext/objhole.py31
-rw-r--r--tests/glibext/portion.py17
-rw-r--r--tests/glibext/re.chrysalide.tests.secstorage.gschema.xml15
-rw-r--r--tests/glibext/secstorage.py153
-rw-r--r--tests/glibext/singleton.py166
-rw-r--r--tests/glibext/storage.py74
-rw-r--r--tests/glibext/strbuilder.py114
-rw-r--r--tests/glibext/work.py26
-rw-r--r--tests/glibext/workqueue.py49
-rw-r--r--tests/plugins/plugin.py219
-rw-r--r--tests/plugins/python.py27
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')