1 /*
2 * Copyright 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package androidx.compose.runtime
18
19 import android.os.HandlerThread
20 import android.view.View
21 import androidx.core.os.HandlerCompat
22 import androidx.test.ext.junit.runners.AndroidJUnit4
23 import androidx.test.filters.MediumTest
24 import java.lang.ref.PhantomReference
25 import java.lang.ref.ReferenceQueue
26 import java.util.concurrent.CountDownLatch
27 import java.util.concurrent.TimeUnit
28 import kotlin.test.assertNotNull
29 import kotlin.test.assertNull
30 import kotlin.test.assertTrue
31 import org.junit.Assert.assertEquals
32 import org.junit.Rule
33 import org.junit.Test
34 import org.junit.runner.RunWith
35
36 @RunWith(AndroidJUnit4::class)
37 class ComposeIntoTests : BaseComposeTest() {
38 @get:Rule override val activityRule = makeTestActivityRule()
39
40 @Test
41 @MediumTest
testMultipleSetContentCallsnull42 fun testMultipleSetContentCalls() {
43 val activity = activityRule.activity
44
45 var initializationCount = 0
46 val composable =
47 @Composable {
48 DisposableEffect(Unit) {
49 initializationCount++
50 onDispose {}
51 }
52 }
53
54 activity.show(composable)
55 activity.waitForAFrame()
56
57 assertEquals(1, initializationCount)
58
59 activity.show(composable)
60 activity.waitForAFrame()
61
62 // if we call setContent multiple times, we want to ensure that it doesn't tear
63 // down the whole hierarchy, so onActive should only get called once.
64 assertEquals(1, initializationCount)
65 }
66
67 @Test // b/153355487
68 @MediumTest
testCommittingFromASeparateThreadnull69 fun testCommittingFromASeparateThread() {
70 val model = mutableStateOf(0)
71 var composed = 0
72 var compositionLatch = CountDownLatch(1)
73 val threadLatch = CountDownLatch(1)
74 activity.show {
75 composed = model.value
76 compositionLatch.countDown()
77 }
78 val thread = HandlerThread("")
79 try {
80 compositionLatch.wait()
81 thread.start()
82 HandlerCompat.createAsync(thread.looper).post {
83 model.value = 1
84 threadLatch.countDown()
85 }
86 compositionLatch = CountDownLatch(1)
87 threadLatch.wait()
88 compositionLatch.wait()
89 assertEquals(1, composed)
90 } finally {
91 activity.runOnUiThread { activity.setContentView(View(activity)) }
92 thread.quitSafely()
93 }
94 }
95
96 @Test
97 @MediumTest
testCompositionCanBeCollectedWithPendingInvalidatenull98 fun testCompositionCanBeCollectedWithPendingInvalidate() {
99 val referenceQueue = ReferenceQueue<Composer>()
100 var scope: RecomposeScope? = null
101 var composer: Composer? = null
102 var phantomReference: PhantomReference<Composer>? = null
103 fun doShow() {
104 val threadLatch = CountDownLatch(1)
105 activity.show {
106 composer = currentComposer
107 scope = currentRecomposeScope
108 threadLatch.countDown()
109 }
110 threadLatch.wait()
111 phantomReference = PhantomReference<Composer>(composer, referenceQueue)
112 }
113
114 doShow()
115 assertNotNull(scope)
116
117 val threadLatch = CountDownLatch(1)
118 activity.runOnUiThread {
119 activity.setContentView(View(activity))
120 composer = null
121 threadLatch.countDown()
122 }
123 threadLatch.wait()
124 assertNull(composer)
125 assertNotNull(phantomReference)
126
127 // Make sure that the composer was collected.
128 repeat(100) {
129 Runtime.getRuntime().gc()
130 val value = referenceQueue.poll()
131 if (value != null) return
132 }
133 error("Failed - composer leaked")
134 }
135 }
136
waitnull137 fun CountDownLatch.wait() = assertTrue(await(1, TimeUnit.SECONDS))
138