Show More
@@ -0,0 +1,171 b'' | |||||
|
1 | # util_win32.py - utility functions that use win32 API | |||
|
2 | # | |||
|
3 | # Copyright 2005 Matt Mackall <mpm@selenic.com> | |||
|
4 | # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com> | |||
|
5 | # | |||
|
6 | # This software may be used and distributed according to the terms of | |||
|
7 | # the GNU General Public License, incorporated herein by reference. | |||
|
8 | ||||
|
9 | # Mark Hammond's win32all package allows better functionality on | |||
|
10 | # Windows. this module overrides definitions in util.py. if not | |||
|
11 | # available, import of this module will fail, and generic code will be | |||
|
12 | # used. | |||
|
13 | ||||
|
14 | import win32api | |||
|
15 | ||||
|
16 | from demandload import * | |||
|
17 | from i18n import gettext as _ | |||
|
18 | demandload(globals(), 'errno os pywintypes win32con win32file win32process') | |||
|
19 | demandload(globals(), 'winerror') | |||
|
20 | ||||
|
21 | class WinError(OSError): | |||
|
22 | winerror_map = { | |||
|
23 | winerror.ERROR_ACCESS_DENIED: errno.EACCES, | |||
|
24 | winerror.ERROR_ACCOUNT_DISABLED: errno.EACCES, | |||
|
25 | winerror.ERROR_ACCOUNT_RESTRICTION: errno.EACCES, | |||
|
26 | winerror.ERROR_ALREADY_ASSIGNED: errno.EBUSY, | |||
|
27 | winerror.ERROR_ALREADY_EXISTS: errno.EEXIST, | |||
|
28 | winerror.ERROR_ARITHMETIC_OVERFLOW: errno.ERANGE, | |||
|
29 | winerror.ERROR_BAD_COMMAND: errno.EIO, | |||
|
30 | winerror.ERROR_BAD_DEVICE: errno.ENODEV, | |||
|
31 | winerror.ERROR_BAD_DRIVER_LEVEL: errno.ENXIO, | |||
|
32 | winerror.ERROR_BAD_EXE_FORMAT: errno.ENOEXEC, | |||
|
33 | winerror.ERROR_BAD_FORMAT: errno.ENOEXEC, | |||
|
34 | winerror.ERROR_BAD_LENGTH: errno.EINVAL, | |||
|
35 | winerror.ERROR_BAD_PATHNAME: errno.ENOENT, | |||
|
36 | winerror.ERROR_BAD_PIPE: errno.EPIPE, | |||
|
37 | winerror.ERROR_BAD_UNIT: errno.ENODEV, | |||
|
38 | winerror.ERROR_BAD_USERNAME: errno.EINVAL, | |||
|
39 | winerror.ERROR_BROKEN_PIPE: errno.EPIPE, | |||
|
40 | winerror.ERROR_BUFFER_OVERFLOW: errno.ENAMETOOLONG, | |||
|
41 | winerror.ERROR_BUSY: errno.EBUSY, | |||
|
42 | winerror.ERROR_BUSY_DRIVE: errno.EBUSY, | |||
|
43 | winerror.ERROR_CALL_NOT_IMPLEMENTED: errno.ENOSYS, | |||
|
44 | winerror.ERROR_CANNOT_MAKE: errno.EACCES, | |||
|
45 | winerror.ERROR_CANTOPEN: errno.EIO, | |||
|
46 | winerror.ERROR_CANTREAD: errno.EIO, | |||
|
47 | winerror.ERROR_CANTWRITE: errno.EIO, | |||
|
48 | winerror.ERROR_CRC: errno.EIO, | |||
|
49 | winerror.ERROR_CURRENT_DIRECTORY: errno.EACCES, | |||
|
50 | winerror.ERROR_DEVICE_IN_USE: errno.EBUSY, | |||
|
51 | winerror.ERROR_DEV_NOT_EXIST: errno.ENODEV, | |||
|
52 | winerror.ERROR_DIRECTORY: errno.EINVAL, | |||
|
53 | winerror.ERROR_DIR_NOT_EMPTY: errno.ENOTEMPTY, | |||
|
54 | winerror.ERROR_DISK_CHANGE: errno.EIO, | |||
|
55 | winerror.ERROR_DISK_FULL: errno.ENOSPC, | |||
|
56 | winerror.ERROR_DRIVE_LOCKED: errno.EBUSY, | |||
|
57 | winerror.ERROR_ENVVAR_NOT_FOUND: errno.EINVAL, | |||
|
58 | winerror.ERROR_EXE_MARKED_INVALID: errno.ENOEXEC, | |||
|
59 | winerror.ERROR_FILENAME_EXCED_RANGE: errno.ENAMETOOLONG, | |||
|
60 | winerror.ERROR_FILE_EXISTS: errno.EEXIST, | |||
|
61 | winerror.ERROR_FILE_INVALID: errno.ENODEV, | |||
|
62 | winerror.ERROR_FILE_NOT_FOUND: errno.ENOENT, | |||
|
63 | winerror.ERROR_GEN_FAILURE: errno.EIO, | |||
|
64 | winerror.ERROR_HANDLE_DISK_FULL: errno.ENOSPC, | |||
|
65 | winerror.ERROR_INSUFFICIENT_BUFFER: errno.ENOMEM, | |||
|
66 | winerror.ERROR_INVALID_ACCESS: errno.EACCES, | |||
|
67 | winerror.ERROR_INVALID_ADDRESS: errno.EFAULT, | |||
|
68 | winerror.ERROR_INVALID_BLOCK: errno.EFAULT, | |||
|
69 | winerror.ERROR_INVALID_DATA: errno.EINVAL, | |||
|
70 | winerror.ERROR_INVALID_DRIVE: errno.ENODEV, | |||
|
71 | winerror.ERROR_INVALID_EXE_SIGNATURE: errno.ENOEXEC, | |||
|
72 | winerror.ERROR_INVALID_FLAGS: errno.EINVAL, | |||
|
73 | winerror.ERROR_INVALID_FUNCTION: errno.ENOSYS, | |||
|
74 | winerror.ERROR_INVALID_HANDLE: errno.EBADF, | |||
|
75 | winerror.ERROR_INVALID_LOGON_HOURS: errno.EACCES, | |||
|
76 | winerror.ERROR_INVALID_NAME: errno.EINVAL, | |||
|
77 | winerror.ERROR_INVALID_OWNER: errno.EINVAL, | |||
|
78 | winerror.ERROR_INVALID_PARAMETER: errno.EINVAL, | |||
|
79 | winerror.ERROR_INVALID_PASSWORD: errno.EPERM, | |||
|
80 | winerror.ERROR_INVALID_PRIMARY_GROUP: errno.EINVAL, | |||
|
81 | winerror.ERROR_INVALID_SIGNAL_NUMBER: errno.EINVAL, | |||
|
82 | winerror.ERROR_INVALID_TARGET_HANDLE: errno.EIO, | |||
|
83 | winerror.ERROR_INVALID_WORKSTATION: errno.EACCES, | |||
|
84 | winerror.ERROR_IO_DEVICE: errno.EIO, | |||
|
85 | winerror.ERROR_IO_INCOMPLETE: errno.EINTR, | |||
|
86 | winerror.ERROR_LOCKED: errno.EBUSY, | |||
|
87 | winerror.ERROR_LOCK_VIOLATION: errno.EACCES, | |||
|
88 | winerror.ERROR_LOGON_FAILURE: errno.EACCES, | |||
|
89 | winerror.ERROR_MAPPED_ALIGNMENT: errno.EINVAL, | |||
|
90 | winerror.ERROR_META_EXPANSION_TOO_LONG: errno.E2BIG, | |||
|
91 | winerror.ERROR_MORE_DATA: errno.EPIPE, | |||
|
92 | winerror.ERROR_NEGATIVE_SEEK: errno.ESPIPE, | |||
|
93 | winerror.ERROR_NOACCESS: errno.EFAULT, | |||
|
94 | winerror.ERROR_NONE_MAPPED: errno.EINVAL, | |||
|
95 | winerror.ERROR_NOT_ENOUGH_MEMORY: errno.ENOMEM, | |||
|
96 | winerror.ERROR_NOT_READY: errno.EAGAIN, | |||
|
97 | winerror.ERROR_NOT_SAME_DEVICE: errno.EXDEV, | |||
|
98 | winerror.ERROR_NO_DATA: errno.EPIPE, | |||
|
99 | winerror.ERROR_NO_MORE_SEARCH_HANDLES: errno.EIO, | |||
|
100 | winerror.ERROR_NO_PROC_SLOTS: errno.EAGAIN, | |||
|
101 | winerror.ERROR_NO_SUCH_PRIVILEGE: errno.EACCES, | |||
|
102 | winerror.ERROR_OPEN_FAILED: errno.EIO, | |||
|
103 | winerror.ERROR_OPEN_FILES: errno.EBUSY, | |||
|
104 | winerror.ERROR_OPERATION_ABORTED: errno.EINTR, | |||
|
105 | winerror.ERROR_OUTOFMEMORY: errno.ENOMEM, | |||
|
106 | winerror.ERROR_PASSWORD_EXPIRED: errno.EACCES, | |||
|
107 | winerror.ERROR_PATH_BUSY: errno.EBUSY, | |||
|
108 | winerror.ERROR_PATH_NOT_FOUND: errno.ENOTDIR, | |||
|
109 | winerror.ERROR_PIPE_BUSY: errno.EBUSY, | |||
|
110 | winerror.ERROR_PIPE_CONNECTED: errno.EPIPE, | |||
|
111 | winerror.ERROR_PIPE_LISTENING: errno.EPIPE, | |||
|
112 | winerror.ERROR_PIPE_NOT_CONNECTED: errno.EPIPE, | |||
|
113 | winerror.ERROR_PRIVILEGE_NOT_HELD: errno.EACCES, | |||
|
114 | winerror.ERROR_READ_FAULT: errno.EIO, | |||
|
115 | winerror.ERROR_SEEK: errno.EIO, | |||
|
116 | winerror.ERROR_SEEK_ON_DEVICE: errno.ESPIPE, | |||
|
117 | winerror.ERROR_SHARING_BUFFER_EXCEEDED: errno.ENFILE, | |||
|
118 | winerror.ERROR_SHARING_VIOLATION: errno.EACCES, | |||
|
119 | winerror.ERROR_STACK_OVERFLOW: errno.ENOMEM, | |||
|
120 | winerror.ERROR_SWAPERROR: errno.ENOENT, | |||
|
121 | winerror.ERROR_TOO_MANY_MODULES: errno.EMFILE, | |||
|
122 | winerror.ERROR_TOO_MANY_OPEN_FILES: errno.EMFILE, | |||
|
123 | winerror.ERROR_UNRECOGNIZED_MEDIA: errno.ENXIO, | |||
|
124 | winerror.ERROR_UNRECOGNIZED_VOLUME: errno.ENODEV, | |||
|
125 | winerror.ERROR_WAIT_NO_CHILDREN: errno.ECHILD, | |||
|
126 | winerror.ERROR_WRITE_FAULT: errno.EIO, | |||
|
127 | winerror.ERROR_WRITE_PROTECT: errno.EROFS, | |||
|
128 | } | |||
|
129 | ||||
|
130 | def __init__(self, err): | |||
|
131 | self.win_errno, self.win_function, self.win_strerror = err | |||
|
132 | OSError.__init__(self, self.winerror_map.get(self.win_errno, 0), | |||
|
133 | self.win_strerror) | |||
|
134 | ||||
|
135 | def os_link(src, dst): | |||
|
136 | # NB will only succeed on NTFS | |||
|
137 | try: | |||
|
138 | win32file.CreateHardLink(dst, src) | |||
|
139 | except pywintypes.error, details: | |||
|
140 | raise WinError(details) | |||
|
141 | ||||
|
142 | def nlinks(pathname): | |||
|
143 | """Return number of hardlinks for the given file.""" | |||
|
144 | try: | |||
|
145 | fh = win32file.CreateFile(pathname, | |||
|
146 | win32file.GENERIC_READ, win32file.FILE_SHARE_READ, | |||
|
147 | None, win32file.OPEN_EXISTING, 0, None) | |||
|
148 | res = win32file.GetFileInformationByHandle(fh) | |||
|
149 | fh.Close() | |||
|
150 | return res[7] | |||
|
151 | except pywintypes.error: | |||
|
152 | return os.stat(pathname).st_nlink | |||
|
153 | ||||
|
154 | def testpid(pid): | |||
|
155 | '''return True if pid is still running or unable to | |||
|
156 | determine, False otherwise''' | |||
|
157 | try: | |||
|
158 | handle = win32api.OpenProcess( | |||
|
159 | win32con.PROCESS_QUERY_INFORMATION, False, pid) | |||
|
160 | if handle: | |||
|
161 | status = win32process.GetExitCodeProcess(handle) | |||
|
162 | return status == win32con.STILL_ACTIVE | |||
|
163 | except pywintypes.error, details: | |||
|
164 | return details[0] != winerror.ERROR_INVALID_PARAMETER | |||
|
165 | return True | |||
|
166 | ||||
|
167 | def system_rcpath(): | |||
|
168 | '''return default os-specific hgrc search path''' | |||
|
169 | proc = win32api.GetCurrentProcess() | |||
|
170 | filename = win32process.GetModuleFileNameEx(proc, 0) | |||
|
171 | return [os.path.join(os.path.dirname(filename), 'mercurial.ini')] |
This diff has been collapsed as it changes many lines, (890 lines changed) Show them Hide them | |||||
@@ -0,0 +1,890 b'' | |||||
|
1 | #!/usr/bin/python | |||
|
2 | # | |||
|
3 | # Perforce Defect Tracking Integration Project | |||
|
4 | # <http://www.ravenbrook.com/project/p4dti/> | |||
|
5 | # | |||
|
6 | # COVERAGE.PY -- COVERAGE TESTING | |||
|
7 | # | |||
|
8 | # Gareth Rees, Ravenbrook Limited, 2001-12-04 | |||
|
9 | # Ned Batchelder, 2004-12-12 | |||
|
10 | # http://nedbatchelder.com/code/modules/coverage.html | |||
|
11 | # | |||
|
12 | # | |||
|
13 | # 1. INTRODUCTION | |||
|
14 | # | |||
|
15 | # This module provides coverage testing for Python code. | |||
|
16 | # | |||
|
17 | # The intended readership is all Python developers. | |||
|
18 | # | |||
|
19 | # This document is not confidential. | |||
|
20 | # | |||
|
21 | # See [GDR 2001-12-04a] for the command-line interface, programmatic | |||
|
22 | # interface and limitations. See [GDR 2001-12-04b] for requirements and | |||
|
23 | # design. | |||
|
24 | ||||
|
25 | """Usage: | |||
|
26 | ||||
|
27 | coverage.py -x MODULE.py [ARG1 ARG2 ...] | |||
|
28 | Execute module, passing the given command-line arguments, collecting | |||
|
29 | coverage data. | |||
|
30 | ||||
|
31 | coverage.py -e | |||
|
32 | Erase collected coverage data. | |||
|
33 | ||||
|
34 | coverage.py -r [-m] [-o dir1,dir2,...] FILE1 FILE2 ... | |||
|
35 | Report on the statement coverage for the given files. With the -m | |||
|
36 | option, show line numbers of the statements that weren't executed. | |||
|
37 | ||||
|
38 | coverage.py -a [-d dir] [-o dir1,dir2,...] FILE1 FILE2 ... | |||
|
39 | Make annotated copies of the given files, marking statements that | |||
|
40 | are executed with > and statements that are missed with !. With | |||
|
41 | the -d option, make the copies in that directory. Without the -d | |||
|
42 | option, make each copy in the same directory as the original. | |||
|
43 | ||||
|
44 | -o dir,dir2,... | |||
|
45 | Omit reporting or annotating files when their filename path starts with | |||
|
46 | a directory listed in the omit list. | |||
|
47 | e.g. python coverage.py -i -r -o c:\python23,lib\enthought\traits | |||
|
48 | ||||
|
49 | Coverage data is saved in the file .coverage by default. Set the | |||
|
50 | COVERAGE_FILE environment variable to save it somewhere else.""" | |||
|
51 | ||||
|
52 | __version__ = "2.5.20051204" # see detailed history at the end of this file. | |||
|
53 | ||||
|
54 | import compiler | |||
|
55 | import compiler.visitor | |||
|
56 | import os | |||
|
57 | import re | |||
|
58 | import string | |||
|
59 | import sys | |||
|
60 | import threading | |||
|
61 | import types | |||
|
62 | ||||
|
63 | # 2. IMPLEMENTATION | |||
|
64 | # | |||
|
65 | # This uses the "singleton" pattern. | |||
|
66 | # | |||
|
67 | # The word "morf" means a module object (from which the source file can | |||
|
68 | # be deduced by suitable manipulation of the __file__ attribute) or a | |||
|
69 | # filename. | |||
|
70 | # | |||
|
71 | # When we generate a coverage report we have to canonicalize every | |||
|
72 | # filename in the coverage dictionary just in case it refers to the | |||
|
73 | # module we are reporting on. It seems a shame to throw away this | |||
|
74 | # information so the data in the coverage dictionary is transferred to | |||
|
75 | # the 'cexecuted' dictionary under the canonical filenames. | |||
|
76 | # | |||
|
77 | # The coverage dictionary is called "c" and the trace function "t". The | |||
|
78 | # reason for these short names is that Python looks up variables by name | |||
|
79 | # at runtime and so execution time depends on the length of variables! | |||
|
80 | # In the bottleneck of this application it's appropriate to abbreviate | |||
|
81 | # names to increase speed. | |||
|
82 | ||||
|
83 | class StatementFindingAstVisitor(compiler.visitor.ASTVisitor): | |||
|
84 | def __init__(self, statements, excluded, suite_spots): | |||
|
85 | compiler.visitor.ASTVisitor.__init__(self) | |||
|
86 | self.statements = statements | |||
|
87 | self.excluded = excluded | |||
|
88 | self.suite_spots = suite_spots | |||
|
89 | self.excluding_suite = 0 | |||
|
90 | ||||
|
91 | def doRecursive(self, node): | |||
|
92 | self.recordNodeLine(node) | |||
|
93 | for n in node.getChildNodes(): | |||
|
94 | self.dispatch(n) | |||
|
95 | ||||
|
96 | visitStmt = visitModule = doRecursive | |||
|
97 | ||||
|
98 | def doCode(self, node): | |||
|
99 | if hasattr(node, 'decorators') and node.decorators: | |||
|
100 | self.dispatch(node.decorators) | |||
|
101 | self.doSuite(node, node.code) | |||
|
102 | ||||
|
103 | visitFunction = visitClass = doCode | |||
|
104 | ||||
|
105 | def getFirstLine(self, node): | |||
|
106 | # Find the first line in the tree node. | |||
|
107 | lineno = node.lineno | |||
|
108 | for n in node.getChildNodes(): | |||
|
109 | f = self.getFirstLine(n) | |||
|
110 | if lineno and f: | |||
|
111 | lineno = min(lineno, f) | |||
|
112 | else: | |||
|
113 | lineno = lineno or f | |||
|
114 | return lineno | |||
|
115 | ||||
|
116 | def getLastLine(self, node): | |||
|
117 | # Find the first line in the tree node. | |||
|
118 | lineno = node.lineno | |||
|
119 | for n in node.getChildNodes(): | |||
|
120 | lineno = max(lineno, self.getLastLine(n)) | |||
|
121 | return lineno | |||
|
122 | ||||
|
123 | def doStatement(self, node): | |||
|
124 | self.recordLine(self.getFirstLine(node)) | |||
|
125 | ||||
|
126 | visitAssert = visitAssign = visitAssTuple = visitDiscard = visitPrint = \ | |||
|
127 | visitPrintnl = visitRaise = visitSubscript = visitDecorators = \ | |||
|
128 | doStatement | |||
|
129 | ||||
|
130 | def recordNodeLine(self, node): | |||
|
131 | return self.recordLine(node.lineno) | |||
|
132 | ||||
|
133 | def recordLine(self, lineno): | |||
|
134 | # Returns a bool, whether the line is included or excluded. | |||
|
135 | if lineno: | |||
|
136 | # Multi-line tests introducing suites have to get charged to their | |||
|
137 | # keyword. | |||
|
138 | if lineno in self.suite_spots: | |||
|
139 | lineno = self.suite_spots[lineno][0] | |||
|
140 | # If we're inside an exluded suite, record that this line was | |||
|
141 | # excluded. | |||
|
142 | if self.excluding_suite: | |||
|
143 | self.excluded[lineno] = 1 | |||
|
144 | return 0 | |||
|
145 | # If this line is excluded, or suite_spots maps this line to | |||
|
146 | # another line that is exlcuded, then we're excluded. | |||
|
147 | elif self.excluded.has_key(lineno) or \ | |||
|
148 | self.suite_spots.has_key(lineno) and \ | |||
|
149 | self.excluded.has_key(self.suite_spots[lineno][1]): | |||
|
150 | return 0 | |||
|
151 | # Otherwise, this is an executable line. | |||
|
152 | else: | |||
|
153 | self.statements[lineno] = 1 | |||
|
154 | return 1 | |||
|
155 | return 0 | |||
|
156 | ||||
|
157 | default = recordNodeLine | |||
|
158 | ||||
|
159 | def recordAndDispatch(self, node): | |||
|
160 | self.recordNodeLine(node) | |||
|
161 | self.dispatch(node) | |||
|
162 | ||||
|
163 | def doSuite(self, intro, body, exclude=0): | |||
|
164 | exsuite = self.excluding_suite | |||
|
165 | if exclude or (intro and not self.recordNodeLine(intro)): | |||
|
166 | self.excluding_suite = 1 | |||
|
167 | self.recordAndDispatch(body) | |||
|
168 | self.excluding_suite = exsuite | |||
|
169 | ||||
|
170 | def doPlainWordSuite(self, prevsuite, suite): | |||
|
171 | # Finding the exclude lines for else's is tricky, because they aren't | |||
|
172 | # present in the compiler parse tree. Look at the previous suite, | |||
|
173 | # and find its last line. If any line between there and the else's | |||
|
174 | # first line are excluded, then we exclude the else. | |||
|
175 | lastprev = self.getLastLine(prevsuite) | |||
|
176 | firstelse = self.getFirstLine(suite) | |||
|
177 | for l in range(lastprev+1, firstelse): | |||
|
178 | if self.suite_spots.has_key(l): | |||
|
179 | self.doSuite(None, suite, exclude=self.excluded.has_key(l)) | |||
|
180 | break | |||
|
181 | else: | |||
|
182 | self.doSuite(None, suite) | |||
|
183 | ||||
|
184 | def doElse(self, prevsuite, node): | |||
|
185 | if node.else_: | |||
|
186 | self.doPlainWordSuite(prevsuite, node.else_) | |||
|
187 | ||||
|
188 | def visitFor(self, node): | |||
|
189 | self.doSuite(node, node.body) | |||
|
190 | self.doElse(node.body, node) | |||
|
191 | ||||
|
192 | def visitIf(self, node): | |||
|
193 | # The first test has to be handled separately from the rest. | |||
|
194 | # The first test is credited to the line with the "if", but the others | |||
|
195 | # are credited to the line with the test for the elif. | |||
|
196 | self.doSuite(node, node.tests[0][1]) | |||
|
197 | for t, n in node.tests[1:]: | |||
|
198 | self.doSuite(t, n) | |||
|
199 | self.doElse(node.tests[-1][1], node) | |||
|
200 | ||||
|
201 | def visitWhile(self, node): | |||
|
202 | self.doSuite(node, node.body) | |||
|
203 | self.doElse(node.body, node) | |||
|
204 | ||||
|
205 | def visitTryExcept(self, node): | |||
|
206 | self.doSuite(node, node.body) | |||
|
207 | for i in range(len(node.handlers)): | |||
|
208 | a, b, h = node.handlers[i] | |||
|
209 | if not a: | |||
|
210 | # It's a plain "except:". Find the previous suite. | |||
|
211 | if i > 0: | |||
|
212 | prev = node.handlers[i-1][2] | |||
|
213 | else: | |||
|
214 | prev = node.body | |||
|
215 | self.doPlainWordSuite(prev, h) | |||
|
216 | else: | |||
|
217 | self.doSuite(a, h) | |||
|
218 | self.doElse(node.handlers[-1][2], node) | |||
|
219 | ||||
|
220 | def visitTryFinally(self, node): | |||
|
221 | self.doSuite(node, node.body) | |||
|
222 | self.doPlainWordSuite(node.body, node.final) | |||
|
223 | ||||
|
224 | def visitGlobal(self, node): | |||
|
225 | # "global" statements don't execute like others (they don't call the | |||
|
226 | # trace function), so don't record their line numbers. | |||
|
227 | pass | |||
|
228 | ||||
|
229 | the_coverage = None | |||
|
230 | ||||
|
231 | class coverage: | |||
|
232 | error = "coverage error" | |||
|
233 | ||||
|
234 | # Name of the cache file (unless environment variable is set). | |||
|
235 | cache_default = ".coverage" | |||
|
236 | ||||
|
237 | # Environment variable naming the cache file. | |||
|
238 | cache_env = "COVERAGE_FILE" | |||
|
239 | ||||
|
240 | # A dictionary with an entry for (Python source file name, line number | |||
|
241 | # in that file) if that line has been executed. | |||
|
242 | c = {} | |||
|
243 | ||||
|
244 | # A map from canonical Python source file name to a dictionary in | |||
|
245 | # which there's an entry for each line number that has been | |||
|
246 | # executed. | |||
|
247 | cexecuted = {} | |||
|
248 | ||||
|
249 | # Cache of results of calling the analysis2() method, so that you can | |||
|
250 | # specify both -r and -a without doing double work. | |||
|
251 | analysis_cache = {} | |||
|
252 | ||||
|
253 | # Cache of results of calling the canonical_filename() method, to | |||
|
254 | # avoid duplicating work. | |||
|
255 | canonical_filename_cache = {} | |||
|
256 | ||||
|
257 | def __init__(self): | |||
|
258 | global the_coverage | |||
|
259 | if the_coverage: | |||
|
260 | raise self.error, "Only one coverage object allowed." | |||
|
261 | self.usecache = 1 | |||
|
262 | self.cache = None | |||
|
263 | self.exclude_re = '' | |||
|
264 | self.nesting = 0 | |||
|
265 | self.cstack = [] | |||
|
266 | self.xstack = [] | |||
|
267 | self.relative_dir = os.path.normcase(os.path.abspath(os.curdir)+os.path.sep) | |||
|
268 | ||||
|
269 | # t(f, x, y). This method is passed to sys.settrace as a trace function. | |||
|
270 | # See [van Rossum 2001-07-20b, 9.2] for an explanation of sys.settrace and | |||
|
271 | # the arguments and return value of the trace function. | |||
|
272 | # See [van Rossum 2001-07-20a, 3.2] for a description of frame and code | |||
|
273 | # objects. | |||
|
274 | ||||
|
275 | def t(self, f, w, a): #pragma: no cover | |||
|
276 | #print w, f.f_code.co_filename, f.f_lineno | |||
|
277 | if w == 'line': | |||
|
278 | self.c[(f.f_code.co_filename, f.f_lineno)] = 1 | |||
|
279 | for c in self.cstack: | |||
|
280 | c[(f.f_code.co_filename, f.f_lineno)] = 1 | |||
|
281 | return self.t | |||
|
282 | ||||
|
283 | def help(self, error=None): | |||
|
284 | if error: | |||
|
285 | print error | |||
|
286 | ||||
|
287 | print __doc__ | |||
|
288 | sys.exit(1) | |||
|
289 | ||||
|
290 | def command_line(self): | |||
|
291 | import getopt | |||
|
292 | settings = {} | |||
|
293 | optmap = { | |||
|
294 | '-a': 'annotate', | |||
|
295 | '-d:': 'directory=', | |||
|
296 | '-e': 'erase', | |||
|
297 | '-h': 'help', | |||
|
298 | '-i': 'ignore-errors', | |||
|
299 | '-m': 'show-missing', | |||
|
300 | '-r': 'report', | |||
|
301 | '-x': 'execute', | |||
|
302 | '-o': 'omit=', | |||
|
303 | } | |||
|
304 | short_opts = string.join(map(lambda o: o[1:], optmap.keys()), '') | |||
|
305 | long_opts = optmap.values() | |||
|
306 | options, args = getopt.getopt(sys.argv[1:], short_opts, long_opts) | |||
|
307 | for o, a in options: | |||
|
308 | if optmap.has_key(o): | |||
|
309 | settings[optmap[o]] = 1 | |||
|
310 | elif optmap.has_key(o + ':'): | |||
|
311 | settings[optmap[o + ':']] = a | |||
|
312 | elif o[2:] in long_opts: | |||
|
313 | settings[o[2:]] = 1 | |||
|
314 | elif o[2:] + '=' in long_opts: | |||
|
315 | settings[o[2:]] = a | |||
|
316 | else: | |||
|
317 | self.help("Unknown option: '%s'." % o) | |||
|
318 | if settings.get('help'): | |||
|
319 | self.help() | |||
|
320 | for i in ['erase', 'execute']: | |||
|
321 | for j in ['annotate', 'report']: | |||
|
322 | if settings.get(i) and settings.get(j): | |||
|
323 | self.help("You can't specify the '%s' and '%s' " | |||
|
324 | "options at the same time." % (i, j)) | |||
|
325 | args_needed = (settings.get('execute') | |||
|
326 | or settings.get('annotate') | |||
|
327 | or settings.get('report')) | |||
|
328 | action = settings.get('erase') or args_needed | |||
|
329 | if not action: | |||
|
330 | self.help("You must specify at least one of -e, -x, -r, or -a.") | |||
|
331 | if not args_needed and args: | |||
|
332 | self.help("Unexpected arguments %s." % args) | |||
|
333 | ||||
|
334 | self.get_ready() | |||
|
335 | self.exclude('#pragma[: ]+[nN][oO] [cC][oO][vV][eE][rR]') | |||
|
336 | ||||
|
337 | if settings.get('erase'): | |||
|
338 | self.erase() | |||
|
339 | if settings.get('execute'): | |||
|
340 | if not args: | |||
|
341 | self.help("Nothing to do.") | |||
|
342 | sys.argv = args | |||
|
343 | self.start() | |||
|
344 | import __main__ | |||
|
345 | sys.path[0] = os.path.dirname(sys.argv[0]) | |||
|
346 | execfile(sys.argv[0], __main__.__dict__) | |||
|
347 | if not args: | |||
|
348 | args = self.cexecuted.keys() | |||
|
349 | ignore_errors = settings.get('ignore-errors') | |||
|
350 | show_missing = settings.get('show-missing') | |||
|
351 | directory = settings.get('directory') | |||
|
352 | omit = filter(None, settings.get('omit', '').split(',')) | |||
|
353 | ||||
|
354 | if settings.get('report'): | |||
|
355 | self.report(args, show_missing, ignore_errors, omit_prefixes=omit) | |||
|
356 | if settings.get('annotate'): | |||
|
357 | self.annotate(args, directory, ignore_errors, omit_prefixes=omit) | |||
|
358 | ||||
|
359 | def use_cache(self, usecache): | |||
|
360 | self.usecache = usecache | |||
|
361 | ||||
|
362 | def get_ready(self): | |||
|
363 | if self.usecache and not self.cache: | |||
|
364 | self.cache = os.path.abspath(os.environ.get(self.cache_env, | |||
|
365 | self.cache_default)) | |||
|
366 | self.restore() | |||
|
367 | self.analysis_cache = {} | |||
|
368 | ||||
|
369 | def start(self): | |||
|
370 | self.get_ready() | |||
|
371 | if self.nesting == 0: #pragma: no cover | |||
|
372 | sys.settrace(self.t) | |||
|
373 | if hasattr(threading, 'settrace'): | |||
|
374 | threading.settrace(self.t) | |||
|
375 | self.nesting += 1 | |||
|
376 | ||||
|
377 | def stop(self): | |||
|
378 | self.nesting -= 1 | |||
|
379 | if self.nesting == 0: #pragma: no cover | |||
|
380 | sys.settrace(None) | |||
|
381 | if hasattr(threading, 'settrace'): | |||
|
382 | threading.settrace(None) | |||
|
383 | ||||
|
384 | def erase(self): | |||
|
385 | self.c = {} | |||
|
386 | self.analysis_cache = {} | |||
|
387 | self.cexecuted = {} | |||
|
388 | if self.cache and os.path.exists(self.cache): | |||
|
389 | os.remove(self.cache) | |||
|
390 | self.exclude_re = "" | |||
|
391 | ||||
|
392 | def exclude(self, re): | |||
|
393 | if self.exclude_re: | |||
|
394 | self.exclude_re += "|" | |||
|
395 | self.exclude_re += "(" + re + ")" | |||
|
396 | ||||
|
397 | def begin_recursive(self): | |||
|
398 | self.cstack.append(self.c) | |||
|
399 | self.xstack.append(self.exclude_re) | |||
|
400 | ||||
|
401 | def end_recursive(self): | |||
|
402 | self.c = self.cstack.pop() | |||
|
403 | self.exclude_re = self.xstack.pop() | |||
|
404 | ||||
|
405 | # save(). Save coverage data to the coverage cache. | |||
|
406 | ||||
|
407 | def save(self): | |||
|
408 | # move to directory that must exist. | |||
|
409 | os.chdir(os.sep) | |||
|
410 | if self.usecache and self.cache: | |||
|
411 | self.canonicalize_filenames() | |||
|
412 | cache = open(self.cache, 'wb') | |||
|
413 | import marshal | |||
|
414 | marshal.dump(self.cexecuted, cache) | |||
|
415 | cache.close() | |||
|
416 | ||||
|
417 | # restore(). Restore coverage data from the coverage cache (if it exists). | |||
|
418 | ||||
|
419 | def restore(self): | |||
|
420 | self.c = {} | |||
|
421 | self.cexecuted = {} | |||
|
422 | assert self.usecache | |||
|
423 | if not os.path.exists(self.cache): | |||
|
424 | return | |||
|
425 | try: | |||
|
426 | cache = open(self.cache, 'rb') | |||
|
427 | import marshal | |||
|
428 | cexecuted = marshal.load(cache) | |||
|
429 | cache.close() | |||
|
430 | if isinstance(cexecuted, types.DictType): | |||
|
431 | self.cexecuted = cexecuted | |||
|
432 | except: | |||
|
433 | pass | |||
|
434 | ||||
|
435 | # canonical_filename(filename). Return a canonical filename for the | |||
|
436 | # file (that is, an absolute path with no redundant components and | |||
|
437 | # normalized case). See [GDR 2001-12-04b, 3.3]. | |||
|
438 | ||||
|
439 | def canonical_filename(self, filename): | |||
|
440 | if not self.canonical_filename_cache.has_key(filename): | |||
|
441 | f = filename | |||
|
442 | if os.path.isabs(f) and not os.path.exists(f): | |||
|
443 | f = os.path.basename(f) | |||
|
444 | if not os.path.isabs(f): | |||
|
445 | for path in [os.curdir] + sys.path: | |||
|
446 | g = os.path.join(path, f) | |||
|
447 | if os.path.exists(g): | |||
|
448 | f = g | |||
|
449 | break | |||
|
450 | cf = os.path.normcase(os.path.abspath(f)) | |||
|
451 | self.canonical_filename_cache[filename] = cf | |||
|
452 | return self.canonical_filename_cache[filename] | |||
|
453 | ||||
|
454 | # canonicalize_filenames(). Copy results from "c" to "cexecuted", | |||
|
455 | # canonicalizing filenames on the way. Clear the "c" map. | |||
|
456 | ||||
|
457 | def canonicalize_filenames(self): | |||
|
458 | for filename, lineno in self.c.keys(): | |||
|
459 | f = self.canonical_filename(filename) | |||
|
460 | if not self.cexecuted.has_key(f): | |||
|
461 | self.cexecuted[f] = {} | |||
|
462 | self.cexecuted[f][lineno] = 1 | |||
|
463 | self.c = {} | |||
|
464 | ||||
|
465 | # morf_filename(morf). Return the filename for a module or file. | |||
|
466 | ||||
|
467 | def morf_filename(self, morf): | |||
|
468 | if isinstance(morf, types.ModuleType): | |||
|
469 | if not hasattr(morf, '__file__'): | |||
|
470 | raise self.error, "Module has no __file__ attribute." | |||
|
471 | file = morf.__file__ | |||
|
472 | else: | |||
|
473 | file = morf | |||
|
474 | return self.canonical_filename(file) | |||
|
475 | ||||
|
476 | # analyze_morf(morf). Analyze the module or filename passed as | |||
|
477 | # the argument. If the source code can't be found, raise an error. | |||
|
478 | # Otherwise, return a tuple of (1) the canonical filename of the | |||
|
479 | # source code for the module, (2) a list of lines of statements | |||
|
480 | # in the source code, and (3) a list of lines of excluded statements. | |||
|
481 | ||||
|
482 | def analyze_morf(self, morf): | |||
|
483 | if self.analysis_cache.has_key(morf): | |||
|
484 | return self.analysis_cache[morf] | |||
|
485 | filename = self.morf_filename(morf) | |||
|
486 | ext = os.path.splitext(filename)[1] | |||
|
487 | if ext == '.pyc': | |||
|
488 | if not os.path.exists(filename[0:-1]): | |||
|
489 | raise self.error, ("No source for compiled code '%s'." | |||
|
490 | % filename) | |||
|
491 | filename = filename[0:-1] | |||
|
492 | elif ext != '.py': | |||
|
493 | raise self.error, "File '%s' not Python source." % filename | |||
|
494 | source = open(filename, 'r') | |||
|
495 | lines, excluded_lines = self.find_executable_statements( | |||
|
496 | source.read(), exclude=self.exclude_re | |||
|
497 | ) | |||
|
498 | source.close() | |||
|
499 | result = filename, lines, excluded_lines | |||
|
500 | self.analysis_cache[morf] = result | |||
|
501 | return result | |||
|
502 | ||||
|
503 | def get_suite_spots(self, tree, spots): | |||
|
504 | import symbol, token | |||
|
505 | for i in range(1, len(tree)): | |||
|
506 | if type(tree[i]) == type(()): | |||
|
507 | if tree[i][0] == symbol.suite: | |||
|
508 | # Found a suite, look back for the colon and keyword. | |||
|
509 | lineno_colon = lineno_word = None | |||
|
510 | for j in range(i-1, 0, -1): | |||
|
511 | if tree[j][0] == token.COLON: | |||
|
512 | lineno_colon = tree[j][2] | |||
|
513 | elif tree[j][0] == token.NAME: | |||
|
514 | if tree[j][1] == 'elif': | |||
|
515 | # Find the line number of the first non-terminal | |||
|
516 | # after the keyword. | |||
|
517 | t = tree[j+1] | |||
|
518 | while t and token.ISNONTERMINAL(t[0]): | |||
|
519 | t = t[1] | |||
|
520 | if t: | |||
|
521 | lineno_word = t[2] | |||
|
522 | else: | |||
|
523 | lineno_word = tree[j][2] | |||
|
524 | break | |||
|
525 | elif tree[j][0] == symbol.except_clause: | |||
|
526 | # "except" clauses look like: | |||
|
527 | # ('except_clause', ('NAME', 'except', lineno), ...) | |||
|
528 | if tree[j][1][0] == token.NAME: | |||
|
529 | lineno_word = tree[j][1][2] | |||
|
530 | break | |||
|
531 | if lineno_colon and lineno_word: | |||
|
532 | # Found colon and keyword, mark all the lines | |||
|
533 | # between the two with the two line numbers. | |||
|
534 | for l in range(lineno_word, lineno_colon+1): | |||
|
535 | spots[l] = (lineno_word, lineno_colon) | |||
|
536 | self.get_suite_spots(tree[i], spots) | |||
|
537 | ||||
|
538 | def find_executable_statements(self, text, exclude=None): | |||
|
539 | # Find lines which match an exclusion pattern. | |||
|
540 | excluded = {} | |||
|
541 | suite_spots = {} | |||
|
542 | if exclude: | |||
|
543 | reExclude = re.compile(exclude) | |||
|
544 | lines = text.split('\n') | |||
|
545 | for i in range(len(lines)): | |||
|
546 | if reExclude.search(lines[i]): | |||
|
547 | excluded[i+1] = 1 | |||
|
548 | ||||
|
549 | import parser | |||
|
550 | tree = parser.suite(text+'\n\n').totuple(1) | |||
|
551 | self.get_suite_spots(tree, suite_spots) | |||
|
552 | ||||
|
553 | # Use the compiler module to parse the text and find the executable | |||
|
554 | # statements. We add newlines to be impervious to final partial lines. | |||
|
555 | statements = {} | |||
|
556 | ast = compiler.parse(text+'\n\n') | |||
|
557 | visitor = StatementFindingAstVisitor(statements, excluded, suite_spots) | |||
|
558 | compiler.walk(ast, visitor, walker=visitor) | |||
|
559 | ||||
|
560 | lines = statements.keys() | |||
|
561 | lines.sort() | |||
|
562 | excluded_lines = excluded.keys() | |||
|
563 | excluded_lines.sort() | |||
|
564 | return lines, excluded_lines | |||
|
565 | ||||
|
566 | # format_lines(statements, lines). Format a list of line numbers | |||
|
567 | # for printing by coalescing groups of lines as long as the lines | |||
|
568 | # represent consecutive statements. This will coalesce even if | |||
|
569 | # there are gaps between statements, so if statements = | |||
|
570 | # [1,2,3,4,5,10,11,12,13,14] and lines = [1,2,5,10,11,13,14] then | |||
|
571 | # format_lines will return "1-2, 5-11, 13-14". | |||
|
572 | ||||
|
573 | def format_lines(self, statements, lines): | |||
|
574 | pairs = [] | |||
|
575 | i = 0 | |||
|
576 | j = 0 | |||
|
577 | start = None | |||
|
578 | pairs = [] | |||
|
579 | while i < len(statements) and j < len(lines): | |||
|
580 | if statements[i] == lines[j]: | |||
|
581 | if start == None: | |||
|
582 | start = lines[j] | |||
|
583 | end = lines[j] | |||
|
584 | j = j + 1 | |||
|
585 | elif start: | |||
|
586 | pairs.append((start, end)) | |||
|
587 | start = None | |||
|
588 | i = i + 1 | |||
|
589 | if start: | |||
|
590 | pairs.append((start, end)) | |||
|
591 | def stringify(pair): | |||
|
592 | start, end = pair | |||
|
593 | if start == end: | |||
|
594 | return "%d" % start | |||
|
595 | else: | |||
|
596 | return "%d-%d" % (start, end) | |||
|
597 | return string.join(map(stringify, pairs), ", ") | |||
|
598 | ||||
|
599 | # Backward compatibility with version 1. | |||
|
600 | def analysis(self, morf): | |||
|
601 | f, s, _, m, mf = self.analysis2(morf) | |||
|
602 | return f, s, m, mf | |||
|
603 | ||||
|
604 | def analysis2(self, morf): | |||
|
605 | filename, statements, excluded = self.analyze_morf(morf) | |||
|
606 | self.canonicalize_filenames() | |||
|
607 | if not self.cexecuted.has_key(filename): | |||
|
608 | self.cexecuted[filename] = {} | |||
|
609 | missing = [] | |||
|
610 | for line in statements: | |||
|
611 | if not self.cexecuted[filename].has_key(line): | |||
|
612 | missing.append(line) | |||
|
613 | return (filename, statements, excluded, missing, | |||
|
614 | self.format_lines(statements, missing)) | |||
|
615 | ||||
|
616 | def relative_filename(self, filename): | |||
|
617 | """ Convert filename to relative filename from self.relative_dir. | |||
|
618 | """ | |||
|
619 | return filename.replace(self.relative_dir, "") | |||
|
620 | ||||
|
621 | def morf_name(self, morf): | |||
|
622 | """ Return the name of morf as used in report. | |||
|
623 | """ | |||
|
624 | if isinstance(morf, types.ModuleType): | |||
|
625 | return morf.__name__ | |||
|
626 | else: | |||
|
627 | return self.relative_filename(os.path.splitext(morf)[0]) | |||
|
628 | ||||
|
629 | def filter_by_prefix(self, morfs, omit_prefixes): | |||
|
630 | """ Return list of morfs where the morf name does not begin | |||
|
631 | with any one of the omit_prefixes. | |||
|
632 | """ | |||
|
633 | filtered_morfs = [] | |||
|
634 | for morf in morfs: | |||
|
635 | for prefix in omit_prefixes: | |||
|
636 | if self.morf_name(morf).startswith(prefix): | |||
|
637 | break | |||
|
638 | else: | |||
|
639 | filtered_morfs.append(morf) | |||
|
640 | ||||
|
641 | return filtered_morfs | |||
|
642 | ||||
|
643 | def morf_name_compare(self, x, y): | |||
|
644 | return cmp(self.morf_name(x), self.morf_name(y)) | |||
|
645 | ||||
|
646 | def report(self, morfs, show_missing=1, ignore_errors=0, file=None, omit_prefixes=[]): | |||
|
647 | if not isinstance(morfs, types.ListType): | |||
|
648 | morfs = [morfs] | |||
|
649 | morfs = self.filter_by_prefix(morfs, omit_prefixes) | |||
|
650 | morfs.sort(self.morf_name_compare) | |||
|
651 | ||||
|
652 | max_name = max([5,] + map(len, map(self.morf_name, morfs))) | |||
|
653 | fmt_name = "%%- %ds " % max_name | |||
|
654 | fmt_err = fmt_name + "%s: %s" | |||
|
655 | header = fmt_name % "Name" + " Stmts Exec Cover" | |||
|
656 | fmt_coverage = fmt_name + "% 6d % 6d % 5d%%" | |||
|
657 | if show_missing: | |||
|
658 | header = header + " Missing" | |||
|
659 | fmt_coverage = fmt_coverage + " %s" | |||
|
660 | if not file: | |||
|
661 | file = sys.stdout | |||
|
662 | print >>file, header | |||
|
663 | print >>file, "-" * len(header) | |||
|
664 | total_statements = 0 | |||
|
665 | total_executed = 0 | |||
|
666 | for morf in morfs: | |||
|
667 | name = self.morf_name(morf) | |||
|
668 | try: | |||
|
669 | _, statements, _, missing, readable = self.analysis2(morf) | |||
|
670 | n = len(statements) | |||
|
671 | m = n - len(missing) | |||
|
672 | if n > 0: | |||
|
673 | pc = 100.0 * m / n | |||
|
674 | else: | |||
|
675 | pc = 100.0 | |||
|
676 | args = (name, n, m, pc) | |||
|
677 | if show_missing: | |||
|
678 | args = args + (readable,) | |||
|
679 | print >>file, fmt_coverage % args | |||
|
680 | total_statements = total_statements + n | |||
|
681 | total_executed = total_executed + m | |||
|
682 | except KeyboardInterrupt: #pragma: no cover | |||
|
683 | raise | |||
|
684 | except: | |||
|
685 | if not ignore_errors: | |||
|
686 | type, msg = sys.exc_info()[0:2] | |||
|
687 | print >>file, fmt_err % (name, type, msg) | |||
|
688 | if len(morfs) > 1: | |||
|
689 | print >>file, "-" * len(header) | |||
|
690 | if total_statements > 0: | |||
|
691 | pc = 100.0 * total_executed / total_statements | |||
|
692 | else: | |||
|
693 | pc = 100.0 | |||
|
694 | args = ("TOTAL", total_statements, total_executed, pc) | |||
|
695 | if show_missing: | |||
|
696 | args = args + ("",) | |||
|
697 | print >>file, fmt_coverage % args | |||
|
698 | ||||
|
699 | # annotate(morfs, ignore_errors). | |||
|
700 | ||||
|
701 | blank_re = re.compile(r"\s*(#|$)") | |||
|
702 | else_re = re.compile(r"\s*else\s*:\s*(#|$)") | |||
|
703 | ||||
|
704 | def annotate(self, morfs, directory=None, ignore_errors=0, omit_prefixes=[]): | |||
|
705 | morfs = self.filter_by_prefix(morfs, omit_prefixes) | |||
|
706 | for morf in morfs: | |||
|
707 | try: | |||
|
708 | filename, statements, excluded, missing, _ = self.analysis2(morf) | |||
|
709 | self.annotate_file(filename, statements, excluded, missing, directory) | |||
|
710 | except KeyboardInterrupt: | |||
|
711 | raise | |||
|
712 | except: | |||
|
713 | if not ignore_errors: | |||
|
714 | raise | |||
|
715 | ||||
|
716 | def annotate_file(self, filename, statements, excluded, missing, directory=None): | |||
|
717 | source = open(filename, 'r') | |||
|
718 | if directory: | |||
|
719 | dest_file = os.path.join(directory, | |||
|
720 | os.path.basename(filename) | |||
|
721 | + ',cover') | |||
|
722 | else: | |||
|
723 | dest_file = filename + ',cover' | |||
|
724 | dest = open(dest_file, 'w') | |||
|
725 | lineno = 0 | |||
|
726 | i = 0 | |||
|
727 | j = 0 | |||
|
728 | covered = 1 | |||
|
729 | while 1: | |||
|
730 | line = source.readline() | |||
|
731 | if line == '': | |||
|
732 | break | |||
|
733 | lineno = lineno + 1 | |||
|
734 | while i < len(statements) and statements[i] < lineno: | |||
|
735 | i = i + 1 | |||
|
736 | while j < len(missing) and missing[j] < lineno: | |||
|
737 | j = j + 1 | |||
|
738 | if i < len(statements) and statements[i] == lineno: | |||
|
739 | covered = j >= len(missing) or missing[j] > lineno | |||
|
740 | if self.blank_re.match(line): | |||
|
741 | dest.write(' ') | |||
|
742 | elif self.else_re.match(line): | |||
|
743 | # Special logic for lines containing only 'else:'. | |||
|
744 | # See [GDR 2001-12-04b, 3.2]. | |||
|
745 | if i >= len(statements) and j >= len(missing): | |||
|
746 | dest.write('! ') | |||
|
747 | elif i >= len(statements) or j >= len(missing): | |||
|
748 | dest.write('> ') | |||
|
749 | elif statements[i] == missing[j]: | |||
|
750 | dest.write('! ') | |||
|
751 | else: | |||
|
752 | dest.write('> ') | |||
|
753 | elif lineno in excluded: | |||
|
754 | dest.write('- ') | |||
|
755 | elif covered: | |||
|
756 | dest.write('> ') | |||
|
757 | else: | |||
|
758 | dest.write('! ') | |||
|
759 | dest.write(line) | |||
|
760 | source.close() | |||
|
761 | dest.close() | |||
|
762 | ||||
|
763 | # Singleton object. | |||
|
764 | the_coverage = coverage() | |||
|
765 | ||||
|
766 | # Module functions call methods in the singleton object. | |||
|
767 | def use_cache(*args, **kw): return the_coverage.use_cache(*args, **kw) | |||
|
768 | def start(*args, **kw): return the_coverage.start(*args, **kw) | |||
|
769 | def stop(*args, **kw): return the_coverage.stop(*args, **kw) | |||
|
770 | def erase(*args, **kw): return the_coverage.erase(*args, **kw) | |||
|
771 | def begin_recursive(*args, **kw): return the_coverage.begin_recursive(*args, **kw) | |||
|
772 | def end_recursive(*args, **kw): return the_coverage.end_recursive(*args, **kw) | |||
|
773 | def exclude(*args, **kw): return the_coverage.exclude(*args, **kw) | |||
|
774 | def analysis(*args, **kw): return the_coverage.analysis(*args, **kw) | |||
|
775 | def analysis2(*args, **kw): return the_coverage.analysis2(*args, **kw) | |||
|
776 | def report(*args, **kw): return the_coverage.report(*args, **kw) | |||
|
777 | def annotate(*args, **kw): return the_coverage.annotate(*args, **kw) | |||
|
778 | def annotate_file(*args, **kw): return the_coverage.annotate_file(*args, **kw) | |||
|
779 | ||||
|
780 | # Save coverage data when Python exits. (The atexit module wasn't | |||
|
781 | # introduced until Python 2.0, so use sys.exitfunc when it's not | |||
|
782 | # available.) | |||
|
783 | try: | |||
|
784 | import atexit | |||
|
785 | atexit.register(the_coverage.save) | |||
|
786 | except ImportError: | |||
|
787 | sys.exitfunc = the_coverage.save | |||
|
788 | ||||
|
789 | # Command-line interface. | |||
|
790 | if __name__ == '__main__': | |||
|
791 | the_coverage.command_line() | |||
|
792 | ||||
|
793 | ||||
|
794 | # A. REFERENCES | |||
|
795 | # | |||
|
796 | # [GDR 2001-12-04a] "Statement coverage for Python"; Gareth Rees; | |||
|
797 | # Ravenbrook Limited; 2001-12-04; | |||
|
798 | # <http://www.nedbatchelder.com/code/modules/rees-coverage.html>. | |||
|
799 | # | |||
|
800 | # [GDR 2001-12-04b] "Statement coverage for Python: design and | |||
|
801 | # analysis"; Gareth Rees; Ravenbrook Limited; 2001-12-04; | |||
|
802 | # <http://www.nedbatchelder.com/code/modules/rees-design.html>. | |||
|
803 | # | |||
|
804 | # [van Rossum 2001-07-20a] "Python Reference Manual (releae 2.1.1)"; | |||
|
805 | # Guide van Rossum; 2001-07-20; | |||
|
806 | # <http://www.python.org/doc/2.1.1/ref/ref.html>. | |||
|
807 | # | |||
|
808 | # [van Rossum 2001-07-20b] "Python Library Reference"; Guido van Rossum; | |||
|
809 | # 2001-07-20; <http://www.python.org/doc/2.1.1/lib/lib.html>. | |||
|
810 | # | |||
|
811 | # | |||
|
812 | # B. DOCUMENT HISTORY | |||
|
813 | # | |||
|
814 | # 2001-12-04 GDR Created. | |||
|
815 | # | |||
|
816 | # 2001-12-06 GDR Added command-line interface and source code | |||
|
817 | # annotation. | |||
|
818 | # | |||
|
819 | # 2001-12-09 GDR Moved design and interface to separate documents. | |||
|
820 | # | |||
|
821 | # 2001-12-10 GDR Open cache file as binary on Windows. Allow | |||
|
822 | # simultaneous -e and -x, or -a and -r. | |||
|
823 | # | |||
|
824 | # 2001-12-12 GDR Added command-line help. Cache analysis so that it | |||
|
825 | # only needs to be done once when you specify -a and -r. | |||
|
826 | # | |||
|
827 | # 2001-12-13 GDR Improved speed while recording. Portable between | |||
|
828 | # Python 1.5.2 and 2.1.1. | |||
|
829 | # | |||
|
830 | # 2002-01-03 GDR Module-level functions work correctly. | |||
|
831 | # | |||
|
832 | # 2002-01-07 GDR Update sys.path when running a file with the -x option, | |||
|
833 | # so that it matches the value the program would get if it were run on | |||
|
834 | # its own. | |||
|
835 | # | |||
|
836 | # 2004-12-12 NMB Significant code changes. | |||
|
837 | # - Finding executable statements has been rewritten so that docstrings and | |||
|
838 | # other quirks of Python execution aren't mistakenly identified as missing | |||
|
839 | # lines. | |||
|
840 | # - Lines can be excluded from consideration, even entire suites of lines. | |||
|
841 | # - The filesystem cache of covered lines can be disabled programmatically. | |||
|
842 | # - Modernized the code. | |||
|
843 | # | |||
|
844 | # 2004-12-14 NMB Minor tweaks. Return 'analysis' to its original behavior | |||
|
845 | # and add 'analysis2'. Add a global for 'annotate', and factor it, adding | |||
|
846 | # 'annotate_file'. | |||
|
847 | # | |||
|
848 | # 2004-12-31 NMB Allow for keyword arguments in the module global functions. | |||
|
849 | # Thanks, Allen. | |||
|
850 | # | |||
|
851 | # 2005-12-02 NMB Call threading.settrace so that all threads are measured. | |||
|
852 | # Thanks Martin Fuzzey. Add a file argument to report so that reports can be | |||
|
853 | # captured to a different destination. | |||
|
854 | # | |||
|
855 | # 2005-12-03 NMB coverage.py can now measure itself. | |||
|
856 | # | |||
|
857 | # 2005-12-04 NMB Adapted Greg Rogers' patch for using relative filenames, | |||
|
858 | # and sorting and omitting files to report on. | |||
|
859 | # | |||
|
860 | # C. COPYRIGHT AND LICENCE | |||
|
861 | # | |||
|
862 | # Copyright 2001 Gareth Rees. All rights reserved. | |||
|
863 | # Copyright 2004-2005 Ned Batchelder. All rights reserved. | |||
|
864 | # | |||
|
865 | # Redistribution and use in source and binary forms, with or without | |||
|
866 | # modification, are permitted provided that the following conditions are | |||
|
867 | # met: | |||
|
868 | # | |||
|
869 | # 1. Redistributions of source code must retain the above copyright | |||
|
870 | # notice, this list of conditions and the following disclaimer. | |||
|
871 | # | |||
|
872 | # 2. Redistributions in binary form must reproduce the above copyright | |||
|
873 | # notice, this list of conditions and the following disclaimer in the | |||
|
874 | # documentation and/or other materials provided with the | |||
|
875 | # distribution. | |||
|
876 | # | |||
|
877 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |||
|
878 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |||
|
879 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |||
|
880 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |||
|
881 | # HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |||
|
882 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |||
|
883 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS | |||
|
884 | # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |||
|
885 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR | |||
|
886 | # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE | |||
|
887 | # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH | |||
|
888 | # DAMAGE. | |||
|
889 | # | |||
|
890 | # $Id: coverage.py 26 2005-12-04 18:42:44Z ned $ |
@@ -0,0 +1,19 b'' | |||||
|
1 | #!/bin/sh | |||
|
2 | ||||
|
3 | hg init a | |||
|
4 | cd a | |||
|
5 | hg init b | |||
|
6 | echo x > b/x | |||
|
7 | echo '# should print nothing' | |||
|
8 | hg st | |||
|
9 | echo '# should print ? b/x' | |||
|
10 | hg st b/x | |||
|
11 | ||||
|
12 | hg add b/x | |||
|
13 | ||||
|
14 | echo '# should print A b/x' | |||
|
15 | hg st | |||
|
16 | echo '# should forget b/x' | |||
|
17 | hg forget | |||
|
18 | echo '# should print nothing' | |||
|
19 | hg st b |
@@ -0,0 +1,8 b'' | |||||
|
1 | # should print nothing | |||
|
2 | # should print ? b/x | |||
|
3 | ? b/x | |||
|
4 | # should print A b/x | |||
|
5 | A b/x | |||
|
6 | # should forget b/x | |||
|
7 | forgetting b/x | |||
|
8 | # should print nothing |
@@ -8,6 +8,7 b' syntax: glob' | |||||
8 | *.pyc |
|
8 | *.pyc | |
9 | *.swp |
|
9 | *.swp | |
10 | *.prof |
|
10 | *.prof | |
|
11 | tests/.coverage* | |||
11 | tests/*.err |
|
12 | tests/*.err | |
12 | build |
|
13 | build | |
13 | dist |
|
14 | dist |
@@ -2,13 +2,13 b' include hg' | |||||
2 | recursive-include mercurial *.py |
|
2 | recursive-include mercurial *.py | |
3 | include hgweb.cgi hgwebdir.cgi |
|
3 | include hgweb.cgi hgwebdir.cgi | |
4 | include hgeditor rewrite-log |
|
4 | include hgeditor rewrite-log | |
5 | include tests/README tests/run-tests tests/test-*[a-z0-9] tests/*.out |
|
5 | include tests/README tests/run-tests tests/md5sum.py tests/test-*[a-z0-9] tests/*.out | |
6 | prune tests/*.err |
|
6 | prune tests/*.err | |
7 | include *.txt |
|
7 | include *.txt | |
8 | include templates/map templates/map-*[a-z0-9] |
|
8 | include templates/map templates/map-*[a-z0-9] | |
9 | include templates/*.tmpl |
|
9 | include templates/*.tmpl | |
10 | include templates/static/* |
|
10 | include templates/static/* | |
11 | include doc/README doc/Makefile doc/*.txt doc/*.html doc/*.[0-9] |
|
11 | include doc/README doc/Makefile doc/gendoc.py doc/*.txt doc/*.html doc/*.[0-9] | |
12 | recursive-include contrib * |
|
12 | recursive-include contrib * | |
13 | include README |
|
13 | include README | |
14 | include CONTRIBUTORS |
|
14 | include CONTRIBUTORS |
@@ -3448,7 +3448,7 b' proc domktag {} {' | |||||
3448 | return |
|
3448 | return | |
3449 | } |
|
3449 | } | |
3450 | if {[catch { |
|
3450 | if {[catch { | |
3451 |
set out [exec hg tag $tag |
|
3451 | set out [exec hg tag -r $id $tag] | |
3452 | } err]} { |
|
3452 | } err]} { | |
3453 | error_popup "Error creating tag: $err" |
|
3453 | error_popup "Error creating tag: $err" | |
3454 | return |
|
3454 | return |
@@ -2259,7 +2259,7 b' def recover(ui, repo):' | |||||
2259 | """ |
|
2259 | """ | |
2260 | if repo.recover(): |
|
2260 | if repo.recover(): | |
2261 | return repo.verify() |
|
2261 | return repo.verify() | |
2262 |
return |
|
2262 | return 1 | |
2263 |
|
2263 | |||
2264 | def remove(ui, repo, pat, *pats, **opts): |
|
2264 | def remove(ui, repo, pat, *pats, **opts): | |
2265 | """remove the specified files on the next commit |
|
2265 | """remove the specified files on the next commit | |
@@ -3259,38 +3259,32 b' def dispatch(args):' | |||||
3259 | u = ui.ui() |
|
3259 | u = ui.ui() | |
3260 | except util.Abort, inst: |
|
3260 | except util.Abort, inst: | |
3261 | sys.stderr.write(_("abort: %s\n") % inst) |
|
3261 | sys.stderr.write(_("abort: %s\n") % inst) | |
3262 |
|
|
3262 | return -1 | |
3263 |
|
3263 | |||
3264 | external = [] |
|
3264 | external = [] | |
3265 | for x in u.extensions(): |
|
3265 | for x in u.extensions(): | |
3266 | def on_exception(exc, inst): |
|
3266 | try: | |
3267 | u.warn(_("*** failed to import extension %s\n") % x[1]) |
|
3267 | if x[1]: | |
3268 | u.warn("%s\n" % inst) |
|
|||
3269 | if "--traceback" in sys.argv[1:]: |
|
|||
3270 | traceback.print_exc() |
|
|||
3271 | if x[1]: |
|
|||
3272 | try: |
|
|||
3273 | mod = imp.load_source(x[0], x[1]) |
|
3268 | mod = imp.load_source(x[0], x[1]) | |
3274 | except Exception, inst: |
|
3269 | else: | |
3275 | on_exception(Exception, inst) |
|
3270 | def importh(name): | |
3276 | continue |
|
3271 | mod = __import__(name) | |
3277 | else: |
|
3272 | components = name.split('.') | |
3278 | def importh(name): |
|
3273 | for comp in components[1:]: | |
3279 | mod = __import__(name) |
|
3274 | mod = getattr(mod, comp) | |
3280 | components = name.split('.') |
|
3275 | return mod | |
3281 | for comp in components[1:]: |
|
|||
3282 | mod = getattr(mod, comp) |
|
|||
3283 | return mod |
|
|||
3284 | try: |
|
|||
3285 | try: |
|
3276 | try: | |
3286 | mod = importh("hgext." + x[0]) |
|
3277 | mod = importh("hgext." + x[0]) | |
3287 | except ImportError: |
|
3278 | except ImportError: | |
3288 | mod = importh(x[0]) |
|
3279 | mod = importh(x[0]) | |
3289 | except Exception, inst: |
|
3280 | external.append(mod) | |
3290 |
|
|
3281 | except Exception, inst: | |
3291 | continue |
|
3282 | u.warn(_("*** failed to import extension %s: %s\n") % (x[0], inst)) | |
3292 |
|
3283 | if "--traceback" in sys.argv[1:]: | ||
3293 | external.append(mod) |
|
3284 | traceback.print_exc() | |
|
3285 | return 1 | |||
|
3286 | continue | |||
|
3287 | ||||
3294 | for x in external: |
|
3288 | for x in external: | |
3295 | cmdtable = getattr(x, 'cmdtable', {}) |
|
3289 | cmdtable = getattr(x, 'cmdtable', {}) | |
3296 | for t in cmdtable: |
|
3290 | for t in cmdtable: | |
@@ -3332,14 +3326,11 b' def dispatch(args):' | |||||
3332 | repo = path and hg.repository(u, path=path) or None |
|
3326 | repo = path and hg.repository(u, path=path) or None | |
3333 |
|
3327 | |||
3334 | if options['help']: |
|
3328 | if options['help']: | |
3335 | help_(u, cmd, options['version']) |
|
3329 | return help_(u, cmd, options['version']) | |
3336 | sys.exit(0) |
|
|||
3337 | elif options['version']: |
|
3330 | elif options['version']: | |
3338 | show_version(u) |
|
3331 | return show_version(u) | |
3339 | sys.exit(0) |
|
|||
3340 | elif not cmd: |
|
3332 | elif not cmd: | |
3341 | help_(u, 'shortlist') |
|
3333 | return help_(u, 'shortlist') | |
3342 | sys.exit(0) |
|
|||
3343 |
|
3334 | |||
3344 | if cmd not in norepo.split(): |
|
3335 | if cmd not in norepo.split(): | |
3345 | try: |
|
3336 | try: | |
@@ -3394,15 +3385,12 b' def dispatch(args):' | |||||
3394 | else: |
|
3385 | else: | |
3395 | u.warn(_("hg: %s\n") % inst.args[1]) |
|
3386 | u.warn(_("hg: %s\n") % inst.args[1]) | |
3396 | help_(u, 'shortlist') |
|
3387 | help_(u, 'shortlist') | |
3397 | sys.exit(-1) |
|
|||
3398 | except AmbiguousCommand, inst: |
|
3388 | except AmbiguousCommand, inst: | |
3399 | u.warn(_("hg: command '%s' is ambiguous:\n %s\n") % |
|
3389 | u.warn(_("hg: command '%s' is ambiguous:\n %s\n") % | |
3400 | (inst.args[0], " ".join(inst.args[1]))) |
|
3390 | (inst.args[0], " ".join(inst.args[1]))) | |
3401 | sys.exit(1) |
|
|||
3402 | except UnknownCommand, inst: |
|
3391 | except UnknownCommand, inst: | |
3403 | u.warn(_("hg: unknown command '%s'\n") % inst.args[0]) |
|
3392 | u.warn(_("hg: unknown command '%s'\n") % inst.args[0]) | |
3404 | help_(u, 'shortlist') |
|
3393 | help_(u, 'shortlist') | |
3405 | sys.exit(1) |
|
|||
3406 | except hg.RepoError, inst: |
|
3394 | except hg.RepoError, inst: | |
3407 | u.warn(_("abort: "), inst, "!\n") |
|
3395 | u.warn(_("abort: "), inst, "!\n") | |
3408 | except lock.LockHeld, inst: |
|
3396 | except lock.LockHeld, inst: | |
@@ -3449,7 +3437,6 b' def dispatch(args):' | |||||
3449 | u.warn(_("abort: %s\n") % inst.strerror) |
|
3437 | u.warn(_("abort: %s\n") % inst.strerror) | |
3450 | except util.Abort, inst: |
|
3438 | except util.Abort, inst: | |
3451 | u.warn(_('abort: '), inst.args[0] % inst.args[1:], '\n') |
|
3439 | u.warn(_('abort: '), inst.args[0] % inst.args[1:], '\n') | |
3452 | sys.exit(1) |
|
|||
3453 | except TypeError, inst: |
|
3440 | except TypeError, inst: | |
3454 | # was this an argument error? |
|
3441 | # was this an argument error? | |
3455 | tb = traceback.extract_tb(sys.exc_info()[2]) |
|
3442 | tb = traceback.extract_tb(sys.exc_info()[2]) | |
@@ -3458,9 +3445,10 b' def dispatch(args):' | |||||
3458 | u.debug(inst, "\n") |
|
3445 | u.debug(inst, "\n") | |
3459 | u.warn(_("%s: invalid arguments\n") % cmd) |
|
3446 | u.warn(_("%s: invalid arguments\n") % cmd) | |
3460 | help_(u, cmd) |
|
3447 | help_(u, cmd) | |
3461 | except SystemExit: |
|
3448 | except SystemExit, inst: | |
3462 | # don't catch this in the catch-all below |
|
3449 | # Commands shouldn't sys.exit directly, but give a return code. | |
3463 | raise |
|
3450 | # Just in case catch this and and pass exit code to caller. | |
|
3451 | return inst.code | |||
3464 | except: |
|
3452 | except: | |
3465 | u.warn(_("** unknown exception encountered, details follow\n")) |
|
3453 | u.warn(_("** unknown exception encountered, details follow\n")) | |
3466 | u.warn(_("** report bug details to mercurial@selenic.com\n")) |
|
3454 | u.warn(_("** report bug details to mercurial@selenic.com\n")) | |
@@ -3468,4 +3456,4 b' def dispatch(args):' | |||||
3468 | % version.get_version()) |
|
3456 | % version.get_version()) | |
3469 | raise |
|
3457 | raise | |
3470 |
|
3458 | |||
3471 | sys.exit(-1) |
|
3459 | return -1 |
@@ -342,7 +342,16 b' class dirstate(object):' | |||||
342 | names.sort() |
|
342 | names.sort() | |
343 | # nd is the top of the repository dir tree |
|
343 | # nd is the top of the repository dir tree | |
344 | nd = util.normpath(top[len(self.root) + 1:]) |
|
344 | nd = util.normpath(top[len(self.root) + 1:]) | |
345 |
if nd == '.': |
|
345 | if nd == '.': | |
|
346 | nd = '' | |||
|
347 | else: | |||
|
348 | # do not recurse into a repo contained in this | |||
|
349 | # one. use bisect to find .hg directory so speed | |||
|
350 | # is good on big directory. | |||
|
351 | hg = bisect.bisect_left(names, '.hg') | |||
|
352 | if hg < len(names) and names[hg] == '.hg': | |||
|
353 | if os.path.isdir(os.path.join(top, '.hg')): | |||
|
354 | continue | |||
346 | for f in names: |
|
355 | for f in names: | |
347 | np = util.pconvert(os.path.join(nd, f)) |
|
356 | np = util.pconvert(os.path.join(nd, f)) | |
348 | if seen(np): |
|
357 | if seen(np): |
@@ -1594,8 +1594,9 b' class localrepository(object):' | |||||
1594 | self.ui.debug(_(" remote %s is newer, get\n") % f) |
|
1594 | self.ui.debug(_(" remote %s is newer, get\n") % f) | |
1595 | get[f] = m2[f] |
|
1595 | get[f] = m2[f] | |
1596 | s = 1 |
|
1596 | s = 1 | |
1597 | elif f in umap: |
|
1597 | elif f in umap or f in added: | |
1598 | # this unknown file is the same as the checkout |
|
1598 | # this unknown file is the same as the checkout | |
|
1599 | # we need to reset the dirstate if the file was added | |||
1599 | get[f] = m2[f] |
|
1600 | get[f] = m2[f] | |
1600 |
|
1601 | |||
1601 | if not s and mfw[f] != mf2[f]: |
|
1602 | if not s and mfw[f] != mf2[f]: |
@@ -71,10 +71,23 b' def filter(s, cmd):' | |||||
71 | return fn(s, cmd[len(name):].lstrip()) |
|
71 | return fn(s, cmd[len(name):].lstrip()) | |
72 | return pipefilter(s, cmd) |
|
72 | return pipefilter(s, cmd) | |
73 |
|
73 | |||
|
74 | def find_in_path(name, path, default=None): | |||
|
75 | '''find name in search path. path can be string (will be split | |||
|
76 | with os.pathsep), or iterable thing that returns strings. if name | |||
|
77 | found, return path to name. else return default.''' | |||
|
78 | if isinstance(path, str): | |||
|
79 | path = path.split(os.pathsep) | |||
|
80 | for p in path: | |||
|
81 | p_name = os.path.join(p, name) | |||
|
82 | if os.path.exists(p_name): | |||
|
83 | return p_name | |||
|
84 | return default | |||
|
85 | ||||
74 | def patch(strip, patchname, ui): |
|
86 | def patch(strip, patchname, ui): | |
75 | """apply the patch <patchname> to the working directory. |
|
87 | """apply the patch <patchname> to the working directory. | |
76 | a list of patched files is returned""" |
|
88 | a list of patched files is returned""" | |
77 | fp = os.popen('patch -p%d < "%s"' % (strip, patchname)) |
|
89 | patcher = find_in_path('gpatch', os.environ.get('PATH', ''), 'patch') | |
|
90 | fp = os.popen('"%s" -p%d < "%s"' % (patcher, strip, patchname)) | |||
78 | files = {} |
|
91 | files = {} | |
79 | for line in fp: |
|
92 | for line in fp: | |
80 | line = line.rstrip() |
|
93 | line = line.rstrip() | |
@@ -373,8 +386,10 b' def unlink(f):' | |||||
373 | """unlink and remove the directory if it is empty""" |
|
386 | """unlink and remove the directory if it is empty""" | |
374 | os.unlink(f) |
|
387 | os.unlink(f) | |
375 | # try removing directories that might now be empty |
|
388 | # try removing directories that might now be empty | |
376 | try: os.removedirs(os.path.dirname(f)) |
|
389 | try: | |
377 | except: pass |
|
390 | os.removedirs(os.path.dirname(f)) | |
|
391 | except OSError: | |||
|
392 | pass | |||
378 |
|
393 | |||
379 | def copyfiles(src, dst, hardlink=None): |
|
394 | def copyfiles(src, dst, hardlink=None): | |
380 | """Copy a directory tree using hardlinks if possible""" |
|
395 | """Copy a directory tree using hardlinks if possible""" | |
@@ -530,18 +545,13 b" if os.name == 'nt':" | |||||
530 |
|
545 | |||
531 | sys.stdout = winstdout(sys.stdout) |
|
546 | sys.stdout = winstdout(sys.stdout) | |
532 |
|
547 | |||
|
548 | def system_rcpath(): | |||
|
549 | return [r'c:\mercurial\mercurial.ini'] | |||
|
550 | ||||
533 | def os_rcpath(): |
|
551 | def os_rcpath(): | |
534 | '''return default os-specific hgrc search path''' |
|
552 | '''return default os-specific hgrc search path''' | |
535 | try: |
|
553 | return system_rcpath() + [os.path.join(os.path.expanduser('~'), | |
536 | import win32api, win32process |
|
554 | 'mercurial.ini')] | |
537 | proc = win32api.GetCurrentProcess() |
|
|||
538 | filename = win32process.GetModuleFileNameEx(proc, 0) |
|
|||
539 | systemrc = os.path.join(os.path.dirname(filename), 'mercurial.ini') |
|
|||
540 | except ImportError: |
|
|||
541 | systemrc = r'c:\mercurial\mercurial.ini' |
|
|||
542 |
|
||||
543 | return [systemrc, |
|
|||
544 | os.path.join(os.path.expanduser('~'), 'mercurial.ini')] |
|
|||
545 |
|
555 | |||
546 | def parse_patch_output(output_line): |
|
556 | def parse_patch_output(output_line): | |
547 | """parses the output produced by patch and returns the file name""" |
|
557 | """parses the output produced by patch and returns the file name""" | |
@@ -550,43 +560,9 b" if os.name == 'nt':" | |||||
550 | pf = pf[1:-1] # Remove the quotes |
|
560 | pf = pf[1:-1] # Remove the quotes | |
551 | return pf |
|
561 | return pf | |
552 |
|
562 | |||
553 | try: # Mark Hammond's win32all package allows better functionality on Windows |
|
563 | def testpid(pid): | |
554 | import win32api, win32con, win32file, pywintypes |
|
564 | '''return False if pid dead, True if running or not known''' | |
555 |
|
565 | return True | ||
556 | # create hard links using win32file module |
|
|||
557 | def os_link(src, dst): # NB will only succeed on NTFS |
|
|||
558 | win32file.CreateHardLink(dst, src) |
|
|||
559 |
|
||||
560 | def nlinks(pathname): |
|
|||
561 | """Return number of hardlinks for the given file.""" |
|
|||
562 | try: |
|
|||
563 | fh = win32file.CreateFile(pathname, |
|
|||
564 | win32file.GENERIC_READ, win32file.FILE_SHARE_READ, |
|
|||
565 | None, win32file.OPEN_EXISTING, 0, None) |
|
|||
566 | res = win32file.GetFileInformationByHandle(fh) |
|
|||
567 | fh.Close() |
|
|||
568 | return res[7] |
|
|||
569 | except: |
|
|||
570 | return os.stat(pathname).st_nlink |
|
|||
571 |
|
||||
572 | def testpid(pid): |
|
|||
573 | '''return True if pid is still running or unable to |
|
|||
574 | determine, False otherwise''' |
|
|||
575 | try: |
|
|||
576 | import win32process, winerror |
|
|||
577 | handle = win32api.OpenProcess( |
|
|||
578 | win32con.PROCESS_QUERY_INFORMATION, False, pid) |
|
|||
579 | if handle: |
|
|||
580 | status = win32process.GetExitCodeProcess(handle) |
|
|||
581 | return status == win32con.STILL_ACTIVE |
|
|||
582 | except pywintypes.error, details: |
|
|||
583 | return details[0] != winerror.ERROR_INVALID_PARAMETER |
|
|||
584 | return True |
|
|||
585 |
|
||||
586 | except ImportError: |
|
|||
587 | def testpid(pid): |
|
|||
588 | '''return False if pid dead, True if running or not known''' |
|
|||
589 | return True |
|
|||
590 |
|
566 | |||
591 | def is_exec(f, last): |
|
567 | def is_exec(f, last): | |
592 | return last |
|
568 | return last | |
@@ -612,6 +588,12 b" if os.name == 'nt':" | |||||
612 | def explain_exit(code): |
|
588 | def explain_exit(code): | |
613 | return _("exited with status %d") % code, code |
|
589 | return _("exited with status %d") % code, code | |
614 |
|
590 | |||
|
591 | try: | |||
|
592 | # override functions with win32 versions if possible | |||
|
593 | from util_win32 import * | |||
|
594 | except ImportError: | |||
|
595 | pass | |||
|
596 | ||||
615 | else: |
|
597 | else: | |
616 | nulldev = '/dev/null' |
|
598 | nulldev = '/dev/null' | |
617 |
|
599 |
@@ -1,4 +1,9 b'' | |||||
1 | #!/bin/sh -e |
|
1 | #!/bin/sh -e | |
|
2 | # | |||
|
3 | # environment variables: | |||
|
4 | # | |||
|
5 | # TEST_COVERAGE - set non-empty if you want to print test coverage report | |||
|
6 | # COVERAGE_STDLIB - set non-empty to report coverage of standard library | |||
2 |
|
7 | |||
3 | LANG="C"; export LANG |
|
8 | LANG="C"; export LANG | |
4 | LC_CTYPE="C"; export LC_CTYPE |
|
9 | LC_CTYPE="C"; export LC_CTYPE | |
@@ -19,6 +24,16 b' HGEDITOR=true; export HGEDITOR' | |||||
19 | HGMERGE=true; export HGMERGE |
|
24 | HGMERGE=true; export HGMERGE | |
20 | HGUSER="test"; export HGUSER |
|
25 | HGUSER="test"; export HGUSER | |
21 | HGRCPATH=""; export HGRCPATH |
|
26 | HGRCPATH=""; export HGRCPATH | |
|
27 | OS=`uname` | |||
|
28 | ||||
|
29 | case "$OS" in | |||
|
30 | HP-UX|SunOS) | |||
|
31 | DIFFOPTS= | |||
|
32 | ;; | |||
|
33 | *) | |||
|
34 | DIFFOPTS=-u | |||
|
35 | ;; | |||
|
36 | esac | |||
22 |
|
37 | |||
23 | if [ `echo -n HG` = "-n HG" ] |
|
38 | if [ `echo -n HG` = "-n HG" ] | |
24 | then |
|
39 | then | |
@@ -64,7 +79,19 b' else' | |||||
64 | fi |
|
79 | fi | |
65 | cd "$TESTDIR" |
|
80 | cd "$TESTDIR" | |
66 |
|
81 | |||
67 | BINDIR="$INST/bin" |
|
82 | BINDIR="$INST/bin"; export BINDIR | |
|
83 | if [ -n "$TEST_COVERAGE" ]; then | |||
|
84 | COVERAGE_FILE="$TESTDIR/.coverage"; export COVERAGE_FILE | |||
|
85 | rm -f "$COVERAGE_FILE" | |||
|
86 | mv "$BINDIR/hg" "$BINDIR/hg.py" | |||
|
87 | { | |||
|
88 | echo '#!/bin/sh' | |||
|
89 | echo "exec \"${PYTHON-python}\" \"$TESTDIR/coverage.py\"" \ | |||
|
90 | "-x \"$BINDIR/hg.py\" \"\$@\"" | |||
|
91 | } > "$BINDIR/hg" | |||
|
92 | chmod 700 "$BINDIR/hg" | |||
|
93 | fi | |||
|
94 | ||||
68 | PATH="$BINDIR:$PATH"; export PATH |
|
95 | PATH="$BINDIR:$PATH"; export PATH | |
69 | if [ -n "$PYTHON" ]; then |
|
96 | if [ -n "$PYTHON" ]; then | |
70 | { |
|
97 | { | |
@@ -101,13 +128,13 b' run_one() {' | |||||
101 | cat "$ERR" |
|
128 | cat "$ERR" | |
102 | fail=1 |
|
129 | fail=1 | |
103 | elif [ -r "$OUTOK" ]; then |
|
130 | elif [ -r "$OUTOK" ]; then | |
104 |
if diff |
|
131 | if diff $DIFFOPTS "$OUTOK" "$OUT" > /dev/null; then | |
105 | : no differences |
|
132 | : no differences | |
106 | else |
|
133 | else | |
107 | cp "$OUT" "$ERR" |
|
134 | cp "$OUT" "$ERR" | |
108 | echo |
|
135 | echo | |
109 | echo "$1 output changed:" |
|
136 | echo "$1 output changed:" | |
110 |
diff |
|
137 | diff $DIFFOPTS "$OUTOK" "$ERR" || true | |
111 | fail=1 |
|
138 | fail=1 | |
112 | fi |
|
139 | fi | |
113 | fi |
|
140 | fi | |
@@ -153,6 +180,17 b' done' | |||||
153 | echo |
|
180 | echo | |
154 | echo "Ran $tests tests, $failed failed." |
|
181 | echo "Ran $tests tests, $failed failed." | |
155 |
|
182 | |||
|
183 | if [ -n "$TEST_COVERAGE" ]; then | |||
|
184 | unset PYTHONPATH | |||
|
185 | $ECHO_N "$BINDIR,$TESTDIR,$HGTMP/test-," > "$HGTMP/omit" | |||
|
186 | if [ -z "$COVERAGE_STDLIB" ]; then | |||
|
187 | "${PYTHON-python}" -c 'import sys; print ",".join(sys.path)' \ | |||
|
188 | >> "$HGTMP/omit" | |||
|
189 | fi | |||
|
190 | cd "$PYTHONDIR" | |||
|
191 | "${PYTHON-python}" "$TESTDIR/coverage.py" -r --omit="`cat \"$HGTMP/omit\"`" | |||
|
192 | fi | |||
|
193 | ||||
156 | if [ $failed -gt 0 ] ; then |
|
194 | if [ $failed -gt 0 ] ; then | |
157 | exit 1 |
|
195 | exit 1 | |
158 | fi |
|
196 | fi |
@@ -6,7 +6,7 b' 255' | |||||
6 | abort: repository a not found! |
|
6 | abort: repository a not found! | |
7 | 255 |
|
7 | 255 | |
8 | abort: destination '../a' already exists |
|
8 | abort: destination '../a' already exists | |
9 | 1 |
|
9 | 255 | |
10 | abort: repository a not found! |
|
10 | abort: repository a not found! | |
11 | 255 |
|
11 | 255 | |
12 | abort: destination 'q' already exists |
|
12 | abort: destination 'q' already exists |
@@ -55,3 +55,15 b' hg --debug up -f -m' | |||||
55 | hg parents |
|
55 | hg parents | |
56 | hg diff | sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \ |
|
56 | hg diff | sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \ | |
57 | -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/" |
|
57 | -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/" | |
|
58 | ||||
|
59 | # test a local add | |||
|
60 | cd .. | |||
|
61 | hg init a | |||
|
62 | hg init b | |||
|
63 | echo a > a/a | |||
|
64 | echo a > b/a | |||
|
65 | hg --cwd a commit -A -m a | |||
|
66 | cd b | |||
|
67 | hg add a | |||
|
68 | hg pull -u ../a | |||
|
69 | hg st |
@@ -136,3 +136,10 b' diff -r 802f095af299 a' | |||||
136 | @@ -1,1 +1,1 @@ a2 |
|
136 | @@ -1,1 +1,1 @@ a2 | |
137 | -a2 |
|
137 | -a2 | |
138 | +abc |
|
138 | +abc | |
|
139 | adding a | |||
|
140 | pulling from ../a | |||
|
141 | requesting all changes | |||
|
142 | adding changesets | |||
|
143 | adding manifests | |||
|
144 | adding file changes | |||
|
145 | added 1 changesets with 1 changes to 1 files |
General Comments 0
You need to be logged in to leave comments.
Login now