/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to brzlib/tests/test_source.py

  • Committer: Jelmer Vernooij
  • Date: 2017-05-21 12:41:27 UTC
  • mto: This revision was merged to the branch mainline in revision 6623.
  • Revision ID: jelmer@jelmer.uk-20170521124127-iv8etg0vwymyai6y
s/bzr/brz/ in apport config.

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
"""These tests are tests about the source code of breezy itself.
 
17
"""These tests are tests about the source code of brzlib itself.
18
18
 
19
19
They are useful for testing code quality, checking coverage metric etc.
20
20
"""
26
26
import sys
27
27
import token
28
28
 
29
 
from breezy import (
 
29
from brzlib import (
30
30
    osutils,
31
31
    )
32
 
import breezy.branch
33
 
from breezy.tests import (
34
 
    features,
 
32
import brzlib.branch
 
33
from brzlib.tests import (
35
34
    TestCase,
36
35
    TestSkipped,
37
36
    )
40
39
# Files which are listed here will be skipped when testing for Copyright (or
41
40
# GPL) statements.
42
41
COPYRIGHT_EXCEPTIONS = [
43
 
    'breezy/_bencode_py.py',
44
 
    'breezy/doc_generate/conf.py',
45
 
    'breezy/lsprof.py',
 
42
    'brzlib/_bencode_py.py',
 
43
    'brzlib/doc_generate/conf.py',
 
44
    'brzlib/lsprof.py',
46
45
    ]
47
46
 
48
47
LICENSE_EXCEPTIONS = [
49
 
    'breezy/_bencode_py.py',
50
 
    'breezy/doc_generate/conf.py',
51
 
    'breezy/lsprof.py',
 
48
    'brzlib/_bencode_py.py',
 
49
    'brzlib/doc_generate/conf.py',
 
50
    'brzlib/lsprof.py',
52
51
    ]
53
 
# Technically, 'breezy/lsprof.py' should be 'breezy/util/lsprof.py',
54
 
# (we do not check breezy/util/, since that is code bundled from elsewhere)
 
52
# Technically, 'brzlib/lsprof.py' should be 'brzlib/util/lsprof.py',
 
53
# (we do not check brzlib/util/, since that is code bundled from elsewhere)
55
54
# but for compatibility with previous releases, we don't want to move it.
56
55
#
57
56
# sphinx_conf is semi-autogenerated.
75
74
    def find_occurences(self, rule, filename):
76
75
        """Find the number of occurences of rule in a file."""
77
76
        occurences = 0
78
 
        source = open(filename, 'r')
 
77
        source = file(filename, 'r')
79
78
        for line in source:
80
79
            if line.find(rule) > -1:
81
80
                occurences += 1
84
83
    def test_branch_working_tree(self):
85
84
        """Test that the number of uses of working_tree in branch is stable."""
86
85
        occurences = self.find_occurences('self.working_tree()',
87
 
                                          self.source_file_name(breezy.branch))
 
86
                                          self.source_file_name(brzlib.branch))
88
87
        # do not even think of increasing this number. If you think you need to
89
88
        # increase it, then you almost certainly are doing something wrong as
90
89
        # the relationship from working_tree to branch is one way.
91
90
        # Note that this is an exact equality so that when the number drops,
92
 
        # it is not given a buffer but rather has this test updated immediately.
 
91
        #it is not given a buffer but rather has this test updated immediately.
93
92
        self.assertEqual(0, occurences)
94
93
 
95
94
    def test_branch_WorkingTree(self):
96
95
        """Test that the number of uses of working_tree in branch is stable."""
97
96
        occurences = self.find_occurences('WorkingTree',
98
 
                                          self.source_file_name(breezy.branch))
 
97
                                          self.source_file_name(brzlib.branch))
99
98
        # Do not even think of increasing this number. If you think you need to
100
99
        # increase it, then you almost certainly are doing something wrong as
101
100
        # the relationship from working_tree to branch is one way.
105
104
 
106
105
class TestSource(TestSourceHelper):
107
106
 
108
 
    def get_breezy_dir(self):
109
 
        """Get the path to the root of breezy"""
110
 
        source = self.source_file_name(breezy)
 
107
    def get_brzlib_dir(self):
 
108
        """Get the path to the root of brzlib"""
 
109
        source = self.source_file_name(brzlib)
111
110
        source_dir = os.path.dirname(source)
112
111
 
113
 
        # Avoid the case when breezy is packaged in a zip file
 
112
        # Avoid the case when brzlib is packaged in a zip file
114
113
        if not os.path.isdir(source_dir):
115
114
            raise TestSkipped(
116
 
                'Cannot find breezy source directory. Expected %s'
 
115
                'Cannot find brzlib source directory. Expected %s'
117
116
                % source_dir)
118
117
        return source_dir
119
118
 
120
119
    def get_source_files(self, extensions=None):
121
 
        """Yield all source files for bzr and breezy
 
120
        """Yield all source files for bzr and brzlib
122
121
 
123
122
        :param our_files_only: If true, exclude files from included libraries
124
123
            or plugins.
125
124
        """
126
 
        breezy_dir = self.get_breezy_dir()
 
125
        brzlib_dir = self.get_brzlib_dir()
127
126
        if extensions is None:
128
127
            extensions = ('.py',)
129
128
 
131
130
        bzr_path = self.get_brz_path()
132
131
        yield bzr_path
133
132
 
134
 
        for root, dirs, files in os.walk(breezy_dir):
 
133
        for root, dirs, files in os.walk(brzlib_dir):
135
134
            for d in dirs:
136
135
                if d.endswith('.tmp'):
137
136
                    dirs.remove(d)
146
145
 
147
146
    def get_source_file_contents(self, extensions=None):
148
147
        for fname in self.get_source_files(extensions=extensions):
149
 
            with open(fname, 'r') as f:
150
 
                yield fname, f.read()
 
148
            f = open(fname, 'rb')
 
149
            try:
 
150
                text = f.read()
 
151
            finally:
 
152
                f.close()
 
153
            yield fname, text
151
154
 
152
155
    def is_our_code(self, fname):
153
 
        """True if it's a "real" part of breezy rather than external code"""
 
156
        """True if it's a "real" part of brzlib rather than external code"""
154
157
        if '/util/' in fname or '/plugins/' in fname:
155
158
            return False
156
159
        else:
159
162
    def is_copyright_exception(self, fname):
160
163
        """Certain files are allowed to be different"""
161
164
        if not self.is_our_code(fname):
 
165
            # We don't ask that external utilities or plugins be
 
166
            # (C) Canonical Ltd
162
167
            return True
163
168
        for exc in COPYRIGHT_EXCEPTIONS:
164
169
            if fname.endswith(exc):
187
192
        incorrect = []
188
193
 
189
194
        copyright_re = re.compile('#\\s*copyright.*(?=\n)', re.I)
190
 
        copyright_statement_re = re.compile(
 
195
        copyright_canonical_re = re.compile(
191
196
            r'# Copyright \(C\) '  # Opening "# Copyright (C)"
192
 
            r'(\d+?)((, |-)\d+)*'  # followed by a series of dates
193
 
            r' [^ ]*')             # and then whoever.
 
197
            r'(\d+)(, \d+)*'       # followed by a series of dates
 
198
            r'.*Canonical Ltd')    # and containing 'Canonical Ltd'.
194
199
 
195
200
        for fname, text in self.get_source_file_contents(
196
201
                extensions=('.py', '.pyx')):
197
202
            if self.is_copyright_exception(fname):
198
203
                continue
199
 
            match = copyright_statement_re.search(text)
 
204
            match = copyright_canonical_re.search(text)
200
205
            if not match:
201
206
                match = copyright_re.search(text)
202
207
                if match:
206
211
            else:
207
212
                if 'by Canonical' in match.group():
208
213
                    incorrect.append((fname,
209
 
                                      'should not have: "by Canonical": %s'
210
 
                                      % (match.group(),)))
 
214
                        'should not have: "by Canonical": %s'
 
215
                        % (match.group(),)))
211
216
 
212
217
        if incorrect:
213
218
            help_text = ["Some files have missing or incorrect copyright"
215
220
                         "",
216
221
                         "Please either add them to the list of"
217
222
                         " COPYRIGHT_EXCEPTIONS in"
218
 
                         " breezy/tests/test_source.py",
 
223
                         " brzlib/tests/test_source.py",
219
224
                         # this is broken to prevent a false match
220
225
                         "or add '# Copyright (C)"
221
 
                         " 2007 Bazaar hackers' to these files:",
 
226
                         " 2007 Canonical Ltd' to these files:",
222
227
                         "",
223
 
                         ]
 
228
                        ]
224
229
            for fname, comment in incorrect:
225
230
                help_text.append(fname)
226
231
                help_text.append((' ' * 4) + comment)
260
265
                         "",
261
266
                         "Please either add them to the list of"
262
267
                         " LICENSE_EXCEPTIONS in"
263
 
                         " breezy/tests/test_source.py",
 
268
                         " brzlib/tests/test_source.py",
264
269
                         "Or add the following text to the beginning:",
265
270
                         gpl_txt]
266
271
            for fname in incorrect:
275
280
            dict_[fname].append(line_no)
276
281
 
277
282
    def _format_message(self, dict_, message):
278
 
        files = sorted(["%s: %s" % (f, ', '.join([str(i + 1) for i in lines]))
279
 
                        for f, lines in dict_.items()])
 
283
        files = ["%s: %s" % (f, ', '.join([str(i + 1) for i in lines]))
 
284
                for f, lines in dict_.items()]
 
285
        files.sort()
280
286
        return message + '\n\n    %s' % ('\n    '.join(files))
281
287
 
282
288
    def test_coding_style(self):
310
316
        problems = []
311
317
        if tabs:
312
318
            problems.append(self._format_message(tabs,
313
 
                                                 'Tab characters were found in the following source files.'
314
 
                                                 '\nThey should either be replaced by "\\t" or by spaces:'))
 
319
                'Tab characters were found in the following source files.'
 
320
                '\nThey should either be replaced by "\\t" or by spaces:'))
315
321
        if illegal_newlines:
316
322
            problems.append(self._format_message(illegal_newlines,
317
 
                                                 'Non-unix newlines were found in the following source files:'))
 
323
                'Non-unix newlines were found in the following source files:'))
318
324
        if no_newline_at_eof:
319
325
            no_newline_at_eof.sort()
320
326
            problems.append("The following source files doesn't have a "
321
 
                            "newline at the end:"
322
 
                            '\n\n    %s'
323
 
                            % ('\n    '.join(no_newline_at_eof)))
 
327
                "newline at the end:"
 
328
               '\n\n    %s'
 
329
               % ('\n    '.join(no_newline_at_eof)))
324
330
        if problems:
325
331
            self.fail('\n\n'.join(problems))
326
332
 
327
 
    def test_flake8(self):
328
 
        self.requireFeature(features.flake8)
329
 
        # Older versions of flake8 don't support the 'paths'
330
 
        # variable
331
 
        new_path = list(sys.path)
332
 
        new_path.insert(
333
 
            0, os.path.join(os.path.dirname(__file__), '..', '..', 'tools'))
334
 
        self.overrideAttr(sys, 'path', new_path)
335
 
        from flake8.main.application import Application
336
 
        from flake8.formatting.base import BaseFormatter
337
 
        app = Application()
338
 
        app.config = u'setup.cfg'
339
 
        app.jobs = 1
340
 
 
341
 
        class Formatter(BaseFormatter):
342
 
 
343
 
            def __init__(self):
344
 
                self.errors = []
345
 
 
346
 
            def start(self):
347
 
                pass
348
 
 
349
 
            def stop(self):
350
 
                app.file_checker_manager.report()
351
 
 
352
 
            def handle(self, error):
353
 
                self.errors.append(error)
354
 
 
355
 
        app.formatter = Formatter()
356
 
        app.initialize([])
357
 
        app.run_checks()
358
 
        app.report()
359
 
        self.assertEqual(app.formatter.errors, [])
360
 
 
361
333
    def test_no_asserts(self):
362
334
        """bzr shouldn't use the 'assert' statement."""
363
335
        # assert causes too much variation between -O and not, and tends to
381
353
                continue
382
354
            if not assert_re.search(text):
383
355
                continue
384
 
            st = parser.suite(text)
385
 
            code = parser.st2tuple(st)
386
 
            if search(code):
 
356
            ast = parser.ast2tuple(parser.suite(text))
 
357
            if search(ast):
387
358
                badfiles.append(fname)
388
359
        if badfiles:
389
360
            self.fail(
399
370
        """
400
371
        both_exc_and_no_exc = []
401
372
        missing_except = []
402
 
        common_classes = ('StaticTuple',)
403
373
        class_re = re.compile(r'^(cdef\s+)?(public\s+)?'
404
374
                              r'(api\s+)?class (\w+).*:', re.MULTILINE)
 
375
        extern_class_re = re.compile(r'## extern cdef class (\w+)',
 
376
                                     re.MULTILINE)
405
377
        except_re = re.compile(
406
378
            r'cdef\s+'        # start with cdef
407
379
            r'([\w *]*?)\s*'  # this is the return signature
411
383
            r'\s*(#\s*cannot[- _]raise)?')  # cannot raise comment
412
384
        for fname, text in self.get_source_file_contents(
413
385
                extensions=('.pyx',)):
414
 
            known_classes = {m[-1] for m in class_re.findall(text)}
415
 
            known_classes.update(common_classes)
 
386
            known_classes = set([m[-1] for m in class_re.findall(text)])
 
387
            known_classes.update(extern_class_re.findall(text))
416
388
            cdefs = except_re.findall(text)
417
389
            for sig, func, exc_clause, no_exc_comment in cdefs:
418
390
                if sig.startswith('api '):
444
416
            error_msg.extend(('', ''))
445
417
        if error_msg:
446
418
            self.fail('\n'.join(error_msg))
 
419
 
 
420
    def test_feature_absolute_import(self):
 
421
        """Using absolute imports means avoiding unnecesary stat and
 
422
        open calls.
 
423
 
 
424
        Make sure that all non-test files have absolute imports enabled.
 
425
        """
 
426
        missing_absolute_import = []
 
427
        for fname, text in self.get_source_file_contents(
 
428
                extensions=('.py', )):
 
429
            if "/tests/" in fname or "test_" in fname:
 
430
                # We don't really care about tests
 
431
                continue
 
432
            if not "from __future__ import absolute_import" in text:
 
433
                missing_absolute_import.append(fname)
 
434
 
 
435
        if missing_absolute_import:
 
436
            self.fail(
 
437
                'The following files do not have absolute_import enabled:\n'
 
438
                '\n' + '\n'.join(missing_absolute_import))