Hide keyboard shortcuts

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

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

r""" 

Fixtures to help testing functionality 

 

Utilities which modify or replace code to help with doctesting functionality. 

Wrappers, proxies and mockups are typical examples of fixtures. 

 

AUTHORS: 

 

- Martin von Gagern (2014-12-15): AttributeAccessTracerProxy and trace_method 

- Martin von Gagern (2015-01-02): Factor out TracerHelper and reproducible_repr 

 

EXAMPLES: 

 

You can use :func:`trace_method` to see how a method 

communicates with its surroundings:: 

 

sage: class Foo(object): 

....: def f(self): 

....: self.y = self.g(self.x) 

....: def g(self, arg): 

....: return arg + 1 

....: 

sage: foo = Foo() 

sage: foo.x = 3 

sage: from sage.doctest.fixtures import trace_method 

sage: trace_method(foo, "f") 

sage: foo.f() 

enter f() 

read x = 3 

call g(3) -> 4 

write y = 4 

exit f -> None 

""" 

 

#***************************************************************************** 

# Copyright (C) 2014 Martin von Gagern <Martin.vGagern@gmx.net> 

# 

# This program is free software: you can redistribute it and/or modify 

# it under the terms of the GNU General Public License as published by 

# the Free Software Foundation, either version 2 of the License, or 

# (at your option) any later version. 

# http://www.gnu.org/licenses/ 

#***************************************************************************** 

 

from functools import wraps 

 

 

def reproducible_repr(val): 

r""" 

String representation of an object in a reproducible way. 

 

This tries to ensure that the returned string does not depend on 

factors outside the control of the doctest. 

One example is the order of elements in a hash-based structure. 

For most objects, this is simply the ``repr`` of the object. 

 

All types for which special handling had been implemented are 

covered by the examples below. If a doctest requires special 

handling for additional types, this function may be extended 

apropriately. It is an error if an argument to this function has 

a non-reproducible ``repr`` implementation and is not explicitely 

mentioned in an example case below. 

 

INPUT: 

 

- ``val`` -- an object to be represented 

 

OUTPUT: 

 

A string representation of that object, similar to what ``repr`` 

returns but for certain cases with more guarantees to ensure 

exactly the same result for semantically equivalent objects. 

 

EXAMPLES:: 

 

sage: from sage.doctest.fixtures import reproducible_repr 

sage: print(reproducible_repr(set(["a", "c", "b", "d"]))) 

set(['a', 'b', 'c', 'd']) 

sage: print(reproducible_repr(frozenset(["a", "c", "b", "d"]))) 

frozenset(['a', 'b', 'c', 'd']) 

sage: print(reproducible_repr([1, frozenset("cab"), set("bar"), 0])) 

[1, frozenset(['a', 'b', 'c']), set(['a', 'b', 'r']), 0] 

sage: print(reproducible_repr({3.0:"three","2":"two",1:"one"})) 

{'2': 'two', 1: 'one', 3.00000000000000: 'three'} 

sage: print(reproducible_repr("foo\nbar")) # demonstrate default case 

'foo\nbar' 

""" 

 

def sorted_pairs(iterable, pairs=False): 

# We don't know whether container data structures will have 

# homogeneous types, and if not, whether comparisons will work 

# in a sane way. For this reason, we sort by representation first. 

res = sorted((reproducible_repr(item), item) for item in iterable) 

if not pairs: 

res = [r for r, i in res] 

return res 

 

if isinstance(val, frozenset): 

itms = sorted_pairs(val) 

return "frozenset([{}])".format(", ".join(itms)) 

if isinstance(val, set): 

itms = sorted_pairs(val) 

return "set([{}])".format(", ".join(itms)) 

if isinstance(val, dict): 

keys = sorted_pairs(val.keys(), True) 

itms = ["{}: {}".format(r, reproducible_repr(val[k])) for r, k in keys] 

return ("{{{}}}".format(", ".join(itms))) 

if isinstance(val, list): 

itms = map(reproducible_repr, val) 

return ("[{}]".format(", ".join(itms))) 

return repr(val) 

 

 

class AttributeAccessTracerHelper(object): 

 

def __init__(self, delegate, prefix=" ", reads=True): 

r""" 

Helper to print proxied access to attributes. 

 

This class does the actual printing of access traces 

for objects proxied by :class:`AttributeAccessTracerProxy`. 

The fact that it's not a proxy at the same time 

helps avoiding complicated attribute access syntax. 

 

INPUT: 

 

- ``delegate`` -- the actual object to be proxied. 

 

- ``prefix`` -- (default: ``" "``) 

string to prepend to each printed output. 

 

- ``reads`` -- (default: ``True``) 

whether to trace read access as well. 

 

EXAMPLES:: 

 

sage: class Foo(object): 

....: def f(self, *args): 

....: return self.x*self.x 

....: 

sage: foo = Foo() 

sage: from sage.doctest.fixtures import AttributeAccessTracerHelper 

sage: pat = AttributeAccessTracerHelper(foo) 

sage: pat.set("x", 2) 

write x = 2 

sage: pat.get("x") 

read x = 2 

2 

sage: pat.get("f")(3) 

call f(3) -> 4 

4 

""" 

self.delegate = delegate 

self.prefix = prefix 

self.reads = reads 

 

def get(self, name): 

r""" 

Read an attribute from the wrapped delegate object. 

 

If that value is a method (i.e. a callable object which is not 

contained in the dictionary of the object itself but instead 

inherited from some class) then it is replaced by a wrapper 

function to report arguments and return value. 

Otherwise an attribute read access is reported. 

 

EXAMPLES:: 

 

sage: class Foo(object): 

....: def f(self, *args): 

....: return self.x*self.x 

....: 

sage: foo = Foo() 

sage: foo.x = 2 

sage: from sage.doctest.fixtures import AttributeAccessTracerHelper 

sage: pat = AttributeAccessTracerHelper(foo) 

sage: pat.get("x") 

read x = 2 

2 

sage: pat.get("f")(3) 

call f(3) -> 4 

4 

""" 

val = getattr(self.delegate, name) 

if callable(val) and name not in self.delegate.__dict__: 

@wraps(val) 

def wrapper(*args, **kwds): 

arglst = [reproducible_repr(arg) for arg in args] 

arglst.extend("{}={}".format(k, reproducible_repr(v)) 

for k, v in sorted(kwds.items())) 

res = val(*args, **kwds) 

print("{}call {}({}) -> {}" 

.format(self.prefix, name, ", ".join(arglst), 

reproducible_repr(res))) 

return res 

return wrapper 

else: 

if self.reads: 

print("{}read {} = {}".format(self.prefix, name, 

reproducible_repr(val))) 

return val 

 

def set(self, name, val): 

r""" 

Write an attribute to the wrapped delegate object. 

 

The name and new value are also reported in the output. 

 

EXAMPLES:: 

 

sage: class Foo(object): 

....: pass 

....: 

sage: foo = Foo() 

sage: from sage.doctest.fixtures import AttributeAccessTracerHelper 

sage: pat = AttributeAccessTracerHelper(foo) 

sage: pat.set("x", 2) 

write x = 2 

sage: foo.x 

2 

""" 

print("{}write {} = {}".format(self.prefix, name, 

reproducible_repr(val))) 

setattr(self.delegate, name, val) 

 

 

class AttributeAccessTracerProxy(object): 

 

def __init__(self, delegate, **kwds): 

r""" 

Proxy object which prints all attribute and method access to an object. 

 

The implementation is kept lean since all access to attributes of 

the proxy itself requires complicated syntax. 

For this reason, the actual handling of attribute access 

is delegated to a :class:`AttributeAccessTracerHelper`. 

 

INPUT: 

 

- ``delegate`` -- the actual object to be proxied. 

 

- ``prefix`` -- (default: ``" "``) 

string to prepend to each printed output. 

 

- ``reads`` -- (default: ``True``) 

whether to trace read access as well. 

 

EXAMPLES:: 

 

sage: class Foo(object): 

....: def f(self, *args): 

....: return self.x*self.x 

....: 

sage: foo = Foo() 

sage: from sage.doctest.fixtures import AttributeAccessTracerProxy 

sage: pat = AttributeAccessTracerProxy(foo) 

sage: pat.x = 2 

write x = 2 

sage: pat.x 

read x = 2 

2 

sage: pat.f(3) 

call f(3) -> 4 

4 

 

.. automethod:: __getattribute__ 

.. automethod:: __setattr__ 

""" 

helper = AttributeAccessTracerHelper(delegate, **kwds) 

object.__setattr__(self, "helper", helper) 

 

def __getattribute__(self, name): 

r""" 

Read an attribute from the wrapped delegate object. 

 

If that value is a method (i.e. a callable object which is not 

contained in the dictionary of the object itself but instead 

inherited from some class) then it is replaced by a wrapper 

function to report arguments and return value. 

Otherwise an attribute read access is reported. 

 

EXAMPLES:: 

 

sage: class Foo(object): 

....: def f(self, *args): 

....: return self.x*self.x 

....: 

sage: foo = Foo() 

sage: foo.x = 2 

sage: from sage.doctest.fixtures import AttributeAccessTracerProxy 

sage: pat = AttributeAccessTracerProxy(foo) 

sage: pat.x 

read x = 2 

2 

sage: pat.f(3) 

call f(3) -> 4 

4 

""" 

helper = object.__getattribute__(self, "helper") 

return helper.get(name) 

 

def __setattr__(self, name, val): 

r""" 

Write an attribute to the wrapped delegate object. 

 

The name and new value are also reported in the output. 

 

EXAMPLES:: 

 

sage: class Foo(object): 

....: pass 

....: 

sage: foo = Foo() 

sage: from sage.doctest.fixtures import AttributeAccessTracerProxy 

sage: pat = AttributeAccessTracerProxy(foo) 

sage: pat.x = 2 

write x = 2 

sage: foo.x 

2 

""" 

helper = object.__getattribute__(self, "helper") 

return helper.set(name, val) 

 

 

def trace_method(obj, meth, **kwds): 

r""" 

Trace the doings of a given method. 

It prints method entry with arguments, 

access to members and other methods during method execution 

as well as method exit with return value. 

 

INPUT: 

 

- ``obj`` -- the object containing the method. 

 

- ``meth`` -- the name of the method to be traced. 

 

- ``prefix`` -- (default: ``" "``) 

string to prepend to each printed output. 

 

- ``reads`` -- (default: ``True``) 

whether to trace read access as well. 

 

 

EXAMPLES:: 

 

sage: class Foo(object): 

....: def f(self, arg=None): 

....: self.y = self.g(self.x) 

....: if arg: return arg*arg 

....: def g(self, arg): 

....: return arg + 1 

....: 

sage: foo = Foo() 

sage: foo.x = 3 

sage: from sage.doctest.fixtures import trace_method 

sage: trace_method(foo, "f") 

sage: foo.f() 

enter f() 

read x = 3 

call g(3) -> 4 

write y = 4 

exit f -> None 

sage: foo.f(3) 

enter f(3) 

read x = 3 

call g(3) -> 4 

write y = 4 

exit f -> 9 

9 

""" 

f = getattr(obj, meth).__func__ 

t = AttributeAccessTracerProxy(obj, **kwds) 

@wraps(f) 

def g(*args, **kwds): 

arglst = [reproducible_repr(arg) for arg in args] 

arglst.extend("{}={}".format(k, reproducible_repr(v)) 

for k, v in sorted(kwds.items())) 

print("enter {}({})".format(meth, ", ".join(arglst))) 

res = f(t, *args, **kwds) 

print("exit {} -> {}".format(meth, reproducible_repr(res))) 

return res 

setattr(obj, meth, g)