changeset e032ae3f4015 in tryton:default
details: https://hg.tryton.org/tryton?cmd=changeset;node=e032ae3f4015
description:
        Add unittest

        issue8905
        review258541002
diffstat:

 .drone.yml                                |    25 +
 setup.py                                  |     1 +
 tox.ini                                   |     6 +
 tryton/common/domain_parser.py            |   973 ------------------------
 tryton/common/selection.py                |     7 -
 tryton/common/timedelta.py                |    49 -
 tryton/tests/__init__.py                  |    29 +
 tryton/tests/test_common_domain_parser.py |  1146 +++++++++++++++++++++++++++++
 tryton/tests/test_common_selection.py     |    18 +
 tryton/tests/test_common_timedelta.py     |    54 +
 10 files changed, 1279 insertions(+), 1029 deletions(-)

diffs (2418 lines):

diff -r a70914b38278 -r e032ae3f4015 .drone.yml
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/.drone.yml        Wed Dec 18 22:41:05 2019 +0100
@@ -0,0 +1,25 @@
+clone:
+    hg:
+        image: plugins/hg
+        environment:
+            - HG_SHARE_POOL=/root/.cache/hg
+        volumes:
+            - cache:/root/.cache
+
+pipeline:
+    tox:
+        image: ${IMAGE}
+        commands:
+            - pip install tox
+            - tox -e "${TOXENV}"
+        volumes:
+             - cache:/root/.cache
+
+matrix:
+    include:
+        - IMAGE: tryton/tryton-test:3.5
+          TOXENV: py35
+        - IMAGE: tryton/tryton-test:3.6
+          TOXENV: py36
+        - IMAGE: tryton/tryton-test:3.7
+          TOXENV: py37
diff -r a70914b38278 -r e032ae3f4015 setup.py
--- a/setup.py  Wed Dec 18 21:43:09 2019 +0100
+++ b/setup.py  Wed Dec 18 22:41:05 2019 +0100
@@ -144,5 +144,6 @@
         'calendar': ['GooCalendar>=0.5'],
         },
     zip_safe=False,
+    test_suite='tryton.tests',
     **args
     )
diff -r a70914b38278 -r e032ae3f4015 tox.ini
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/tox.ini   Wed Dec 18 22:41:05 2019 +0100
@@ -0,0 +1,6 @@
+[tox]
+envlist = py35,py36,py37
+
+[testenv]
+commands = {envpython} setup.py test
+install_command = pip install --pre --find-links https://trydevpi.tryton.org/ 
{opts} {packages}
diff -r a70914b38278 -r e032ae3f4015 tryton/common/domain_parser.py
--- a/tryton/common/domain_parser.py    Wed Dec 18 21:43:09 2019 +0100
+++ b/tryton/common/domain_parser.py    Wed Dec 18 22:41:05 2019 +0100
@@ -98,14 +98,6 @@
         yield cur
 
 
-def test_group_operator():
-    assert list(group_operator(iter(['a', '>', '=']))) == ['a', '>=']
-    assert list(group_operator(iter(['>', '=', 'b']))) == ['>=', 'b']
-    assert list(group_operator(iter(['a', '=', 'b']))) == ['a', '=', 'b']
-    assert list(group_operator(iter(['a', '>', '=', 'b']))) == ['a', '>=', 'b']
-    assert list(group_operator(iter(['a', '>', '=', '=']))) == ['a', '>=', '=']
-
-
 def likify(value):
     "Add % if needed"
     if not value:
@@ -131,13 +123,6 @@
     return value
 
 
-def test_quote():
-    assert quote('test') == 'test'
-    assert quote('foo bar') == '"foo bar"'
-    assert quote('"foo"') == '\\\"foo\\\"'
-    assert quote('foo\\bar') == 'foo\\\\bar'
-
-
 def ending_clause(domain, deep=0):
     "Return the ending clause"
     if not domain:
@@ -210,25 +195,6 @@
     return target, value
 
 
-def test_split_target_value():
-    field = {
-        'type': 'reference',
-        'selection': [
-            ('spam', 'Spam'),
-            ('ham', 'Ham'),
-            ('e', 'Eggs'),
-            ]
-        }
-    for value, result in (
-            ('Spam', (None, 'Spam')),
-            ('foo', (None, 'foo')),
-            ('Spam,', ('spam', '')),
-            ('Ham,bar', ('ham', 'bar')),
-            ('Eggs,foo', ('e', 'foo')),
-            ):
-        assert split_target_value(field, value) == result
-
-
 def convert_value(field, value, context=None):
     "Convert value for field"
     if context is None:
@@ -320,168 +286,6 @@
     return converts.get(field['type'], lambda: value)()
 
 
-def test_convert_boolean():
-    field = {
-        'type': 'boolean',
-        }
-    for value, result in (
-            ('Y', True),
-            ('yes', True),
-            ('t', True),
-            ('1', True),
-            ('N', False),
-            ('False', False),
-            ('no', False),
-            ('0', False),
-            (None, None),
-            ):
-        assert convert_value(field, value) == result
-
-
-def test_convert_float():
-    field = {
-        'type': 'float',
-        }
-    for value, result in (
-            ('1', 1.0),
-            ('1.5', 1.5),
-            ('', None),
-            ('test', None),
-            (None, None),
-            ):
-        assert convert_value(field, value) == result
-
-
-def test_convert_float_factor():
-    field = {
-        'type': 'float',
-        'factor': '100',
-        }
-    assert convert_value(field, '42') == 0.42
-
-
-def test_convert_integer():
-    field = {
-        'type': 'integer',
-        }
-    for value, result in (
-            ('1', 1),
-            ('1.5', 1),
-            ('', None),
-            ('test', None),
-            (None, None),
-            ):
-        assert convert_value(field, value) == result
-
-
-def test_convert_integer_factor():
-    field = {
-        'type': 'integer',
-        'factor': '2',
-        }
-    assert convert_value(field, '6') == 3
-
-
-def test_convert_numeric():
-    field = {
-        'type': 'numeric',
-        }
-    for value, result in (
-            ('1', Decimal(1)),
-            ('1.5', Decimal('1.5')),
-            ('', None),
-            ('test', None),
-            (None, None),
-            ):
-        assert convert_value(field, value) == result
-
-
-def test_convert_numeric_factor():
-    field = {
-        'type': 'numeric',
-        'factor': '5',
-        }
-    assert convert_value(field, '1') == Decimal('0.2')
-
-
-def test_convert_selection():
-    field = {
-        'type': 'selection',
-        'selection': [
-            ('male', 'Male'),
-            ('female', 'Female'),
-            ],
-        }
-    field_with_empty = field.copy()
-    field_with_empty['selection'] = (field_with_empty['selection']
-        + [('', '')])
-    for value, result in (
-            ('Male', 'male'),
-            ('male', 'male'),
-            ('test', 'test'),
-            (None, None),
-            ('', ''),
-            ):
-        assert convert_value(field, value) == result
-        assert convert_value(field_with_empty, value) == result
-
-
-def test_convert_datetime():
-    field = {
-        'type': 'datetime',
-        'format': '"%H:%M:%S"',
-        }
-    for value, result in (
-            ('12/04/2002', untimezoned_date(datetime.datetime(2002, 12, 4))),
-            ('12/04/2002 12:30:00', untimezoned_date(
-                    datetime.datetime(2002, 12, 4, 12, 30))),
-            ('02/03/04', untimezoned_date(datetime.datetime(2004, 2, 3))),
-            ('02/03/04 05:06:07', untimezoned_date(
-                    datetime.datetime(2004, 2, 3, 5, 6, 7))),
-            ('test', None),
-            (None, None),
-            ):
-        assert convert_value(field, value) == result, (value,
-            convert_value(field, value), result)
-
-
-def test_convert_date():
-    field = {
-        'type': 'date',
-        }
-    for value, result in (
-            ('12/04/2002', datetime.date(2002, 12, 4)),
-            ('test', None),
-            (None, None),
-            ):
-        assert convert_value(field, value) == result
-
-
-def test_convert_time():
-    field = {
-        'type': 'time',
-        'format': '"%H:%M:%S"',
-        }
-    for value, result in (
-            ('12:30:00', datetime.time(12, 30, 0)),
-            ('test', None),
-            (None, None),
-            ):
-        assert convert_value(field, value) == result
-
-
-def test_convert_timedelta():
-    field = {
-        'type': 'timedelta',
-        }
-    for value, result in [
-            ('1d 2:00', datetime.timedelta(days=1, hours=2)),
-            ('foo', datetime.timedelta()),
-            (None, None),
-            ]:
-        assert convert_value(field, value) == result
-
-
 def format_value(field, value, target=None, context=None):
     "Format value for field"
     if context is None:
@@ -580,167 +384,6 @@
             lambda: value if value is not None else '')())
 
 
-def test_format_boolean():
-    field = {
-        'type': 'boolean',
-        }
-    for value, result in (
-            (True, 'True'),
-            (False, 'False'),
-            (None, ''),
-            ):
-        assert format_value(field, value) == result
-
-
-def test_format_integer():
-    field = {
-        'type': 'integer',
-        }
-    for value, result in (
-            (1, '1'),
-            (1.5, '1'),
-            (0, '0'),
-            (0.0, '0'),
-            (False, ''),
-            (None, ''),
-            ):
-        assert format_value(field, value) == result
-
-
-def test_format_integer_factor():
-    field = {
-        'type': 'integer',
-        'factor': '2',
-        }
-    assert format_value(field, 3) == '6'
-
-
-def test_format_float():
-    field = {
-        'type': 'float',
-        }
-    for value, result in (
-            (1, '1'),
-            (1.5, '1.5'),
-            (1.50, '1.5'),
-            (150.79, '150.79'),
-            (0, '0'),
-            (0.0, '0'),
-            (False, ''),
-            (None, ''),
-            ):
-        assert format_value(field, value) == result
-
-
-def test_format_float_factor():
-    field = {
-        'type': 'float',
-        'factor': '100',
-        }
-    assert format_value(field, 0.42) == '42'
-
-
-def test_format_numeric():
-    field = {
-        'type': 'numeric',
-        }
-    for value, result in (
-            (Decimal(1), '1'),
-            (Decimal('1.5'), '1.5'),
-            (Decimal('1.50'), '1.5'),
-            (Decimal('150.79'), '150.79'),
-            (Decimal(0), '0'),
-            (Decimal('0.0'), '0'),
-            (False, ''),
-            (None, ''),
-            ):
-        assert format_value(field, value) == result
-
-
-def test_format_numeric_factor():
-    field = {
-        'type': 'numeric',
-        'factor': '5',
-        }
-    assert format_value(field, Decimal('0.2')) == '1'
-
-
-def test_format_selection():
-    field = {
-        'type': 'selection',
-        'selection': [
-            ('male', 'Male'),
-            ('female', 'Female'),
-            ],
-        }
-    field_with_empty = field.copy()
-    field_with_empty['selection'] = (field_with_empty['selection']
-        + [('', '')])
-    for value, result in (
-            ('male', 'Male'),
-            ('test', 'test'),
-            (False, ''),
-            (None, ''),
-            ('', ''),
-            ):
-        assert format_value(field, value) == result
-        assert format_value(field_with_empty, value) == result
-
-
-def test_format_datetime():
-    field = {
-        'type': 'datetime',
-        'format': '"%H:%M:%S"',
-        }
-    for value, result in (
-            (datetime.date(2002, 12, 4), '12/04/2002'),
-            (untimezoned_date(datetime.datetime(2002, 12, 4)), '12/04/2002'),
-            (untimezoned_date(datetime.datetime(2002, 12, 4, 12, 30)),
-                '"12/04/2002 12:30:00"'),
-            (False, ''),
-            (None, ''),
-            ):
-        assert format_value(field, value) == result
-
-
-def test_format_date():
-    field = {
-        'type': 'date',
-        }
-    for value, result in (
-            (datetime.date(2002, 12, 4), '12/04/2002'),
-            (False, ''),
-            (None, ''),
-            ):
-        assert format_value(field, value) == result
-
-
-def test_format_time():
-    field = {
-        'type': 'time',
-        'format': '"%H:%M:%S"',
-        }
-    for value, result in (
-            (datetime.time(12, 30, 0), '"12:30:00"'),
-            (False, ''),
-            (None, ''),
-            ):
-        assert format_value(field, value) == result
-
-
-def test_format_timedelta():
-    field = {
-        'type': 'timedelta',
-        }
-    for value, result in [
-            (datetime.timedelta(days=1, hours=2), '"1d 02:00"'),
-            (datetime.timedelta(), ''),
-            (None, ''),
-            ('', ''),
-            ]:
-        assert format_value(field, value) == result
-
-
 def complete_value(field, value):
     "Complete value for field"
 
@@ -799,68 +442,6 @@
     return completes.get(field['type'], lambda: [])()
 
 
-def test_complete_boolean():
-    field = {
-        'type': 'boolean',
-        }
-    for value, result in [
-            (None, [True, False]),
-            (True, [False]),
-            (False, [True]),
-            ]:
-        assert list(complete_value(field, value)) == result
-
-
-def test_complete_selection():
-    field = {
-        'type': 'selection',
-        'selection': [
-            ('male', 'Male'),
-            ('female', 'Female'),
-            ],
-        }
-    for value, result in (
-            ('m', ['male']),
-            ('test', []),
-            ('', ['male', 'female']),
-            (None, ['male', 'female']),
-            (['male', 'f'], [['male', 'female']]),
-            (['male', None], [['male', 'male'], ['male', 'female']]),
-            ):
-        assert list(complete_value(field, value)) == result
-
-    field_with_empty = field.copy()
-    field_with_empty['selection'] = (field_with_empty['selection']
-        + [('', '')])
-    for value, result in (
-            ('m', ['male']),
-            ('test', []),
-            ('', ['male', 'female', '']),
-            (None, ['male', 'female', '']),
-            (['male', 'f'], [['male', 'female']]),
-            ):
-        assert list(complete_value(field_with_empty, value)) == result
-
-
-def test_complete_reference():
-    field = {
-        'type': 'reference',
-        'selection': [
-            ('spam', 'Spam'),
-            ('ham', 'Ham'),
-            ('', ''),
-            ],
-        }
-    for value, result in (
-            ('s', ['%spam%']),
-            ('test', []),
-            ('', ['%spam%', '%ham%', '%']),
-            (None, ['%spam%', '%ham%', '%']),
-            (['spam', 'h'], [['spam', 'ham']]),
-            ):
-        assert list(complete_value(field, value)) == result
-
-
 def parenthesize(tokens):
     "Nest tokens according to parenthesis"
     for token in tokens:
@@ -872,22 +453,6 @@
             yield token
 
 
-def test_parenthesize():
-    for value, result in (
-            (['a'], ['a']),
-            (['a', 'b'], ['a', 'b']),
-            (['(', 'a', ')'], [['a']]),
-            (['a', 'b', '(', 'c', '(', 'd', 'e', ')', 'f', ')', 'g'],
-                ['a', 'b', ['c', ['d', 'e'], 'f'], 'g']),
-            (['a', 'b', '(', 'c'], ['a', 'b', ['c']]),
-            (['a', 'b', '(', 'c', '(', 'd', 'e', ')', 'f'],
-                ['a', 'b', ['c', ['d', 'e'], 'f']]),
-            (['a', 'b', ')'], ['a', 'b']),
-            (['a', 'b', ')', 'c', ')', 'd)'], ['a', 'b']),
-            ):
-        assert rlist(parenthesize(iter(value))) == result
-
-
 def operatorize(tokens, operator='or'):
     "Convert operators"
     test = (operator, (operator,))
@@ -927,43 +492,6 @@
             yield cur
 
 
-def test_operatorize():
-    a = ('a', 'a', 'a')
-    b = ('b', 'b', 'b')
-    c = ('c', 'c', 'c')
-    null_ = ('d', None, 'x')
-    double_null_ = ('e', None, None)
-    for value, result in (
-            (['a'], ['a']),
-            (['a', 'or', 'b'], [['OR', 'a', 'b']]),
-            (['a', 'or', 'b', 'or', 'c'], [['OR', ['OR', 'a', 'b'], 'c']]),
-            (['a', 'b', 'or', 'c'], ['a', ['OR', 'b', 'c']]),
-            (['a', 'or', 'b', 'c'], [['OR', 'a', 'b'], 'c']),
-            (['a', iter(['b', 'c'])], ['a', ['b', 'c']]),
-            (['a', iter(['b', 'c']), 'd'], ['a', ['b', 'c'], 'd']),
-            (['a', 'or', iter(['b', 'c'])], [['OR', 'a', ['b', 'c']]]),
-            (['a', 'or', iter(['b', 'c']), 'd'],
-                [['OR', 'a', ['b', 'c']], 'd']),
-            (['a', iter(['b', 'c']), 'or', 'd'],
-                ['a', ['OR', ['b', 'c'], 'd']]),
-            (['a', 'or', iter(['b', 'or', 'c'])],
-                [['OR', 'a', [['OR', 'b', 'c']]]]),
-            (['or'], []),
-            (['or', 'a'], ['a']),
-            (['a', iter(['or', 'b'])], ['a', ['b']]),
-            (['a', 'or', 'or', 'b'], [['OR', 'a', 'b']]),
-            (['or', 'or', 'a'], ['a']),
-            (['or', 'or', 'a', 'b'], ['a', 'b']),
-            (['or', 'or', 'a', 'or', 'b'], [['OR', 'a', 'b']]),
-            (['a', iter(['b', 'or', 'c'])], ['a', [['OR', 'b', 'c']]]),
-            ([a, iter([b, ('or',), c])], [a, [['OR', b, c]]]),
-            (['a', iter(['b', 'or'])], ['a', [['OR', 'b']]]),
-            ([null_], [null_]),
-            ([null_, 'or', double_null_], [['OR', null_, double_null_]]),
-            ):
-        assert rlist(operatorize(iter(value))) == result
-
-
 class DomainParser(object):
     "A parser for domain"
 
@@ -1321,504 +849,3 @@
                         yield (field_name, operator, value, target)
                     else:
                         yield field_name, operator, value
-
-
-def test_stringable():
-    dom = DomainParser({
-            'name': {
-                'string': 'Name',
-                'type': 'char',
-                },
-            'multiselection': {
-                'string': "MultiSelection",
-                'type': 'multiselection',
-                'selection': [
-                    ('foo', "Foo"),
-                    ('bar', "Bar"),
-                    ('baz', "Baz"),
-                    ],
-                },
-            'relation': {
-                'string': 'Relation',
-                'type': 'many2one',
-                'relation_fields': {
-                    'name': {
-                        'string': "Name",
-                        'type': 'char',
-                        },
-                    },
-                },
-            'relations': {
-                'string': 'Relations',
-                'type': 'many2many',
-                },
-            })
-    valid = ('name', '=', 'Doe')
-    invalid = ('surname', '=', 'John')
-    assert dom.stringable([valid])
-    assert not dom.stringable([invalid])
-    assert dom.stringable(['AND', valid])
-    assert not dom.stringable(['AND', valid, invalid])
-    assert dom.stringable([[valid]])
-    assert not dom.stringable([[valid], [invalid]])
-    assert dom.stringable([('multiselection', '=', None)])
-    assert dom.stringable([('multiselection', '=', '')])
-    assert not dom.stringable([('multiselection', '=', 'foo')])
-    assert dom.stringable([('multiselection', '=', ['foo'])])
-    assert dom.stringable([('relation', '=', None)])
-    assert dom.stringable([('relation', '=', "Foo")])
-    assert dom.stringable([('relation.rec_name', '=', "Foo")])
-    assert not dom.stringable([('relation', '=', 1)])
-    assert dom.stringable([('relations', '=', "Foo")])
-    assert dom.stringable([('relations', 'in', ["Foo"])])
-    assert not dom.stringable([('relations', 'in', [42])])
-    assert dom.stringable([('relation.name', '=', "Foo")])
-
-
-def test_string():
-    dom = DomainParser({
-            'name': {
-                'string': 'Name',
-                'type': 'char',
-                },
-            'surname': {
-                'string': '(Sur)Name',
-                'type': 'char',
-                },
-            'date': {
-                'string': 'Date',
-                'type': 'date',
-                },
-            'selection': {
-                'string': 'Selection',
-                'type': 'selection',
-                'selection': [
-                    ('male', 'Male'),
-                    ('female', 'Female'),
-                    ('', ''),
-                    ],
-                },
-            'multiselection': {
-                'string': "MultiSelection",
-                'type': 'multiselection',
-                'selection': [
-                    ('foo', "Foo"),
-                    ('bar', "Bar"),
-                    ('baz', "Baz"),
-                    ],
-                },
-            'reference': {
-                'string': 'Reference',
-                'type': 'reference',
-                'selection': [
-                    ('spam', 'Spam'),
-                    ('ham', 'Ham'),
-                    ]
-                },
-            'many2one': {
-                'string': 'Many2One',
-                'name': 'many2one',
-                'type': 'many2one',
-                'relation_fields': {
-                    'name': {
-                        'string': "Name",
-                        'type': 'char',
-                        },
-                    },
-                },
-            })
-    assert dom.string([('name', '=', 'Doe')]) == 'Name: =Doe'
-    assert dom.string([('name', '=', None)]) == 'Name: ='
-    assert dom.string([('name', '=', '')]) == 'Name: =""'
-    assert dom.string([('name', 'ilike', '%')]) == 'Name: '
-    assert dom.string([('name', 'ilike', '%Doe%')]) == 'Name: Doe'
-    assert dom.string([('name', 'ilike', '%<%')]) == 'Name: "" "<"'
-    assert dom.string([('name', 'ilike', 'Doe')]) == 'Name: =Doe'
-    assert dom.string([('name', 'ilike', 'Doe%')]) == 'Name: Doe%'
-    assert dom.string([('name', 'ilike', 'Doe%%')]) == 'Name: =Doe%'
-    assert dom.string([('name', 'not ilike', '%Doe%')]) == 'Name: !Doe'
-    assert dom.string([('name', 'in', ['John', 'Jane'])]) == 'Name: John;Jane'
-    assert dom.string([('name', 'not in', ['John', 'Jane'])]) == \
-        'Name: !John;Jane'
-    assert dom.string([
-            ('name', 'ilike', '%Doe%'),
-            ('name', 'ilike', '%Jane%')]) == 'Name: Doe Name: Jane'
-    assert dom.string(['AND',
-            ('name', 'ilike', '%Doe%'),
-            ('name', 'ilike', '%Jane%')]) == 'Name: Doe Name: Jane'
-    assert dom.string(['OR',
-            ('name', 'ilike', '%Doe%'),
-            ('name', 'ilike', '%Jane%')]) == 'Name: Doe or Name: Jane'
-    assert dom.string([
-            ('name', 'ilike', '%Doe%'),
-            ['OR',
-                ('name', 'ilike', '%John%'),
-                ('name', 'ilike', '%Jane%')]]) == \
-        'Name: Doe (Name: John or Name: Jane)'
-    assert dom.string([]) == ''
-    assert dom.string([('surname', 'ilike', '%Doe%')]) == '"(Sur)Name": Doe'
-    assert dom.string([('date', '>=', datetime.date(2012, 10, 24))]) == \
-        'Date: >=10/24/2012'
-    assert dom.string([('selection', '=', '')]) == 'Selection: '
-    assert dom.string([('selection', '=', None)]) == 'Selection: '
-    assert dom.string([('selection', '!=', '')]) == 'Selection: !""'
-    assert dom.string([('selection', '=', 'male')]) == 'Selection: Male'
-    assert dom.string([('selection', '!=', 'male')]) == 'Selection: !Male'
-    assert dom.string([('multiselection', '=', None)]) == "MultiSelection: ="
-    assert dom.string([('multiselection', '=', '')]) == "MultiSelection: ="
-    assert dom.string([('multiselection', '!=', '')]) == "MultiSelection: !="
-    assert dom.string([('multiselection', '=', ['foo'])]) == \
-        "MultiSelection: =Foo"
-    assert dom.string([('multiselection', '!=', ['foo'])]) == \
-        "MultiSelection: !=Foo"
-    assert dom.string([('multiselection', '=', ['foo', 'bar'])]) == \
-        "MultiSelection: =Foo;Bar"
-    assert dom.string([('multiselection', '!=', ['foo', 'bar'])]) == \
-        "MultiSelection: !=Foo;Bar"
-    assert dom.string([('multiselection', 'in', ['foo'])]) == \
-        "MultiSelection: Foo"
-    assert dom.string([('multiselection', 'not in', ['foo'])]) == \
-        "MultiSelection: !Foo"
-    assert dom.string([('multiselection', '=', ['foo', 'bar'])]) == \
-        "MultiSelection: =Foo;Bar"
-    assert dom.string([('multiselection', '!=', ['foo', 'bar'])]) == \
-        "MultiSelection: !=Foo;Bar"
-    assert dom.string([('multiselection', 'in', ['foo', 'bar'])]) == \
-        "MultiSelection: Foo;Bar"
-    assert dom.string([('multiselection', 'not in', ['foo', 'bar'])]) == \
-        "MultiSelection: !Foo;Bar"
-    assert dom.string([('reference', 'ilike', '%foo%')]) == \
-        'Reference: foo'
-    assert dom.string([('reference.rec_name', 'ilike', '%bar%', 'spam')]) == \
-        'Reference: Spam,bar'
-    assert dom.string([('reference', 'in', ['foo', 'bar'])]) == \
-        'Reference: foo;bar'
-    assert dom.string([('many2one', 'ilike', '%John%')]) == 'Many2One: John'
-    assert dom.string([('many2one.rec_name', 'in', ['John', 'Jane'])]) == \
-        'Many2One: John;Jane'
-    assert dom.string([('many2one.name', 'ilike', '%Foo%')]) == \
-        "Many2One.Name: Foo"
-
-
-def test_group():
-    dom = DomainParser({
-            'name': {
-                'string': 'Name',
-                },
-            'firstname': {
-                'string': 'First Name',
-                },
-            'surname': {
-                'string': '(Sur)Name',
-                },
-            'relation': {
-                'string': "Relation",
-                'relation': 'relation',
-                'relation_fields': {
-                    'name': {
-                        'string': "Name",
-                        },
-                    },
-                },
-            })
-    assert rlist(dom.group(udlex('Name: Doe'))) == [('Name', None, 'Doe')]
-    assert rlist(dom.group(udlex('"(Sur)Name": Doe'))) == [
-        ('(Sur)Name', None, 'Doe'),
-        ]
-    assert rlist(dom.group(udlex('Name: Doe Name: John'))) == [
-        ('Name', None, 'Doe'),
-        ('Name', None, 'John')]
-    assert rlist(dom.group(udlex('Name: Name: John'))) == [
-        ('Name', None, None),
-        ('Name', None, 'John')]
-    assert rlist(dom.group(udlex('First Name: John'))) == [
-        ('First Name', None, 'John'),
-        ]
-    assert rlist(dom.group(udlex('Name: Doe First Name: John'))) == [
-        ('Name', None, 'Doe'),
-        ('First Name', None, 'John'),
-        ]
-    assert rlist(dom.group(udlex('First Name: John Name: Doe'))) == [
-        ('First Name', None, 'John'),
-        ('Name', None, 'Doe'),
-        ]
-    assert rlist(dom.group(udlex('First Name: John First Name: Jane'))) == [
-        ('First Name', None, 'John'),
-        ('First Name', None, 'Jane'),
-        ]
-    assert rlist(dom.group(udlex('Name: John Doe'))) == [
-        ('Name', None, 'John'),
-        ('Doe',),
-        ]
-    assert rlist(dom.group(udlex('Name: "John Doe"'))) == [
-        ('Name', None, 'John Doe'),
-        ]
-    assert rlist(dom.group(udlex('Doe Name: John'))) == [
-        ('Doe',), ('Name', None, 'John')]
-    assert rlist(dom.group(udlex('Name: =Doe'))) == [('Name', '=', 'Doe')]
-    assert rlist(dom.group(udlex('Name: =Doe Name: >John'))) == [
-        ('Name', '=', 'Doe'),
-        ('Name', '>', 'John'),
-        ]
-    assert rlist(dom.group(udlex('First Name: =John First Name: =Jane'))) == [
-        ('First Name', '=', 'John'),
-        ('First Name', '=', 'Jane'),
-        ]
-    assert rlist(dom.group(udlex('Name: John;Jane'))) == [
-        ('Name', None, ['John', 'Jane'])
-        ]
-    assert rlist(dom.group(udlex('Name: John;'))) == [
-        ('Name', None, ['John'])
-        ]
-    assert rlist(dom.group(udlex('Name: John;Jane Name: Doe'))) == [
-        ('Name', None, ['John', 'Jane']),
-        ('Name', None, 'Doe'),
-        ]
-    assert rlist(dom.group(udlex('Name: John; Name: Doe'))) == [
-        ('Name', None, ['John']),
-        ('Name', None, 'Doe'),
-        ]
-    assert rlist(dom.group(udlex('Name:'))) == [
-        ('Name', None, None),
-        ]
-    assert rlist(dom.group(udlex('Name: ='))) == [
-        ('Name', '=', None),
-        ]
-    assert rlist(dom.group(udlex('Name: =""'))) == [
-        ('Name', '=', ''),
-        ]
-    assert rlist(dom.group(udlex('Name: = ""'))) == [
-        ('Name', '=', ''),
-        ]
-    assert rlist(dom.group(udlex('Name: = Name: Doe'))) == [
-        ('Name', '=', None),
-        ('Name', None, 'Doe'),
-        ]
-    assert rlist(dom.group(udlex('Name: \\"foo\\"'))) == [
-        ('Name', None, '"foo"'),
-        ]
-    assert rlist(dom.group(udlex('Name: "" <'))) == [
-        ('Name', '', '<'),
-        ]
-    assert rlist(dom.group(udlex('Relation.Name: Test'))) == [
-        ('Relation.Name', None, "Test"),
-        ]
-
-
-def test_parse_clause():
-    dom = DomainParser({
-            'name': {
-                'string': 'Name',
-                'name': 'name',
-                'type': 'char',
-                },
-            'integer': {
-                'string': 'Integer',
-                'name': 'integer',
-                'type': 'integer',
-                },
-            'selection': {
-                'string': 'Selection',
-                'name': 'selection',
-                'type': 'selection',
-                'selection': [
-                    ('male', 'Male'),
-                    ('female', 'Female'),
-                    ],
-                },
-            'multiselection': {
-                'string': "MultiSelection",
-                'name': 'multiselection',
-                'type': 'multiselection',
-                'selection': [
-                    ('foo', "Foo"),
-                    ('bar', "Bar"),
-                    ('baz', "Baz"),
-                    ],
-                },
-            'reference': {
-                'string': 'Reference',
-                'name': 'reference',
-                'type': 'reference',
-                'selection': [
-                    ('spam', 'Spam'),
-                    ('ham', 'Ham'),
-                    ]
-                },
-            'many2one': {
-                'string': 'Many2One',
-                'name': 'many2one',
-                'type': 'many2one',
-                },
-            'relation': {
-                'string': "Relation",
-                'relation': 'relation',
-                'name': 'relation',
-                'relation_fields': {
-                    'name': {
-                        'string': "Name",
-                        'name': 'name',
-                        'type': 'char',
-                        },
-                    },
-                },
-            })
-    assert rlist(dom.parse_clause([('John',)])) == [
-        ('rec_name', 'ilike', '%John%')]
-    assert rlist(dom.parse_clause([('Name', None, None)])) == [
-        ('name', 'ilike', '%')]
-    assert rlist(dom.parse_clause([('Name', '', None)])) == [
-        ('name', 'ilike', '%')]
-    assert rlist(dom.parse_clause([('Name', '=', None)])) == [
-        ('name', '=', None)]
-    assert rlist(dom.parse_clause([('Name', '=', '')])) == [
-        ('name', '=', '')]
-    assert rlist(dom.parse_clause([('Name', None, 'Doe')])) == [
-        ('name', 'ilike', '%Doe%')]
-    assert rlist(dom.parse_clause([('Name', '!', 'Doe')])) == [
-        ('name', 'not ilike', '%Doe%')]
-    assert rlist(dom.parse_clause([('Name', None, ['John', 'Jane'])])) == [
-        ('name', 'in', ['John', 'Jane']),
-        ]
-    assert rlist(dom.parse_clause([('Name', '!', ['John', 'Jane'])])) == [
-        ('name', 'not in', ['John', 'Jane']),
-        ]
-    assert rlist(dom.parse_clause([('Selection', None, None)])) == [
-        ('selection', '=', None),
-        ]
-    assert rlist(dom.parse_clause([('Selection', None, '')])) == [
-        ('selection', '=', ''),
-        ]
-    assert rlist(dom.parse_clause([('Selection', None, ['Male', 'Female'])])) \
-        == [
-            ('selection', 'in', ['male', 'female'])
-            ]
-    assert rlist(dom.parse_clause([('MultiSelection', None, None)])) == [
-        ('multiselection', '=', None),
-        ]
-    assert rlist(dom.parse_clause([('MultiSelection', None, '')])) == [
-        ('multiselection', 'in', ['']),
-        ]
-    assert rlist(dom.parse_clause([('MultiSelection', '=', '')])) == [
-        ('multiselection', '=', ['']),
-        ]
-    assert rlist(dom.parse_clause([('MultiSelection', '!', '')])) == [
-        ('multiselection', 'not in', ['']),
-        ]
-    assert rlist(dom.parse_clause([('MultiSelection', '!=', '')])) == [
-        ('multiselection', '!=', ['']),
-        ]
-    assert rlist(dom.parse_clause(
-            [('MultiSelection', None, ['Foo', 'Bar'])])) == [
-        ('multiselection', 'in', ['foo', 'bar']),
-        ]
-    assert rlist(dom.parse_clause(
-            [('MultiSelection', '=', ['Foo', 'Bar'])])) == [
-        ('multiselection', '=', ['foo', 'bar']),
-        ]
-    assert rlist(dom.parse_clause(
-            [('MultiSelection', '!', ['Foo', 'Bar'])])) == [
-        ('multiselection', 'not in', ['foo', 'bar']),
-        ]
-    assert rlist(dom.parse_clause(
-            [('MultiSelection', '!=', ['Foo', 'Bar'])])) == [
-        ('multiselection', '!=', ['foo', 'bar']),
-        ]
-    assert rlist(dom.parse_clause([('Integer', None, None)])) == [
-        ('integer', '=', None),
-        ]
-    assert rlist(dom.parse_clause([('Integer', None, '3..5')])) == [[
-            ('integer', '>=', 3),
-            ('integer', '<=', 5),
-            ]]
-    assert rlist(dom.parse_clause([('Reference', None, 'foo')])) == [
-        ('reference', 'ilike', '%foo%'),
-        ]
-    assert rlist(dom.parse_clause([('Reference', None, 'Spam')])) == [
-        ('reference', 'ilike', '%spam%'),
-        ]
-    assert rlist(dom.parse_clause([('Reference', None, 'Spam,bar')])) == [
-        ('reference.rec_name', 'ilike', '%bar%', 'spam'),
-        ]
-    assert rlist(dom.parse_clause([('Reference', None, ['foo', 'bar'])])) == [
-        ('reference', 'in', ['foo', 'bar']),
-        ]
-    assert rlist(dom.parse_clause(['OR',
-                ('Name', None, 'John'), ('Name', None, 'Jane')])) == ['OR',
-        ('name', 'ilike', '%John%'),
-        ('name', 'ilike', '%Jane%'),
-        ]
-    assert rlist(dom.parse_clause([('Many2One', None, 'John')])) == [
-        ('many2one', 'ilike', '%John%'),
-        ]
-    assert rlist(dom.parse_clause([('Many2One', None, ['John', 'Jane'])])) == [
-        ('many2one.rec_name', 'in', ['John', 'Jane']),
-        ]
-    assert rlist(dom.parse_clause(iter([iter([['John']])]))) == [
-        [('rec_name', 'ilike', '%John%')]]
-    assert rlist(dom.parse_clause(
-            iter([['Relation.Name', None, "Test"]]))) == [
-                ('relation.name', 'ilike', "%Test%")]
-    assert rlist(dom.parse_clause(
-            iter([['OR']]))) == [
-                ('rec_name', 'ilike', "%OR%")]
-    assert rlist(dom.parse_clause(
-            iter([['AND']]))) == [
-                ('rec_name', 'ilike', "%AND%")]
-
-
-def test_completion():
-    dom = DomainParser({
-            'name': {
-                'string': 'Name',
-                'name': 'name',
-                'type': 'char',
-                },
-            })
-    assert list(dom.completion('Nam')) == ['Name: ']
-    assert list(dom.completion('Name:')) == ['Name: ']
-    assert list(dom.completion('Name: foo')) == []
-    assert list(dom.completion('Name: !=')) == []
-    assert list(dom.completion('Name: !=foo')) == []
-    assert list(dom.completion('')) == ['Name: ']
-    assert list(dom.completion(' ')) == ['', 'Name: ']
-
-    dom = DomainParser({
-            'relation': {
-                'name': 'relation',
-                'string': "Relation",
-                'type': 'many2one',
-                'relation_fields': {
-                    'name': {
-                        'name': 'name',
-                        'string': "Name",
-                        'type': 'char',
-                        },
-                    },
-                },
-            })
-    assert list(dom.completion('Relatio')) == [
-        'Relation: ', 'Relation.Name: ']
-
-    dom = DomainParser({
-            'name': {
-                'string': "Active",
-                'name': 'active',
-                'type': 'boolean',
-                },
-            })
-
-    assert list(dom.completion("Act")) == ["Active: "]
-    assert list(dom.completion("Active:")) == [
-        "Active: ", "Active: True", "Active: False"]
-    assert list(dom.completion("Active: t")) == [
-        "Active: True", "Active: False"]
-    assert list(dom.completion("Active: f")) == [
-        "Active: False", "Active: True"]
-
-
-if __name__ == '__main__':
-    for name in list(globals()):
-        if name.startswith('test_'):
-            func = globals()[name]
-            func()
diff -r a70914b38278 -r e032ae3f4015 tryton/common/selection.py
--- a/tryton/common/selection.py        Wed Dec 18 21:43:09 2019 +0100
+++ b/tryton/common/selection.py        Wed Dec 18 22:41:05 2019 +0100
@@ -210,10 +210,3 @@
             # When setting no item GTK doesn't clear the entry
             entry.get_child().set_text('')
         return True
-
-
-def test_freeze_value():
-    assert freeze_value({'foo': 'bar'}) == (('foo', 'bar'),)
-    assert freeze_value([1, 42, 2, 3]) == (1, 42, 2, 3)
-    assert freeze_value('foo') == 'foo'
-    assert freeze_value({'foo': {'bar': 42}}) == (('foo', (('bar', 42),)),)
diff -r a70914b38278 -r e032ae3f4015 tryton/common/timedelta.py
--- a/tryton/common/timedelta.py        Wed Dec 18 21:43:09 2019 +0100
+++ b/tryton/common/timedelta.py        Wed Dec 18 22:41:05 2019 +0100
@@ -107,52 +107,3 @@
     if '-' in text:
         seconds *= -1
     return datetime.timedelta(seconds=seconds)
-
-
-_tests = [
-    (None, ''),
-    (datetime.timedelta(), '00:00'),
-    (datetime.timedelta(days=3, hours=5, minutes=30), '3d 05:30'),
-    (datetime.timedelta(weeks=48), '11M 6d'),
-    (datetime.timedelta(weeks=50), '11M 2w 6d'),
-    (datetime.timedelta(weeks=52), '12M 4d'),
-    (datetime.timedelta(days=360), '12M'),
-    (datetime.timedelta(days=364), '12M 4d'),
-    (datetime.timedelta(days=365), '1Y'),
-    (datetime.timedelta(days=366), '1Y 1d'),
-    (datetime.timedelta(hours=2, minutes=5, seconds=10), '02:05:10'),
-    (datetime.timedelta(minutes=15, microseconds=42), '00:15:00.000042'),
-    (datetime.timedelta(days=1, microseconds=42), '1d .000042'),
-    (datetime.timedelta(seconds=-1), '-00:00:01'),
-    (datetime.timedelta(days=-1, hours=-5, minutes=-30), '-1d 05:30'),
-    ]
-
-
-def test_format():
-    for timedelta, text in _tests:
-        assert format(timedelta) == text
-
-
-_tests_parse = [
-    (datetime.timedelta(), '  '),
-    (datetime.timedelta(), 'foo'),
-    (datetime.timedelta(days=1.5), '1.5d'),
-    (datetime.timedelta(days=-2), '1d -1d'),
-    (datetime.timedelta(hours=1, minutes=5, seconds=10), '1:5:10:42'),
-    (datetime.timedelta(hours=2), '1: 1:'),
-    (datetime.timedelta(hours=.25), ':15'),
-    (datetime.timedelta(hours=1), '1h'),
-    (datetime.timedelta(hours=.25), '.25h'),
-    ]
-
-
-def test_parse():
-    for timedelta, text, in _tests + _tests_parse:
-        assert parse(text) == timedelta
-
-
-if __name__ == '__main__':
-    for name in list(globals()):
-        if name.startswith('test_'):
-            func = globals()[name]
-            func()
diff -r a70914b38278 -r e032ae3f4015 tryton/tests/__init__.py
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/tryton/tests/__init__.py  Wed Dec 18 22:41:05 2019 +0100
@@ -0,0 +1,29 @@
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+import os
+import sys
+import unittest
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+    loader = unittest.TestLoader()
+    for filename in os.listdir(os.path.dirname(__file__)):
+        if filename.startswith("test") and filename.endswith(".py"):
+            modname = "tryton.tests." + filename[:-3]
+            __import__(modname)
+            module = sys.modules[modname]
+            suite.addTests(loader.loadTestsFromModule(module))
+    return suite
+
+
+def main():
+    suite = test_suite()
+    runner = unittest.TextTestRunner()
+    return runner.run(suite)
+
+
+if __name__ == '__main__':
+    sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(
+        os.path.abspath(__file__)))))
+    sys.exit(not main().wasSuccessful())
diff -r a70914b38278 -r e032ae3f4015 tryton/tests/test_common_domain_parser.py
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/tryton/tests/test_common_domain_parser.py Wed Dec 18 22:41:05 2019 +0100
@@ -0,0 +1,1146 @@
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+import datetime as dt
+from decimal import Decimal
+from unittest import TestCase
+
+from tryton.common import untimezoned_date
+from tryton.common.domain_parser import (
+    group_operator, quote, split_target_value, convert_value, format_value,
+    complete_value, parenthesize, rlist, operatorize, DomainParser, udlex)
+
+
+class DomainParserTestCase(TestCase):
+    "Test common domain_parser"
+
+    def test_group_operator(self):
+        "Test group_operator"
+        self.assertEqual(
+            list(group_operator(iter(['a', '>', '=']))), ['a', '>='])
+        self.assertEqual(
+            list(group_operator(iter(['>', '=', 'b']))), ['>=', 'b'])
+        self.assertEqual(
+            list(group_operator(iter(['a', '=', 'b']))), ['a', '=', 'b'])
+        self.assertEqual(
+            list(group_operator(iter(['a', '>', '=', 'b']))), ['a', '>=', 'b'])
+        self.assertEqual(
+            list(group_operator(iter(['a', '>', '=', '=']))), ['a', '>=', '='])
+
+    def test_quote(self):
+        "Test quote"
+        self.assertEqual(quote('test'), 'test')
+        self.assertEqual(quote('foo bar'), '"foo bar"')
+        self.assertEqual(quote('"foo"'), '\\\"foo\\\"')
+        self.assertEqual(quote('foo\\bar'), 'foo\\\\bar')
+
+    def test_split_target_value(self):
+        "Test split_target_value"
+        field = {
+            'type': 'reference',
+            'selection': [
+                ('spam', 'Spam'),
+                ('ham', 'Ham'),
+                ('e', 'Eggs'),
+                ]
+            }
+        for value, result in (
+                ('Spam', (None, 'Spam')),
+                ('foo', (None, 'foo')),
+                ('Spam,', ('spam', '')),
+                ('Ham,bar', ('ham', 'bar')),
+                ('Eggs,foo', ('e', 'foo')),
+                ):
+            self.assertEqual(
+                split_target_value(field, value), result,
+                msg="split_target_value(%r, %r)" % (field, value))
+
+    def test_convert_boolean(self):
+        "Test convert boolean"
+        field = {
+            'type': 'boolean',
+            }
+        for value, result in (
+                ('Y', True),
+                ('yes', True),
+                ('t', True),
+                ('1', True),
+                ('N', False),
+                ('False', False),
+                ('no', False),
+                ('0', False),
+                (None, None),
+                ):
+            self.assertEqual(
+                convert_value(field, value), result,
+                msg="convert_value(%r, %r)" % (field, value))
+
+    def test_convert_float(self):
+        "Test convert float"
+        field = {
+            'type': 'float',
+            }
+        for value, result in (
+                ('1', 1.0),
+                ('1.5', 1.5),
+                ('', None),
+                ('test', None),
+                (None, None),
+                ):
+            self.assertEqual(
+                convert_value(field, value), result,
+                msg="convert_value(%r, %r)" % (field, value))
+
+    def test_convert_float_factor(self):
+        "Test convert float with factor"
+        field = {
+            'type': 'float',
+            'factor': '100',
+            }
+        self.assertEqual(convert_value(field, '42'), 0.42)
+
+    def test_convert_integer(self):
+        "Test convert integer"
+        field = {
+            'type': 'integer',
+            }
+        for value, result in (
+                ('1', 1),
+                ('1.5', 1),
+                ('', None),
+                ('test', None),
+                (None, None),
+                ):
+            self.assertEqual(
+                convert_value(field, value), result,
+                msg="convert_value(%r, %r)" % (field, value))
+
+    def test_convert_integer_factor(self):
+        "Test convert integer with factor"
+        field = {
+            'type': 'integer',
+            'factor': '2',
+            }
+        self.assertEqual(convert_value(field, '6'), 3)
+
+    def test_convert_numeric(self):
+        "Test convert numeric"
+        field = {
+            'type': 'numeric',
+            }
+        for value, result in (
+                ('1', Decimal(1)),
+                ('1.5', Decimal('1.5')),
+                ('', None),
+                ('test', None),
+                (None, None),
+                ):
+            self.assertEqual(
+                convert_value(field, value), result,
+                msg="convert_value(%r, %r)" % (field, value))
+
+    def test_convert_numeric_factor(self):
+        "Test convert numeric with factor"
+        field = {
+            'type': 'numeric',
+            'factor': '5',
+            }
+        self.assertEqual(convert_value(field, '1'), Decimal('0.2'))
+
+    def test_convert_selection(self):
+        "Test convert selection"
+        field = {
+            'type': 'selection',
+            'selection': [
+                ('male', 'Male'),
+                ('female', 'Female'),
+                ],
+            }
+        field_with_empty = field.copy()
+        field_with_empty['selection'] = (field_with_empty['selection']
+            + [('', '')])
+        for value, result in (
+                ('Male', 'male'),
+                ('male', 'male'),
+                ('test', 'test'),
+                (None, None),
+                ('', ''),
+                ):
+            self.assertEqual(
+                convert_value(field, value), result,
+                msg="convert_value(%r, %r)" % (field, value))
+            self.assertEqual(
+                convert_value(field_with_empty, value), result,
+                msg="convert_value(%r, %r)" % (field_with_empty, value))
+
+    def test_convert_datetime(self):
+        "Test convert datetime"
+        field = {
+            'type': 'datetime',
+            'format': '"%H:%M:%S"',
+            }
+        for value, result in (
+                ('12/04/2002', untimezoned_date(dt.datetime(2002, 12, 4))),
+                ('12/04/2002 12:30:00', untimezoned_date(
+                        dt.datetime(2002, 12, 4, 12, 30))),
+                ('02/03/04', untimezoned_date(dt.datetime(2004, 2, 3))),
+                ('02/03/04 05:06:07', untimezoned_date(
+                        dt.datetime(2004, 2, 3, 5, 6, 7))),
+                ('test', None),
+                (None, None),
+                ):
+            self.assertEqual(
+                convert_value(field, value), result,
+                msg="convert_value(%r, %r)" % (field, value))
+
+    def test_convert_date(self):
+        "Test convert date"
+        field = {
+            'type': 'date',
+            }
+        for value, result in (
+                ('12/04/2002', dt.date(2002, 12, 4)),
+                ('test', None),
+                (None, None),
+                ):
+            self.assertEqual(
+                convert_value(field, value), result,
+                msg="convert_value(%r, %r)" % (field, value))
+
+    def test_convert_time(self):
+        "Test convert time"
+        field = {
+            'type': 'time',
+            'format': '"%H:%M:%S"',
+            }
+        for value, result in (
+                ('12:30:00', dt.time(12, 30, 0)),
+                ('test', None),
+                (None, None),
+                ):
+            self.assertEqual(
+                convert_value(field, value), result,
+                msg="convert_value(%r, %r)" % (field, value))
+
+    def test_convert_timedelta(self):
+        "Test convert timedelta"
+        field = {
+            'type': 'timedelta',
+            }
+        for value, result in [
+                ('1d 2:00', dt.timedelta(days=1, hours=2)),
+                ('foo', dt.timedelta()),
+                (None, None),
+                ]:
+            self.assertEqual(
+                convert_value(field, value), result,
+                msg="convert_value(%r, %r)" % (field, value))
+
+    def test_format_boolean(self):
+        "Test format boolean"
+        field = {
+            'type': 'boolean',
+            }
+        for value, result in (
+                (True, 'True'),
+                (False, 'False'),
+                (None, ''),
+                ):
+            self.assertEqual(
+                format_value(field, value), result,
+                msg="format_value(%r, %r)" % (field, value))
+
+    def test_format_integer(self):
+        "Test format integer"
+        field = {
+            'type': 'integer',
+            }
+        for value, result in (
+                (1, '1'),
+                (1.5, '1'),
+                (0, '0'),
+                (0.0, '0'),
+                (False, ''),
+                (None, ''),
+                ):
+            self.assertEqual(
+                format_value(field, value), result,
+                msg="format_value(%r, %r)" % (field, value))
+
+    def test_format_integer_factor(self):
+        "Test format integer with factor"
+        field = {
+            'type': 'integer',
+            'factor': '2',
+            }
+        self.assertEqual(format_value(field, 3), '6')
+
+    def test_format_float(self):
+        "Test format float"
+        field = {
+            'type': 'float',
+            }
+        for value, result in (
+                (1, '1'),
+                (1.5, '1.5'),
+                (1.50, '1.5'),
+                (150.79, '150.79'),
+                (0, '0'),
+                (0.0, '0'),
+                (False, ''),
+                (None, ''),
+                ):
+            self.assertEqual(
+                format_value(field, value), result,
+                msg="format_value(%r, %r)" % (field, value))
+
+    def test_format_float_factor(self):
+        "Test format float with factor"
+        field = {
+            'type': 'float',
+            'factor': '100',
+            }
+        self.assertEqual(format_value(field, 0.42), '42')
+
+    def test_format_numeric(self):
+        "Test format numeric"
+        field = {
+            'type': 'numeric',
+            }
+        for value, result in (
+                (Decimal(1), '1'),
+                (Decimal('1.5'), '1.5'),
+                (Decimal('1.50'), '1.5'),
+                (Decimal('150.79'), '150.79'),
+                (Decimal(0), '0'),
+                (Decimal('0.0'), '0'),
+                (False, ''),
+                (None, ''),
+                ):
+            self.assertEqual(
+                format_value(field, value), result,
+                msg="format_value(%r, %r)" % (field, value))
+
+    def test_format_numeric_factor(self):
+        "Test format numeric with factor"
+        field = {
+            'type': 'numeric',
+            'factor': '5',
+            }
+        self.assertEqual(format_value(field, Decimal('0.2')), '1')
+
+    def test_format_selection(self):
+        "Test format selection"
+        field = {
+            'type': 'selection',
+            'selection': [
+                ('male', 'Male'),
+                ('female', 'Female'),
+                ],
+            }
+        field_with_empty = field.copy()
+        field_with_empty['selection'] = (field_with_empty['selection']
+            + [('', '')])
+        for value, result in (
+                ('male', 'Male'),
+                ('test', 'test'),
+                (False, ''),
+                (None, ''),
+                ('', ''),
+                ):
+            self.assertEqual(
+                format_value(field, value), result,
+                msg="format_value(%r, %r)" % (field, value))
+            self.assertEqual(
+                format_value(field_with_empty, value), result,
+                msg="format_value(%r, %r)" % (field_with_empty, value))
+
+    def test_format_datetime(self):
+        "Test format datetime"
+        field = {
+            'type': 'datetime',
+            'format': '"%H:%M:%S"',
+            }
+        for value, result in (
+                (dt.date(2002, 12, 4), dt.date(2002, 12, 4).strftime('%x')),
+                (untimezoned_date(dt.datetime(2002, 12, 4)),
+                    dt.date(2002, 12, 4).strftime('%x')),
+                (untimezoned_date(dt.datetime(2002, 12, 4, 12, 30)),
+                    dt.datetime(2002, 12, 4, 12, 30).strftime('"%x 
%H:%M:%S"')),
+                (False, ''),
+                (None, ''),
+                ):
+            self.assertEqual(
+                format_value(field, value), result,
+                msg="format_value(%r, %r)" % (field, value))
+
+    def test_format_date(self):
+        "Test format date"
+        field = {
+            'type': 'date',
+            }
+        for value, result in (
+                (dt.date(2002, 12, 4), dt.date(2002, 12, 4).strftime('%x')),
+                (False, ''),
+                (None, ''),
+                ):
+            self.assertEqual(
+                format_value(field, value), result,
+                msg="format_value(%r, %r)" % (field, value))
+
+    def test_format_time(self):
+        "Test format time"
+        field = {
+            'type': 'time',
+            'format': '"%H:%M:%S"',
+            }
+        for value, result in (
+                (dt.time(12, 30, 0), '"12:30:00"'),
+                (False, ''),
+                (None, ''),
+                ):
+            self.assertEqual(
+                format_value(field, value), result,
+                msg="format_value(%r, %r)" % (field, value))
+
+    def test_format_timedelta(self):
+        "Test format timedelta"
+        field = {
+            'type': 'timedelta',
+            }
+        for value, result in [
+                (dt.timedelta(days=1, hours=2), '"1d 02:00"'),
+                (dt.timedelta(), ''),
+                (None, ''),
+                ('', ''),
+                ]:
+            self.assertEqual(
+                format_value(field, value), result,
+                msg="format_value(%r, %r)" % (field, value))
+
+    def test_complete_boolean(self):
+        "Test complete boolean"
+        field = {
+            'type': 'boolean',
+            }
+        for value, result in [
+                (None, [True, False]),
+                (True, [False]),
+                (False, [True]),
+                ]:
+            self.assertEqual(
+                list(complete_value(field, value)), result,
+                msg="complete_value(%r, %r)" % (field, value))
+
+    def test_complete_selection(self):
+        "Test complete selection"
+        field = {
+            'type': 'selection',
+            'selection': [
+                ('male', 'Male'),
+                ('female', 'Female'),
+                ],
+            }
+        for value, result in (
+                ('m', ['male']),
+                ('test', []),
+                ('', ['male', 'female']),
+                (None, ['male', 'female']),
+                (['male', 'f'], [['male', 'female']]),
+                (['male', None], [['male', 'male'], ['male', 'female']]),
+                ):
+            self.assertEqual(
+                list(complete_value(field, value)), result,
+                msg="complete_value(%r, %r)" % (field, value))
+
+        field_with_empty = field.copy()
+        field_with_empty['selection'] = (field_with_empty['selection']
+            + [('', '')])
+        for value, result in (
+                ('m', ['male']),
+                ('test', []),
+                ('', ['male', 'female', '']),
+                (None, ['male', 'female', '']),
+                (['male', 'f'], [['male', 'female']]),
+                ):
+            self.assertEqual(
+                list(complete_value(field_with_empty, value)), result,
+                msg="complete_value(%r, %r)" % (field_with_empty, value))
+
+    def test_complete_reference(self):
+        "Test complete reference"
+        field = {
+            'type': 'reference',
+            'selection': [
+                ('spam', 'Spam'),
+                ('ham', 'Ham'),
+                ('', ''),
+                ],
+            }
+        for value, result in (
+                ('s', ['%spam%']),
+                ('test', []),
+                ('', ['%spam%', '%ham%', '%']),
+                (None, ['%spam%', '%ham%', '%']),
+                (['spam', 'h'], [['spam', 'ham']]),
+                ):
+            self.assertEqual(
+                list(complete_value(field, value)), result,
+                msg="complete_value(%r, %r)" % (field, value))
+
+    def test_parenthesize(self):
+        for value, result in (
+                (['a'], ['a']),
+                (['a', 'b'], ['a', 'b']),
+                (['(', 'a', ')'], [['a']]),
+                (['a', 'b', '(', 'c', '(', 'd', 'e', ')', 'f', ')', 'g'],
+                    ['a', 'b', ['c', ['d', 'e'], 'f'], 'g']),
+                (['a', 'b', '(', 'c'], ['a', 'b', ['c']]),
+                (['a', 'b', '(', 'c', '(', 'd', 'e', ')', 'f'],
+                    ['a', 'b', ['c', ['d', 'e'], 'f']]),
+                (['a', 'b', ')'], ['a', 'b']),
+                (['a', 'b', ')', 'c', ')', 'd)'], ['a', 'b']),
+                ):
+            self.assertEqual(
+                rlist(parenthesize(iter(value))), result,
+                msg="parenthesize(%r)" % value)
+
+    def test_operatorize(self):
+        "Test operatorize"
+        a = ('a', 'a', 'a')
+        b = ('b', 'b', 'b')
+        c = ('c', 'c', 'c')
+        null_ = ('d', None, 'x')
+        double_null_ = ('e', None, None)
+        for value, result in (
+                (['a'], ['a']),
+                (['a', 'or', 'b'], [['OR', 'a', 'b']]),
+                (['a', 'or', 'b', 'or', 'c'], [['OR', ['OR', 'a', 'b'], 'c']]),
+                (['a', 'b', 'or', 'c'], ['a', ['OR', 'b', 'c']]),
+                (['a', 'or', 'b', 'c'], [['OR', 'a', 'b'], 'c']),
+                (['a', iter(['b', 'c'])], ['a', ['b', 'c']]),
+                (['a', iter(['b', 'c']), 'd'], ['a', ['b', 'c'], 'd']),
+                (['a', 'or', iter(['b', 'c'])], [['OR', 'a', ['b', 'c']]]),
+                (['a', 'or', iter(['b', 'c']), 'd'],
+                    [['OR', 'a', ['b', 'c']], 'd']),
+                (['a', iter(['b', 'c']), 'or', 'd'],
+                    ['a', ['OR', ['b', 'c'], 'd']]),
+                (['a', 'or', iter(['b', 'or', 'c'])],
+                    [['OR', 'a', [['OR', 'b', 'c']]]]),
+                (['or'], []),
+                (['or', 'a'], ['a']),
+                (['a', iter(['or', 'b'])], ['a', ['b']]),
+                (['a', 'or', 'or', 'b'], [['OR', 'a', 'b']]),
+                (['or', 'or', 'a'], ['a']),
+                (['or', 'or', 'a', 'b'], ['a', 'b']),
+                (['or', 'or', 'a', 'or', 'b'], [['OR', 'a', 'b']]),
+                (['a', iter(['b', 'or', 'c'])], ['a', [['OR', 'b', 'c']]]),
+                ([a, iter([b, ('or',), c])], [a, [['OR', b, c]]]),
+                (['a', iter(['b', 'or'])], ['a', [['OR', 'b']]]),
+                ([null_], [null_]),
+                ([null_, 'or', double_null_], [['OR', null_, double_null_]]),
+                ):
+            self.assertEqual(
+                rlist(operatorize(iter(value))), result,
+                msg="operatorize(%r)" % value)
+
+    def test_stringable(self):
+        "Test stringable"
+        dom = DomainParser({
+                'name': {
+                    'string': 'Name',
+                    'type': 'char',
+                    },
+                'multiselection': {
+                    'string': "MultiSelection",
+                    'type': 'multiselection',
+                    'selection': [
+                        ('foo', "Foo"),
+                        ('bar', "Bar"),
+                        ('baz', "Baz"),
+                        ],
+                    },
+                'relation': {
+                    'string': 'Relation',
+                    'type': 'many2one',
+                    'relation_fields': {
+                        'name': {
+                            'string': "Name",
+                            'type': 'char',
+                            },
+                        },
+                    },
+                'relations': {
+                    'string': 'Relations',
+                    'type': 'many2many',
+                    },
+                })
+        valid = ('name', '=', 'Doe')
+        invalid = ('surname', '=', 'John')
+        self.assertTrue(dom.stringable([valid]))
+        self.assertFalse(dom.stringable([invalid]))
+        self.assertTrue(dom.stringable(['AND', valid]))
+        self.assertFalse(dom.stringable(['AND', valid, invalid]))
+        self.assertTrue(dom.stringable([[valid]]))
+        self.assertFalse(dom.stringable([[valid], [invalid]]))
+        self.assertTrue(dom.stringable([('multiselection', '=', None)]))
+        self.assertTrue(dom.stringable([('multiselection', '=', '')]))
+        self.assertFalse(dom.stringable([('multiselection', '=', 'foo')]))
+        self.assertTrue(dom.stringable([('multiselection', '=', ['foo'])]))
+        self.assertTrue(dom.stringable([('relation', '=', None)]))
+        self.assertTrue(dom.stringable([('relation', '=', "Foo")]))
+        self.assertTrue(dom.stringable([('relation.rec_name', '=', "Foo")]))
+        self.assertFalse(dom.stringable([('relation', '=', 1)]))
+        self.assertTrue(dom.stringable([('relations', '=', "Foo")]))
+        self.assertTrue(dom.stringable([('relations', 'in', ["Foo"])]))
+        self.assertFalse(dom.stringable([('relations', 'in', [42])]))
+        self.assertTrue(dom.stringable([('relation.name', '=', "Foo")]))
+
+    def test_string(self):
+        dom = DomainParser({
+                'name': {
+                    'string': 'Name',
+                    'type': 'char',
+                    },
+                'surname': {
+                    'string': '(Sur)Name',
+                    'type': 'char',
+                    },
+                'date': {
+                    'string': 'Date',
+                    'type': 'date',
+                    },
+                'selection': {
+                    'string': 'Selection',
+                    'type': 'selection',
+                    'selection': [
+                        ('male', 'Male'),
+                        ('female', 'Female'),
+                        ('', ''),
+                        ],
+                    },
+                'multiselection': {
+                    'string': "MultiSelection",
+                    'type': 'multiselection',
+                    'selection': [
+                        ('foo', "Foo"),
+                        ('bar', "Bar"),
+                        ('baz', "Baz"),
+                        ],
+                    },
+                'reference': {
+                    'string': 'Reference',
+                    'type': 'reference',
+                    'selection': [
+                        ('spam', 'Spam'),
+                        ('ham', 'Ham'),
+                        ]
+                    },
+                'many2one': {
+                    'string': 'Many2One',
+                    'name': 'many2one',
+                    'type': 'many2one',
+                    'relation_fields': {
+                        'name': {
+                            'string': "Name",
+                            'type': 'char',
+                            },
+                        },
+                    },
+                })
+        self.assertEqual(dom.string([('name', '=', 'Doe')]), 'Name: =Doe')
+        self.assertEqual(dom.string([('name', '=', None)]), 'Name: =')
+        self.assertEqual(dom.string([('name', '=', '')]), 'Name: =""')
+        self.assertEqual(dom.string([('name', 'ilike', '%')]), 'Name: ')
+        self.assertEqual(dom.string([('name', 'ilike', '%Doe%')]), 'Name: Doe')
+        self.assertEqual(
+            dom.string([('name', 'ilike', '%<%')]), 'Name: "" "<"')
+        self.assertEqual(dom.string([('name', 'ilike', 'Doe')]), 'Name: =Doe')
+        self.assertEqual(dom.string([('name', 'ilike', 'Doe%')]), 'Name: Doe%')
+        self.assertEqual(
+            dom.string([('name', 'ilike', 'Doe%%')]), 'Name: =Doe%')
+        self.assertEqual(
+            dom.string([('name', 'not ilike', '%Doe%')]), 'Name: !Doe')
+        self.assertEqual(
+            dom.string([('name', 'in', ['John', 'Jane'])]), 'Name: John;Jane')
+        self.assertEqual(
+            dom.string([('name', 'not in', ['John', 'Jane'])]),
+            'Name: !John;Jane')
+        self.assertEqual(
+            dom.string([
+                    ('name', 'ilike', '%Doe%'),
+                    ('name', 'ilike', '%Jane%')]),
+            'Name: Doe Name: Jane')
+        self.assertEqual(
+            dom.string(['AND',
+                ('name', 'ilike', '%Doe%'),
+                ('name', 'ilike', '%Jane%')]),
+            'Name: Doe Name: Jane')
+        self.assertEqual(
+            dom.string(['OR',
+                ('name', 'ilike', '%Doe%'),
+                ('name', 'ilike', '%Jane%')]),
+            'Name: Doe or Name: Jane')
+        self.assertEqual(
+            dom.string([
+                ('name', 'ilike', '%Doe%'),
+                ['OR',
+                    ('name', 'ilike', '%John%'),
+                    ('name', 'ilike', '%Jane%')]]),
+            'Name: Doe (Name: John or Name: Jane)')
+        self.assertEqual(dom.string([]), '')
+        self.assertEqual(
+            dom.string([('surname', 'ilike', '%Doe%')]), '"(Sur)Name": Doe')
+        self.assertEqual(
+            dom.string([('date', '>=', dt.date(2012, 10, 24))]),
+            dt.date(2012, 10, 24).strftime('Date: >=%x'))
+        self.assertEqual(dom.string([('selection', '=', '')]), 'Selection: ')
+        self.assertEqual(dom.string([('selection', '=', None)]), 'Selection: ')
+        self.assertEqual(
+            dom.string([('selection', '!=', '')]), 'Selection: !""')
+        self.assertEqual(
+            dom.string([('selection', '=', 'male')]), 'Selection: Male')
+        self.assertEqual(
+            dom.string([('selection', '!=', 'male')]), 'Selection: !Male')
+        self.assertEqual(
+            dom.string([('multiselection', '=', None)]), "MultiSelection: =")
+        self.assertEqual(
+            dom.string([('multiselection', '=', '')]), "MultiSelection: =")
+        self.assertEqual(
+            dom.string([('multiselection', '!=', '')]), "MultiSelection: !=")
+        self.assertEqual(
+            dom.string([('multiselection', '=', ['foo'])]),
+            "MultiSelection: =Foo")
+        self.assertEqual(
+            dom.string([('multiselection', '!=', ['foo'])]),
+            "MultiSelection: !=Foo")
+        self.assertEqual(
+            dom.string([('multiselection', '=', ['foo', 'bar'])]),
+            "MultiSelection: =Foo;Bar")
+        self.assertEqual(
+            dom.string([('multiselection', '!=', ['foo', 'bar'])]),
+            "MultiSelection: !=Foo;Bar")
+        self.assertEqual(
+            dom.string([('multiselection', 'in', ['foo'])]),
+            "MultiSelection: Foo")
+        self.assertEqual(
+            dom.string([('multiselection', 'not in', ['foo'])]),
+            "MultiSelection: !Foo")
+        self.assertEqual(
+            dom.string([('multiselection', '=', ['foo', 'bar'])]),
+            "MultiSelection: =Foo;Bar")
+        self.assertEqual(
+            dom.string([('multiselection', '!=', ['foo', 'bar'])]),
+            "MultiSelection: !=Foo;Bar")
+        self.assertEqual(
+            dom.string([('multiselection', 'in', ['foo', 'bar'])]),
+            "MultiSelection: Foo;Bar")
+        self.assertEqual(
+            dom.string([('multiselection', 'not in', ['foo', 'bar'])]),
+            "MultiSelection: !Foo;Bar")
+        self.assertEqual(
+            dom.string([('reference', 'ilike', '%foo%')]),
+            'Reference: foo')
+        self.assertEqual(
+            dom.string([('reference.rec_name', 'ilike', '%bar%', 'spam')]),
+            'Reference: Spam,bar')
+        self.assertEqual(
+            dom.string([('reference', 'in', ['foo', 'bar'])]),
+            'Reference: foo;bar')
+        self.assertEqual(
+            dom.string([('many2one', 'ilike', '%John%')]), 'Many2One: John')
+        self.assertEqual(
+            dom.string([('many2one.rec_name', 'in', ['John', 'Jane'])]),
+            'Many2One: John;Jane')
+        self.assertEqual(
+            dom.string([('many2one.name', 'ilike', '%Foo%')]),
+            "Many2One.Name: Foo")
+
+    def test_group(self):
+        "Test group"
+        dom = DomainParser({
+                'name': {
+                    'string': 'Name',
+                    },
+                'firstname': {
+                    'string': 'First Name',
+                    },
+                'surname': {
+                    'string': '(Sur)Name',
+                    },
+                'relation': {
+                    'string': "Relation",
+                    'relation': 'relation',
+                    'relation_fields': {
+                        'name': {
+                            'string': "Name",
+                            },
+                        },
+                    },
+                })
+        self.assertEqual(
+            rlist(dom.group(udlex('Name: Doe'))), [('Name', None, 'Doe')])
+        self.assertEqual(
+            rlist(dom.group(udlex('"(Sur)Name": Doe'))), [
+                ('(Sur)Name', None, 'Doe'),
+                ])
+        self.assertEqual(
+            rlist(dom.group(udlex('Name: Doe Name: John'))), [
+                ('Name', None, 'Doe'),
+                ('Name', None, 'John'),
+                ])
+        self.assertEqual(
+            rlist(dom.group(udlex('Name: Name: John'))), [
+                ('Name', None, None),
+                ('Name', None, 'John')])
+        self.assertEqual(
+            rlist(dom.group(udlex('First Name: John'))), [
+                ('First Name', None, 'John'),
+                ])
+        self.assertEqual(
+            rlist(dom.group(udlex('Name: Doe First Name: John'))), [
+                ('Name', None, 'Doe'),
+                ('First Name', None, 'John'),
+                ])
+        self.assertEqual(
+            rlist(dom.group(udlex('First Name: John Name: Doe'))), [
+                ('First Name', None, 'John'),
+                ('Name', None, 'Doe'),
+                ])
+        self.assertEqual(
+            rlist(dom.group(udlex('First Name: John First Name: Jane'))), [
+                ('First Name', None, 'John'),
+                ('First Name', None, 'Jane'),
+                ])
+        self.assertEqual(
+            rlist(dom.group(udlex('Name: John Doe'))), [
+                ('Name', None, 'John'),
+                ('Doe',),
+                ])
+        self.assertEqual(
+            rlist(dom.group(udlex('Name: "John Doe"'))), [
+                ('Name', None, 'John Doe'),
+                ])
+        self.assertEqual(
+            rlist(dom.group(udlex('Doe Name: John'))), [
+                ('Doe',),
+                ('Name', None, 'John'),
+                ])
+        self.assertEqual(
+            rlist(dom.group(udlex('Name: =Doe'))), [('Name', '=', 'Doe')])
+        self.assertEqual(
+            rlist(dom.group(udlex('Name: =Doe Name: >John'))), [
+                ('Name', '=', 'Doe'),
+                ('Name', '>', 'John'),
+                ])
+        self.assertEqual(
+            rlist(dom.group(udlex('First Name: =John First Name: =Jane'))), [
+                ('First Name', '=', 'John'),
+                ('First Name', '=', 'Jane'),
+                ])
+        self.assertEqual(
+            rlist(dom.group(udlex('Name: John;Jane'))), [
+                ('Name', None, ['John', 'Jane'])
+                ])
+        self.assertEqual(
+            rlist(dom.group(udlex('Name: John;'))), [
+                ('Name', None, ['John'])
+                ])
+        self.assertEqual(
+            rlist(dom.group(udlex('Name: John;Jane Name: Doe'))), [
+                ('Name', None, ['John', 'Jane']),
+                ('Name', None, 'Doe'),
+                ])
+        self.assertEqual(
+            rlist(dom.group(udlex('Name: John; Name: Doe'))), [
+                ('Name', None, ['John']),
+                ('Name', None, 'Doe'),
+                ])
+        self.assertEqual(
+            rlist(dom.group(udlex('Name:'))), [
+                ('Name', None, None),
+                ])
+        self.assertEqual(
+            rlist(dom.group(udlex('Name: ='))), [
+                ('Name', '=', None),
+                ])
+        self.assertEqual(
+            rlist(dom.group(udlex('Name: =""'))), [
+                ('Name', '=', ''),
+                ])
+        self.assertEqual(
+            rlist(dom.group(udlex('Name: = ""'))), [
+                ('Name', '=', ''),
+                ])
+        self.assertEqual(
+            rlist(dom.group(udlex('Name: = Name: Doe'))), [
+                ('Name', '=', None),
+                ('Name', None, 'Doe'),
+                ])
+        self.assertEqual(
+            rlist(dom.group(udlex('Name: \\"foo\\"'))), [
+                ('Name', None, '"foo"'),
+                ])
+        self.assertEqual(
+            rlist(dom.group(udlex('Name: "" <'))), [
+                ('Name', '', '<'),
+                ])
+        self.assertEqual(
+            rlist(dom.group(udlex('Relation.Name: Test'))), [
+                ('Relation.Name', None, "Test"),
+                ])
+
+    def test_parse_clause(self):
+        "Test parse clause"
+        dom = DomainParser({
+                'name': {
+                    'string': 'Name',
+                    'name': 'name',
+                    'type': 'char',
+                    },
+                'integer': {
+                    'string': 'Integer',
+                    'name': 'integer',
+                    'type': 'integer',
+                    },
+                'selection': {
+                    'string': 'Selection',
+                    'name': 'selection',
+                    'type': 'selection',
+                    'selection': [
+                        ('male', 'Male'),
+                        ('female', 'Female'),
+                        ],
+                    },
+                'multiselection': {
+                    'string': "MultiSelection",
+                    'name': 'multiselection',
+                    'type': 'multiselection',
+                    'selection': [
+                        ('foo', "Foo"),
+                        ('bar', "Bar"),
+                        ('baz', "Baz"),
+                        ],
+                    },
+                'reference': {
+                    'string': 'Reference',
+                    'name': 'reference',
+                    'type': 'reference',
+                    'selection': [
+                        ('spam', 'Spam'),
+                        ('ham', 'Ham'),
+                        ]
+                    },
+                'many2one': {
+                    'string': 'Many2One',
+                    'name': 'many2one',
+                    'type': 'many2one',
+                    },
+                'relation': {
+                    'string': "Relation",
+                    'relation': 'relation',
+                    'name': 'relation',
+                    'relation_fields': {
+                        'name': {
+                            'string': "Name",
+                            'name': 'name',
+                            'type': 'char',
+                            },
+                        },
+                    },
+                })
+        self.assertEqual(
+            rlist(dom.parse_clause([('John',)])), [
+                ('rec_name', 'ilike', '%John%'),
+                ])
+        self.assertEqual(
+            rlist(dom.parse_clause([('Name', None, None)])), [
+                ('name', 'ilike', '%'),
+                ])
+        self.assertEqual(
+            rlist(dom.parse_clause([('Name', '', None)])), [
+                ('name', 'ilike', '%'),
+                ])
+        self.assertEqual(
+            rlist(dom.parse_clause([('Name', '=', None)])), [
+                ('name', '=', None),
+                ])
+        self.assertEqual(
+            rlist(dom.parse_clause([('Name', '=', '')])), [
+                ('name', '=', ''),
+                ])
+        self.assertEqual(
+            rlist(dom.parse_clause([('Name', None, 'Doe')])), [
+                ('name', 'ilike', '%Doe%'),
+                ])
+        self.assertEqual(
+            rlist(dom.parse_clause([('Name', '!', 'Doe')])), [
+                ('name', 'not ilike', '%Doe%'),
+                ])
+        self.assertEqual(
+            rlist(dom.parse_clause([('Name', None, ['John', 'Jane'])])), [
+                ('name', 'in', ['John', 'Jane']),
+                ])
+        self.assertEqual(
+            rlist(dom.parse_clause([('Name', '!', ['John', 'Jane'])])), [
+                ('name', 'not in', ['John', 'Jane']),
+                ])
+        self.assertEqual(
+            rlist(dom.parse_clause([('Selection', None, None)])), [
+                ('selection', '=', None),
+                ])
+        self.assertEqual(
+            rlist(dom.parse_clause([('Selection', None, '')])), [
+                ('selection', '=', ''),
+                ])
+        self.assertEqual(
+            rlist(dom.parse_clause([('Selection', None, ['Male', 'Female'])])),
+            [('selection', 'in', ['male', 'female'])])
+        self.assertEqual(
+            rlist(dom.parse_clause([('MultiSelection', None, None)])), [
+                ('multiselection', '=', None),
+                ])
+        self.assertEqual(
+            rlist(dom.parse_clause([('MultiSelection', None, '')])), [
+                ('multiselection', 'in', ['']),
+                ])
+        self.assertEqual(
+            rlist(dom.parse_clause([('MultiSelection', '=', '')])), [
+                ('multiselection', '=', ['']),
+                ])
+        self.assertEqual(
+            rlist(dom.parse_clause([('MultiSelection', '!', '')])), [
+                ('multiselection', 'not in', ['']),
+                ])
+        self.assertEqual(
+            rlist(dom.parse_clause([('MultiSelection', '!=', '')])), [
+                ('multiselection', '!=', ['']),
+                ])
+        self.assertEqual(
+            rlist(dom.parse_clause(
+                [('MultiSelection', None, ['Foo', 'Bar'])])), [
+                ('multiselection', 'in', ['foo', 'bar']),
+                ])
+        self.assertEqual(
+            rlist(dom.parse_clause(
+                [('MultiSelection', '=', ['Foo', 'Bar'])])), [
+                ('multiselection', '=', ['foo', 'bar']),
+                ])
+        self.assertEqual(
+            rlist(dom.parse_clause(
+                [('MultiSelection', '!', ['Foo', 'Bar'])])), [
+                ('multiselection', 'not in', ['foo', 'bar']),
+                ])
+        self.assertEqual(
+            rlist(dom.parse_clause(
+                [('MultiSelection', '!=', ['Foo', 'Bar'])])), [
+                ('multiselection', '!=', ['foo', 'bar']),
+                ])
+        self.assertEqual(
+            rlist(dom.parse_clause([('Integer', None, None)])), [
+                ('integer', '=', None),
+                ])
+        self.assertEqual(
+            rlist(dom.parse_clause([('Integer', None, '3..5')])), [[
+                    ('integer', '>=', 3),
+                    ('integer', '<=', 5),
+                    ]])
+        self.assertEqual(
+            rlist(dom.parse_clause([('Reference', None, 'foo')])), [
+                ('reference', 'ilike', '%foo%'),
+                ])
+        self.assertEqual(
+            rlist(dom.parse_clause([('Reference', None, 'Spam')])), [
+                ('reference', 'ilike', '%spam%'),
+                ])
+        self.assertEqual(
+            rlist(dom.parse_clause([('Reference', None, 'Spam,bar')])), [
+                ('reference.rec_name', 'ilike', '%bar%', 'spam'),
+                ])
+        self.assertEqual(
+            rlist(dom.parse_clause([('Reference', None, ['foo', 'bar'])])), [
+                ('reference', 'in', ['foo', 'bar']),
+                ])
+        self.assertEqual(
+            rlist(dom.parse_clause(['OR',
+                    ('Name', None, 'John'), ('Name', None, 'Jane')])),
+            ['OR',
+                ('name', 'ilike', '%John%'),
+                ('name', 'ilike', '%Jane%'),
+                ])
+        self.assertEqual(
+            rlist(dom.parse_clause([('Many2One', None, 'John')])), [
+                ('many2one', 'ilike', '%John%'),
+                ])
+        self.assertEqual(
+            rlist(dom.parse_clause([('Many2One', None, ['John', 'Jane'])])), [
+                ('many2one.rec_name', 'in', ['John', 'Jane']),
+                ])
+        self.assertEqual(
+            rlist(dom.parse_clause(iter([iter([['John']])]))), [
+                [('rec_name', 'ilike', '%John%')]])
+        self.assertEqual(
+            rlist(dom.parse_clause(iter([['Relation.Name', None, "Test"]]))),
+            [('relation.name', 'ilike', "%Test%")])
+        self.assertEqual(
+            rlist(dom.parse_clause(iter([['OR']]))),
+            [('rec_name', 'ilike', "%OR%")])
+        self.assertEqual(
+            rlist(dom.parse_clause(iter([['AND']]))),
+            [('rec_name', 'ilike', "%AND%")])
+
+    def test_completion_char(self):
+        "Test completion char"
+        dom = DomainParser({
+                'name': {
+                    'string': 'Name',
+                    'name': 'name',
+                    'type': 'char',
+                    },
+                })
+        self.assertEqual(list(dom.completion('Nam')), ['Name: '])
+        self.assertEqual(list(dom.completion('Name:')), ['Name: '])
+        self.assertEqual(list(dom.completion('Name: foo')), [])
+        self.assertEqual(list(dom.completion('Name: !=')), [])
+        self.assertEqual(list(dom.completion('Name: !=foo')), [])
+        self.assertEqual(list(dom.completion('')), ['Name: '])
+        self.assertEqual(list(dom.completion(' ')), ['', 'Name: '])
+
+    def test_completion_many2one(self):
+        "Test completion many2one"
+        dom = DomainParser({
+                'relation': {
+                    'name': 'relation',
+                    'string': "Relation",
+                    'type': 'many2one',
+                    'relation_fields': {
+                        'name': {
+                            'name': 'name',
+                            'string': "Name",
+                            'type': 'char',
+                            },
+                        },
+                    },
+                })
+        self.assertEqual(
+            list(dom.completion('Relatio')),
+            ['Relation: ', 'Relation.Name: '])
+
+    def test_completion_boolean(self):
+        "Test completion boolean"
+        dom = DomainParser({
+                'name': {
+                    'string': "Active",
+                    'name': 'active',
+                    'type': 'boolean',
+                    },
+                })
+
+        self.assertEqual(list(dom.completion("Act")), ["Active: "])
+        self.assertEqual(list(dom.completion("Active:")),
+            ["Active: ", "Active: True", "Active: False"])
+        self.assertEqual(
+            list(dom.completion("Active: t")),
+            ["Active: True", "Active: False"])
+        self.assertEqual(
+            list(dom.completion("Active: f")),
+            ["Active: False", "Active: True"])
diff -r a70914b38278 -r e032ae3f4015 tryton/tests/test_common_selection.py
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/tryton/tests/test_common_selection.py     Wed Dec 18 22:41:05 2019 +0100
@@ -0,0 +1,18 @@
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+from unittest import TestCase
+
+from tryton.common.selection import freeze_value
+
+
+class SelectionTestCase(TestCase):
+    "Test common selection"
+
+    def test_freeze_value(self):
+        "Test freeze_value"
+        self.assertEqual(freeze_value({'foo': 'bar'}), (('foo', 'bar'),))
+        self.assertEqual(freeze_value([1, 42, 2, 3]), (1, 42, 2, 3))
+        self.assertEqual(freeze_value('foo'), 'foo')
+        self.assertEqual(
+            freeze_value({'foo': {'bar': 42}}), (('foo', (('bar', 42),)),))
+
diff -r a70914b38278 -r e032ae3f4015 tryton/tests/test_common_timedelta.py
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/tryton/tests/test_common_timedelta.py     Wed Dec 18 22:41:05 2019 +0100
@@ -0,0 +1,54 @@
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+import datetime as dt
+from unittest import TestCase
+
+from tryton.common.timedelta import format, parse
+
+
+class TimeDeltaTestCase(TestCase):
+    "Test common timedelta"
+
+    def _format_values(self):
+        return [
+            (None, ''),
+            (dt.timedelta(), '00:00'),
+            (dt.timedelta(days=3, hours=5, minutes=30), '3d 05:30'),
+            (dt.timedelta(weeks=48), '11M 6d'),
+            (dt.timedelta(weeks=50), '11M 2w 6d'),
+            (dt.timedelta(weeks=52), '12M 4d'),
+            (dt.timedelta(days=360), '12M'),
+            (dt.timedelta(days=364), '12M 4d'),
+            (dt.timedelta(days=365), '1Y'),
+            (dt.timedelta(days=366), '1Y 1d'),
+            (dt.timedelta(hours=2, minutes=5, seconds=10), '02:05:10'),
+            (dt.timedelta(minutes=15, microseconds=42), '00:15:00.000042'),
+            (dt.timedelta(days=1, microseconds=42), '1d .000042'),
+            (dt.timedelta(seconds=-1), '-00:00:01'),
+            (dt.timedelta(days=-1, hours=-5, minutes=-30), '-1d 05:30'),
+            ]
+
+    def test_format(self):
+        "Test format"
+        for timedelta, text in self._format_values():
+            self.assertEqual(format(timedelta), text,
+                msg="format(%r)" % timedelta)
+
+    def _parse_values(self):
+        return self._format_values() + [
+            (dt.timedelta(), '  '),
+            (dt.timedelta(), 'foo'),
+            (dt.timedelta(days=1.5), '1.5d'),
+            (dt.timedelta(days=-2), '1d -1d'),
+            (dt.timedelta(hours=1, minutes=5, seconds=10), '1:5:10:42'),
+            (dt.timedelta(hours=2), '1: 1:'),
+            (dt.timedelta(hours=.25), ':15'),
+            (dt.timedelta(hours=1), '1h'),
+            (dt.timedelta(hours=.25), '.25h'),
+            ]
+
+    def test_parse(self):
+        "Test parse"
+        for timedelta, text in self._parse_values():
+            self.assertEqual(parse(text), timedelta,
+                msg="parse(%r)" % text)

Reply via email to