Passed
Push — develop ( f2f304...7cf658 )
by Jace
01:45
created

  B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 181
Duplicated Lines 54.14 %

Importance

Changes 0
Metric Value
wmc 43
dl 98
loc 181
rs 8.3157
c 0
b 0
f 0

4 Methods

Rating   Name   Duplication   Size   Complexity  
F estStandard.test_function_to_json() 50 51 11
F estStandard.test_function() 43 47 11
A estStandard.test_auto_off() 3 14 3
F estStandard.test_decorator() 0 59 18

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
"""Integration tests for the package."""
2
# pylint: disable=missing-docstring,no-self-use,no-member,misplaced-comparison-constant,attribute-defined-outside-init
3
4
import yorm
5
from yorm.types import Object, String, Integer, Float, Boolean
6
from yorm.types import Markdown, Dictionary, List
7
8
from . import strip, refresh_file_modification_times, log
9
10
11
# CLASSES ######################################################################
12
13
14
class EmptyDictionary(Dictionary):
15
    """Sample dictionary container."""
16
17
18
@yorm.attr(all=Integer)
19
class IntegerList(List):
20
    """Sample list container."""
21
22
23
class SampleStandard:
24
    """Sample class using standard attribute types."""
25
26
    def __init__(self):
27
        # https://docs.python.org/3.4/library/json.html#json.JSONDecoder
28
        self.object = {}
29
        self.array = []
30
        self.string = ""
31
        self.number_int = 0
32
        self.number_real = 0.0
33
        self.truthy = True
34
        self.falsey = False
35
        self.null = None
36
37
    def __repr__(self):
38
        return "<standard {}>".format(id(self))
39
40
41
@yorm.attr(array=IntegerList)
42
@yorm.attr(falsey=Boolean)
43
@yorm.attr(number_int=Integer)
44
@yorm.attr(number_real=Float)
45 View Code Duplication
@yorm.attr(object=EmptyDictionary)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
46
@yorm.attr(string=String)
47
@yorm.attr(truthy=Boolean)
48
@yorm.sync("tmp/{self.category}/{self.name}.yml")
49
class SampleStandardDecorated:
50
    """Sample class using standard attribute types."""
51
52
    def __init__(self, name, category='default'):
53
        self.name = name
54
        self.category = category
55
        # https://docs.python.org/3.4/library/json.html#json.JSONDecoder
56
        self.object = {}
57
        self.array = []
58
        self.string = ""
59
        self.number_int = 0
60
        self.number_real = 0.0
61
        self.truthy = True
62
        self.falsey = False
63
        self.null = None
64
65
    def __repr__(self):
66
        return "<decorated {}>".format(id(self))
67
68
69
@yorm.attr(label=String)
70
@yorm.attr(status=Boolean)
71
class StatusDictionary(Dictionary):
72
    """Sample dictionary container."""
73
74
75
@yorm.attr(all=StatusDictionary)
76
class StatusDictionaryList(List):
77
    """Sample list container."""
78
79
80
class Level(String):
81
    """Sample custom attribute."""
82
83
    @classmethod
84
    def to_data(cls, obj):
85
        value = cls.to_value(obj)
86
        count = value.split('.')
87
        if count == 0:
88
            return int(value)
89
        elif count == 1:
90
            return float(value)
91
        else:
92
            return value
93
94
95
@yorm.sync("tmp/directory/{UUID}.yml", attrs={'level': Level})
96
class SampleCustomDecorated:
97
    """Sample class using custom attribute types."""
98
99
    def __init__(self, name):
100
        self.name = name
101
        self.level = '1.0'
102
103
    def __repr__(self):
104
        return "<custom {}>".format(id(self))
105
106
107
@yorm.attr(string=String)
108
@yorm.sync("tmp/sample.yml", auto_save=False)
109
class SampleDecoratedAutoOff:
110
    """Sample class with automatic storage turned off."""
111
112
    def __init__(self):
113
        self.string = ""
114
115
    def __repr__(self):
116
        return "<auto save off {}>".format(id(self))
117
118
119
@yorm.sync("tmp/sample.yml", auto_track=True)
120
class SampleEmptyDecorated:
121
    """Sample class using standard attribute types."""
122
123
    def __repr__(self):
124
        return "<empty {}>".format(id(self))
125
126
127
class SampleExtended:
128
    """Sample class using extended attribute types."""
129
130
    def __init__(self):
131
        self.text = ""
132
133
    def __repr__(self):
134
        return "<extended {}>".format(id(self))
135
136
137
class SampleNested:
138
    """Sample class using nested attribute types."""
139
140
    def __init__(self):
141
        self.count = 0
142
        self.results = []
143
144
    def __repr__(self):
145
        return "<nested {}>".format(id(self))
146
147
# TESTS ########################################################################
148
149
150
class TestStandard:
151
    """Integration tests for standard attribute types."""
152
153
    @yorm.attr(status=yorm.types.Boolean)
154
    class StatusDictionary(Dictionary):
155
        pass
156
157
    def test_decorator(self, tmpdir):
158
        """Verify standard attribute types dump/parse correctly (decorator)."""
159
        tmpdir.chdir()
160
        sample = SampleStandardDecorated('sample')
161
        assert "tmp/default/sample.yml" == sample.__mapper__.path
162
163
        log("Checking object default values...")
164
        assert {} == sample.object
165
        assert [] == sample.array
166
        assert "" == sample.string
167
        assert 0 == sample.number_int
168
        assert 0.0 == sample.number_real
169
        assert True is sample.truthy
170
        assert False is sample.falsey
171
        assert None is sample.null
172
173
        log("Changing object values...")
174
        sample.object = {'key2': 'value'}
175
        sample.array = [0, 1, 2]
176
        sample.string = "Hello, world!"
177
        sample.number_int = 42
178
        sample.number_real = 4.2
179
        sample.truthy = False
180
        sample.falsey = True
181
182
        log("Checking file contents...")
183
        assert strip("""
184
        array:
185
        - 0
186
        - 1
187
        - 2
188
        falsey: true
189
        number_int: 42
190
        number_real: 4.2
191
        object: {}
192
        string: Hello, world!
193
        truthy: false
194
        """) == sample.__mapper__.text
195
196
        log("Changing file contents...")
197
        refresh_file_modification_times()
198
        sample.__mapper__.text = strip("""
199
        array: [4, 5, 6]
200
        falsey: null
201
        number_int: 42
202
        number_real: '4.2'
203
        object: {'status': false}
204
        string: "abc"
205
        truthy: null
206
        """)
207
208
        log("Checking object values...")
209
        assert {'status': False} == sample.object
210
        assert [4, 5, 6] == sample.array
211
        assert "abc" == sample.string
212
        assert 42 == sample.number_int
213
        assert 4.2 == sample.number_real
214
        assert False is sample.truthy
215
        assert False is sample.falsey
216
217
    def test_function(self, tmpdir):
218
        """Verify standard attribute types dump/parse correctly (function)."""
219
        tmpdir.chdir()
220
        _sample = SampleStandard()
221 View Code Duplication
        attrs = {'object': self.StatusDictionary,
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
222
                 'array': IntegerList,
223
                 'string': String,
224
                 'number_int': Integer,
225
                 'number_real': Float,
226
                 'truthy': Boolean,
227
                 'falsey': Boolean}
228
        sample = yorm.sync(_sample, "tmp/directory/sample.yml", attrs)
229
        assert "tmp/directory/sample.yml" == sample.__mapper__.path
230
231
        # check defaults
232
        assert {'status': False} == sample.object
233
        assert [] == sample.array
234
        assert "" == sample.string
235
        assert 0 == sample.number_int
236
        assert 0.0 == sample.number_real
237
        assert True is sample.truthy
238
        assert False is sample.falsey
239
        assert None is sample.null
240
241
        # change object values
242
        sample.object = {'key': 'value'}
243
        sample.array = [1, 2, 3]
244
        sample.string = "Hello, world!"
245
        sample.number_int = 42
246
        sample.number_real = 4.2
247
        sample.truthy = None
248
        sample.falsey = 1
249
250
        # check file values
251
        assert strip("""
252
        array:
253
        - 1
254
        - 2
255
        - 3
256
        falsey: true
257
        number_int: 42
258
        number_real: 4.2
259
        object:
260
          status: false
261
        string: Hello, world!
262
        truthy: false
263
        """) == sample.__mapper__.text
264
265
    def test_function_to_json(self, tmpdir):
266
        """Verify standard attribute types dump/parse correctly (function)."""
267
        tmpdir.chdir()
268
        _sample = SampleStandard()
269 View Code Duplication
        attrs = {'object': self.StatusDictionary,
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
270
                 'array': IntegerList,
271
                 'string': String,
272
                 'number_int': Integer,
273
                 'number_real': Float,
274
                 'truthy': Boolean,
275
                 'falsey': Boolean}
276
        sample = yorm.sync(_sample, "tmp/directory/sample.json", attrs)
277
        assert "tmp/directory/sample.json" == sample.__mapper__.path
278
279
        # check defaults
280
        assert {'status': False} == sample.object
281
        assert [] == sample.array
282
        assert "" == sample.string
283
        assert 0 == sample.number_int
284
        assert 0.0 == sample.number_real
285
        assert True is sample.truthy
286
        assert False is sample.falsey
287
        assert None is sample.null
288
289
        # change object values
290
        sample.object = {'key': 'value'}
291
        sample.array = [1, 2, 3]
292
        sample.string = "Hello, world!"
293
        sample.number_int = 42
294
        sample.number_real = 4.2
295
        sample.truthy = None
296
        sample.falsey = 1
297
298
        # check file values
299
        assert strip("""
300
        {
301
            "array": [
302
                1,
303
                2,
304
                3
305
            ],
306
            "falsey": true,
307
            "number_int": 42,
308
            "number_real": 4.2,
309
            "object": {
310
                "status": false
311
            },
312
            "string": "Hello, world!",
313
            "truthy": false
314
        }
315
        """, tabs=2, end='') == sample.__mapper__.text
316
317
    def test_auto_off(self, tmpdir):
318
        """Verify file updates are disabled with auto save off."""
319
        tmpdir.chdir()
320
        sample = SampleDecoratedAutoOff()
321
322
        sample.string = "hello"
323
        assert "" == sample.__mapper__.text
324
325
        sample.__mapper__.auto_save = True
326
        sample.string = "world"
327
328
        assert strip("""
329
        string: world
330
        """) == sample.__mapper__.text
331
332
333
class TestContainers:
334
    """Integration tests for attribute containers."""
335
336
    def test_nesting(self, tmpdir):
337
        """Verify standard attribute types can be nested."""
338
        tmpdir.chdir()
339
        _sample = SampleNested()
340
        attrs = {'count': Integer,
341
                 'results': StatusDictionaryList}
342
        sample = yorm.sync(_sample, "tmp/sample.yml", attrs, auto_track=True)
343
344
        # check defaults
345
        assert 0 == sample.count
346
        assert [] == sample.results
347
348
        # change object values
349
        sample.count = 5
350
        sample.results = [{'status': False, 'label': "abc"},
351
                          {'status': None, 'label': None},
352
                          {'label': "def"},
353
                          {'status': True},
354
                          {}]
355
356
        # check file values
357
        assert strip("""
358
        count: 5
359
        results:
360
        - label: abc
361
          status: false
362
        - label: ''
363
          status: false
364
        - label: def
365
          status: false
366
        - label: ''
367
          status: true
368
        - label: ''
369
          status: false
370
        """) == sample.__mapper__.text
371
372
        # change file values
373
        refresh_file_modification_times()
374
        sample.__mapper__.text = strip("""
375
        count: 3
376
        other: 4.2
377
        results:
378
        - label: abc
379
        - label: null
380
          status: false
381
        - status: true
382
        """)
383
384
        # check object values
385
        assert 3 == sample.count
386
        assert 4.2 == sample.other
387
        assert [{'label': 'abc', 'status': False},
388
                {'label': '', 'status': False},
389
                {'label': '', 'status': True}] == sample.results
390
391
    def test_objects(self, tmpdir):
392
        """Verify containers are treated as objects when added."""
393
        tmpdir.chdir()
394
        sample = SampleEmptyDecorated()
395
396
        # change file values
397
        refresh_file_modification_times()
398
        sample.__mapper__.text = strip("""
399
        object: {'key': 'value'}
400
        array: [1, '2', '3.0']
401
        """)
402
403
        # (a mapped attribute must be read first to trigger retrieving)
404
        sample.__mapper__.load()
405
406
        # check object values
407
        assert {'key': 'value'} == sample.object
408
        assert [1, '2', '3.0'] == sample.array
409
410
        # check object types
411
        assert Object == sample.__mapper__.attrs['object']
412
        assert Object == sample.__mapper__.attrs['array']
413
414
415
class TestExtended:
416
    """Integration tests for extended attribute types."""
417
418
    def test_function(self, tmpdir):
419
        """Verify extended attribute types dump/parse correctly."""
420
        tmpdir.chdir()
421
        _sample = SampleExtended()
422
        attrs = {'text': Markdown}
423
        sample = yorm.sync(_sample, "tmp/directory/sample.yml", attrs)
424
425
        # check defaults
426
        assert "" == sample.text
427
428
        # change object values
429
        refresh_file_modification_times()
430
        sample.text = strip("""
431
        This is the first sentence. This is the second sentence.
432
        This is the third sentence.
433
        """)
434
435
        # check file values
436
        assert strip("""
437
        text: |
438
          This is the first sentence.
439
          This is the second sentence.
440
          This is the third sentence.
441
        """) == sample.__mapper__.text
442
443
        # change file values
444
        refresh_file_modification_times()
445
        sample.__mapper__.text = strip("""
446
        text: |
447
          This is a
448
          sentence.
449
        """)
450
451
        # check object values
452
        assert "This is a sentence." == sample.text
453
454
455
class TestCustom:
456
    """Integration tests for custom attribute types."""
457
458
    def test_decorator(self, tmpdir):
459
        """Verify custom attribute types dump/parse correctly."""
460
        tmpdir.chdir()
461
        sample = SampleCustomDecorated('sample')
462
463
        # check defaults
464
        assert '1.0' == sample.level
465
466
        # change values
467
        sample.level = '1.2.3'
468
469
        # check file values
470
        assert strip("""
471
        level: 1.2.3
472
        """) == sample.__mapper__.text
473
474
        # change file values
475
        refresh_file_modification_times()
476
        sample.__mapper__.text = strip("""
477
        level: 1
478
        """)
479
480
        # check object values
481
        assert '1' == sample.level
482