-
-
Save peculiaruc/2e63953249c1af8d731246f737fec588 to your computer and use it in GitHub Desktop.
package org.oppia.android.domain.audio | |
2 | |
3 import android.app.Application | |
4 import android.content.Context | |
5 import android.net.Uri | |
6 import androidx.lifecycle.Observer | |
7 import androidx.test.core.app.ApplicationProvider | |
8 import androidx.test.ext.junit.runners.AndroidJUnit4 | |
9 import com.google.common.truth.Truth.assertThat | |
10 import dagger.BindsInstance | |
11 import dagger.Component | |
12 import dagger.Module | |
13 import dagger.Provides | |
14 import org.junit.Before | |
15 import org.junit.Rule | |
16 import org.junit.Test | |
17 import org.junit.runner.RunWith | |
18 import org.mockito.ArgumentCaptor | |
19 import org.mockito.Captor | |
20 import org.mockito.Mock | |
21 import org.mockito.Mockito.atLeastOnce | |
22 import org.mockito.Mockito.verify | |
23 import org.mockito.junit.MockitoJUnit | |
24 import org.mockito.junit.MockitoRule | |
25 import org.oppia.android.domain.audio.AudioPlayerController.PlayProgress | |
26 import org.oppia.android.domain.audio.AudioPlayerController.PlayStatus | |
27 import org.oppia.android.domain.oppialogger.LogStorageModule | |
28 import org.oppia.android.testing.FakeExceptionLogger | |
29 import org.oppia.android.testing.TestCoroutineDispatchers | |
30 import org.oppia.android.testing.TestDispatcherModule | |
31 import org.oppia.android.testing.TestLogReportingModule | |
32 import org.oppia.android.util.caching.CacheAssetsLocally | |
33 import org.oppia.android.util.data.AsyncResult | |
34 import org.oppia.android.util.logging.EnableConsoleLog | |
35 import org.oppia.android.util.logging.EnableFileLog | |
36 import org.oppia.android.util.logging.GlobalLogLevel | |
37 import org.oppia.android.util.logging.LogLevel | |
38 import org.robolectric.Shadows | |
39 import org.robolectric.annotation.Config | |
40 import org.robolectric.annotation.LooperMode | |
41 import org.robolectric.shadows.ShadowMediaPlayer | |
42 import org.robolectric.shadows.util.DataSource | |
43 import java.io.IOException | |
44 import javax.inject.Inject | |
45 import javax.inject.Singleton | |
46 import kotlin.reflect.KClass | |
47 import kotlin.reflect.full.cast | |
48 import kotlin.test.fail | |
49 | |
50 /** Tests for [AudioPlayerControllerTest]. */ | |
51 @RunWith(AndroidJUnit4::class) | |
52 @LooperMode(LooperMode.Mode.PAUSED) | |
53 @Config(manifest = Config.NONE) | |
54 class AudioPlayerControllerTest { | |
55 | |
56 @Rule | |
57 @JvmField | |
58 val mockitoRule: MockitoRule = MockitoJUnit.rule() | |
59 | |
60 @Mock | |
61 lateinit var mockAudioPlayerObserver: Observer<AsyncResult<PlayProgress>> | |
62 | |
63 @Captor | |
64 lateinit var audioPlayerResultCaptor: | |
65 ArgumentCaptor<AsyncResult<PlayProgress>> | |
66 | |
67 @Inject | |
68 lateinit var context: Context | |
69 | |
70 @Inject | |
71 lateinit var audioPlayerController: AudioPlayerController | |
72 | |
73 @Inject | |
74 lateinit var fakeExceptionLogger: FakeExceptionLogger | |
75 | |
76 @Inject | |
77 lateinit var testCoroutineDispatchers: TestCoroutineDispatchers | |
78 | |
79 private lateinit var shadowMediaPlayer: ShadowMediaPlayer | |
80 | |
81 private val TEST_URL = "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3" | |
82 private val TEST_URL2 = "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3" | |
83 private val TEST_FAIL_URL = "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2" | |
84 | |
85 @Before | |
86 fun setUp() { | |
87 setUpTestApplicationComponent() | |
88 addMediaInfo() | |
89 shadowMediaPlayer = Shadows.shadowOf(audioPlayerController.getTestMediaPlayer()) | |
90 shadowMediaPlayer.dataSource = DataSource.toDataSource(context, Uri.parse(TEST_URL)) | |
91 } | |
92 | |
93 @Test | |
94 fun testController_initializePlayer_invokePrepared_reportsSuccessfulInit() { | |
95 audioPlayerController.initializeMediaPlayer() | |
96 audioPlayerController.changeDataSource(TEST_URL) | |
97 | |
98 shadowMediaPlayer.invokePreparedListener() | |
99 | |
100 assertThat(shadowMediaPlayer.isPrepared).isTrue() | |
101 assertThat(shadowMediaPlayer.isReallyPlaying).isFalse() | |
102 } | |
103 | |
104 @Test | |
105 fun testController_preparePlayer_invokePlay_checkIsPlaying() { | |
106 arrangeMediaPlayer() | |
107 | |
108 audioPlayerController.play() | |
109 | |
110 assertThat(shadowMediaPlayer.isReallyPlaying).isTrue() | |
111 } | |
112 | |
113 @Test | |
114 fun testController_preparePlayer_invokePause_checkNotIsPlaying() { | |
115 arrangeMediaPlayer() | |
116 | |
117 audioPlayerController.pause() | |
118 | |
119 assertThat(shadowMediaPlayer.isReallyPlaying).isFalse() | |
120 } | |
121 | |
122 @Test | |
123 fun testController_preparePlayer_invokeSeekTo_hasCorrectProgress() { | |
124 arrangeMediaPlayer() | |
125 | |
126 audioPlayerController.seekTo(500) | |
127 testCoroutineDispatchers.runCurrent() | |
128 | |
129 assertThat(shadowMediaPlayer.currentPositionRaw).isEqualTo(500) | |
130 } | |
131 | |
132 @Test | |
133 fun testController_preparePlayer_releaseMediaPlayer_hasEndState() { | |
134 arrangeMediaPlayer() | |
135 | |
136 audioPlayerController.releaseMediaPlayer() | |
137 | |
138 assertThat(shadowMediaPlayer.state).isEqualTo(ShadowMediaPlayer.State.END) | |
139 } | |
140 | |
141 @Test | |
142 fun testController_preparePlayer_invokePrepare_capturesPreparedState() { | |
143 arrangeMediaPlayer() | |
144 | |
145 verify(mockAudioPlayerObserver, atLeastOnce()).onChanged(audioPlayerResultCaptor.capture()) | |
146 assertThat(audioPlayerResultCaptor.value.isSuccess()).isTrue() | |
147 assertThat(audioPlayerResultCaptor.value.getOrThrow().type).isEqualTo(PlayStatus.PREPARED) | |
148 } | |
149 | |
150 @Test | |
151 fun testController_releasePlayer_initializePlayer_capturesPendingState() { | |
152 audioPlayerController.initializeMediaPlayer() | |
153 | |
154 audioPlayerController.releaseMediaPlayer() | |
155 audioPlayerController.initializeMediaPlayer().observeForever(mockAudioPlayerObserver) | |
156 audioPlayerController.changeDataSource(TEST_URL) | |
157 | |
158 verify(mockAudioPlayerObserver, atLeastOnce()).onChanged(audioPlayerResultCaptor.capture()) | |
159 assertThat(audioPlayerResultCaptor.value.isPending()).isTrue() | |
160 } | |
161 | |
162 @Test | |
163 fun tesObserver_preparePlayer_invokeCompletion_capturesCompletedState() { | |
164 arrangeMediaPlayer() | |
165 | |
166 shadowMediaPlayer.invokeCompletionListener() | |
167 | |
168 verify(mockAudioPlayerObserver, atLeastOnce()).onChanged(audioPlayerResultCaptor.capture()) | |
169 assertThat(audioPlayerResultCaptor.value.isSuccess()).isTrue() | |
170 assertThat(audioPlayerResultCaptor.value.getOrThrow().type).isEqualTo(PlayStatus.COMPLETED) | |
171 assertThat(audioPlayerResultCaptor.value.getOrThrow().position).isEqualTo(0) | |
172 } | |
173 | |
174 @Test | |
175 fun testObserver_preparePlayer_invokeChangeDataSource_capturesPendingState() { | |
176 arrangeMediaPlayer() | |
177 | |
178 audioPlayerController.changeDataSource(TEST_URL2) | |
179 | |
180 verify(mockAudioPlayerObserver, atLeastOnce()).onChanged(audioPlayerResultCaptor.capture()) | |
181 assertThat(audioPlayerResultCaptor.value.isPending()).isTrue() | |
182 } | |
183 | |
184 @Test | |
185 fun testObserver_preparePlayer_invokeChangeDataSourceAfterPlay_capturesPendingState() { | |
186 arrangeMediaPlayer() | |
187 | |
188 audioPlayerController.play() | |
189 audioPlayerController.changeDataSource(TEST_URL2) | |
190 | |
191 verify(mockAudioPlayerObserver, atLeastOnce()).onChanged(audioPlayerResultCaptor.capture()) | |
192 assertThat(audioPlayerResultCaptor.value.isPending()).isTrue() | |
193 } | |
194 | |
195 @Test | |
196 fun testObserver_preparePlayer_invokePlay_capturesPlayingState() { | |
197 arrangeMediaPlayer() | |
198 | |
199 audioPlayerController.play() | |
200 testCoroutineDispatchers.runCurrent() | |
201 | |
202 verify(mockAudioPlayerObserver, atLeastOnce()).onChanged(audioPlayerResultCaptor.capture()) | |
203 assertThat(audioPlayerResultCaptor.value.isSuccess()).isTrue() | |
204 assertThat(audioPlayerResultCaptor.value.getOrThrow().type).isEqualTo(PlayStatus.PLAYING) | |
205 } | |
206 | |
207 @Test | |
208 fun testObserver_preparePlayer_invokePlayAndAdvance_capturesManyPlayingStates() { | |
209 arrangeMediaPlayer() | |
210 | |
211 // Wait for 1 second for the player to enter a playing state, then forcibly trigger completion. | |
212 audioPlayerController.play() | |
213 testCoroutineDispatchers.advanceTimeBy(1000) | |
214 shadowMediaPlayer.invokeCompletionListener() | |
215 | |
216 verify(mockAudioPlayerObserver, atLeastOnce()).onChanged(audioPlayerResultCaptor.capture()) | |
217 val results = audioPlayerResultCaptor.allValues | |
218 val pendingIndex = results.indexOfLast { it.isPending() } | |
219 val preparedIndex = results.indexOfLast { it.hasStatus(PlayStatus.PREPARED) } | |
220 val playingIndex = results.indexOfLast { it.hasStatus(PlayStatus.PLAYING) } | |
221 val completedIndex = results.indexOfLast { it.hasStatus(PlayStatus.COMPLETED) } | |
222 // Verify that there are at least 4 statuses: pending, prepared, playing, and completed, and in | |
223 // that order. | |
224 assertThat(results.size).isGreaterThan(4) | |
225 assertThat(pendingIndex).isLessThan(preparedIndex) | |
226 assertThat(preparedIndex).isLessThan(playingIndex) | |
227 assertThat(playingIndex).isLessThan(completedIndex) | |
228 } | |
229 | |
230 @Test | |
231 fun testObserver_preparePlayer_invokePause_capturesPausedState() { | |
232 arrangeMediaPlayer() | |
233 | |
234 audioPlayerController.play() | |
235 audioPlayerController.pause() | |
236 | |
237 verify(mockAudioPlayerObserver, atLeastOnce()).onChanged(audioPlayerResultCaptor.capture()) | |
238 assertThat(audioPlayerResultCaptor.value.isSuccess()).isTrue() | |
239 assertThat(audioPlayerResultCaptor.value.getOrThrow().type).isEqualTo(PlayStatus.PAUSED) | |
240 } | |
241 | |
242 @Test | |
243 fun testObserver_preparePlayer_invokePrepared_capturesCorrectPosition() { | |
244 arrangeMediaPlayer() | |
245 | |
246 verify(mockAudioPlayerObserver, atLeastOnce()).onChanged(audioPlayerResultCaptor.capture()) | |
247 assertThat(audioPlayerResultCaptor.value.getOrThrow().position).isEqualTo(0) | |
248 } | |
249 | |
250 @Test | |
251 fun testObserver_preparePlayer_invokeSeekTo_capturesCorrectPosition() { | |
252 arrangeMediaPlayer() | |
253 | |
254 audioPlayerController.seekTo(500) | |
255 testCoroutineDispatchers.runCurrent() | |
256 audioPlayerController.play() | |
257 testCoroutineDispatchers.runCurrent() | |
258 | |
259 verify(mockAudioPlayerObserver, atLeastOnce()).onChanged(audioPlayerResultCaptor.capture()) | |
260 assertThat(audioPlayerResultCaptor.value.getOrThrow().position).isEqualTo(500) | |
261 } | |
262 | |
263 @Test | |
264 fun testObserver_preparePlayer_invokePlay_capturesCorrectDuration() { | |
265 arrangeMediaPlayer() | |
266 | |
267 audioPlayerController.play() | |
268 | |
269 verify(mockAudioPlayerObserver, atLeastOnce()).onChanged(audioPlayerResultCaptor.capture()) | |
270 assertThat(audioPlayerResultCaptor.value.getOrThrow().duration).isEqualTo(2000) | |
271 } | |
272 | |
273 @Test | |
274 fun testObserver_preparePlayer_invokeChangeDataSource_capturesCorrectPosition() { | |
275 arrangeMediaPlayer() | |
276 | |
277 audioPlayerController.seekTo(500) | |
278 audioPlayerController.changeDataSource(TEST_URL2) | |
279 shadowMediaPlayer.invokePreparedListener() | |
280 audioPlayerController.play() | |
281 | |
282 verify(mockAudioPlayerObserver, atLeastOnce()).onChanged(audioPlayerResultCaptor.capture()) | |
283 assertThat(audioPlayerResultCaptor.value.getOrThrow().position).isEqualTo(0) | |
284 } | |
285 | |
286 @Test | |
287 fun testObserver_observeInitPlayer_releasePlayer_initPlayer_checkNoNewUpdates() { | |
288 arrangeMediaPlayer() | |
289 | |
290 audioPlayerController.releaseMediaPlayer() | |
291 audioPlayerController.initializeMediaPlayer() | |
292 | |
293 verify(mockAudioPlayerObserver, atLeastOnce()).onChanged(audioPlayerResultCaptor.capture()) | |
294 // If the observer was still getting updates, the result would be pending | |
295 assertThat(audioPlayerResultCaptor.value.isSuccess()).isTrue() | |
296 assertThat(audioPlayerResultCaptor.value.getOrThrow().type).isEqualTo(PlayStatus.PREPARED) | |
297 } | |
298 | |
299 @Test | |
300 fun testScheduling_preparePlayer_invokePauseAndAdvance_verifyTestDoesNotHang() { | |
301 arrangeMediaPlayer() | |
302 | |
303 audioPlayerController.play() | |
304 testCoroutineDispatchers.advanceTimeBy(500) // Play part of the audio track before pausing. | |
305 audioPlayerController.pause() | |
306 testCoroutineDispatchers.advanceTimeBy(2000) | |
307 | |
308 verify(mockAudioPlayerObserver, atLeastOnce()).onChanged(audioPlayerResultCaptor.capture()) | |
309 assertThat(audioPlayerResultCaptor.value.getOrThrow().type).isEqualTo(PlayStatus.PAUSED) | |
310 // Verify: If the test does not hang, the behavior is correct. | |
311 } | |
312 | |
313 @Test | |
314 fun testScheduling_preparePlayer_invokeCompletionAndAdvance_verifyTestDoesNotHang() { | |
315 arrangeMediaPlayer() | |
316 | |
317 audioPlayerController.play() | |
318 testCoroutineDispatchers.advanceTimeBy(2000) | |
319 shadowMediaPlayer.invokeCompletionListener() | |
320 testCoroutineDispatchers.advanceTimeBy(2000) | |
321 | |
322 verify(mockAudioPlayerObserver, atLeastOnce()).onChanged(audioPlayerResultCaptor.capture()) | |
323 assertThat(audioPlayerResultCaptor.value.getOrThrow().type).isEqualTo(PlayStatus.COMPLETED) | |
324 // Verify: If the test does not hang, the behavior is correct. | |
325 } | |
326 | |
327 @Test | |
328 fun testScheduling_observeData_removeObserver_verifyTestDoesNotHang() { | |
329 val playProgress = audioPlayerController.initializeMediaPlayer() | |
330 audioPlayerController.changeDataSource(TEST_URL) | |
331 testCoroutineDispatchers.runCurrent() | |
332 | |
333 playProgress.observeForever(mockAudioPlayerObserver) | |
334 audioPlayerController.play() | |
335 testCoroutineDispatchers.advanceTimeBy(2000) | |
336 playProgress.removeObserver(mockAudioPlayerObserver) | |
337 | |
338 // Verify: If the test does not hang, the behavior is correct. | |
339 } | |
340 | |
341 @Test | |
342 fun testScheduling_addAndRemoveObservers_verifyTestDoesNotHang() { | |
343 val playProgress = | |
344 audioPlayerController.initializeMediaPlayer() | |
345 audioPlayerController.changeDataSource(TEST_URL) | |
346 testCoroutineDispatchers.runCurrent() | |
347 | |
348 audioPlayerController.play() | |
349 testCoroutineDispatchers.advanceTimeBy(2000) | |
350 playProgress.observeForever(mockAudioPlayerObserver) | |
351 audioPlayerController.pause() | |
352 playProgress.removeObserver(mockAudioPlayerObserver) | |
353 audioPlayerController.play() | |
354 | |
355 // Verify: If the test does not hang, the behavior is correct. | |
356 } | |
357 | |
358 @Test | |
359 fun testController_invokeErrorListener_invokePrepared_verifyAudioStatusIsFailure() { | |
360 audioPlayerController.initializeMediaPlayer().observeForever(mockAudioPlayerObserver) | |
361 audioPlayerController.changeDataSource(TEST_URL) | |
362 | |
363 shadowMediaPlayer.invokeErrorListener(/* what= */ 0, /* extra= */ 0) | |
364 shadowMediaPlayer.invokePreparedListener() | |
365 | |
366 verify(mockAudioPlayerObserver, atLeastOnce()).onChanged(audioPlayerResultCaptor.capture()) | |
367 assertThat(audioPlayerResultCaptor.value.isFailure()).isTrue() | |
368 } | |
369 | |
370 @Test | |
371 fun testController_notInitialized_releasePlayer_fails() { | |
372 val exception = assertThrows(IllegalStateException::class) { | |
373 audioPlayerController.releaseMediaPlayer() | |
374 } | |
375 | |
376 assertThat(exception).hasMessageThat() | |
377 .contains("Media player has not been previously initialized") | |
378 } | |
379 | |
380 @Test | |
381 fun testError_notPrepared_invokePlay_fails() { | |
382 val exception = assertThrows(IllegalStateException::class) { | |
383 audioPlayerController.play() | |
384 } | |
385 | |
386 assertThat(exception).hasMessageThat().contains("Media Player not in a prepared state") | |
387 } | |
388 | |
389 @Test | |
390 fun testError_notPrepared_invokePause_fails() { | |
391 val exception = assertThrows(IllegalStateException::class) { | |
392 audioPlayerController.pause() | |
393 } | |
394 | |
395 assertThat(exception).hasMessageThat().contains("Media Player not in a prepared state") | |
396 } | |
397 | |
398 @Test | |
399 fun testError_notPrepared_invokeSeekTo_fails() { | |
400 val exception = assertThrows(IllegalStateException::class) { | |
401 audioPlayerController.seekTo(500) | |
402 } | |
403 | |
404 assertThat(exception).hasMessageThat().contains("Media Player not in a prepared state") | |
405 } | |
406 | |
407 @Test | |
408 fun testController_initializePlayer_invokePrepared_reportsfailure_logsException() { | |
409 audioPlayerController.initializeMediaPlayer() | |
410 audioPlayerController.changeDataSource(TEST_FAIL_URL) | |
411 | |
412 shadowMediaPlayer.invokePreparedListener() | |
413 val exception = fakeExceptionLogger.getMostRecentException() | |
414 | |
415 assertThat(exception).isInstanceOf(IOException::class.java) | |
416 assertThat(exception).hasMessageThat().contains("Invalid URL") | |
417 } | |
418 | |
419 private fun arrangeMediaPlayer() { | |
420 audioPlayerController.initializeMediaPlayer().observeForever(mockAudioPlayerObserver) | |
421 audioPlayerController.changeDataSource(TEST_URL) | |
422 shadowMediaPlayer.invokePreparedListener() | |
423 testCoroutineDispatchers.runCurrent() | |
424 } | |
425 | |
426 private fun addMediaInfo() { | |
427 val dataSource = DataSource.toDataSource(context, Uri.parse(TEST_URL)) | |
428 val dataSource2 = DataSource.toDataSource(context, Uri.parse(TEST_URL2)) | |
429 val dataSource3 = DataSource.toDataSource(context, Uri.parse(TEST_FAIL_URL)) | |
430 val mediaInfo = ShadowMediaPlayer.MediaInfo( | |
431 /* duration= */ 2000, | |
432 /* preparationDelay= */ 0 | |
433 ) | |
434 ShadowMediaPlayer.addMediaInfo(dataSource, mediaInfo) | |
435 ShadowMediaPlayer.addMediaInfo(dataSource2, mediaInfo) | |
436 ShadowMediaPlayer.addException(dataSource3, IOException("Invalid URL")) | |
437 } | |
438 | |
439 // TODO(#89): Move to a common test library. | |
440 private fun <T : Throwable> assertThrows(type: KClass<T>, operation: () -> Unit): T { | |
441 try { | |
442 operation() | |
443 fail("Expected to encounter exception of $type") | |
444 } catch (t: Throwable) { | |
445 if (type.isInstance(t)) { | |
446 return type.cast(t) | |
447 } | |
448 // Unexpected exception; throw it. | |
449 throw t | |
450 } | |
451 } | |
452 | |
453 private fun AsyncResult<PlayProgress>.hasStatus(playStatus: PlayStatus): Boolean { | |
454 return isCompleted() && getOrThrow().type == playStatus | |
455 } | |
456 | |
457 private fun setUpTestApplicationComponent() { | |
458 DaggerAudioPlayerControllerTest_TestApplicationComponent.builder() | |
459 .setApplication(ApplicationProvider.getApplicationContext()) | |
460 .build() | |
461 .inject(this) | |
462 } | |
463 | |
464 // TODO(#89): Move this to a common test application component. | |
465 @Module | |
466 class TestModule { | |
467 @Provides | |
468 @Singleton | |
469 fun provideContext(application: Application): Context { | |
470 return application | |
471 } | |
472 | |
473 // TODO(#59): Either isolate these to their own shared test module, or use the real logging | |
474 // module in tests to avoid needing to specify these settings for tests. | |
475 @EnableConsoleLog | |
476 @Provides | |
477 fun provideEnableConsoleLog(): Boolean = true | |
478 | |
479 @EnableFileLog | |
480 @Provides | |
481 fun provideEnableFileLog(): Boolean = false | |
482 | |
483 @GlobalLogLevel | |
484 @Provides | |
485 fun provideGlobalLogLevel(): LogLevel = LogLevel.VERBOSE | |
486 | |
487 @CacheAssetsLocally | |
488 @Provides | |
489 fun provideCacheAssetsLocally(): Boolean = false | |
490 } | |
491 | |
492 // TODO(#89): Move this to a common test application component. | |
493 @Singleton | |
494 @Component( | |
495 modules = [ | |
496 TestModule::class, TestLogReportingModule::class, LogStorageModule::class, | |
497 TestDispatcherModule::class | |
498 ] | |
499 ) | |
500 interface TestApplicationComponent { | |
501 @Component.Builder | |
502 interface Builder { | |
503 @BindsInstance | |
504 fun setApplication(application: Application): Builder | |
505 fun build(): TestApplicationComponent | |
506 } | |
507 | |
508 fun inject(audioPlayerControllerTest: AudioPlayerControllerTest) | |
509 } | |
510 } | |
generated on 2021-02-01 23:16 |
Code coverage result of Jacoco:
![Screenshot from 2021-01-16 15-40-51](https://user-images.github usercontent.com/35475543/106527187-296a7e00-64e7-11eb-9135-65a50375b623.png)
Looks good to me. Few points:
- If we are doing any change here in file
AudioPlayerControllerTest
, include this file in PR so that we can understand what changes we are doing here in order to fix code coverage for this file. - Jacoco is giving 100% code coverage but the android studio is giving 96% line code coverage. Can we confirm that this jacoco result is the line code coverage or the method code coverage or it is just a file code coverage that a file has been covered and because of that it is giving 100% result?
- As from my comment here oppia/oppia-android#2466 (comment) in point 1 the result from the android studio and jacoco are different as I am seeing above comment, than which code coverage we should follow? Also, it looks like the android studio is giving more detailed result but can we do line code coverage using
jacoco
?
Looks good to me. Few points:
1. If we are doing any change here in file `AudioPlayerControllerTest`, include this file in PR so that we can understand what changes we are doing here in order to fix code coverage for this file. 2. Jacoco is giving 100% code coverage but the android studio is giving 96% line code coverage. Can we confirm that this jacoco result is the line code coverage or the method code coverage or it is just a file code coverage that a file has been covered and because of that it is giving 100% result? 3. As from my comment here [oppia/oppia-android#2466 (comment)](https://github.com/oppia/oppia-android/pull/2466#issuecomment-768055765) in point 1 the result from the android studio and jacoco are different as I am seeing above comment, than which code coverage we should follow? Also, it looks like the android studio is giving more detailed result but can we do line code coverage using `jacoco`?
For 1: (a) Noted
For 2: (b) I think jacoco is reporting line/method code coverage. But with Jacoco, the total status shows 100% with green colour if they passed, Then it specified the line/method code coverage with a yellow colour when it is ignored but the status shows 100%, If it fails, it will specify the line/method code coverage with red color but the percentage covered changed
For 3: (c) Yes android studio is giving more details. But i am also seeing that jacoco provides details in it own way as seen in No (a) above, But i am trying to find out why the 100% in Jacoco and Android 96% seeing that all test passed.
Code coverage result of Android studio: