forked from google/thread-weaver
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathPlayerTest.java
More file actions
190 lines (161 loc) · 6.11 KB
/
PlayerTest.java
File metadata and controls
190 lines (161 loc) · 6.11 KB
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
/*
* Copyright 2009 Weaver authors
*
* This code is part of the Weaver tutorial and may be freely used.
*/
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import com.google.testing.threadtester.ClassInstrumentation;
import com.google.testing.threadtester.CodePosition;
import com.google.testing.threadtester.Instrumentation;
import com.google.testing.threadtester.InterleavedRunner;
import com.google.testing.threadtester.MainRunnableImpl;
import com.google.testing.threadtester.MethodRecorder;
import com.google.testing.threadtester.RunResult;
import com.google.testing.threadtester.SecondaryRunnableImpl;
import com.google.testing.threadtester.ThreadedTest;
import com.google.testing.threadtester.ThreadedTestRunner;
import junit.framework.TestCase;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* Tests {@link Player} using a {@link CodePosition}.
*
* NOTE: This test will fail. It was written to demonstrate a fault in the class
* under test.
*
* @author alasdair.mackintosh@gmail.com (Alasdair Mackintosh)
*/
public class PlayerTest extends TestCase {
private static final String ASSET = "song";
private static final int TOKEN = 1;
ThreadedTestRunner runner = new ThreadedTestRunner();
public void testThreadedTests() {
runner.runTests(getClass(), Player.class);
}
@ThreadedTest
public void runPlayAsset() throws Exception {
// Create a CodePosition at the point where we want to break the main
// thread.
CodePosition cp = getCodePositionForTest();
// Use an InterleavedRunner to interleave the main and second threads,
// breaking at the specified position.
RunResult result = InterleavedRunner.interleave(new PlayerMain(), new PlayerSecond(),
Arrays.asList(cp));
result.throwExceptionsIfAny();
}
/**
* Creates a CodePosition at the point where we want to break the main thread.
*/
CodePosition getCodePositionForTest() {
// Create a MethodRecorder for the Player
MethodRecorder<Player> recorder = new MethodRecorder<Player>(Player.class);
// Get hold of a dummy Player instance so that we can record method calls.
Player player = recorder.getControl();
// Get hold of a dummy AudioService instance so that we can record method calls.
AudioService service = recorder.createTarget(AudioService.class);
// Specify a position in "playAsset" after calling "service.play"
CodePosition cp = recorder.
in(player.playAsset(ASSET, null)).
afterCalling(service.cue(ASSET, null)).
position();
return cp;
}
/**
* Runs the main thread of execution.
*/
private class PlayerMain extends MainRunnableImpl<Player> {
Player player;
Controller controller = createMock(Controller.class);
AudioService service = createMock(AudioService.class);
@Override
public Class<Player> getClassUnderTest() {
return Player.class;
}
@Override
public Player getMainObject() {
return player;
}
/**
* This is invoked at the start, to set up the test.
*/
@Override
public void initialize() {
// Create mock and test objects, and set expectations
controller = createMock(Controller.class);
service = createMock(AudioService.class);
player = new Player(service);
expect(service.cue(ASSET, player)).andReturn(TOKEN);
controller.onFinished(TOKEN);
expectLastCall();
replay(controller, service);
}
/**
* This is invoked after initialization, to run the main body of the
* test. Becasue of the CodePosition that we have specified, we will break
* in the middle of playAsset(), and the second thread will be run.
*/
@Override
public void run() {
System.out.println("Running main thread");
player.playAsset(ASSET, controller);
System.out.println("Main thread finished");
}
/**
* This is invoked after both threads have run. We can use it to verify
* expectations.
*/
@Override
public void terminate() {
verify(controller, service);
}
}
/**
* Runs the second thread of execution.
*/
private class PlayerSecond extends
SecondaryRunnableImpl<Player, PlayerMain> {
Player player;
/**
* This is invoked at the start, after PlayerMain.initialize has been
* invoked. The Player instance created by PlayerMain is passed in.
*/
@Override
public void initialize(PlayerMain main) {
player = main.getMainObject();
}
/**
* This is invoked after the main thread has paused.
*/
@Override
public void run() {
System.out.printf("Running second thread\n");
player.onAudioPlayed(TOKEN);
System.out.printf("Second thread finished\n");
}
}
CodePosition getCodePositionForTestVersion2() throws Exception {
// An alternative way of declaring CodePositions using Method objects. Unlike
// MethodRecorder, this does not offer compile-time checking of the method
// names.
ClassInstrumentation instr = Instrumentation.getClassInstrumentation(Player.class);
Method playAsset = Player.class.getDeclaredMethod("playAsset", Controller.class, String.class);
Method play = AudioService.class.getDeclaredMethod("cue", String.class, AudioListener.class);
CodePosition cp = instr.afterCall(playAsset, play);
return cp;
}
CodePosition getCodePositionForTestVersion3() {
// Yet another way of declaring CodePositions using method names. This is
// simple, but may cause problems if the names are ambiguous. The position
// is defined at the start of the call to the first method named "cue", no
// matter what class it is invoked upon. Also, if the class under test has
// multiple overloaded methods named "playAsset", the test framework will
// throw an exception.
ClassInstrumentation instr = Instrumentation.getClassInstrumentation(Player.class);
CodePosition cp = instr.afterCall("playAsset", "cue");
return cp;
}
}