From 108309e4070fe1f896c61e19c6dc07f71445e035 Mon Sep 17 00:00:00 2001 From: Amr Yousef Date: Thu, 8 Jun 2023 09:43:50 -0700 Subject: [PATCH] Convert and re-enable TimingModuleTest (#37763) Summary: As part of the effort to Kotlin-fy React Native tests, I've converted [TimingModuleTest](https://github.com/facebook/react-native/blob/main/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.java) to Kotlin. I noticed the test suite was ignored and it was failing locally but managed to get all the tests to pass except one which I kept ignored. ## Changelog: [Internal] [Changed] - Convert TimingModuleTest to Kotlin Pull Request resolved: https://github.com/facebook/react-native/pull/37763 Test Plan: Tests pass: ./gradlew :packages:react-native:ReactAndroid:test Formatted with [KtFmt](https://facebook.github.io/ktfmt/) Reviewed By: cortinico Differential Revision: D46543718 Pulled By: rshest fbshipit-source-id: 761b778a6d07417c9c83457c2777ee87f198f158 --- .../modules/timing/TimingModuleTest.java | 307 ------------------ .../react/modules/timing/TimingModuleTest.kt | 261 +++++++++++++++ 2 files changed, 261 insertions(+), 307 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.java create mode 100644 packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.kt diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.java b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.java deleted file mode 100644 index 613fb81773b..00000000000 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.java +++ /dev/null @@ -1,307 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.react.modules.timing; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; - -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.CatalystInstance; -import com.facebook.react.bridge.JavaOnlyArray; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.common.SystemClock; -import com.facebook.react.devsupport.interfaces.DevSupportManager; -import com.facebook.react.modules.core.ChoreographerCompat; -import com.facebook.react.modules.core.JSTimers; -import com.facebook.react.modules.core.ReactChoreographer; -import com.facebook.react.modules.core.TimingModule; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; -import org.powermock.api.mockito.PowerMockito; -import org.powermock.core.classloader.annotations.PowerMockIgnore; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.rule.PowerMockRule; -import org.robolectric.RobolectricTestRunner; - -/** Tests for {@link TimingModule}. */ -// DISABLED, BROKEN https://circleci.com/gh/facebook/react-native/12068 -@PrepareForTest({Arguments.class, SystemClock.class, ReactChoreographer.class}) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) -@RunWith(RobolectricTestRunner.class) -@Ignore // TODO T13905097 -public class TimingModuleTest { - - private static final long FRAME_TIME_NS = 17 * 1000 * 1000; // 17 ms - - private TimingModule mTimingModule; - private ReactChoreographer mReactChoreographerMock; - private PostFrameCallbackHandler mPostFrameCallbackHandler; - private PostFrameIdleCallbackHandler mIdlePostFrameCallbackHandler; - private long mCurrentTimeNs; - private JSTimers mJSTimersMock; - - @Rule public PowerMockRule rule = new PowerMockRule(); - - @Before - public void prepareModules() { - PowerMockito.mockStatic(Arguments.class); - when(Arguments.createArray()) - .thenAnswer( - new Answer() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - return new JavaOnlyArray(); - } - }); - - PowerMockito.mockStatic(SystemClock.class); - when(SystemClock.uptimeMillis()).thenReturn(mCurrentTimeNs / 1000000); - when(SystemClock.currentTimeMillis()).thenReturn(mCurrentTimeNs / 1000000); - when(SystemClock.nanoTime()).thenReturn(mCurrentTimeNs); - - mReactChoreographerMock = mock(ReactChoreographer.class); - PowerMockito.mockStatic(ReactChoreographer.class); - when(ReactChoreographer.getInstance()).thenReturn(mReactChoreographerMock); - - CatalystInstance reactInstance = mock(CatalystInstance.class); - ReactApplicationContext reactContext = mock(ReactApplicationContext.class); - when(reactContext.getCatalystInstance()).thenReturn(reactInstance); - when(reactContext.hasActiveReactInstance()).thenReturn(true); - - mCurrentTimeNs = 0; - mPostFrameCallbackHandler = new PostFrameCallbackHandler(); - mIdlePostFrameCallbackHandler = new PostFrameIdleCallbackHandler(); - - doAnswer(mPostFrameCallbackHandler) - .when(mReactChoreographerMock) - .postFrameCallback( - eq(ReactChoreographer.CallbackType.TIMERS_EVENTS), - any(ChoreographerCompat.FrameCallback.class)); - - doAnswer(mIdlePostFrameCallbackHandler) - .when(mReactChoreographerMock) - .postFrameCallback( - eq(ReactChoreographer.CallbackType.IDLE_EVENT), - any(ChoreographerCompat.FrameCallback.class)); - - mTimingModule = new TimingModule(reactContext, mock(DevSupportManager.class)); - mJSTimersMock = mock(JSTimers.class); - when(reactContext.getJSModule(JSTimers.class)).thenReturn(mJSTimersMock); - - doAnswer( - new Answer() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - ((Runnable) invocation.getArguments()[0]).run(); - return null; - } - }) - .when(reactContext) - .runOnJSQueueThread(any(Runnable.class)); - - mTimingModule.initialize(); - } - - private void stepChoreographerFrame() { - ChoreographerCompat.FrameCallback callback = - mPostFrameCallbackHandler.getAndResetFrameCallback(); - ChoreographerCompat.FrameCallback idleCallback = - mIdlePostFrameCallbackHandler.getAndResetFrameCallback(); - - mCurrentTimeNs += FRAME_TIME_NS; - when(SystemClock.uptimeMillis()).thenReturn(mCurrentTimeNs / 1000000); - if (callback != null) { - callback.doFrame(mCurrentTimeNs); - } - - if (idleCallback != null) { - idleCallback.doFrame(mCurrentTimeNs); - } - } - - @Test - public void testSimpleTimer() { - mTimingModule.onHostResume(); - mTimingModule.createTimer(1, 1, 0, false); - stepChoreographerFrame(); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(1.0)); - reset(mJSTimersMock); - stepChoreographerFrame(); - verifyNoMoreInteractions(mJSTimersMock); - } - - @Test - public void testSimpleRecurringTimer() { - mTimingModule.createTimer(100, 1, 0, true); - mTimingModule.onHostResume(); - stepChoreographerFrame(); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(100.0)); - - reset(mJSTimersMock); - stepChoreographerFrame(); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(100.0)); - } - - @Test - public void testCancelRecurringTimer() { - mTimingModule.onHostResume(); - mTimingModule.createTimer(105, 1, 0, true); - - stepChoreographerFrame(); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(105.0)); - - reset(mJSTimersMock); - mTimingModule.deleteTimer(105); - stepChoreographerFrame(); - verifyNoMoreInteractions(mJSTimersMock); - } - - @Test - public void testPausingAndResuming() { - mTimingModule.onHostResume(); - mTimingModule.createTimer(41, 1, 0, true); - - stepChoreographerFrame(); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41.0)); - - reset(mJSTimersMock); - mTimingModule.onHostPause(); - stepChoreographerFrame(); - verifyNoMoreInteractions(mJSTimersMock); - - reset(mJSTimersMock); - mTimingModule.onHostResume(); - stepChoreographerFrame(); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41.0)); - } - - @Test - public void testHeadlessJsTaskInBackground() { - mTimingModule.onHostPause(); - mTimingModule.onHeadlessJsTaskStart(42); - mTimingModule.createTimer(41, 1, 0, true); - - stepChoreographerFrame(); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41.0)); - - reset(mJSTimersMock); - mTimingModule.onHeadlessJsTaskFinish(42); - stepChoreographerFrame(); - verifyNoMoreInteractions(mJSTimersMock); - } - - @Test - public void testHeadlessJsTaskInForeground() { - mTimingModule.onHostResume(); - mTimingModule.onHeadlessJsTaskStart(42); - mTimingModule.createTimer(41, 1, 0, true); - - stepChoreographerFrame(); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41.0)); - - reset(mJSTimersMock); - mTimingModule.onHeadlessJsTaskFinish(42); - stepChoreographerFrame(); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41.0)); - - reset(mJSTimersMock); - mTimingModule.onHostPause(); - verifyNoMoreInteractions(mJSTimersMock); - } - - @Test - public void testHeadlessJsTaskIntertwine() { - mTimingModule.onHostResume(); - mTimingModule.onHeadlessJsTaskStart(42); - mTimingModule.createTimer(41, 1, 0, true); - mTimingModule.onHostPause(); - - stepChoreographerFrame(); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41.0)); - - reset(mJSTimersMock); - mTimingModule.onHostResume(); - mTimingModule.onHeadlessJsTaskFinish(42); - stepChoreographerFrame(); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41.0)); - - reset(mJSTimersMock); - mTimingModule.onHostPause(); - stepChoreographerFrame(); - verifyNoMoreInteractions(mJSTimersMock); - } - - @Test - public void testSetTimeoutZero() { - mTimingModule.createTimer(100, 0, 0, false); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(100.0)); - } - - @Test - public void testIdleCallback() { - mTimingModule.onHostResume(); - mTimingModule.setSendIdleEvents(true); - - stepChoreographerFrame(); - verify(mJSTimersMock).callIdleCallbacks(SystemClock.currentTimeMillis()); - } - - @Test - public void testActiveTimersInRange() { - mTimingModule.onHostResume(); - assertThat(mTimingModule.hasActiveTimersInRange(100)).isFalse(); - - mTimingModule.createTimer(41, 1, 0, true); - assertThat(mTimingModule.hasActiveTimersInRange(100)).isFalse(); // Repeating - - mTimingModule.createTimer(42, 150, 0, false); - assertThat(mTimingModule.hasActiveTimersInRange(100)).isFalse(); // Out of range - assertThat(mTimingModule.hasActiveTimersInRange(200)).isTrue(); // In range - } - - private static class PostFrameIdleCallbackHandler implements Answer { - - private ChoreographerCompat.FrameCallback mFrameCallback; - - @Override - public Void answer(InvocationOnMock invocation) throws Throwable { - Object[] args = invocation.getArguments(); - mFrameCallback = (ChoreographerCompat.FrameCallback) args[1]; - return null; - } - - public ChoreographerCompat.FrameCallback getAndResetFrameCallback() { - ChoreographerCompat.FrameCallback callback = mFrameCallback; - mFrameCallback = null; - return callback; - } - } - - private static class PostFrameCallbackHandler implements Answer { - - private ChoreographerCompat.FrameCallback mFrameCallback; - - @Override - public Void answer(InvocationOnMock invocation) throws Throwable { - Object[] args = invocation.getArguments(); - mFrameCallback = (ChoreographerCompat.FrameCallback) args[1]; - return null; - } - - public ChoreographerCompat.FrameCallback getAndResetFrameCallback() { - ChoreographerCompat.FrameCallback callback = mFrameCallback; - mFrameCallback = null; - return callback; - } - } -} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.kt new file mode 100644 index 00000000000..86f295da578 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.kt @@ -0,0 +1,261 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.modules.timing + +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.CatalystInstance +import com.facebook.react.bridge.JavaOnlyArray +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.common.SystemClock +import com.facebook.react.devsupport.interfaces.DevSupportManager +import com.facebook.react.modules.core.ChoreographerCompat.FrameCallback +import com.facebook.react.modules.core.JSTimers +import com.facebook.react.modules.core.ReactChoreographer +import com.facebook.react.modules.core.ReactChoreographer.CallbackType +import com.facebook.react.modules.core.TimingModule +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mockito.mock +import org.mockito.Mockito.reset +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.Mockito.`when` as whenever +import org.mockito.invocation.InvocationOnMock +import org.mockito.stubbing.Answer +import org.powermock.api.mockito.PowerMockito +import org.powermock.core.classloader.annotations.PowerMockIgnore +import org.powermock.core.classloader.annotations.PrepareForTest +import org.powermock.modules.junit4.rule.PowerMockRule +import org.robolectric.RobolectricTestRunner + +@PrepareForTest(Arguments::class, SystemClock::class, ReactChoreographer::class) +@PowerMockIgnore("org.mockito.*", "org.robolectric.*", "androidx.*", "android.*") +@RunWith(RobolectricTestRunner::class) +class TimingModuleTest { + companion object { + const val FRAME_TIME_NS = 17 * 1000 * 1000 + } + + private lateinit var timingModule: TimingModule + private lateinit var reactChoreographerMock: ReactChoreographer + private lateinit var postFrameCallbackHandler: PostFrameCallbackHandler + private lateinit var idlePostFrameCallbackHandler: PostFrameCallbackHandler + private var currentTimeNs = 0L + private lateinit var jSTimersMock: JSTimers + + @get:Rule val powerMockRule = PowerMockRule() + + @Before + fun prepareModules() { + PowerMockito.mockStatic(Arguments::class.java) + whenever(Arguments.createArray()).thenAnswer { + return@thenAnswer JavaOnlyArray() + } + + PowerMockito.mockStatic(SystemClock::class.java) + whenever(SystemClock.uptimeMillis()).thenAnswer { + return@thenAnswer currentTimeNs / 1000000 + } + whenever(SystemClock.currentTimeMillis()).thenAnswer { + return@thenAnswer currentTimeNs / 1000000 + } + whenever(SystemClock.nanoTime()).thenAnswer { + return@thenAnswer currentTimeNs + } + + reactChoreographerMock = mock(ReactChoreographer::class.java) + PowerMockito.mockStatic(ReactChoreographer::class.java) + whenever(ReactChoreographer.getInstance()).thenAnswer { reactChoreographerMock } + + val reactInstance = mock(CatalystInstance::class.java) + val reactContext = mock(ReactApplicationContext::class.java) + whenever(reactContext.catalystInstance).thenReturn(reactInstance) + whenever(reactContext.hasActiveReactInstance()).thenReturn(true) + + postFrameCallbackHandler = PostFrameCallbackHandler() + idlePostFrameCallbackHandler = PostFrameCallbackHandler() + + whenever( + reactChoreographerMock.postFrameCallback( + eq(CallbackType.TIMERS_EVENTS), any(FrameCallback::class.java))) + .thenAnswer { + return@thenAnswer postFrameCallbackHandler.answer(it) + } + + whenever( + reactChoreographerMock.postFrameCallback( + eq(CallbackType.IDLE_EVENT), any(FrameCallback::class.java))) + .thenAnswer { + return@thenAnswer idlePostFrameCallbackHandler.answer(it) + } + + timingModule = TimingModule(reactContext, mock(DevSupportManager::class.java)) + jSTimersMock = mock(JSTimers::class.java) + whenever(reactContext.getJSModule(JSTimers::class.java)).thenReturn(jSTimersMock) + whenever(reactContext.runOnJSQueueThread(any(Runnable::class.java))).thenAnswer { invocation -> + (invocation.arguments[0] as Runnable).run() + return@thenAnswer true + } + + timingModule.initialize() + } + + private fun stepChoreographerFrame() { + val callback = postFrameCallbackHandler.getAndResetFrameCallback() + val idleCallback = idlePostFrameCallbackHandler.getAndResetFrameCallback() + currentTimeNs += FRAME_TIME_NS + whenever(SystemClock.uptimeMillis()).thenAnswer { + return@thenAnswer currentTimeNs / 1000000 + } + callback?.doFrame(currentTimeNs) + idleCallback?.doFrame(currentTimeNs) + } + + @Test + fun testSimpleTimer() { + timingModule.onHostResume() + timingModule.createTimer(1.0, 1.0, 0.0, false) + stepChoreographerFrame() + verify(jSTimersMock).callTimers(JavaOnlyArray.of(1.0)) + reset(jSTimersMock) + stepChoreographerFrame() + verifyNoMoreInteractions(jSTimersMock) + } + + @Test + fun testSimpleRecurringTimer() { + timingModule.createTimer(100.0, 1.0, 0.0, true) + timingModule.onHostResume() + stepChoreographerFrame() + verify(jSTimersMock).callTimers(JavaOnlyArray.of(100.0)) + reset(jSTimersMock) + stepChoreographerFrame() + verify(jSTimersMock).callTimers(JavaOnlyArray.of(100.0)) + } + + @Test + fun testCancelRecurringTimer() { + timingModule.onHostResume() + timingModule.createTimer(105.0, 1.0, 0.0, true) + stepChoreographerFrame() + verify(jSTimersMock).callTimers(JavaOnlyArray.of(105.0)) + reset(jSTimersMock) + timingModule.deleteTimer(105.0) + stepChoreographerFrame() + verifyNoMoreInteractions(jSTimersMock) + } + + @Test + fun testPausingAndResuming() { + timingModule.onHostResume() + timingModule.createTimer(41.0, 1.0, 0.0, true) + stepChoreographerFrame() + verify(jSTimersMock).callTimers(JavaOnlyArray.of(41.0)) + reset(jSTimersMock) + timingModule.onHostPause() + stepChoreographerFrame() + verifyNoMoreInteractions(jSTimersMock) + reset(jSTimersMock) + timingModule.onHostResume() + stepChoreographerFrame() + verify(jSTimersMock).callTimers(JavaOnlyArray.of(41.0)) + } + + @Test + fun testHeadlessJsTaskInBackground() { + timingModule.onHostPause() + timingModule.onHeadlessJsTaskStart(42) + timingModule.createTimer(41.0, 1.0, 0.0, true) + stepChoreographerFrame() + verify(jSTimersMock).callTimers(JavaOnlyArray.of(41.0)) + reset(jSTimersMock) + timingModule.onHeadlessJsTaskFinish(42) + stepChoreographerFrame() + verifyNoMoreInteractions(jSTimersMock) + } + + @Test + fun testHeadlessJsTaskInForeground() { + timingModule.onHostResume() + timingModule.onHeadlessJsTaskStart(42) + timingModule.createTimer(41.0, 1.0, 0.0, true) + stepChoreographerFrame() + verify(jSTimersMock).callTimers(JavaOnlyArray.of(41.0)) + reset(jSTimersMock) + timingModule.onHeadlessJsTaskFinish(42) + stepChoreographerFrame() + verify(jSTimersMock).callTimers(JavaOnlyArray.of(41.0)) + reset(jSTimersMock) + timingModule.onHostPause() + verifyNoMoreInteractions(jSTimersMock) + } + + @Test + fun testHeadlessJsTaskIntertwine() { + timingModule.onHostResume() + timingModule.onHeadlessJsTaskStart(42) + timingModule.createTimer(41.0, 1.0, 0.0, true) + timingModule.onHostPause() + stepChoreographerFrame() + verify(jSTimersMock).callTimers(JavaOnlyArray.of(41.0)) + reset(jSTimersMock) + timingModule.onHostResume() + timingModule.onHeadlessJsTaskFinish(42) + stepChoreographerFrame() + verify(jSTimersMock).callTimers(JavaOnlyArray.of(41.0)) + reset(jSTimersMock) + timingModule.onHostPause() + stepChoreographerFrame() + verifyNoMoreInteractions(jSTimersMock) + } + + @Test + fun testSetTimeoutZero() { + timingModule.createTimer(100.0, 0.0, 0.0, false) + verify(jSTimersMock).callTimers(JavaOnlyArray.of(100.0)) + } + + @Test + fun testActiveTimersInRange() { + timingModule.onHostResume() + assertThat(timingModule.hasActiveTimersInRange(100)).isFalse + timingModule.createTimer(41.0, 1.0, 0.0, true) + assertThat(timingModule.hasActiveTimersInRange(100)).isFalse // Repeating + timingModule.createTimer(42.0, 150.0, 0.0, false) + assertThat(timingModule.hasActiveTimersInRange(100)).isFalse // Out of range + assertThat(timingModule.hasActiveTimersInRange(200)).isTrue // In range + } + + @Test + fun testIdleCallback() { + timingModule.setSendIdleEvents(true) + timingModule.onHostResume() + stepChoreographerFrame() + verify(jSTimersMock).callIdleCallbacks(SystemClock.currentTimeMillis().toDouble()) + } + + private class PostFrameCallbackHandler : Answer { + + private var frameCallback: FrameCallback? = null + + override fun answer(invocation: InvocationOnMock) { + invocation.arguments[1]?.let { frameCallback = it as FrameCallback } + } + + fun getAndResetFrameCallback(): FrameCallback? { + val callback = frameCallback + frameCallback = null + return callback + } + } +}