File: test_metadata.py

package info (click to toggle)
python-daemon 2.1.2-1
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 612 kB
  • ctags: 728
  • sloc: python: 4,519; sh: 32; makefile: 21
file content (380 lines) | stat: -rw-r--r-- 13,514 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
# -*- coding: utf-8 -*-
#
# test/test_metadata.py
# Part of ‘python-daemon’, an implementation of PEP 3143.
#
# Copyright © 2008–2016 Ben Finney <ben+python@benfinney.id.au>
#
# This is free software: you may copy, modify, and/or distribute this work
# under the terms of the Apache License, version 2.0 as published by the
# Apache Software Foundation.
# No warranty expressed or implied. See the file ‘LICENSE.ASF-2’ for details.

""" Unit test for ‘_metadata’ private module.
    """

from __future__ import (absolute_import, unicode_literals)

import sys
import errno
import re
try:
    # Python 3 standard library.
    import urllib.parse as urlparse
except ImportError:
    # Python 2 standard library.
    import urlparse
import functools
import collections
import json

import pkg_resources
import mock
import testtools.helpers
import testtools.matchers
import testscenarios

from . import scaffold
from .scaffold import (basestring, unicode)

import daemon._metadata as metadata


class HasAttribute(testtools.matchers.Matcher):
    """ A matcher to assert an object has a named attribute. """

    def __init__(self, name):
        self.attribute_name = name

    def match(self, instance):
        """ Assert the object `instance` has an attribute named `name`. """
        result = None
        if not testtools.helpers.safe_hasattr(instance, self.attribute_name):
            result = AttributeNotFoundMismatch(instance, self.attribute_name)
        return result


class AttributeNotFoundMismatch(testtools.matchers.Mismatch):
    """ The specified instance does not have the named attribute. """

    def __init__(self, instance, name):
        self.instance = instance
        self.attribute_name = name

    def describe(self):
        """ Emit a text description of this mismatch. """
        text = (
                "{instance!r}"
                " has no attribute named {name!r}").format(
                    instance=self.instance, name=self.attribute_name)
        return text


class metadata_value_TestCase(scaffold.TestCaseWithScenarios):
    """ Test cases for metadata module values. """

    expected_str_attributes = set([
            'version_installed',
            'author',
            'copyright',
            'license',
            'url',
            ])

    scenarios = [
            (name, {'attribute_name': name})
            for name in expected_str_attributes]
    for (name, params) in scenarios:
        if name == 'version_installed':
            # No duck typing, this attribute might be None.
            params['ducktype_attribute_name'] = NotImplemented
            continue
        # Expect an attribute of ‘str’ to test this value.
        params['ducktype_attribute_name'] = 'isdigit'

    def test_module_has_attribute(self):
        """ Metadata should have expected value as a module attribute. """
        self.assertThat(
                metadata, HasAttribute(self.attribute_name))

    def test_module_attribute_has_duck_type(self):
        """ Metadata value should have expected duck-typing attribute. """
        if self.ducktype_attribute_name == NotImplemented:
            self.skipTest("Can't assert this attribute's type")
        instance = getattr(metadata, self.attribute_name)
        self.assertThat(
                instance, HasAttribute(self.ducktype_attribute_name))


class parse_person_field_TestCase(
        testscenarios.WithScenarios, testtools.TestCase):
    """ Test cases for ‘get_latest_version’ function. """

    scenarios = [
            ('simple', {
                'test_person': "Foo Bar <foo.bar@example.com>",
                'expected_result': ("Foo Bar", "foo.bar@example.com"),
                }),
            ('empty', {
                'test_person': "",
                'expected_result': (None, None),
                }),
            ('none', {
                'test_person': None,
                'expected_error': TypeError,
                }),
            ('no email', {
                'test_person': "Foo Bar",
                'expected_result': ("Foo Bar", None),
                }),
            ]

    def test_returns_expected_result(self):
        """ Should return expected result. """
        if hasattr(self, 'expected_error'):
            self.assertRaises(
                    self.expected_error,
                    metadata.parse_person_field, self.test_person)
        else:
            result = metadata.parse_person_field(self.test_person)
            self.assertEqual(self.expected_result, result)


class YearRange_TestCase(scaffold.TestCaseWithScenarios):
    """ Test cases for ‘YearRange’ class. """

    scenarios = [
            ('simple', {
                'begin_year': 1970,
                'end_year': 1979,
                'expected_text': "1970–1979",
                }),
            ('same year', {
                'begin_year': 1970,
                'end_year': 1970,
                'expected_text': "1970",
                }),
            ('no end year', {
                'begin_year': 1970,
                'end_year': None,
                'expected_text': "1970",
                }),
            ]

    def setUp(self):
        """ Set up test fixtures. """
        super(YearRange_TestCase, self).setUp()

        self.test_instance = metadata.YearRange(
                self.begin_year, self.end_year)

    def test_text_representation_as_expected(self):
        """ Text representation should be as expected. """
        result = unicode(self.test_instance)
        self.assertEqual(result, self.expected_text)


FakeYearRange = collections.namedtuple('FakeYearRange', ['begin', 'end'])

@mock.patch.object(metadata, 'YearRange', new=FakeYearRange)
class make_year_range_TestCase(scaffold.TestCaseWithScenarios):
    """ Test cases for ‘make_year_range’ function. """

    scenarios = [
            ('simple', {
                'begin_year': "1970",
                'end_date': "1979-01-01",
                'expected_range': FakeYearRange(begin=1970, end=1979),
                }),
            ('same year', {
                'begin_year': "1970",
                'end_date': "1970-01-01",
                'expected_range': FakeYearRange(begin=1970, end=1970),
                }),
            ('no end year', {
                'begin_year': "1970",
                'end_date': None,
                'expected_range': FakeYearRange(begin=1970, end=None),
                }),
            ('end date UNKNOWN token', {
                'begin_year': "1970",
                'end_date': "UNKNOWN",
                'expected_range': FakeYearRange(begin=1970, end=None),
                }),
            ('end date FUTURE token', {
                'begin_year': "1970",
                'end_date': "FUTURE",
                'expected_range': FakeYearRange(begin=1970, end=None),
                }),
            ]

    def test_result_matches_expected_range(self):
        """ Result should match expected YearRange. """
        result = metadata.make_year_range(self.begin_year, self.end_date)
        self.assertEqual(result, self.expected_range)


class metadata_content_TestCase(scaffold.TestCase):
    """ Test cases for content of metadata. """

    def test_copyright_formatted_correctly(self):
        """ Copyright statement should be formatted correctly. """
        regex_pattern = (
                "Copyright © "
                "\d{4}" # four-digit year
                "(?:–\d{4})?" # optional range dash and ending four-digit year
                )
        regex_flags = re.UNICODE
        self.assertThat(
                metadata.copyright,
                testtools.matchers.MatchesRegex(regex_pattern, regex_flags))

    def test_author_formatted_correctly(self):
        """ Author information should be formatted correctly. """
        regex_pattern = (
                ".+ " # name
                "<[^>]+>" # email address, in angle brackets
                )
        regex_flags = re.UNICODE
        self.assertThat(
                metadata.author,
                testtools.matchers.MatchesRegex(regex_pattern, regex_flags))

    def test_copyright_contains_author(self):
        """ Copyright information should contain author information. """
        self.assertThat(
                metadata.copyright,
                testtools.matchers.Contains(metadata.author))

    def test_url_parses_correctly(self):
        """ Homepage URL should parse correctly. """
        result = urlparse.urlparse(metadata.url)
        self.assertIsInstance(
                result, urlparse.ParseResult,
                "URL value {url!r} did not parse correctly".format(
                    url=metadata.url))


try:
    FileNotFoundError
except NameError:
    # Python 2 uses IOError.
    FileNotFoundError = functools.partial(IOError, errno.ENOENT)

version_info_filename = "version_info.json"

def fake_func_has_metadata(testcase, resource_name):
    """ Fake the behaviour of ‘pkg_resources.Distribution.has_metadata’. """
    if (
            resource_name != testcase.expected_resource_name
            or not hasattr(testcase, 'test_version_info')):
        return False
    return True


def fake_func_get_metadata(testcase, resource_name):
    """ Fake the behaviour of ‘pkg_resources.Distribution.get_metadata’. """
    if not fake_func_has_metadata(testcase, resource_name):
        error = FileNotFoundError(resource_name)
        raise error
    content = testcase.test_version_info
    return content


def fake_func_get_distribution(testcase, distribution_name):
    """ Fake the behaviour of ‘pkg_resources.get_distribution’. """
    if distribution_name != metadata.distribution_name:
        raise pkg_resources.DistributionNotFound
    if hasattr(testcase, 'get_distribution_error'):
        raise testcase.get_distribution_error
    mock_distribution = testcase.mock_distribution
    mock_distribution.has_metadata.side_effect = functools.partial(
            fake_func_has_metadata, testcase)
    mock_distribution.get_metadata.side_effect = functools.partial(
            fake_func_get_metadata, testcase)
    return mock_distribution


@mock.patch.object(metadata, 'distribution_name', new="mock-dist")
class get_distribution_version_info_TestCase(scaffold.TestCaseWithScenarios):
    """ Test cases for ‘get_distribution_version_info’ function. """

    default_version_info = {
            'release_date': "UNKNOWN",
            'version': "UNKNOWN",
            'maintainer': "UNKNOWN",
            }

    scenarios = [
            ('version 0.0', {
                'test_version_info': json.dumps({
                    'version': "0.0",
                    }),
                'expected_version_info': {'version': "0.0"},
                }),
            ('version 1.0', {
                'test_version_info': json.dumps({
                    'version': "1.0",
                    }),
                'expected_version_info': {'version': "1.0"},
                }),
            ('file lorem_ipsum.json', {
                'version_info_filename': "lorem_ipsum.json",
                'test_version_info': json.dumps({
                    'version': "1.0",
                    }),
                'expected_version_info': {'version': "1.0"},
                }),
            ('not installed', {
                'get_distribution_error': pkg_resources.DistributionNotFound(),
                'expected_version_info': default_version_info,
                }),
            ('no version_info', {
                'expected_version_info': default_version_info,
                }),
            ]

    def setUp(self):
        """ Set up test fixtures. """
        super(get_distribution_version_info_TestCase, self).setUp()

        if hasattr(self, 'expected_resource_name'):
            self.test_args = {'filename': self.expected_resource_name}
        else:
            self.test_args = {}
            self.expected_resource_name = version_info_filename

        self.mock_distribution = mock.MagicMock()
        func_patcher_get_distribution = mock.patch.object(
                pkg_resources, 'get_distribution')
        func_patcher_get_distribution.start()
        self.addCleanup(func_patcher_get_distribution.stop)
        pkg_resources.get_distribution.side_effect = functools.partial(
                fake_func_get_distribution, self)

    def test_requests_installed_distribution(self):
        """ The package distribution should be retrieved. """
        expected_distribution_name = metadata.distribution_name
        version_info = metadata.get_distribution_version_info(**self.test_args)
        pkg_resources.get_distribution.assert_called_with(
                expected_distribution_name)

    def test_requests_specified_filename(self):
        """ The specified metadata resource name should be requested. """
        if hasattr(self, 'get_distribution_error'):
            self.skipTest("No access to distribution")
        version_info = metadata.get_distribution_version_info(**self.test_args)
        self.mock_distribution.has_metadata.assert_called_with(
                self.expected_resource_name)

    def test_result_matches_expected_items(self):
        """ The result should match the expected items. """
        version_info = metadata.get_distribution_version_info(**self.test_args)
        self.assertEqual(self.expected_version_info, version_info)


# Local variables:
# coding: utf-8
# mode: python
# End:
# vim: fileencoding=utf-8 filetype=python :