263 lines
6.1 KiB
Python
263 lines
6.1 KiB
Python
from __future__ import print_function, unicode_literals, division, absolute_import
|
|
|
|
import Cython.Coverage
|
|
import os
|
|
import coverage # @UnresolvedImport
|
|
import cPickle
|
|
import ast
|
|
|
|
ROOT = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
|
def _find_c_source(base_path):
|
|
|
|
base_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "module", "gen.coverage", os.path.basename(base_path))
|
|
|
|
if os.path.exists(base_path + '.c'):
|
|
c_file = base_path + '.c'
|
|
elif os.path.exists(base_path + '.cpp'):
|
|
c_file = base_path + '.cpp'
|
|
else:
|
|
c_file = None
|
|
|
|
return c_file
|
|
|
|
Cython.Coverage._find_c_source = _find_c_source
|
|
|
|
|
|
class FixedCythonReporter(coverage.FileReporter):
|
|
|
|
def __init__(self, old, fn):
|
|
# fn = old.filename
|
|
#
|
|
# if not os.path.exists(fn):
|
|
# new_fn = os.path.join(os.path.dirname(fn), "module", os.path.basename(fn))
|
|
# if os.path.exists(new_fn):
|
|
# fn = new_fn
|
|
|
|
super(FixedCythonReporter, self).__init__(fn)
|
|
|
|
if old._code is None:
|
|
self._lines = set()
|
|
else:
|
|
self._lines = set(old._code)
|
|
|
|
def lines(self):
|
|
return self._lines
|
|
|
|
|
|
class CythonCoverage(Cython.Coverage.Plugin):
|
|
|
|
def _find_source_files(self, filename):
|
|
|
|
if not filename.endswith(".pyx"):
|
|
return None, None
|
|
|
|
pyx_base = os.path.basename(filename)
|
|
|
|
pyx_search = [
|
|
"module",
|
|
"module/gen.coverage",
|
|
"module/pysdlsound",
|
|
"renpy",
|
|
"renpy/display",
|
|
"renpy/gl",
|
|
"renpy/styledata",
|
|
"renpy/text",
|
|
"renpy/audio",
|
|
]
|
|
|
|
for i in pyx_search:
|
|
pyx_fn = os.path.join(ROOT, i, pyx_base)
|
|
|
|
if os.path.exists(pyx_fn):
|
|
break
|
|
else:
|
|
return None, None
|
|
|
|
c_base = os.path.basename(filename)[:-4] + ".c"
|
|
|
|
modules = [
|
|
"",
|
|
"renpy.",
|
|
"renpy.display.",
|
|
"renpy.gl.",
|
|
"renpy.styledata.",
|
|
"renpy.text.",
|
|
"renpy.audio.",
|
|
"pysdlsound."
|
|
]
|
|
|
|
for i in modules:
|
|
c_fn = os.path.join(ROOT, "module", "gen.coverage", i + c_base)
|
|
if os.path.exists(c_fn):
|
|
break
|
|
else:
|
|
print("Could not find C source for", filename)
|
|
return None, None
|
|
|
|
return c_fn, pyx_fn
|
|
|
|
def file_tracer(self, filename):
|
|
|
|
if not filename.endswith(".pyx"):
|
|
return None
|
|
|
|
_, pyx_fn = self._find_source_files(filename)
|
|
|
|
rv = super(CythonCoverage, self).file_tracer(pyx_fn)
|
|
|
|
def source_filename():
|
|
return pyx_fn
|
|
|
|
rv.source_filename = source_filename
|
|
|
|
return rv
|
|
|
|
def file_reporter(self, filename):
|
|
|
|
_, pyx_fn = self._find_source_files(filename)
|
|
|
|
r = super(CythonCoverage, self).file_reporter(filename)
|
|
return FixedCythonReporter(r, pyx_fn)
|
|
|
|
|
|
class RenpyTracer(coverage.FileTracer):
|
|
|
|
def __init__(self, filename):
|
|
self.filename = filename
|
|
|
|
def source_filename(self):
|
|
return self.filename
|
|
|
|
import renpy
|
|
renpy_import_all = False
|
|
|
|
|
|
class PycodeVisitor(ast.NodeVisitor):
|
|
|
|
def __init__(self, lines):
|
|
self.lines = lines
|
|
|
|
def statement(self, n):
|
|
self.lines.add(n.lineno)
|
|
self.generic_visit(n)
|
|
|
|
visit_FunctionDef = statement
|
|
visit_ClassDef = statement
|
|
visit_Return = statement
|
|
visit_Delete = statement
|
|
visit_Assign = statement
|
|
visit_AugAssign = statement
|
|
|
|
visit_Print = statement
|
|
|
|
visit_For = statement
|
|
visit_While = statement
|
|
visit_If = statement
|
|
visit_With = statement
|
|
|
|
visit_Raise = statement
|
|
visit_TryExcept = statement
|
|
visit_TryFinally = statement
|
|
|
|
visit_Assert = statement
|
|
|
|
visit_Import = statement
|
|
visit_ImportFrom = statement
|
|
|
|
# Global
|
|
visit_Exec = statement
|
|
|
|
# pass
|
|
visit_Break = statement
|
|
visit_Continue = statement
|
|
|
|
def visit_Expr(self, n):
|
|
if not isinstance(n.value, ast.Str):
|
|
self.lines.add(n.lineno)
|
|
|
|
self.generic_visit(n)
|
|
|
|
|
|
class RenpyReporter(coverage.FileReporter):
|
|
|
|
def __init__(self, filename):
|
|
super(RenpyReporter, self).__init__(filename)
|
|
|
|
global renpy_import_all
|
|
|
|
if not renpy_import_all:
|
|
import pygame_sdl2
|
|
pygame_sdl2.import_as_pygame()
|
|
|
|
renpy.import_all()
|
|
|
|
renpy_import_all = True
|
|
|
|
renpy.config.basedir = '/'
|
|
renpy.config.renpy_base = '/'
|
|
|
|
# renpy.game.contexts = [ renpy.execution.Context(False) ]
|
|
renpy.game.script = renpy.script.Script()
|
|
|
|
# stmts = renpy.parser.parse(filename)
|
|
|
|
with open(filename + "c", "rb") as f:
|
|
data = renpy.game.script.read_rpyc_data(f, 1)
|
|
|
|
try:
|
|
stmts = cPickle.loads(data)[1]
|
|
except:
|
|
stmts = [ ]
|
|
print(filename + "c", "failed")
|
|
|
|
all_stmts = [ ]
|
|
for i in stmts:
|
|
i.get_children(all_stmts.append)
|
|
|
|
self._lines = set()
|
|
for i in all_stmts:
|
|
if not isinstance(i, renpy.ast.Init):
|
|
self._lines.add(i.linenumber)
|
|
|
|
for i in renpy.game.script.all_pycode:
|
|
self.pycode_lines(i)
|
|
|
|
renpy.game.script.all_pycode = [ ]
|
|
|
|
def pycode_lines(self, pycode):
|
|
if pycode.mode != 'exec':
|
|
return
|
|
|
|
nodes = renpy.python.py_compile(pycode.source, pycode.mode, pycode.location[0], pycode.location[1], ast_node=True)
|
|
|
|
v = PycodeVisitor(self._lines)
|
|
for i in nodes:
|
|
v.visit(i)
|
|
|
|
def lines(self):
|
|
return self._lines
|
|
|
|
|
|
class RenpyCoverage(coverage.CoveragePlugin):
|
|
|
|
def file_tracer(self, filename):
|
|
|
|
for i in [ ".rpy", ".rpym" ]:
|
|
if filename.endswith(i):
|
|
break
|
|
else:
|
|
return None
|
|
|
|
return RenpyTracer(filename)
|
|
|
|
def file_reporter(self, filename):
|
|
|
|
return RenpyReporter(filename)
|
|
|
|
|
|
def coverage_init(reg, options):
|
|
reg.add_file_tracer(RenpyCoverage())
|
|
reg.add_file_tracer(CythonCoverage())
|