diff options
Diffstat (limited to 'tests')
-rw-r--r-- | tests/analysis/storage/storage.py | 81 | ||||
-rw-r--r-- | tests/arch/immediate.py | 62 | ||||
-rw-r--r-- | tests/arch/instruction.py | 52 | ||||
-rw-r--r-- | tests/arch/operand.py | 72 | ||||
-rw-r--r-- | tests/arch/operands/immediate.py | 86 | ||||
-rw-r--r-- | tests/common/leb128.py | 80 | ||||
-rw-r--r-- | tests/constval.py | 44 | ||||
-rw-r--r-- | tests/format/program.py | 9 | ||||
-rw-r--r-- | tests/glibext/comparable.py | 132 | ||||
-rw-r--r-- | tests/glibext/configuration.py | 106 | ||||
-rw-r--r-- | tests/glibext/hashable.py | 190 | ||||
-rw-r--r-- | tests/glibext/objhole.py | 31 | ||||
-rw-r--r-- | tests/glibext/re.chrysalide.tests.secstorage.gschema.xml | 15 | ||||
-rw-r--r-- | tests/glibext/secstorage.py | 153 | ||||
-rw-r--r-- | tests/glibext/singleton.py | 166 | ||||
-rw-r--r-- | tests/glibext/storage.py | 74 | ||||
-rw-r--r-- | tests/glibext/strbuilder.py | 114 | ||||
-rw-r--r-- | tests/plugins/plugin.py | 219 | ||||
-rw-r--r-- | tests/plugins/python.py | 27 |
19 files changed, 1119 insertions, 594 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/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/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/program.py b/tests/format/program.py index 7a649b8..7027cdf 100644 --- a/tests/format/program.py +++ b/tests/format/program.py @@ -1,6 +1,7 @@ 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 @@ -17,12 +18,16 @@ class TestProgramFormat(ChrysalideTestCase): 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() + cf = CustomFormat(cnt) self.assertEqual(cf.endianness, SourceEndian.BIG) @@ -30,7 +35,7 @@ class TestProgramFormat(ChrysalideTestCase): class EmptyCustomFormat(ProgramFormat): pass - cf = EmptyCustomFormat() + cf = EmptyCustomFormat(cnt) self.assertEqual(cf.endianness, SourceEndian.LITTLE) 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/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/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') |