root/livinglogic.python.pycoco/src/pycoco/__init__.py @ 69:843a49ed896c

Revision 69:843a49ed896c, 7.9 KB (checked in by Walter Doerwald <walter@…>, 10 years ago)

Port template to UL4.

Line 
1# -*- coding: utf-8 -*-
2
3
4from __future__ import with_statement
5
6import sys, os, datetime, urllib, optparse, contextlib
7
8from ll import sisyphus, url, ul4c
9
10from pycoco import xmlns
11
12
13class File(object):
14    def __init__(self, name):
15        self.name = name
16        self.lines = [] # list of lines with tuples (# of executions, line)
17
18    def __repr__(self):
19        return "<File name=%r at 0x%x>" % (self.name, id(self))
20
21
22class Python_GenerateCodeCoverage(sisyphus.Job):
23    def __init__(self, outputdir):
24        sisyphus.Job.__init__(self, 2*60*60, name="Python_GenerateCodeCoverage", raiseerrors=1)
25        self.url = "http://svn.python.org/snapshots/python.tar.bz2"
26        self.tarfile = "python.tar.bz2"
27        self.outputdir = url.Dir(outputdir)
28
29        self.configurecmd = "./configure --enable-unicode=ucs4 --with-pydebug"
30
31        # Options to add to the various variables in the Makefile
32        self.opts = {
33            "OPT": "-coverage",
34            "CCSHARED": "-coverage",
35            "LDSHARED": "-coverage",
36            "BLDSHARED": "-coverage",
37            "LINKFORSHARED": "-coverage",
38        }
39
40        self.gcovcmd = os.environ.get("COV", "gcov")
41        self.makefile = "python/Makefile"
42
43        self.buildlog = [] # the output of configuring and building Python
44        self.testlog = [] # the output of running the test suite
45
46    def cmd(self, cmd):
47        self.logProgress(">>> %s" % cmd)
48        pipe = os.popen(cmd + " 2>&1", "rb", 1)
49        lines = []
50        for line in pipe:
51            self.logProgress("... " + line)
52            lines.append(line)
53        return lines
54
55    def files(self, base):
56        self.logProgress("### finding files")
57        allfiles = []
58        for (root, dirs, files) in os.walk(base):
59            for file in files:
60                if file.endswith(".py") or file.endswith(".c"):
61                    allfiles.append(File(os.path.join(root, file)))
62        self.logProgress("### found %d files" % len(allfiles))
63        return allfiles
64
65    def download(self):
66        self.logProgress("### downloading %s to %s" % (self.url, self.tarfile))
67        urllib.urlretrieve(self.url, self.tarfile)
68
69    def unpack(self):
70        self.logProgress("### unpacking %s" % self.tarfile)
71        self.cmd("tar xvjf %s" % self.tarfile)
72        lines = list(open("python/.timestamp", "r"))
73        self.timestamp = datetime.datetime.fromtimestamp(int(lines[0]))
74        self.revision = lines[2]
75
76    def configure(self):
77        self.logProgress("### configuring")
78        lines = self.cmd("cd python; %s" % self.configurecmd)
79        self.buildlog.extend(lines)
80        makelines = []
81        self.logProgress("### adding compiler options %s" % self.opts)
82        for line in open(self.makefile, "r"):
83            for (opt, value) in self.opts.iteritems():
84                if line.startswith(opt) and line[len(opt):].strip().startswith("="):
85                    line = line.rstrip("\n") + " " + value + "\n"
86                    break
87            makelines.append(line)
88        file = open(self.makefile, "w")
89        file.writelines(makelines)
90        file.close()
91
92    def make(self):
93        self.logProgress("### running make")
94        self.buildlog.extend(self.cmd("cd python && make"))
95
96    def test(self):
97        self.logProgress("### running test")
98        lines = self.cmd("cd python && ./python Lib/test/regrtest.py -T -N -uurlfetch,largefile,network,decimal")
99        self.testlog.extend(lines)
100
101    def cleanup(self):
102        self.logProgress("### cleaning up files from previous run")
103        self.cmd("rm -rf python")
104        self.cmd("rm %s" % self.tarfile)
105
106    def coveruncovered(self, file):
107        self.logProgress("### faking coverage info for uncovered file %r" % file.name)
108        file.lines = [(None, line.rstrip("\n")) for line in open(file.name, "r")]
109
110    def coverpy(self, file):
111        coverfilename = os.path.splitext(file.name)[0] + ".cover"
112        self.logProgress("### fetching coverage info for Python file %r from %r" % (file.name, coverfilename))
113        try:
114            f = open(coverfilename, "r")
115        except IOError, exc:
116            self.logError(exc)
117            self.coveruncovered(file)
118        else:
119            for line in f:
120                line = line.rstrip("\n")
121                prefix, line = line[:7], line[7:]
122                prefix = prefix.strip()
123                if prefix == "." or prefix == "":
124                    file.lines.append((-1, line))
125                elif prefix == ">"*len(prefix):
126                    file.lines.append((0, line))
127                else:
128                    file.lines.append((int(prefix.rstrip(":")), line))
129            f.close()
130
131    def coverc(self, file):
132        self.logProgress("### fetching coverage info for C file %r" % file.name)
133        dirname = os.path.dirname(file.name).split("/", 1)[-1]
134        basename = os.path.basename(file.name)
135        self.cmd("cd python && %s %s -o %s" % (self.gcovcmd, basename, dirname))
136        try:
137            f = open("python/%s.gcov" % basename, "r")
138        except IOError, exc:
139            self.logError(exc)
140            self.coveruncovered(file)
141        else:
142            for line in f:
143                line = line.rstrip("\n")
144                if line.count(":") < 2:
145                    continue
146                (count, lineno, line) = line.split(":", 2)
147                count = count.strip()
148                lineno = lineno.strip()
149                if lineno == "0": # just the header
150                    continue
151                if count == "-": # not executable
152                    file.lines.append((-1, line))
153                elif count == "#####": # not executed
154                    file.lines.append((0, line))
155                else:
156                    file.lines.append((int(count), line))
157            f.close()
158
159    def makehtml(self, files):
160        # Generate main page
161        self.logProgress("### generating index page")
162        template = ul4c.compile(xmlns.page(xmlns.filelist(), onload="files_prepare()").conv().string())
163        s = template.renders(
164            filename=None,
165            now=datetime.datetime.now(),
166            timestamp=self.timestamp,
167            revision=self.revision,
168            crumbs=[
169                dict(title="Core Development", href="http://www.python.org/dev/"),
170                dict(title="Code coverage", href=None),
171            ],
172            files=[
173                dict(
174                    name=file.name.split("/", 1)[-1],
175                    lines=len(file.lines),
176                    coverablelines=sum(line[0]>=0 for line in file.lines),
177                    coveredlines=sum(line[0]>0 for line in file.lines),
178                ) for file in files
179            ],
180        )
181        u = self.outputdir/"index.html"
182        with contextlib.closing(u.openwrite()) as f:
183            f.write(s.encode("utf-8"))
184
185        # Generate page for each source file
186        template = ul4c.compile(xmlns.page(xmlns.filecontent()).conv().string())
187        for (i, file) in enumerate(files):
188            filename = file.name.split("/", 1)[-1]
189            self.logProgress("### generating HTML %d/%d for %s" % (i+1, len(files), filename))
190            s = template.renders(
191                filename=filename,
192                crumbs=[
193                    dict(title="Core Development", href="http://www.python.org/dev/"),
194                    dict(title="Code coverage", href="/index.html"),
195                    dict(title=filename, href=None),
196                ],
197                lines=(
198                    dict(count=count, content=content.decode("latin-1").expandtabs(8)) for (count, content) in file.lines
199                ),
200            )
201            u = self.outputdir/(filename + ".html")
202            with contextlib.closing(u.openwrite()) as f:
203                f.write(s.encode("utf-8"))
204
205        # Copy CSS/JS files
206        for filename in ("coverage.css", "coverage_sortfilelist.css", "coverage.js"):
207            self.logProgress("### copying %s" % filename)
208            try:
209                import pkg_resources
210            except ImportError:
211                data = open(os.path.join(os.path.dirname(__file__), filename), "rb").read()
212            else:
213                data = pkg_resources.resource_string(__name__, filename)
214            with contextlib.closing((self.outputdir/filename).openwrite()) as f:
215                f.write(data)
216
217        self.logProgress("### creating buildlog.txt")
218        with contextlib.closing((self.outputdir/"buildlog.txt").openwrite()) as f:
219            f.write("".join(self.buildlog))
220
221        self.logProgress("### creating testlog.txt")
222        with contextlib.closing((self.outputdir/"testlog.txt").openwrite()) as f:
223            f.write("".join(self.testlog))
224
225    def execute(self):
226        self.cleanup()
227        self.download()
228        self.unpack()
229        self.configure()
230        files = self.files("python")
231        self.make()
232        self.test()
233        for file in files:
234            if file.name.endswith(".py"):
235                self.coverpy(file)
236            elif file.name.endswith(".c"):
237                self.coverc(file)
238        self.makehtml(files)
239        self.logLoop("done with project Python (%s; %d files)" % (self.timestamp.strftime("%Y-%m-%d %H:%M:%S"), len(files)))
240
241
242def main(args=None):
243    p = optparse.OptionParser(usage="usage: %prog [options]")
244    p.add_option("-o", "--outputdir", dest="outputdir", help="Directory where to put the HTML files", default="~/pycoco")
245    (options, args) = p.parse_args(args)
246    if len(args) != 0:
247        p.error("incorrect number of arguments")
248        return 1
249    sisyphus.execute(Python_GenerateCodeCoverage(options.outputdir))
250    return 0
251
252
253if __name__=="__main__":
254    sys.exit(main())
Note: See TracBrowser for help on using the browser.