Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

""" recording warnings during test function execution. """ 

 

import inspect 

 

import _pytest._code 

import py 

import sys 

import warnings 

import pytest 

 

 

@pytest.yield_fixture 

def recwarn(request): 

    """Return a WarningsRecorder instance that provides these methods: 

 

    * ``pop(category=None)``: return last warning matching the category. 

    * ``clear()``: clear list of warnings 

 

    See http://docs.python.org/library/warnings.html for information 

    on warning categories. 

    """ 

    wrec = WarningsRecorder() 

    with wrec: 

        warnings.simplefilter('default') 

        yield wrec 

 

 

def pytest_namespace(): 

    return {'deprecated_call': deprecated_call, 

            'warns': warns} 

 

 

def deprecated_call(func=None, *args, **kwargs): 

    """ assert that calling ``func(*args, **kwargs)`` triggers a 

    ``DeprecationWarning`` or ``PendingDeprecationWarning``. 

 

    This function can be used as a context manager:: 

 

        >>> with deprecated_call(): 

        ...    myobject.deprecated_method() 

 

    Note: we cannot use WarningsRecorder here because it is still subject 

    to the mechanism that prevents warnings of the same type from being 

    triggered twice for the same module. See #1190. 

    """ 

    if not func: 

        return WarningsChecker(expected_warning=DeprecationWarning) 

 

    categories = [] 

 

    def warn_explicit(message, category, *args, **kwargs): 

        categories.append(category) 

        old_warn_explicit(message, category, *args, **kwargs) 

 

    def warn(message, category=None, *args, **kwargs): 

        if isinstance(message, Warning): 

            categories.append(message.__class__) 

        else: 

            categories.append(category) 

        old_warn(message, category, *args, **kwargs) 

 

    old_warn = warnings.warn 

    old_warn_explicit = warnings.warn_explicit 

    warnings.warn_explicit = warn_explicit 

    warnings.warn = warn 

    try: 

        ret = func(*args, **kwargs) 

    finally: 

        warnings.warn_explicit = old_warn_explicit 

        warnings.warn = old_warn 

    deprecation_categories = (DeprecationWarning, PendingDeprecationWarning) 

    if not any(issubclass(c, deprecation_categories) for c in categories): 

        __tracebackhide__ = True 

        raise AssertionError("%r did not produce DeprecationWarning" % (func,)) 

    return ret 

 

 

def warns(expected_warning, *args, **kwargs): 

    """Assert that code raises a particular class of warning. 

 

    Specifically, the input @expected_warning can be a warning class or 

    tuple of warning classes, and the code must return that warning 

    (if a single class) or one of those warnings (if a tuple). 

 

    This helper produces a list of ``warnings.WarningMessage`` objects, 

    one for each warning raised. 

 

    This function can be used as a context manager, or any of the other ways 

    ``pytest.raises`` can be used:: 

 

        >>> with warns(RuntimeWarning): 

        ...    warnings.warn("my warning", RuntimeWarning) 

    """ 

    wcheck = WarningsChecker(expected_warning) 

    if not args: 

        return wcheck 

    elif isinstance(args[0], str): 

        code, = args 

        assert isinstance(code, str) 

        frame = sys._getframe(1) 

        loc = frame.f_locals.copy() 

        loc.update(kwargs) 

 

        with wcheck: 

            code = _pytest._code.Source(code).compile() 

            py.builtin.exec_(code, frame.f_globals, loc) 

    else: 

        func = args[0] 

        with wcheck: 

            return func(*args[1:], **kwargs) 

 

 

class RecordedWarning(object): 

    def __init__(self, message, category, filename, lineno, file, line): 

        self.message = message 

        self.category = category 

        self.filename = filename 

        self.lineno = lineno 

        self.file = file 

        self.line = line 

 

 

class WarningsRecorder(object): 

    """A context manager to record raised warnings. 

 

    Adapted from `warnings.catch_warnings`. 

    """ 

 

    def __init__(self, module=None): 

        self._module = sys.modules['warnings'] if module is None else module 

        self._entered = False 

        self._list = [] 

 

    @property 

    def list(self): 

        """The list of recorded warnings.""" 

        return self._list 

 

    def __getitem__(self, i): 

        """Get a recorded warning by index.""" 

        return self._list[i] 

 

    def __iter__(self): 

        """Iterate through the recorded warnings.""" 

        return iter(self._list) 

 

    def __len__(self): 

        """The number of recorded warnings.""" 

        return len(self._list) 

 

    def pop(self, cls=Warning): 

        """Pop the first recorded warning, raise exception if not exists.""" 

        for i, w in enumerate(self._list): 

            if issubclass(w.category, cls): 

                return self._list.pop(i) 

        __tracebackhide__ = True 

        raise AssertionError("%r not found in warning list" % cls) 

 

    def clear(self): 

        """Clear the list of recorded warnings.""" 

        self._list[:] = [] 

 

    def __enter__(self): 

        if self._entered: 

            __tracebackhide__ = True 

            raise RuntimeError("Cannot enter %r twice" % self) 

        self._entered = True 

        self._filters = self._module.filters 

        self._module.filters = self._filters[:] 

        self._showwarning = self._module.showwarning 

 

        def showwarning(message, category, filename, lineno, 

                        file=None, line=None): 

            self._list.append(RecordedWarning( 

                message, category, filename, lineno, file, line)) 

 

            # still perform old showwarning functionality 

            self._showwarning( 

                message, category, filename, lineno, file=file, line=line) 

 

        self._module.showwarning = showwarning 

 

        # allow the same warning to be raised more than once 

 

        self._module.simplefilter('always') 

        return self 

 

    def __exit__(self, *exc_info): 

        if not self._entered: 

            __tracebackhide__ = True 

            raise RuntimeError("Cannot exit %r without entering first" % self) 

        self._module.filters = self._filters 

        self._module.showwarning = self._showwarning 

 

 

class WarningsChecker(WarningsRecorder): 

    def __init__(self, expected_warning=None, module=None): 

        super(WarningsChecker, self).__init__(module=module) 

 

        msg = ("exceptions must be old-style classes or " 

               "derived from Warning, not %s") 

        if isinstance(expected_warning, tuple): 

            for exc in expected_warning: 

                if not inspect.isclass(exc): 

                    raise TypeError(msg % type(exc)) 

        elif inspect.isclass(expected_warning): 

            expected_warning = (expected_warning,) 

        elif expected_warning is not None: 

            raise TypeError(msg % type(expected_warning)) 

 

        self.expected_warning = expected_warning 

 

    def __exit__(self, *exc_info): 

        super(WarningsChecker, self).__exit__(*exc_info) 

 

        # only check if we're not currently handling an exception 

        if all(a is None for a in exc_info): 

            if self.expected_warning is not None: 

                if not any(r.category in self.expected_warning for r in self): 

                    __tracebackhide__ = True 

                    pytest.fail("DID NOT WARN")