File: mach_commands.py

package info (click to toggle)
mozjs78 78.15.0-7
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 739,892 kB
  • sloc: javascript: 1,344,214; cpp: 1,215,708; python: 526,544; ansic: 433,835; xml: 118,736; sh: 26,176; asm: 16,664; makefile: 11,537; yacc: 4,486; perl: 2,564; ada: 1,681; lex: 1,414; pascal: 1,139; cs: 879; exp: 499; java: 164; ruby: 68; sql: 45; csh: 35; sed: 18; lisp: 2
file content (113 lines) | stat: -rw-r--r-- 3,676 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

from __future__ import absolute_import, print_function, unicode_literals

import os
import sys
import json

from mozbuild.base import (
    MachCommandBase,
)

from mach.decorators import (
    CommandArgument,
    CommandProvider,
    Command,
)

import mozpack.path as mozpath
from mozpack.files import FileFinder

from mozlog import commandline

here = os.path.abspath(os.path.dirname(__file__))


@CommandProvider
class MachCommands(MachCommandBase):
    @Command('python-safety', category='testing',
             description='Run python requirements safety checks')
    @CommandArgument('--python',
                     default='3.5',
                     help='Version of Python for Pipenv to use. When given a '
                          'Python version, Pipenv will automatically scan your '
                          'system for a Python that matches that given version.')
    def python_safety(self, python=None, **kwargs):
        self.logger = commandline.setup_logging(
            "python-safety", {"raw": sys.stdout})

        self.activate_pipenv(pipfile=os.path.join(here, 'Pipfile'), python=python, populate=True)

        pattern = '**/*requirements*.txt'
        path = mozpath.normsep(os.path.dirname(os.path.dirname(here)))
        finder = FileFinder(path)
        files = [os.path.join(path, p) for p, _ in finder.find(pattern)]

        return_code = 0

        self.logger.suite_start(tests=files)
        for filepath in files:
            self._run_python_safety(filepath)

        self.logger.suite_end()
        return return_code

    def _run_python_safety(self, test_path):
        from mozprocess import ProcessHandler

        output = []
        self.logger.test_start(test_path)

        def _line_handler(line):
            output.append(line.decode('UTF-8'))

        cmd = ['safety', 'check', '--cache', '--json', '-r', test_path]
        env = os.environ.copy()
        env['PYTHONDONTWRITEBYTECODE'] = '1'

        proc = ProcessHandler(
            cmd, env=env, processOutputLine=_line_handler, storeOutput=False)
        proc.run()

        return_code = proc.wait()

        """
        Example output for an error in json.
        [
            "pycrypto",
            "<=2.6.1",
            "2.6",
            "Heap-based buffer overflow in the ALGnew...",
            "35015"
        ]
        """
        # Warnings are currently interleaved with json, see
        # https://github.com/pyupio/safety/issues/133
        for warning in output:
            if warning.startswith('Warning'):
                self.logger.warning(warning)
        output = [line for line in output if not line.startswith('Warning')]
        if output:
            output_json = json.loads("".join(output))
            affected = set()
            for entry in output_json:
                affected.add(entry[0])
                message = "{0} installed:{2} affected:{1} description:{3}\n".format(
                    *entry)
                self.logger.test_status(test=test_path,
                                        subtest=entry[0],
                                        status='FAIL',
                                        message=message
                                        )

        if return_code != 0:
            status = 'FAIL'
        else:
            status = 'PASS'
        self.logger.test_end(test_path, status=status,
                             expected='PASS', message=" ".join(affected))

        return return_code