~jocave/checkbox/hybrid-amd-gpu-mods

« back to all changes in this revision

Viewing changes to plainbox/plainbox/impl/test_pod.py

  • Committer: Tarmac
  • Author(s): Brendan Donegan
  • Date: 2013-06-03 11:12:58 UTC
  • mfrom: (2154.2.1 bug1185759)
  • Revision ID: tarmac-20130603111258-1b3m5ydvkf1accts
"[r=zkrynicki][bug=1185759][author=brendan-donegan] automatic merge by tarmac"

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# This file is part of Checkbox.
2
 
#
3
 
# Copyright 2012-2015 Canonical Ltd.
4
 
# Written by:
5
 
#   Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
6
 
#
7
 
# Checkbox is free software: you can redistribute it and/or modify
8
 
# it under the terms of the GNU General Public License version 3,
9
 
# as published by the Free Software Foundation.
10
 
#
11
 
# Checkbox is distributed in the hope that it will be useful,
12
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 
# GNU General Public License for more details.
15
 
#
16
 
# You should have received a copy of the GNU General Public License
17
 
# along with Checkbox.  If not, see <http://www.gnu.org/licenses/>.
18
 
 
19
 
"""Tests for the plainbox.impl.pod module."""
20
 
 
21
 
from doctest import DocTestSuite
22
 
from unittest import TestCase
23
 
 
24
 
from plainbox.impl.pod import Field
25
 
from plainbox.impl.pod import MANDATORY
26
 
from plainbox.impl.pod import POD
27
 
from plainbox.impl.pod import UNSET
28
 
from plainbox.impl.pod import _FieldCollection
29
 
from plainbox.impl.pod import read_only_assign_filter
30
 
from plainbox.impl.pod import sequence_type_check_assign_filter
31
 
from plainbox.impl.pod import type_check_assign_filter
32
 
from plainbox.impl.pod import type_convert_assign_filter
33
 
from plainbox.impl.pod import unset_or_sequence_type_check_assign_filter
34
 
from plainbox.impl.pod import unset_or_type_check_assign_filter
35
 
from plainbox.vendor import mock
36
 
 
37
 
 
38
 
def load_tests(loader, tests, ignore):
39
 
    """
40
 
    Protocol for loading unit tests.
41
 
 
42
 
    This function ensures that doctests are executed as well.
43
 
    """
44
 
    tests.addTests(DocTestSuite('plainbox.impl.pod'))
45
 
    return tests
46
 
 
47
 
 
48
 
class SingletonTests(TestCase):
49
 
 
50
 
    """Tests for several singleton objects."""
51
 
 
52
 
    def test_MANDATORY_repr(self):
53
 
        """MANDATORY.repr() returns "MANDATORY"."""
54
 
        self.assertEqual(repr(MANDATORY), "MANDATORY")
55
 
 
56
 
    def test_UNSET_repr(self):
57
 
        """UNSET.repr() returns "UNSET"."""
58
 
        self.assertEqual(repr(UNSET), "UNSET")
59
 
 
60
 
 
61
 
class FieldTests(TestCase):
62
 
 
63
 
    """Tests for the Field class."""
64
 
 
65
 
    FIELD_CLS = Field
66
 
 
67
 
    def setUp(self):
68
 
        """Common set-up code."""
69
 
        self.doc = "doc"  # not a mock because it gets set to __doc__
70
 
        self.type = mock.Mock(name='type')
71
 
        self.initial = mock.Mock(name='initial')
72
 
        self.initial_fn = mock.Mock(name='initial_fn')
73
 
        self.field = self.FIELD_CLS(
74
 
            self.doc, self.type, self.initial, self.initial_fn)
75
 
        self.instance = mock.Mock(name='instance')
76
 
        self.owner = mock.Mock(name='owner')
77
 
 
78
 
    def test_initializer(self):
79
 
        """.__init__() stored data correctly."""
80
 
        self.assertEqual(self.field.__doc__, self.doc)
81
 
        self.assertEqual(self.field.type, self.type)
82
 
        self.assertEqual(self.field.initial, self.initial)
83
 
        self.assertEqual(self.field.initial_fn, self.initial_fn)
84
 
 
85
 
    def test_gain_name(self):
86
 
        """.gain_name() sets three extra attributes."""
87
 
        self.assertIsNone(self.field.name)
88
 
        self.assertIsNone(self.field.instance_attr)
89
 
        self.assertIsNone(self.field.signal_name)
90
 
        self.field.gain_name("abcd")
91
 
        self.assertEqual(self.field.name, "abcd")
92
 
        self.assertEqual(self.field.instance_attr, "_abcd")
93
 
        self.assertEqual(self.field.signal_name, "on_abcd_changed")
94
 
 
95
 
    def test_repr(self):
96
 
        """.repr() works as expected."""
97
 
        self.field.gain_name("field")
98
 
        self.assertEqual(repr(self.field), "<Field name:'field'>")
99
 
 
100
 
    def test_is_mandatory(self):
101
 
        """.is_mandatory looks for initial value of MANDATORY."""
102
 
        self.field.initial = None
103
 
        self.assertFalse(self.field.is_mandatory)
104
 
        self.field.initial = MANDATORY
105
 
        self.assertTrue(self.field.is_mandatory)
106
 
 
107
 
    def test_cls_reads(self):
108
 
        """.__get__() returns the field if accessed via a class."""
109
 
        self.assertIs(self.field.__get__(None, self.owner), self.field)
110
 
 
111
 
    def test_obj_reads(self):
112
 
        """.__get__() reads POD data if accessed via an object."""
113
 
        # Reading the field requires the field to know its name
114
 
        self.field.gain_name("field")
115
 
        self.assertEqual(
116
 
            self.field.__get__(self.instance, self.owner),
117
 
            self.instance._field)
118
 
 
119
 
    def test_obj_writes(self):
120
 
        """.__set__() writes POD data."""
121
 
        # Writing the field requires the field to know its name
122
 
        self.field.gain_name("field")
123
 
        self.field.__set__(self.instance, "data")
124
 
        self.assertEqual(self.instance._field, "data")
125
 
 
126
 
    def test_obj_writes_fires_notification(self):
127
 
        """.__set__() fires change notifications."""
128
 
        # Let's enable notification and set the name so that the field knows
129
 
        # what to do when it gets set. Let's set the instance data to "old" to
130
 
        # track the actual change.
131
 
        self.field.notify = True
132
 
        self.field.gain_name("field")
133
 
        self.instance._field = "old"
134
 
        # Let's set the data to "new" now
135
 
        self.field.__set__(self.instance, "new")
136
 
        # And check that the notification system worked
137
 
        self.instance.on_field_changed.assert_called_with("old", "new")
138
 
 
139
 
    def test_obj_writes_uses_assign_chain(self):
140
 
        """.__set__() uses the assign filter list."""
141
 
        # Let's enable the assign filter composed out of two functions
142
 
        # and set some data using the field.
143
 
        fn1 = mock.Mock()
144
 
        fn2 = mock.Mock()
145
 
        self.field.assign_filter_list = [fn1, fn2]
146
 
        self.field.gain_name("field")
147
 
        self.instance._field = "old"
148
 
        self.field.__set__(self.instance, "new")
149
 
        # The current value in the field should be the return value of fn2()
150
 
        # and both fn1() and fn2() were called with the right arguments.
151
 
        fn1.assert_called_with(self.instance, self.field, "old", "new")
152
 
        fn2.assert_called_with(self.instance, self.field, "old", fn1())
153
 
        self.assertEqual(self.instance._field, fn2())
154
 
 
155
 
    def test_alter_cls_without_notification(self):
156
 
        """.alter_cls() doesn't do anything if notify is False."""
157
 
        cls = mock.Mock(name='cls')
158
 
        del cls.on_field_changed
159
 
        self.field.notify = False
160
 
        self.field.gain_name('field')
161
 
        self.field.alter_cls(cls)
162
 
        self.assertFalse(hasattr(cls, "on_field_changed"))
163
 
 
164
 
    def test_alter_cls_with_notification(self):
165
 
        """.alter_cls() adds a change signal if notify is True."""
166
 
        cls = mock.Mock(name='cls')
167
 
        del cls.on_field_changed
168
 
        cls.__name__ = "Klass"
169
 
        self.field.notify = True
170
 
        self.field.gain_name('field')
171
 
        self.field.alter_cls(cls)
172
 
        self.assertTrue(hasattr(cls, "on_field_changed"))
173
 
        self.assertEqual(
174
 
            cls.on_field_changed.signal_name, "Klass.on_field_changed")
175
 
 
176
 
 
177
 
class FieldCollectionTests(TestCase):
178
 
 
179
 
    """Tests for the _FieldCollection class."""
180
 
 
181
 
    def setUp(self):
182
 
        """Common set-up code."""
183
 
        self.foo = Field()
184
 
        self.bar = Field()
185
 
        self.ns = {
186
 
            'foo': self.foo,
187
 
            'bar': self.bar,
188
 
            'do_sth': lambda: True,
189
 
            'DATA': 42,
190
 
        }
191
 
        self.fc = _FieldCollection()
192
 
 
193
 
    def set_field_names(self):
194
 
        """Set names of the foo and bar fields."""
195
 
        self.foo.gain_name('foo')
196
 
        self.bar.gain_name('bar')
197
 
 
198
 
    def test_add_field_builds_field_list(self):
199
 
        """.add_field() appends new fields to field_list."""
200
 
        # because we're not calling inspect_namespace() which does that
201
 
        self.set_field_names()
202
 
        self.fc.add_field(self.foo, 'cls')
203
 
        self.assertEqual(self.fc.field_list, [self.foo])
204
 
        self.fc.add_field(self.bar, 'cls')
205
 
        self.assertEqual(self.fc.field_list, [self.foo, self.bar])
206
 
 
207
 
    def test_add_field_builds_field_origin_map(self):
208
 
        """.add_field() builds and maintains field_origin_map."""
209
 
        # because we're not calling inspect_namespace() which does that
210
 
        self.set_field_names()
211
 
        self.fc.add_field(self.foo, 'cls')
212
 
        self.assertEqual(self.fc.field_origin_map, {'foo': 'cls'})
213
 
        self.fc.add_field(self.bar, 'cls')
214
 
        self.assertEqual(
215
 
            self.fc.field_origin_map, {'foo': 'cls', 'bar': 'cls'})
216
 
 
217
 
    def test_add_field_detects_clashes(self):
218
 
        """.add_Field() detects field clashes and raises TypeError."""
219
 
        foo_clash = Field()
220
 
        foo_clash.name = 'foo'
221
 
        # because we're not calling inspect_namespace() which does that
222
 
        self.set_field_names()
223
 
        self.fc.add_field(self.foo, 'cls')
224
 
        with self.assertRaisesRegex(
225
 
                TypeError, 'field other_cls.foo clashes with cls.foo'):
226
 
            self.fc.add_field(foo_clash, 'other_cls')
227
 
 
228
 
    def test_inspect_base_classes_calls_add_field(self):
229
 
        """.inspect_base_classes() calls add_field() on each Field found."""
230
 
        class Base1(POD):
231
 
            foo = Field()
232
 
            bar = Field()
233
 
 
234
 
        class Base2(POD):
235
 
            froz = Field()
236
 
 
237
 
        class Unrelated:
238
 
            field_list = [mock.Mock('fake_field')]
239
 
 
240
 
        with mock.patch.object(self.fc, 'add_field') as mock_add_field:
241
 
            self.fc.inspect_base_classes((Base1, Base2, Unrelated))
242
 
            mock_add_field.assert_has_calls([
243
 
                ((Base1.foo, 'Base1'), {}),
244
 
                ((Base1.bar, 'Base1'), {}),
245
 
                ((Base2.froz, 'Base2'), {}),
246
 
            ])
247
 
 
248
 
    def test_inspect_namespace_calls_add_field(self):
249
 
        """.inspect_namespace() calls add_field() on each Field."""
250
 
        with mock.patch.object(self.fc, 'add_field') as mock_add_field:
251
 
            self.fc.inspect_namespace(self.ns, 'cls')
252
 
            calls = [mock.call(self.foo, 'cls'), mock.call(self.bar, 'cls')]
253
 
            mock_add_field.assert_has_calls(calls, any_order=True)
254
 
 
255
 
    def test_inspect_namespace_sets_field_name(self):
256
 
        """.inspect_namespace() sets .name of each field."""
257
 
        self.assertIsNone(self.foo.name)
258
 
        self.assertIsNone(self.bar.name)
259
 
        fc = _FieldCollection()
260
 
        fc.inspect_namespace(self.ns, 'cls')
261
 
        self.assertEqual(self.foo.name, 'foo')
262
 
        self.assertEqual(self.bar.name, 'bar')
263
 
 
264
 
    def test_inspect_namespace_sets_field_instance_attr(self):
265
 
        """.inspect_namespace() sets .instance_attr of each field."""
266
 
        self.assertIsNone(self.foo.instance_attr)
267
 
        self.assertIsNone(self.bar.instance_attr)
268
 
        fc = _FieldCollection()
269
 
        fc.inspect_namespace(self.ns, 'cls')
270
 
        self.assertEqual(self.foo.instance_attr, '_foo')
271
 
        self.assertEqual(self.bar.instance_attr, '_bar')
272
 
 
273
 
    def test_notifier(self):
274
 
        """@field.change_notifier changes the notify function."""
275
 
        @self.foo.change_notifier
276
 
        def on_foo_changed(pod, old, new):
277
 
            pass
278
 
        self.assertTrue(self.foo.notify)
279
 
        self.assertEqual(self.foo.notify_fn, on_foo_changed)
280
 
 
281
 
 
282
 
class PODTests(TestCase):
283
 
 
284
 
    """Tests for the POD class."""
285
 
 
286
 
    def test_field_list(self):
287
 
        """.field_list is set by PODMeta."""
288
 
        m = mock.Mock()
289
 
 
290
 
        class T(POD):
291
 
            f1 = Field()
292
 
            f2 = Field(initial='default')
293
 
            f3 = Field(initial_fn=lambda: m())
294
 
 
295
 
        self.assertEqual(T.field_list, [T.f1, T.f2, T.f3])
296
 
 
297
 
    def test_namedtuple_cls(self):
298
 
        """Check that .namedtuple_cls is set up by PODMeta."""
299
 
        m = mock.Mock()
300
 
 
301
 
        class T(POD):
302
 
            f1 = Field()
303
 
            f2 = Field(initial='default')
304
 
            f3 = Field(initial_fn=lambda: m())
305
 
 
306
 
        self.assertEqual(T.namedtuple_cls.__name__, 'T')
307
 
        self.assertIsInstance(T.namedtuple_cls.f1, property)
308
 
        self.assertIsInstance(T.namedtuple_cls.f2, property)
309
 
        self.assertIsInstance(T.namedtuple_cls.f3, property)
310
 
 
311
 
    def test_initializer_positional_arguments(self):
312
 
        """.__init__() works correctly with positional arguments."""
313
 
        m = mock.Mock()
314
 
 
315
 
        class T(POD):
316
 
            f1 = Field()
317
 
            f2 = Field(initial='default')
318
 
            f3 = Field(initial_fn=lambda: m())
319
 
 
320
 
        self.assertEqual(T().f1, None)
321
 
        self.assertEqual(T().f2, "default")
322
 
        self.assertEqual(T().f3, m())
323
 
        self.assertEqual(T(1).f1, 1)
324
 
        self.assertEqual(T(1).f2, 'default')
325
 
        self.assertEqual(T(1).f3, m())
326
 
        self.assertEqual(T(1, 2).f1, 1)
327
 
        self.assertEqual(T(1, 2).f2, 2)
328
 
        self.assertEqual(T(1, 2, 3).f3, 3)
329
 
 
330
 
    def test_initializer_keyword_arguments(self):
331
 
        """.__init__() works correctly with keyword arguments."""
332
 
        m = mock.Mock()
333
 
 
334
 
        class T(POD):
335
 
            f1 = Field()
336
 
            f2 = Field(initial='default')
337
 
            f3 = Field(initial_fn=lambda: m())
338
 
 
339
 
        self.assertEqual(T().f1, None)
340
 
        self.assertEqual(T().f2, "default")
341
 
        self.assertEqual(T().f3, m())
342
 
        self.assertEqual(T(f1=1).f1, 1)
343
 
        self.assertEqual(T(f1=1).f2, 'default')
344
 
        self.assertEqual(T(f1=1).f3, m())
345
 
        self.assertEqual(T(f1=1, f2=2).f1, 1)
346
 
        self.assertEqual(T(f1=1, f2=2).f2, 2)
347
 
        self.assertEqual(T(f1=1, f2=2).f3, m())
348
 
        self.assertEqual(T(f1=1, f2=2, f3=3).f1, 1)
349
 
        self.assertEqual(T(f1=1, f2=2, f3=3).f2, 2)
350
 
        self.assertEqual(T(f1=1, f2=2, f3=3).f3, 3)
351
 
 
352
 
    def test_initializer_mandatory_arguments(self):
353
 
        """.__init__() understands MANDATORY fields."""
354
 
        class T(POD):
355
 
            m1 = Field(initial=MANDATORY)
356
 
            m2 = Field(initial=MANDATORY)
357
 
 
358
 
        with self.assertRaisesRegex(
359
 
                TypeError, "mandatory argument missing: m1"):
360
 
            T()
361
 
        with self.assertRaisesRegex(
362
 
                TypeError, "mandatory argument missing: m1"):
363
 
            T(m2=2)
364
 
        with self.assertRaisesRegex(
365
 
                TypeError, "mandatory argument missing: m2"):
366
 
            T(1)
367
 
        with self.assertRaisesRegex(
368
 
                TypeError, "mandatory argument missing: m2"):
369
 
            T(m1=1)
370
 
 
371
 
    def test_initializer_default_arguments(self):
372
 
        """.__init__() understands initial (default) field values."""
373
 
        class T(POD):
374
 
            f = Field(initial=42)
375
 
        self.assertEqual(T().f, 42)
376
 
        self.assertEqual(T(1).f, 1)
377
 
        self.assertEqual(T(f=1).f, 1)
378
 
 
379
 
    def test_initializer_duplicate_field_value(self):
380
 
        """.__init__() prevents double-initialization."""
381
 
        class T(POD):
382
 
            f = Field()
383
 
        with self.assertRaisesRegex(
384
 
                TypeError, "field initialized twice: f"):
385
 
            T(1, f=2)
386
 
 
387
 
    def test_initializer_unknown_field(self):
388
 
        """.__init__() prevents initializing unknown fields."""
389
 
        class T(POD):
390
 
            pass
391
 
        with self.assertRaisesRegex(TypeError, "too many arguments"):
392
 
            T(1)
393
 
        with self.assertRaisesRegex(TypeError, "no such field: f"):
394
 
            T(f=1)
395
 
 
396
 
    def test_smoke(self):
397
 
        """Check that basic POD behavior works okay."""
398
 
        class Person(POD):
399
 
            name = Field()
400
 
            age = Field()
401
 
 
402
 
            def __str__(self):
403
 
                return 'Mr. {}'.format(self.name)
404
 
 
405
 
        class Employee(Person):
406
 
            salary = Field()
407
 
 
408
 
        self.assertEqual(
409
 
            Person.field_list, [Person.name, Person.age])
410
 
        joe = Employee('Joe')
411
 
        # Methods still work
412
 
        self.assertEqual(str(joe), 'Mr. Joe')
413
 
        # Reading attributes works
414
 
        self.assertEqual(joe.name, 'Joe')
415
 
        self.assertEqual(joe.age, None)
416
 
        # Setting attributes works
417
 
        joe.age = 42
418
 
        self.assertEqual(joe.age, 42)
419
 
        joe.salary = 1000
420
 
        self.assertEqual(joe.salary, 1000)
421
 
        # Comparison to other PODs works
422
 
        self.assertEqual(joe, Employee('Joe', 42, 1000))
423
 
        self.assertLess(joe, Employee('Joe', 45, 1000))
424
 
        # The .as_{tuple,dict}() methods work
425
 
        self.assertEqual(joe.as_tuple(), ('Joe', 42, 1000))
426
 
        self.assertEqual(
427
 
            joe.as_dict(), {'name': 'Joe', 'age': 42, 'salary': 1000})
428
 
        # The return value of repr is useful
429
 
        self.assertEqual(
430
 
            repr(joe), "Employee(name='Joe', age=42, salary=1000)")
431
 
 
432
 
    def test_as_dict_filters_out_UNSET(self):
433
 
        """.as_dict() filters out UNSET values."""
434
 
        class P(POD):
435
 
            f = Field()
436
 
 
437
 
        p = P()
438
 
        p.f = UNSET
439
 
        self.assertEqual(p.as_dict(), {})
440
 
 
441
 
    def test_notifications(self):
442
 
        """.on_{field}_changed() gets fired by field modification."""
443
 
        class T(POD):
444
 
            f = Field(notify=True)
445
 
 
446
 
        field_callback = mock.Mock(name='field_callback')
447
 
        # Create a POD and connect signal listeners
448
 
        pod = T()
449
 
        pod.on_f_changed.connect(field_callback)
450
 
        # Modify a field
451
 
        pod.f = 1
452
 
        # Ensure the modification worked
453
 
        self.assertEqual(pod.f, 1)
454
 
        # Ensure signals fired
455
 
        field_callback.assert_called_with(None, 1)
456
 
 
457
 
    def test_pod_inheritance(self):
458
 
        """Check that PODs can be subclassed and new fields can be added."""
459
 
        class B(POD):
460
 
            f1 = Field(notify=True)
461
 
 
462
 
        class D(B):
463
 
            f2 = Field()
464
 
 
465
 
        # D doesn't shadow B.f1
466
 
        self.assertIs(B.on_f1_changed, D.on_f1_changed)
467
 
        # B and D has correct field lists
468
 
        self.assertEqual(B.field_list, [B.f1])
469
 
        self.assertEqual(D.field_list, [B.f1, D.f2])
470
 
 
471
 
    def test_pod_ordering(self):
472
 
        """Check that comparison among single POD class works okay."""
473
 
        class A(POD):
474
 
            a = Field()
475
 
 
476
 
        B = A  # easier to understand subsequent tests
477
 
        self.assertTrue(A(1) == B(1))
478
 
        self.assertTrue(A(1) != B(0))
479
 
        self.assertTrue(A(0) < B(1))
480
 
        self.assertTrue(A(1) > B(0))
481
 
        self.assertTrue(A(1) >= B(1))
482
 
        self.assertTrue(A(1) <= B(1))
483
 
 
484
 
    def test_pod_ordering_tricky1(self):
485
 
        """Check that comparison among different POD classes works okay."""
486
 
        class A(POD):
487
 
            f = Field()
488
 
 
489
 
        class B(POD):
490
 
            f = Field()
491
 
 
492
 
        self.assertTrue(A(1) == B(1))
493
 
        self.assertTrue(A(1) != B(0))
494
 
        self.assertTrue(A(0) < B(1))
495
 
        self.assertTrue(A(1) > B(0))
496
 
        self.assertTrue(A(1) >= B(1))
497
 
        self.assertTrue(A(1) <= B(1))
498
 
 
499
 
    def test_pod_ordering_tricky2(self):
500
 
        """Check that comparison doesn't care about field names."""
501
 
        class A(POD):
502
 
            a = Field()
503
 
 
504
 
        class B(POD):
505
 
            b = Field()
506
 
 
507
 
        self.assertTrue(A(1) == B(1))
508
 
        self.assertTrue(A(1) != B(0))
509
 
        self.assertTrue(A(0) < B(1))
510
 
        self.assertTrue(A(1) > B(0))
511
 
        self.assertTrue(A(1) >= B(1))
512
 
        self.assertTrue(A(1) <= B(1))
513
 
 
514
 
    def test_pod_ordering_other_types(self):
515
 
        """Check that comparison between POD and not-POD types doesn't work."""
516
 
        class A(POD):
517
 
            f = Field()
518
 
 
519
 
        self.assertFalse(A(1) == (1,))
520
 
        self.assertFalse(A(1) == [1])
521
 
        self.assertFalse(A(1) == 1)
522
 
 
523
 
 
524
 
class AssignFilterTests(TestCase):
525
 
 
526
 
    """Tests for assignment filters."""
527
 
 
528
 
    def test_read_only_assign_filter(self):
529
 
        """The read_only_assign_filter works as designed."""
530
 
        instance = mock.Mock(name='instance')
531
 
        instance.__class__.__name__ = 'cls'
532
 
        field = mock.Mock(name='field')
533
 
        field.name = 'field'
534
 
        old = 'old'
535
 
        new = 'new'
536
 
        # The filter passes the initial data (when old is UNSET)
537
 
        self.assertEqual(
538
 
            read_only_assign_filter(instance, field, UNSET, new), new)
539
 
        # But rejects everything after that
540
 
        with self.assertRaisesRegex(AttributeError, "cls.field is read-only"):
541
 
            read_only_assign_filter(instance, field, old, new)
542
 
 
543
 
    def test_type_convert_assign_filter(self):
544
 
        """The type_convert_assign_filter works as designed."""
545
 
        instance = mock.Mock(name='instance')
546
 
        old = mock.Mock(name='old')
547
 
        field = mock.Mock(name='field')
548
 
        field.type = int
549
 
        # The filter converts values
550
 
        self.assertEqual(
551
 
            type_convert_assign_filter(instance, field, old, '10'), 10)
552
 
        # And can be used for crude type checking
553
 
        msg = "invalid literal for int\\(\\) with base 10: 'hello\\?'"
554
 
        with self.assertRaisesRegex(ValueError, msg):
555
 
            type_convert_assign_filter(instance, field, old, 'hello?')
556
 
 
557
 
    def test_type_check_assign_filter(self):
558
 
        """The type_check_assign_filter works as designed."""
559
 
        instance = mock.Mock(name='instance')
560
 
        instance.__class__.__name__ = 'cls'
561
 
        old = mock.Mock(name='old')
562
 
        field = mock.Mock(name='field')
563
 
        field.name = 'field'
564
 
        field.type = int
565
 
        # The filter type-checks values without any conversion
566
 
        msg = "cls.field requires objects of type int"
567
 
        with self.assertRaisesRegex(TypeError, msg):
568
 
            type_check_assign_filter(instance, field, old, '10')
569
 
        # The filter passes-through correctly-typed values
570
 
        self.assertEqual(
571
 
            type_check_assign_filter(instance, field, old, 10), 10)
572
 
 
573
 
    def test_sequence_type_check_assign_filter(self):
574
 
        """The sequence_type_check_assign_filter works as designed."""
575
 
        instance = mock.Mock(name='instance')
576
 
        instance.__class__.__name__ = 'cls'
577
 
        old = mock.Mock(name='old')
578
 
        field = mock.Mock(name='field')
579
 
        field.name = 'field'
580
 
        # The filter type-checks values without any conversion
581
 
        msg = "cls.field requires all sequence elements of type int"
582
 
        with self.assertRaisesRegex(TypeError, msg):
583
 
            sequence_type_check_assign_filter(int)(
584
 
                instance, field, old, ['10'])
585
 
        # The filter passes-through correctly-typed values
586
 
        self.assertEqual(
587
 
            sequence_type_check_assign_filter(int)(
588
 
                instance, field, old, [10, 20]),
589
 
            [10, 20])
590
 
        self.assertEqual(
591
 
            sequence_type_check_assign_filter(int)(
592
 
                instance, field, old, (10, 20,)),
593
 
            (10, 20,))
594
 
 
595
 
    def test_unset_or_type_check_assign_filter(self):
596
 
        """The unset_or_type_check_assign_filter works as designed."""
597
 
        instance = mock.Mock(name='instance')
598
 
        instance.__class__.__name__ = 'cls'
599
 
        old = mock.Mock(name='old')
600
 
        field = mock.Mock(name='field')
601
 
        field.name = 'field'
602
 
        field.type = int
603
 
        # The filter type-checks values without any conversion
604
 
        msg = "cls.field requires objects of type int"
605
 
        with self.assertRaisesRegex(TypeError, msg):
606
 
            unset_or_type_check_assign_filter(instance, field, old, '10')
607
 
        # The filter passes-through correctly-typed values
608
 
        self.assertEqual(
609
 
            unset_or_type_check_assign_filter(instance, field, old, 10), 10)
610
 
        # The filter also passes UNSET values.
611
 
        self.assertEqual(
612
 
            unset_or_type_check_assign_filter(instance, field, old, UNSET),
613
 
            UNSET)
614
 
 
615
 
    def test_unset_or_sequence_type_check_assign_filter(self):
616
 
        """The unset_or_sequence_type_check_assign_filter works as designed."""
617
 
        instance = mock.Mock(name='instance')
618
 
        instance.__class__.__name__ = 'cls'
619
 
        old = mock.Mock(name='old')
620
 
        field = mock.Mock(name='field')
621
 
        field.name = 'field'
622
 
        # The filter type-checks values without any conversion
623
 
        msg = "cls.field requires all sequence elements of type int"
624
 
        with self.assertRaisesRegex(TypeError, msg):
625
 
            sequence_type_check_assign_filter(int)(
626
 
                instance, field, old, ['10'])
627
 
        # The filter passes-through correctly-typed values
628
 
        self.assertEqual(
629
 
            unset_or_sequence_type_check_assign_filter(int)(
630
 
                instance, field, old, [10, 20]),
631
 
            [10, 20])
632
 
        self.assertEqual(
633
 
            unset_or_sequence_type_check_assign_filter(int)(
634
 
                instance, field, old, (10, 20,)),
635
 
            (10, 20,))
636
 
        # The filter also passes UNSET values.
637
 
        self.assertEqual(
638
 
            unset_or_sequence_type_check_assign_filter(int)(
639
 
                instance, field, old, UNSET),
640
 
            UNSET)