##// END OF EJS Templates
tests: compatibility fix....
Dan Villiom Podlaski Christiansen -
r12367:3acd5f7a default
parent child Browse files
Show More
@@ -1,291 +1,292 b''
1 1 #!/usr/bin/env python
2 2 #
3 3 # check-code - a style and portability checker for Mercurial
4 4 #
5 5 # Copyright 2010 Matt Mackall <mpm@selenic.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 import re, glob, os, sys
11 11 import optparse
12 12
13 13 def repquote(m):
14 14 t = re.sub(r"\w", "x", m.group('text'))
15 15 t = re.sub(r"[^\sx]", "o", t)
16 16 return m.group('quote') + t + m.group('quote')
17 17
18 18 def reppython(m):
19 19 comment = m.group('comment')
20 20 if comment:
21 21 return "#" * len(comment)
22 22 return repquote(m)
23 23
24 24 def repcomment(m):
25 25 return m.group(1) + "#" * len(m.group(2))
26 26
27 27 def repccomment(m):
28 28 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
29 29 return m.group(1) + t + "*/"
30 30
31 31 def repcallspaces(m):
32 32 t = re.sub(r"\n\s+", "\n", m.group(2))
33 33 return m.group(1) + t
34 34
35 35 def repinclude(m):
36 36 return m.group(1) + "<foo>"
37 37
38 38 def rephere(m):
39 39 t = re.sub(r"\S", "x", m.group(2))
40 40 return m.group(1) + t
41 41
42 42
43 43 testpats = [
44 44 (r'(pushd|popd)', "don't use 'pushd' or 'popd', use 'cd'"),
45 45 (r'\W\$?\(\([^\)]*\)\)', "don't use (()) or $(()), use 'expr'"),
46 46 (r'^function', "don't use 'function', use old style"),
47 47 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
48 48 (r'echo.*\\n', "don't use 'echo \\n', use printf"),
49 49 (r'echo -n', "don't use 'echo -n', use printf"),
50 50 (r'^diff.*-\w*N', "don't use 'diff -N'"),
51 51 (r'(^| )wc[^|]*$', "filter wc output"),
52 52 (r'head -c', "don't use 'head -c', use 'dd'"),
53 53 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
54 54 (r'printf.*\\\d\d\d', "don't use 'printf \NNN', use Python"),
55 55 (r'printf.*\\x', "don't use printf \\x, use Python"),
56 56 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
57 57 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
58 58 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
59 59 "use egrep for extended grep syntax"),
60 60 (r'/bin/', "don't use explicit paths for tools"),
61 61 (r'\$PWD', "don't use $PWD, use `pwd`"),
62 62 (r'[^\n]\Z', "no trailing newline"),
63 63 (r'export.*=', "don't export and assign at once"),
64 64 ('^([^"\']|("[^"]*")|(\'[^\']*\'))*\\^', "^ must be quoted"),
65 65 (r'^source\b', "don't use 'source', use '.'"),
66 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
66 67 ]
67 68
68 69 testfilters = [
69 70 (r"( *)(#([^\n]*\S)?)", repcomment),
70 71 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
71 72 ]
72 73
73 74 uprefix = r"^ \$ "
74 75 utestpats = [
75 76 (uprefix + r'.*\|\s*sed', "use regex test output patterns instead of sed"),
76 77 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
77 78 (uprefix + r'.*\$\?', "explicit exit code checks unnecessary"),
78 79 (uprefix + r'.*\|\| echo.*(fail|error)',
79 80 "explicit exit code checks unnecessary"),
80 81 (uprefix + r'set -e', "don't use set -e"),
81 82 ]
82 83
83 84 for p, m in testpats:
84 85 if p.startswith('^'):
85 86 p = uprefix + p[1:]
86 87 else:
87 88 p = uprefix + p
88 89 utestpats.append((p, m))
89 90
90 91 utestfilters = [
91 92 (r"( *)(#([^\n]*\S)?)", repcomment),
92 93 ]
93 94
94 95 pypats = [
95 96 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
96 97 "tuple parameter unpacking not available in Python 3+"),
97 98 (r'lambda\s*\(.*,.*\)',
98 99 "tuple parameter unpacking not available in Python 3+"),
99 100 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
100 101 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
101 102 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
102 103 (r'^\s*\t', "don't use tabs"),
103 104 (r'\S;\s*\n', "semicolon"),
104 105 (r'\w,\w', "missing whitespace after ,"),
105 106 (r'\w[+/*\-<>]\w', "missing whitespace in expression"),
106 107 (r'^\s+\w+=\w+[^,)]$', "missing whitespace in assignment"),
107 108 (r'.{85}', "line too long"),
108 109 (r'.{81}', "warning: line over 80 characters"),
109 110 (r'[^\n]\Z', "no trailing newline"),
110 111 # (r'^\s+[^_ ][^_. ]+_[^_]+\s*=', "don't use underbars in identifiers"),
111 112 # (r'\w*[a-z][A-Z]\w*\s*=', "don't use camelcase in identifiers"),
112 113 (r'^\s*(if|while|def|class|except|try)\s[^[]*:\s*[^\]#\s]+',
113 114 "linebreak after :"),
114 115 (r'class\s[^(]:', "old-style class, use class foo(object)"),
115 116 (r'^\s+del\(', "del isn't a function"),
116 117 (r'^\s+except\(', "except isn't a function"),
117 118 (r',]', "unneeded trailing ',' in list"),
118 119 # (r'class\s[A-Z][^\(]*\((?!Exception)',
119 120 # "don't capitalize non-exception classes"),
120 121 # (r'in range\(', "use xrange"),
121 122 # (r'^\s*print\s+', "avoid using print in core and extensions"),
122 123 (r'[\x80-\xff]', "non-ASCII character literal"),
123 124 (r'("\')\.format\(', "str.format() not available in Python 2.4"),
124 125 (r'^\s*with\s+', "with not available in Python 2.4"),
125 126 (r'(?<!def)\s+(any|all|format)\(',
126 127 "any/all/format not available in Python 2.4"),
127 128 (r'(?<!def)\s+(callable)\(',
128 129 "callable not available in Python 3, use hasattr(f, '__call__')"),
129 130 (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
130 131 (r'([\(\[]\s\S)|(\S\s[\)\]])', "gratuitous whitespace in () or []"),
131 132 # (r'\s\s=', "gratuitous whitespace before ="),
132 133 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\S',
133 134 "missing whitespace around operator"),
134 135 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\s',
135 136 "missing whitespace around operator"),
136 137 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=)\S',
137 138 "missing whitespace around operator"),
138 139 (r'[^+=*!<>&| -](\s=|=\s)[^= ]',
139 140 "wrong whitespace around ="),
140 141 (r'raise Exception', "don't raise generic exceptions"),
141 142 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
142 143 "warning: unwrapped ui message"),
143 144 ]
144 145
145 146 pyfilters = [
146 147 (r"""(?msx)(?P<comment>\#.*?$)|
147 148 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
148 149 (?P<text>(([^\\]|\\.)*?))
149 150 (?P=quote))""", reppython),
150 151 ]
151 152
152 153 cpats = [
153 154 (r'//', "don't use //-style comments"),
154 155 (r'^ ', "don't use spaces to indent"),
155 156 (r'\S\t', "don't use tabs except for indent"),
156 157 (r'(\S\s+|^\s+)\n', "trailing whitespace"),
157 158 (r'.{85}', "line too long"),
158 159 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
159 160 (r'return\(', "return is not a function"),
160 161 (r' ;', "no space before ;"),
161 162 (r'\w+\* \w+', "use int *foo, not int* foo"),
162 163 (r'\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
163 164 (r'\S+ (\+\+|--)', "use foo++, not foo ++"),
164 165 (r'\w,\w', "missing whitespace after ,"),
165 166 (r'\w[+/*]\w', "missing whitespace in expression"),
166 167 (r'^#\s+\w', "use #foo, not # foo"),
167 168 (r'[^\n]\Z', "no trailing newline"),
168 169 ]
169 170
170 171 cfilters = [
171 172 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
172 173 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
173 174 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
174 175 (r'(\()([^)]+\))', repcallspaces),
175 176 ]
176 177
177 178 checks = [
178 179 ('python', r'.*\.(py|cgi)$', pyfilters, pypats),
179 180 ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
180 181 ('c', r'.*\.c$', cfilters, cpats),
181 182 ('unified test', r'.*\.t$', utestfilters, utestpats),
182 183 ]
183 184
184 185 class norepeatlogger(object):
185 186 def __init__(self):
186 187 self._lastseen = None
187 188
188 189 def log(self, fname, lineno, line, msg, blame):
189 190 """print error related a to given line of a given file.
190 191
191 192 The faulty line will also be printed but only once in the case
192 193 of multiple errors.
193 194
194 195 :fname: filename
195 196 :lineno: line number
196 197 :line: actual content of the line
197 198 :msg: error message
198 199 """
199 200 msgid = fname, lineno, line
200 201 if msgid != self._lastseen:
201 202 if blame:
202 203 print "%s:%d (%s):" % (fname, lineno, blame)
203 204 else:
204 205 print "%s:%d:" % (fname, lineno)
205 206 print " > %s" % line
206 207 self._lastseen = msgid
207 208 print " " + msg
208 209
209 210 _defaultlogger = norepeatlogger()
210 211
211 212 def getblame(f):
212 213 lines = []
213 214 for l in os.popen('hg annotate -un %s' % f):
214 215 start, line = l.split(':', 1)
215 216 user, rev = start.split()
216 217 lines.append((line[1:-1], user, rev))
217 218 return lines
218 219
219 220 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
220 221 blame=False):
221 222 """checks style and portability of a given file
222 223
223 224 :f: filepath
224 225 :logfunc: function used to report error
225 226 logfunc(filename, linenumber, linecontent, errormessage)
226 227 :maxerr: number of error to display before arborting.
227 228 Set to None (default) to report all errors
228 229
229 230 return True if no error is found, False otherwise.
230 231 """
231 232 blamecache = None
232 233 result = True
233 234 for name, match, filters, pats in checks:
234 235 fc = 0
235 236 if not re.match(match, f):
236 237 continue
237 238 pre = post = open(f).read()
238 239 if "no-" + "check-code" in pre:
239 240 break
240 241 for p, r in filters:
241 242 post = re.sub(p, r, post)
242 243 # print post # uncomment to show filtered version
243 244 z = enumerate(zip(pre.splitlines(), post.splitlines(True)))
244 245 for n, l in z:
245 246 if "check-code" + "-ignore" in l[0]:
246 247 continue
247 248 for p, msg in pats:
248 249 if not warnings and msg.startswith("warning"):
249 250 continue
250 251 if re.search(p, l[1]):
251 252 bd = ""
252 253 if blame:
253 254 bd = 'working directory'
254 255 if not blamecache:
255 256 blamecache = getblame(f)
256 257 if n < len(blamecache):
257 258 bl, bu, br = blamecache[n]
258 259 if bl == l[0]:
259 260 bd = '%s@%s' % (bu, br)
260 261 logfunc(f, n + 1, l[0], msg, bd)
261 262 fc += 1
262 263 result = False
263 264 if maxerr is not None and fc >= maxerr:
264 265 print " (too many errors, giving up)"
265 266 break
266 267 break
267 268 return result
268 269
269 270 if __name__ == "__main__":
270 271 parser = optparse.OptionParser("%prog [options] [files]")
271 272 parser.add_option("-w", "--warnings", action="store_true",
272 273 help="include warning-level checks")
273 274 parser.add_option("-p", "--per-file", type="int",
274 275 help="max warnings per file")
275 276 parser.add_option("-b", "--blame", action="store_true",
276 277 help="use annotate to generate blame info")
277 278
278 279 parser.set_defaults(per_file=15, warnings=False, blame=False)
279 280 (options, args) = parser.parse_args()
280 281
281 282 if len(args) == 0:
282 283 check = glob.glob("*")
283 284 else:
284 285 check = args
285 286
286 287 for f in check:
287 288 ret = 0
288 289 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
289 290 blame=options.blame):
290 291 ret = 1
291 292 sys.exit(ret)
@@ -1,235 +1,235 b''
1 1 $ mkdir test
2 2 $ cd test
3 3 $ hg init
4 4 $ echo foo>foo
5 5 $ hg commit -Am 1 -d '1 0'
6 6 adding foo
7 7 $ echo bar>bar
8 8 $ hg commit -Am 2 -d '2 0'
9 9 adding bar
10 10 $ mkdir baz
11 11 $ echo bletch>baz/bletch
12 12 $ hg commit -Am 3 -d '1000000000 0'
13 13 adding baz/bletch
14 14 $ echo "[web]" >> .hg/hgrc
15 15 $ echo "name = test-archive" >> .hg/hgrc
16 16 $ cp .hg/hgrc .hg/hgrc-base
17 17 > test_archtype() {
18 18 > echo "allow_archive = $1" >> .hg/hgrc
19 19 > hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
20 20 > cat hg.pid >> $DAEMON_PIDS
21 21 > echo % $1 allowed should give 200
22 22 > "$TESTDIR/get-with-headers.py" localhost:$HGPORT "/archive/tip.$2" | head -n 1
23 23 > echo % $3 and $4 disallowed should both give 403
24 24 > "$TESTDIR/get-with-headers.py" localhost:$HGPORT "/archive/tip.$3" | head -n 1
25 25 > "$TESTDIR/get-with-headers.py" localhost:$HGPORT "/archive/tip.$4" | head -n 1
26 26 > "$TESTDIR/killdaemons.py"
27 27 > cat errors.log
28 28 > cp .hg/hgrc-base .hg/hgrc
29 29 > }
30 30
31 31 check http return codes
32 32
33 33
34 34 $ test_archtype gz tar.gz tar.bz2 zip
35 35 % gz allowed should give 200
36 36 200 Script output follows
37 37 % tar.bz2 and zip disallowed should both give 403
38 38 403 Archive type not allowed: bz2
39 39 403 Archive type not allowed: zip
40 40 $ test_archtype bz2 tar.bz2 zip tar.gz
41 41 % bz2 allowed should give 200
42 42 200 Script output follows
43 43 % zip and tar.gz disallowed should both give 403
44 44 403 Archive type not allowed: zip
45 45 403 Archive type not allowed: gz
46 46 $ test_archtype zip zip tar.gz tar.bz2
47 47 % zip allowed should give 200
48 48 200 Script output follows
49 49 % tar.gz and tar.bz2 disallowed should both give 403
50 50 403 Archive type not allowed: gz
51 51 403 Archive type not allowed: bz2
52 52
53 53 $ echo "allow_archive = gz bz2 zip" >> .hg/hgrc
54 54 $ hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
55 55 $ cat hg.pid >> $DAEMON_PIDS
56 56
57 57 invalid arch type should give 404
58 58
59 59 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT "/archive/tip.invalid" | head -n 1
60 60 404 Unsupported archive type: None
61 61
62 62 $ TIP=`hg id -v | cut -f1 -d' '`
63 63 $ QTIP=`hg id -q`
64 64 $ cat > getarchive.py <<EOF
65 65 > import os, sys, urllib2
66 66 > try:
67 67 > # Set stdout to binary mode for win32 platforms
68 68 > import msvcrt
69 69 > msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
70 70 > except ImportError:
71 71 > pass
72 72 > node, archive = sys.argv[1:]
73 73 > f = urllib2.urlopen('http://127.0.0.1:%s/?cmd=archive;node=%s;type=%s'
74 74 > % (os.environ['HGPORT'], node, archive))
75 75 > sys.stdout.write(f.read())
76 76 > EOF
77 77 $ python getarchive.py "$TIP" gz | gunzip | tar tf - 2>/dev/null
78 78 test-archive-2c0277f05ed4/.hg_archival.txt
79 79 test-archive-2c0277f05ed4/bar
80 80 test-archive-2c0277f05ed4/baz/bletch
81 81 test-archive-2c0277f05ed4/foo
82 82 $ python getarchive.py "$TIP" bz2 | bunzip2 | tar tf - 2>/dev/null
83 83 test-archive-2c0277f05ed4/.hg_archival.txt
84 84 test-archive-2c0277f05ed4/bar
85 85 test-archive-2c0277f05ed4/baz/bletch
86 86 test-archive-2c0277f05ed4/foo
87 87 $ python getarchive.py "$TIP" zip > archive.zip
88 88 $ unzip -t archive.zip
89 89 Archive: archive.zip
90 90 testing: test-archive-2c0277f05ed4/.hg_archival.txt OK
91 91 testing: test-archive-2c0277f05ed4/bar OK
92 92 testing: test-archive-2c0277f05ed4/baz/bletch OK
93 93 testing: test-archive-2c0277f05ed4/foo OK
94 94 No errors detected in compressed data of archive.zip.
95 95
96 96 $ "$TESTDIR/killdaemons.py"
97 97
98 98 $ hg archive -t tar test.tar
99 99 $ tar tf test.tar
100 100 test/.hg_archival.txt
101 101 test/bar
102 102 test/baz/bletch
103 103 test/foo
104 104
105 105 $ hg archive -t tbz2 -X baz test.tar.bz2
106 106 $ bunzip2 -dc test.tar.bz2 | tar tf - 2>/dev/null
107 107 test/.hg_archival.txt
108 108 test/bar
109 109 test/foo
110 110
111 111 $ hg archive -t tgz -p %b-%h test-%h.tar.gz
112 112 $ gzip -dc test-$QTIP.tar.gz | tar tf - 2>/dev/null
113 113 test-2c0277f05ed4/.hg_archival.txt
114 114 test-2c0277f05ed4/bar
115 115 test-2c0277f05ed4/baz/bletch
116 116 test-2c0277f05ed4/foo
117 117
118 118 $ hg archive autodetected_test.tar
119 119 $ tar tf autodetected_test.tar
120 120 autodetected_test/.hg_archival.txt
121 121 autodetected_test/bar
122 122 autodetected_test/baz/bletch
123 123 autodetected_test/foo
124 124
125 125 The '-t' should override autodetection
126 126
127 127 $ hg archive -t tar autodetect_override_test.zip
128 128 $ tar tf autodetect_override_test.zip
129 129 autodetect_override_test.zip/.hg_archival.txt
130 130 autodetect_override_test.zip/bar
131 131 autodetect_override_test.zip/baz/bletch
132 132 autodetect_override_test.zip/foo
133 133
134 134 $ for ext in tar tar.gz tgz tar.bz2 tbz2 zip; do
135 135 > hg archive auto_test.$ext
136 136 > if [ -d auto_test.$ext ]; then
137 137 > echo "extension $ext was not autodetected."
138 138 > fi
139 139 > done
140 140
141 141 $ cat > md5comp.py <<EOF
142 142 > try:
143 143 > from hashlib import md5
144 144 > except ImportError:
145 145 > from md5 import md5
146 146 > import sys
147 147 > f1, f2 = sys.argv[1:3]
148 148 > h1 = md5(file(f1, 'rb').read()).hexdigest()
149 149 > h2 = md5(file(f2, 'rb').read()).hexdigest()
150 150 > print h1 == h2 or "md5 differ: " + repr((h1, h2))
151 151 > EOF
152 152
153 153 archive name is stored in the archive, so create similar
154 154
155 155 archives and rename them afterwards.
156 156
157 157 $ hg archive -t tgz tip.tar.gz
158 158 $ mv tip.tar.gz tip1.tar.gz
159 159 $ sleep 1
160 160 $ hg archive -t tgz tip.tar.gz
161 161 $ mv tip.tar.gz tip2.tar.gz
162 162 $ python md5comp.py tip1.tar.gz tip2.tar.gz
163 163 True
164 164
165 165 $ hg archive -t zip -p /illegal test.zip
166 166 abort: archive prefix contains illegal components
167 167 [255]
168 168 $ hg archive -t zip -p very/../bad test.zip
169 169
170 170 $ hg archive --config ui.archivemeta=false -t zip -r 2 test.zip
171 171 $ unzip -t test.zip
172 172 Archive: test.zip
173 173 testing: test/bar OK
174 174 testing: test/baz/bletch OK
175 175 testing: test/foo OK
176 176 No errors detected in compressed data of test.zip.
177 177
178 178 $ hg archive -t tar - | tar tf - 2>/dev/null
179 179 test-2c0277f05ed4/.hg_archival.txt
180 180 test-2c0277f05ed4/bar
181 181 test-2c0277f05ed4/baz/bletch
182 182 test-2c0277f05ed4/foo
183 183
184 184 $ hg archive -r 0 -t tar rev-%r.tar
185 185 $ if [ -f rev-0.tar ]; then
186 186 $ fi
187 187
188 188 test .hg_archival.txt
189 189
190 190 $ hg archive ../test-tags
191 191 $ cat ../test-tags/.hg_archival.txt
192 192 repo: daa7f7c60e0a224faa4ff77ca41b2760562af264
193 193 node: 2c0277f05ed49d1c8328fb9ba92fba7a5ebcb33e
194 194 branch: default
195 195 latesttag: null
196 196 latesttagdistance: 3
197 197 $ hg tag -r 2 mytag
198 198 $ hg tag -r 2 anothertag
199 199 $ hg archive -r 2 ../test-lasttag
200 200 $ cat ../test-lasttag/.hg_archival.txt
201 201 repo: daa7f7c60e0a224faa4ff77ca41b2760562af264
202 202 node: 2c0277f05ed49d1c8328fb9ba92fba7a5ebcb33e
203 203 branch: default
204 204 tag: anothertag
205 205 tag: mytag
206 206
207 207 $ hg archive -t bogus test.bogus
208 208 abort: unknown archive type 'bogus'
209 209 [255]
210 210
211 211 server errors
212 212
213 213 $ cat errors.log
214 214
215 215 empty repo
216 216
217 217 $ hg init ../empty
218 218 $ cd ../empty
219 219 $ hg archive ../test-empty
220 220 abort: no working directory: please specify a revision
221 221 [255]
222 222 old file -- date clamped to 1980
223 223
224 $ touch -d 1975-01-01 old
224 $ touch -t 19750101 old
225 225 $ hg add old
226 226 $ hg commit -m old
227 227 $ hg archive ../old.zip
228 228 $ unzip -l ../old.zip
229 229 Archive: ../old.zip
230 230 \s*Length.*
231 231 .*-----.*
232 232 .*147.*80.*00:00.*old/.hg_archival.txt
233 233 .*0.*80.*00:00.*old/old
234 234 .*-----.*
235 235 \s*147\s+2 files
General Comments 0
You need to be logged in to leave comments. Login now