1 """Code unit (module) handling for Coverage.""" |
1 """Code unit (module) handling for Coverage.""" |
2 |
2 |
3 import glob, os |
3 import glob, os |
4 |
4 |
5 from .backward import string_class, StringIO |
5 from .backward import open_source, string_class, StringIO |
6 from .misc import CoverageException |
6 from .misc import CoverageException |
7 |
7 |
8 |
8 |
9 def code_unit_factory(morfs, file_locator, omit_prefixes=None): |
9 def code_unit_factory(morfs, file_locator): |
10 """Construct a list of CodeUnits from polymorphic inputs. |
10 """Construct a list of CodeUnits from polymorphic inputs. |
11 |
11 |
12 `morfs` is a module or a filename, or a list of same. |
12 `morfs` is a module or a filename, or a list of same. |
|
13 |
13 `file_locator` is a FileLocator that can help resolve filenames. |
14 `file_locator` is a FileLocator that can help resolve filenames. |
14 `omit_prefixes` is a list of prefixes. CodeUnits that match those prefixes |
|
15 will be omitted from the list. |
|
16 |
15 |
17 Returns a list of CodeUnit objects. |
16 Returns a list of CodeUnit objects. |
18 |
17 |
19 """ |
18 """ |
20 |
|
21 # Be sure we have a list. |
19 # Be sure we have a list. |
22 if not isinstance(morfs, (list, tuple)): |
20 if not isinstance(morfs, (list, tuple)): |
23 morfs = [morfs] |
21 morfs = [morfs] |
24 |
22 |
25 # On Windows, the shell doesn't expand wildcards. Do it here. |
23 # On Windows, the shell doesn't expand wildcards. Do it here. |
31 globbed.append(morf) |
29 globbed.append(morf) |
32 morfs = globbed |
30 morfs = globbed |
33 |
31 |
34 code_units = [CodeUnit(morf, file_locator) for morf in morfs] |
32 code_units = [CodeUnit(morf, file_locator) for morf in morfs] |
35 |
33 |
36 if omit_prefixes: |
|
37 assert not isinstance(omit_prefixes, string_class) # common mistake |
|
38 prefixes = [file_locator.abs_file(p) for p in omit_prefixes] |
|
39 filtered = [] |
|
40 for cu in code_units: |
|
41 for prefix in prefixes: |
|
42 if cu.filename.startswith(prefix): |
|
43 break |
|
44 else: |
|
45 filtered.append(cu) |
|
46 |
|
47 code_units = filtered |
|
48 |
|
49 return code_units |
34 return code_units |
50 |
35 |
51 |
36 |
52 class CodeUnit(object): |
37 class CodeUnit(object): |
53 """Code unit: a filename or module. |
38 """Code unit: a filename or module. |
57 `name` is a human-readable name for this code unit. |
42 `name` is a human-readable name for this code unit. |
58 `filename` is the os path from which we can read the source. |
43 `filename` is the os path from which we can read the source. |
59 `relative` is a boolean. |
44 `relative` is a boolean. |
60 |
45 |
61 """ |
46 """ |
62 |
|
63 def __init__(self, morf, file_locator): |
47 def __init__(self, morf, file_locator): |
64 self.file_locator = file_locator |
48 self.file_locator = file_locator |
65 |
49 |
66 if hasattr(morf, '__file__'): |
50 if hasattr(morf, '__file__'): |
67 f = morf.__file__ |
51 f = morf.__file__ |
68 else: |
52 else: |
69 f = morf |
53 f = morf |
70 # .pyc files should always refer to a .py instead. |
54 # .pyc files should always refer to a .py instead. |
71 if f.endswith('.pyc'): |
55 if f.endswith('.pyc') or f.endswith('.pyo'): |
72 f = f[:-1] |
56 f = f[:-1] |
|
57 elif f.endswith('$py.class'): # Jython |
|
58 f = f[:-9] + ".py" |
73 self.filename = self.file_locator.canonical_filename(f) |
59 self.filename = self.file_locator.canonical_filename(f) |
74 |
60 |
75 if hasattr(morf, '__name__'): |
61 if hasattr(morf, '__name__'): |
76 n = modname = morf.__name__ |
62 n = modname = morf.__name__ |
77 self.relative = True |
63 self.relative = True |
93 # Annoying comparison operators. Py3k wants __lt__ etc, and Py2k needs all |
79 # Annoying comparison operators. Py3k wants __lt__ etc, and Py2k needs all |
94 # of them defined. |
80 # of them defined. |
95 |
81 |
96 def __lt__(self, other): |
82 def __lt__(self, other): |
97 return self.name < other.name |
83 return self.name < other.name |
98 |
|
99 def __le__(self, other): |
84 def __le__(self, other): |
100 return self.name <= other.name |
85 return self.name <= other.name |
101 |
|
102 def __eq__(self, other): |
86 def __eq__(self, other): |
103 return self.name == other.name |
87 return self.name == other.name |
104 |
|
105 def __ne__(self, other): |
88 def __ne__(self, other): |
106 return self.name != other.name |
89 return self.name != other.name |
107 |
|
108 def __gt__(self, other): |
90 def __gt__(self, other): |
109 return self.name > other.name |
91 return self.name > other.name |
110 |
|
111 def __ge__(self, other): |
92 def __ge__(self, other): |
112 return self.name >= other.name |
93 return self.name >= other.name |
113 |
94 |
114 def flat_rootname(self): |
95 def flat_rootname(self): |
115 """A base for a flat filename to correspond to this code unit. |
96 """A base for a flat filename to correspond to this code unit. |
122 |
103 |
123 """ |
104 """ |
124 if self.modname: |
105 if self.modname: |
125 return self.modname.replace('.', '_') |
106 return self.modname.replace('.', '_') |
126 else: |
107 else: |
127 root = os.path.splitdrive(os.path.splitext(self.name)[0])[1] |
108 root = os.path.splitdrive(self.name)[1] |
128 return root.replace('\\', '_').replace('/', '_') |
109 return root.replace('\\', '_').replace('/', '_').replace('.', '_') |
129 |
110 |
130 def source_file(self): |
111 def source_file(self): |
131 """Return an open file for reading the source of the code unit.""" |
112 """Return an open file for reading the source of the code unit.""" |
132 if os.path.exists(self.filename): |
113 if os.path.exists(self.filename): |
133 # A regular text file: open it. |
114 # A regular text file: open it. |
134 return open(self.filename) |
115 return open_source(self.filename) |
135 |
116 |
136 # Maybe it's in a zip file? |
117 # Maybe it's in a zip file? |
137 source = self.file_locator.get_zip_data(self.filename) |
118 source = self.file_locator.get_zip_data(self.filename) |
138 if source is not None: |
119 if source is not None: |
139 return StringIO(source) |
120 return StringIO(source) |
140 |
121 |
141 # Couldn't find source. |
122 # Couldn't find source. |
142 raise CoverageException( |
123 raise CoverageException( |
143 "No source for code %r." % self.filename |
124 "No source for code '%s'." % self.filename |
144 ) |
125 ) |
145 |
126 |
146 # |
127 def should_be_python(self): |
147 # eflag: FileType = Python2 |
128 """Does it seem like this file should contain Python? |
|
129 |
|
130 This is used to decide if a file reported as part of the exection of |
|
131 a program was really likely to have contained Python in the first |
|
132 place. |
|
133 |
|
134 """ |
|
135 # Get the file extension. |
|
136 _, ext = os.path.splitext(self.filename) |
|
137 |
|
138 # Anything named *.py* should be Python. |
|
139 if ext.startswith('.py'): |
|
140 return True |
|
141 # A file with no extension should be Python. |
|
142 if not ext: |
|
143 return True |
|
144 # Everything else is probably not Python. |
|
145 return False |