<lambda>null1 package leakcanary
2
3 import android.os.Bundle
4 import android.view.LayoutInflater
5 import android.view.View
6 import android.view.ViewGroup
7 import androidx.fragment.app.Fragment
8 import androidx.lifecycle.ViewModel
9 import com.squareup.leakcanary.instrumentation.test.R
10 import leakcanary.TestUtils.assertLeak
11 import org.assertj.core.api.Assertions.assertThat
12 import org.junit.After
13 import org.junit.Before
14 import org.junit.Rule
15 import org.junit.Test
16 import shark.LeakTraceObject.LeakingStatus
17
18 class LifecycleLeaksTest : HasActivityTestRule<TestActivity> {
19
20 class TestViewModel : ViewModel()
21
22 class TestFragment : Fragment() {
23 override fun onCreateView(
24 inflater: LayoutInflater,
25 container: ViewGroup?,
26 savedInstanceState: Bundle?
27 ): View {
28 return View(context)
29 }
30 }
31
32 class FragmentHoldingLeaky : Fragment() {
33 val leaky = Any()
34 }
35
36 @get:Rule
37 override val activityRule = activityTestRule<TestActivity>(
38 initialTouchMode = false,
39 launchActivity = false
40 )
41
42 @Before fun setUp() {
43 AppWatcher.objectWatcher
44 .clearWatchedObjects()
45 }
46
47 @After fun tearDown() {
48 AppWatcher.objectWatcher
49 .clearWatchedObjects()
50 }
51
52 @Test fun activityLeakDetected() {
53 triggersOnActivityCreated {
54 activityRule.launchActivity(null)
55 }
56
57 activity retained {
58 triggersOnActivityDestroyed {
59 activityRule.finishActivity()
60 }
61 assertLeak(TestActivity::class.java)
62 }
63 }
64
65 @Test fun activityViewModelLeakDetected() {
66 triggersOnActivityCreated {
67 activityRule.launchActivity(null)
68 }
69
70 val viewModel = getOnMainSync {
71 activity.installViewModel(TestViewModel::class)
72 }
73
74 viewModel retained {
75 triggersOnActivityDestroyed {
76 activityRule.finishActivity()
77 }
78 assertLeak(TestViewModel::class.java)
79 }
80 }
81
82 @Test fun fragmentViewModelLeakDetected() {
83 triggersOnActivityCreated {
84 activityRule.launchActivity(null)
85 }
86
87 val viewModel = getOnMainSync {
88 val fragment = Fragment()
89 activity.addFragmentNow(fragment)
90 val viewModel = fragment.installViewModel(TestViewModel::class)
91 activity.removeFragmentNow(fragment)
92 viewModel
93 }
94
95 viewModel retained {
96 assertLeak(TestViewModel::class.java)
97 }
98 }
99
100 @Test
101 fun fragmentLeakDetected() {
102 triggersOnActivityCreated {
103 activityRule.launchActivity(null)
104 }
105
106 val fragment = getOnMainSync {
107 val fragment = Fragment()
108 activity.addFragmentNow(fragment)
109 activity.removeFragmentNow(fragment)
110 fragment
111 }
112
113 fragment retained {
114 val expectedLeakClass = Fragment::class.java
115 assertLeak { (heapAnalysis, leakTrace) ->
116 val className = leakTrace.leakingObject.className
117 assertThat(className)
118 .describedAs("$heapAnalysis")
119 .isEqualTo(expectedLeakClass.name)
120 assertThat(leakTrace.leakingObject.leakingStatusReason)
121 .describedAs("$heapAnalysis")
122 .contains("Fragment.mLifecycleRegistry.state is DESTROYED")
123 }
124 }
125 }
126
127 @Test
128 fun fragmentNotLeakingDetected() {
129 triggersOnActivityCreated {
130 activityRule.launchActivity(null)
131 }
132
133 getOnMainSync {
134 val fragment = FragmentHoldingLeaky()
135 activity.addFragmentNow(fragment)
136 AppWatcher.objectWatcher.expectWeaklyReachable(fragment.leaky, "leaky leaks")
137 }
138
139 assertLeak { (heapAnalysis, leakTrace) ->
140 val refToLeaky = leakTrace.referencePath.last()
141 assertThat(refToLeaky.referenceName)
142 .describedAs("$heapAnalysis")
143 .isEqualTo("leaky")
144 val fragment = refToLeaky.originObject
145 // AssertJ uses lambdas when comparing enum values, which fails on older Android versions.
146 if (fragment.leakingStatus != LeakingStatus.NOT_LEAKING) {
147 throw AssertionError(
148 "${fragment.leakingStatus} should be ${LeakingStatus.NOT_LEAKING}"
149 )
150 }
151 assertThat(fragment.leakingStatusReason).isEqualTo(
152 "Fragment.mLifecycleRegistry.state is RESUMED"
153 )
154 }
155 }
156
157 @Test
158 fun fragmentViewLeakDetected() {
159 triggersOnActivityCreated {
160 activityRule.launchActivity(null)
161 }
162
163 val fragment = triggersOnFragmentCreated {
164 getOnMainSync {
165 val fragment = TestFragment()
166 activity.replaceWithBackStack(fragment, R.id.fragments)
167 fragment
168 }
169 }
170
171 val fragmentView = getOnMainSync {
172 fragment.view!!
173 }
174
175 triggersOnFragmentViewDestroyed {
176 runOnMainSync {
177 // Add a new fragment again, which destroys the view of the previous fragment and puts
178 // the first fragment in the backstack.
179 activity.replaceWithBackStack(Fragment(), R.id.fragments)
180 }
181 }
182
183 fragmentView retained {
184 assertLeak(View::class.java)
185 }
186 }
187 }
188