Skip to content

Commit fa287bb

Browse files
Bartlomiej Bloniarzmeta-codesync[bot]
authored andcommitted
Add JVM tests for the Android pull model plumbing (#57583)
Summary: Pull Request resolved: #57583 Robolectric tests for the notification path: a notification pulls synchronously on the UI thread; one pull item is enqueued per notification and dispatched in order (redundant pulls no-op in C++); no binding is a clean no-op. Adds `ShadowFabricUIManagerBinding` and `ShadowPerformanceTracer` shadows. ## Changelog: [Internal] - Add JVM tests for the Android pull-model plumbing Reviewed By: cortinico Differential Revision: D112309051
1 parent 4affde9 commit fa287bb

3 files changed

Lines changed: 158 additions & 0 deletions

File tree

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
@file:Suppress("DEPRECATION")
9+
10+
package com.facebook.react.fabric
11+
12+
import com.facebook.react.bridge.ReactApplicationContext
13+
import com.facebook.react.bridge.ReactTestHelper
14+
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests
15+
import com.facebook.react.uimanager.ViewManagerRegistry
16+
import com.facebook.testutils.fakes.FakeBatchEventDispatchedListener
17+
import com.facebook.testutils.shadows.ShadowFabricUIManagerBinding
18+
import com.facebook.testutils.shadows.ShadowNativeLoader
19+
import com.facebook.testutils.shadows.ShadowPerformanceTracer
20+
import com.facebook.testutils.shadows.ShadowSoLoader
21+
import org.assertj.core.api.Assertions.assertThat
22+
import org.junit.Before
23+
import org.junit.Test
24+
import org.junit.runner.RunWith
25+
import org.robolectric.RobolectricTestRunner
26+
import org.robolectric.annotation.Config
27+
import org.robolectric.shadow.api.Shadow
28+
29+
/**
30+
* Tests for the pull-model notification path: [FabricUIManager.onTransactionAvailable] enqueues a
31+
* PullTransactionMountItem that asks the (shadowed, JNI-backed) [FabricUIManagerBinding] to pull
32+
* and apply the surface's pending transaction.
33+
*
34+
* The Robolectric test thread is the UI thread, so a notification posted from it dispatches the
35+
* queued mount items synchronously; notifications posted from a background thread stay queued until
36+
* the next UI-thread dispatch (in production, the dispatcher's frame callback).
37+
*/
38+
@RunWith(RobolectricTestRunner::class)
39+
@Config(
40+
shadows =
41+
[
42+
ShadowSoLoader::class,
43+
ShadowNativeLoader::class,
44+
ShadowPerformanceTracer::class,
45+
ShadowFabricUIManagerBinding::class,
46+
]
47+
)
48+
class FabricUIManagerPullModelTest {
49+
50+
private lateinit var reactContext: ReactApplicationContext
51+
private lateinit var underTest: FabricUIManager
52+
private lateinit var binding: FabricUIManagerBinding
53+
private lateinit var shadowBinding: ShadowFabricUIManagerBinding
54+
55+
@Before
56+
fun setup() {
57+
ReactNativeFeatureFlagsForTests.setUp()
58+
reactContext = ReactTestHelper.createCatalystContextForTest()
59+
underTest =
60+
FabricUIManager(
61+
reactContext,
62+
ViewManagerRegistry(emptyList()),
63+
FakeBatchEventDispatchedListener(),
64+
)
65+
binding = FabricUIManagerBinding()
66+
shadowBinding = Shadow.extract(binding)
67+
underTest.setBinding(binding)
68+
}
69+
70+
private fun runOnBackgroundThread(block: () -> Unit) {
71+
var error: Throwable? = null
72+
val thread = Thread {
73+
try {
74+
block()
75+
} catch (t: Throwable) {
76+
error = t
77+
}
78+
}
79+
thread.start()
80+
thread.join()
81+
error?.let { throw it }
82+
}
83+
84+
@Test
85+
fun onTransactionAvailable_onUiThread_pullsSynchronously() {
86+
underTest.onTransactionAvailable(1)
87+
88+
assertThat(shadowBinding.pulledSurfaceIds).containsExactly(1)
89+
}
90+
91+
@Test
92+
fun onTransactionAvailable_enqueuesOnePullPerNotification() {
93+
// Mirrors iOS: one transaction block per commit, in notification order. Redundant pulls
94+
// (revisions already consumed by an earlier accumulating pull) no-op in C++, not here.
95+
runOnBackgroundThread {
96+
underTest.onTransactionAvailable(1)
97+
underTest.onTransactionAvailable(2)
98+
underTest.onTransactionAvailable(1)
99+
}
100+
// Notifications from a background (commit) thread are queued, not dispatched.
101+
assertThat(shadowBinding.pulledSurfaceIds).isEmpty()
102+
103+
// The next UI-thread dispatch drains them in order.
104+
underTest.onTransactionAvailable(3)
105+
106+
assertThat(shadowBinding.pulledSurfaceIds).containsExactly(1, 2, 1, 3)
107+
}
108+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
package com.facebook.testutils.shadows
9+
10+
import org.robolectric.annotation.Implementation
11+
import org.robolectric.annotation.Implements
12+
13+
/**
14+
* [FabricUIManagerBinding] is JNI-backed and Mockito can't mock native methods, so shadow the
15+
* methods tests need instead. The binding class is `internal`, so it is referenced by name via
16+
* [Implements.className] rather than a compile-time class literal.
17+
*/
18+
@Implements(className = "com.facebook.react.fabric.FabricUIManagerBinding")
19+
class ShadowFabricUIManagerBinding {
20+
val pulledSurfaceIds: MutableList<Int> = mutableListOf()
21+
22+
@Implementation
23+
fun pullAndExecuteTransaction(surfaceId: Int) {
24+
pulledSurfaceIds.add(surfaceId)
25+
}
26+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
package com.facebook.testutils.shadows
9+
10+
import com.facebook.react.internal.tracing.PerformanceTracer
11+
import kotlin.jvm.JvmStatic
12+
import org.robolectric.annotation.Implementation
13+
import org.robolectric.annotation.Implements
14+
15+
/**
16+
* [PerformanceTracer] is JNI-backed (react_tracingjni); shadow it so code paths that check
17+
* [PerformanceTracer.isTracing] can run on the JVM. Tracing reports as disabled.
18+
*/
19+
@Implements(PerformanceTracer::class)
20+
class ShadowPerformanceTracer {
21+
companion object {
22+
@JvmStatic @Implementation fun isTracing(): Boolean = false
23+
}
24+
}

0 commit comments

Comments
 (0)