-
Notifications
You must be signed in to change notification settings - Fork 7
/
oldmw.txt
127 lines (96 loc) · 4.25 KB
/
oldmw.txt
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
Label `OpeningRepositoryBrowser' multiply defined
Label `SaveUncommited' multiply defined
Label `scr:InitiGame' multiply defined
Label `scr:colorClassInit' multiply defined
Label `sec:varieties' multiply defined
Reference `fig:fileOut' on page 70 undefined on input line 1515
Reference `fig:finder' on page 28 undefined on input line 639
Reference `fig:worldMenu' on page 15 undefined on input line 391
Reference `fig:worldMenu' on page 16 undefined on input line 401
!!!!!!!!!!!!!!!!!!!!!!!!NEXT CHAPTER NEEDS A REVIEW!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!Using method wrappers to perform test coverage
Method wrappers are a well-known technique for intercepting messages. In the
original implementation(http://www.squeaksource.com/MethodWrappers.html), a
method wrapper is an instance of a subclass of ==CompiledMethod==. When
installed, a method wrapper can perform special actions before or after invoking
the original method. When uninstalled, the original method is returned to its
rightful position in the method dictionary.
In Pharo, method wrappers can be implemented more easily by implementing
==run:with:in:== instead of by subclassing ==CompiledMethod==. In fact, there
exists a lightweight implementation of objects as method
wrappers (http://www.squeaksource.com/ObjectsAsMethodsWrap.html), but it is not
part of standard Pharo at the time of this writing.
Nevertheless, the Pharo Test Runner uses precisely this technique to evaluate
test coverage. Let's have a quick look at how it works.
The entry point for test coverage is the method ==TestRunner>>runCoverage==:
[[[
TestRunner >> runCoverage
| packages methods |
... "identify methods to check for coverage"
self collectCoverageFor: methods
]]]
The method ==TestRunner>>collectCoverageFor:== clearly illustrates the coverage
checking algorithm:
[[[
TestRunner >> collectCoverageFor: methods
| suite link notExecuted |
suite := self
resetResult;
suiteForAllSelected.
link := MetaLink new
selector: #tagExecuted;
metaObject: #node.
[ methods do: [ :meth | meth ast link: link].
[ self runSuite: suite ] ensure: [ link uninstall ] ] valueUnpreemptively.
notExecuted := methods reject: [:each | each ast hasBeenExecuted].
notExecuted isEmpty
ifTrue: [ UIManager default inform: 'Congratulations. Your tests cover all code under analysis.' ]
ifFalse: ...
]]]
!!!!!!!!!!!!!!CODE NOT UPDATED FROM HERE TO NEXT EXCALAMATION!!!!!!!!!!!!!!!!!!!
A wrapper is created for each method to be checked, and each wrapper is
installed. The tests are run, and all wrappers are uninstalled. Finally the user
obtains feedback concerning the methods that have not been covered.
How does the wrapper itself work? The ==TestCoverage== wrapper has three
instance variables, ==hasRun==, ==reference== and ==method==. They are
initialized as follows:
[[[
TestCoverage class >> on: aMethodReference
^ self new initializeOn: aMethodReference
TestCoverage >> initializeOn: aMethodReference
hasRun := false.
reference := aMethodReference.
method := reference compiledMethod
]]]
The install and uninstall methods simply update the method dictionary in the
obvious way:
[[[
TestCoverage >> install
reference actualClass methodDict
at: reference selector
put: self
TestCoverage >> uninstall
reference actualClass methodDict
at: reference selector
put: method
]]]
The ==run:with:in:== method simply updates the ==hasRun== variable,
uninstalls the wrapper (since coverage has been verified), and resends the
message to the original method.
[[[
run: aSelector with: anArray in: aReceiver
self mark; uninstall.
^ aReceiver withArgs: anArray executeMethod: method
mark
hasRun := true
]]]
Take a look at ==ProtoObject>>withArgs:executeMethod:== to see how a method
displaced from its method dictionary can be invoked.
That's all there is to it!
Method wrappers can be used to perform any kind of suitable behaviour before or
after the normal operation of a method. Typical applications are
instrumentation (collecting statistics about the calling patterns of methods),
checking optional pre- and post-conditions, and memoization (optionally cacheing
computed values of methods).
!!!!!!!!!!!!!!!!!!!!!CORRECTION IN THE SUMMARY AS WELL!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!