source: trunk/misc/build_helpers/run-deprecations.py

Last change on this file was b856238, checked in by Alexandre Detiste <alexandre.detiste@…>, at 2024-02-15T15:53:34Z

remove old Python2 future statements

  • Property mode set to 100644
File size: 3.8 KB
Line 
1
2import sys, os, io, re
3from twisted.internet import reactor, protocol, task, defer
4from twisted.python.procutils import which
5from twisted.python import usage
6
7# run the command with python's deprecation warnings turned on, capturing
8# stderr. When done, scan stderr for warnings, write them to a separate
9# logfile (so the buildbot can see them), and return rc=1 if there were any.
10
11class Options(usage.Options):
12    optParameters = [
13        ["warnings", None, None, "file to write warnings into at end of test run"],
14        ["package", None, None, "Python package to which to restrict warning collection"]
15        ]
16
17    def parseArgs(self, command, *args):
18        self["command"] = command
19        self["args"] = list(args)
20
21    description = """Run as:
22python run-deprecations.py [--warnings=STDERRFILE] [--package=PYTHONPACKAGE ] COMMAND ARGS..
23"""
24
25class RunPP(protocol.ProcessProtocol):
26    def outReceived(self, data):
27        self.stdout.write(data)
28        sys.stdout.write(str(data, sys.stdout.encoding))
29    def errReceived(self, data):
30        self.stderr.write(data)
31        sys.stderr.write(str(data, sys.stdout.encoding))
32    def processEnded(self, reason):
33        signal = reason.value.signal
34        rc = reason.value.exitCode
35        self.d.callback((signal, rc))
36
37
38def make_matcher(options):
39    """
40    Make a function that matches a line with a relevant deprecation.
41
42    A deprecation warning line looks something like this::
43
44      somepath/foo/bar/baz.py:43: DeprecationWarning: Foo is deprecated, try bar instead.
45
46    Sadly there is no guarantee warnings begin at the beginning of a line
47    since they are written to output without coordination with whatever other
48    Python code is running in the process.
49
50    :return: A one-argument callable that accepts a string and returns
51        ``True`` if it contains an interesting warning and ``False``
52        otherwise.
53    """
54    pattern = r".*\.py[oc]?:\d+:" # (Pending)?DeprecationWarning: .*"
55    if options["package"]:
56        pattern = r".*/{}/".format(
57            re.escape(options["package"]),
58        ) + pattern
59    expression = re.compile(pattern)
60    def match(line):
61        return expression.match(line) is not None
62    return match
63
64
65@defer.inlineCallbacks
66def run_command(main):
67    config = Options()
68    config.parseOptions()
69
70    command = config["command"]
71    if "/" in command:
72        # don't search
73        exe = command
74    else:
75        executables = which(command)
76        if not executables:
77            raise ValueError("unable to find '%s' in PATH (%s)" %
78                             (command, os.environ.get("PATH")))
79        exe = executables[0]
80
81    pp = RunPP()
82    pp.d = defer.Deferred()
83    pp.stdout = io.BytesIO()
84    pp.stderr = io.BytesIO()
85    reactor.spawnProcess(pp, exe, [exe] + config["args"], env=None)
86    (signal, rc) = yield pp.d
87
88    match = make_matcher(config)
89
90    # maintain ordering, but ignore duplicates (for some reason, either the
91    # 'warnings' module or twisted.python.deprecate isn't quashing them)
92    already = set()
93    warnings = []
94    def add(line):
95        if line in already:
96            return
97        already.add(line)
98        warnings.append(line)
99
100    pp.stdout.seek(0)
101    for line in pp.stdout.readlines():
102        line = str(line, sys.stdout.encoding)
103        if match(line):
104            add(line) # includes newline
105
106    pp.stderr.seek(0)
107    for line in pp.stderr.readlines():
108        line = str(line, sys.stdout.encoding)
109        if match(line):
110            add(line)
111
112    if warnings:
113        if config["warnings"]:
114            with open(config["warnings"], "w") as f:
115                print("".join(warnings), file=f)
116        print("ERROR: %d deprecation warnings found" % len(warnings))
117        sys.exit(1)
118
119    print("no deprecation warnings")
120    if signal:
121        sys.exit(signal)
122    sys.exit(rc)
123
124
125task.react(run_command)
Note: See TracBrowser for help on using the repository browser.