Skip to content

Commit 4f7605b

Browse files
author
hui.zhao
committed
unit test
1 parent 04b4f26 commit 4f7605b

File tree

8 files changed

+281
-53
lines changed

8 files changed

+281
-53
lines changed

android-godeye/src/main/java/cn/hikyson/godeye/core/internal/modules/pageload/PageLifecycleMethodEventTypes.java

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,21 @@
88

99
import cn.hikyson.methodcanary.lib.MethodEvent;
1010

11-
public class PageLifecycleMethodEventTypes {
11+
class PageLifecycleMethodEventTypes {
1212

1313
@Keep
14-
public static class MethodInfoForLifecycle implements Serializable {
14+
static class MethodInfoForLifecycle implements Serializable {
1515
PageType pageType;
1616
String methodName;
1717
String methodDesc;
1818

19-
public MethodInfoForLifecycle(PageType pageType, String methodName, String methodDesc) {
19+
MethodInfoForLifecycle(PageType pageType, String methodName, String methodDesc) {
2020
this.pageType = pageType;
2121
this.methodName = methodName;
2222
this.methodDesc = methodDesc;
2323
}
2424

25-
public MethodInfoForLifecycle(PageType pageType, MethodEvent methodEvent) {
25+
MethodInfoForLifecycle(PageType pageType, MethodEvent methodEvent) {
2626
this.pageType = pageType;
2727
this.methodName = methodEvent.methodName;
2828
this.methodDesc = methodEvent.methodDesc;
@@ -60,26 +60,26 @@ public int hashCode() {
6060
}
6161

6262

63-
static MethodInfoForLifecycle ON_ACTIVITY_CREATE = new MethodInfoForLifecycle(PageType.ACTIVITY, "onCreate", "(Landroid/os/Bundle;)V");
64-
static MethodInfoForLifecycle ON_ACTIVITY_START = new MethodInfoForLifecycle(PageType.ACTIVITY, "onStart", "()V");
65-
static MethodInfoForLifecycle ON_ACTIVITY_RESUME = new MethodInfoForLifecycle(PageType.ACTIVITY, "onResume", "()V");
66-
static MethodInfoForLifecycle ON_ACTIVITY_PAUSE = new MethodInfoForLifecycle(PageType.ACTIVITY, "onPause", "()V");
67-
static MethodInfoForLifecycle ON_ACTIVITY_STOP = new MethodInfoForLifecycle(PageType.ACTIVITY, "onStop", "()V");
68-
static MethodInfoForLifecycle ON_ACTIVITY_SAVE_INSTANCE_STATE = new MethodInfoForLifecycle(PageType.ACTIVITY, "onSaveInstanceState", "(Landroid/os/Bundle;)V");
69-
static MethodInfoForLifecycle ON_ACTIVITY_DESTORY = new MethodInfoForLifecycle(PageType.ACTIVITY, "onDestroy", "()V");
70-
static MethodInfoForLifecycle ON_FRAGMENT_ATTACH = new MethodInfoForLifecycle(PageType.FRAGMENT, "onAttach", "(Landroid/app/Activity;)V");
71-
static MethodInfoForLifecycle ON_FRAGMENT_CREATE = new MethodInfoForLifecycle(PageType.FRAGMENT, "onCreate", "(Landroid/os/Bundle;)V");
72-
static MethodInfoForLifecycle ON_FRAGMENT_VIEW_CREATE = new MethodInfoForLifecycle(PageType.FRAGMENT, "onCreateView", "(Landroid/view/LayoutInflater;Landroid/view/ViewGroup;Landroid/os/Bundle;)Landroid/view/View;");
73-
static MethodInfoForLifecycle ON_FRAGMENT_START = new MethodInfoForLifecycle(PageType.FRAGMENT, "onStart", "()V");
74-
static MethodInfoForLifecycle ON_FRAGMENT_RESUME = new MethodInfoForLifecycle(PageType.FRAGMENT, "onResume", "()V");
75-
static MethodInfoForLifecycle ON_FRAGMENT_PAUSE = new MethodInfoForLifecycle(PageType.FRAGMENT, "onPause", "()V");
76-
static MethodInfoForLifecycle ON_FRAGMENT_STOP = new MethodInfoForLifecycle(PageType.FRAGMENT, "onStop", "()V");
77-
static MethodInfoForLifecycle ON_FRAGMENT_SAVE_INSTANCE_STATE = new MethodInfoForLifecycle(PageType.FRAGMENT, "onSaveInstanceState", "(Landroid/os/Bundle;)V");
78-
static MethodInfoForLifecycle ON_FRAGMENT_VIEW_DESTROY = new MethodInfoForLifecycle(PageType.FRAGMENT, "onDestroyView", "()V");
79-
static MethodInfoForLifecycle ON_FRAGMENT_DESTROY = new MethodInfoForLifecycle(PageType.FRAGMENT, "onDestory", "()V");
80-
static MethodInfoForLifecycle ON_FRAGMENT_DETACH = new MethodInfoForLifecycle(PageType.FRAGMENT, "onDetach", "()V");
81-
82-
static Map<MethodInfoForLifecycle, LifecycleEvent> sPairsOfLifecycleAndMethods;
63+
private static MethodInfoForLifecycle ON_ACTIVITY_CREATE = new MethodInfoForLifecycle(PageType.ACTIVITY, "onCreate", "(Landroid/os/Bundle;)V");
64+
private static MethodInfoForLifecycle ON_ACTIVITY_START = new MethodInfoForLifecycle(PageType.ACTIVITY, "onStart", "()V");
65+
private static MethodInfoForLifecycle ON_ACTIVITY_RESUME = new MethodInfoForLifecycle(PageType.ACTIVITY, "onResume", "()V");
66+
private static MethodInfoForLifecycle ON_ACTIVITY_PAUSE = new MethodInfoForLifecycle(PageType.ACTIVITY, "onPause", "()V");
67+
private static MethodInfoForLifecycle ON_ACTIVITY_STOP = new MethodInfoForLifecycle(PageType.ACTIVITY, "onStop", "()V");
68+
private static MethodInfoForLifecycle ON_ACTIVITY_SAVE_INSTANCE_STATE = new MethodInfoForLifecycle(PageType.ACTIVITY, "onSaveInstanceState", "(Landroid/os/Bundle;)V");
69+
private static MethodInfoForLifecycle ON_ACTIVITY_DESTORY = new MethodInfoForLifecycle(PageType.ACTIVITY, "onDestroy", "()V");
70+
private static MethodInfoForLifecycle ON_FRAGMENT_ATTACH = new MethodInfoForLifecycle(PageType.FRAGMENT, "onAttach", "(Landroid/app/Activity;)V");
71+
private static MethodInfoForLifecycle ON_FRAGMENT_CREATE = new MethodInfoForLifecycle(PageType.FRAGMENT, "onCreate", "(Landroid/os/Bundle;)V");
72+
private static MethodInfoForLifecycle ON_FRAGMENT_VIEW_CREATE = new MethodInfoForLifecycle(PageType.FRAGMENT, "onCreateView", "(Landroid/view/LayoutInflater;Landroid/view/ViewGroup;Landroid/os/Bundle;)Landroid/view/View;");
73+
private static MethodInfoForLifecycle ON_FRAGMENT_START = new MethodInfoForLifecycle(PageType.FRAGMENT, "onStart", "()V");
74+
private static MethodInfoForLifecycle ON_FRAGMENT_RESUME = new MethodInfoForLifecycle(PageType.FRAGMENT, "onResume", "()V");
75+
private static MethodInfoForLifecycle ON_FRAGMENT_PAUSE = new MethodInfoForLifecycle(PageType.FRAGMENT, "onPause", "()V");
76+
private static MethodInfoForLifecycle ON_FRAGMENT_STOP = new MethodInfoForLifecycle(PageType.FRAGMENT, "onStop", "()V");
77+
private static MethodInfoForLifecycle ON_FRAGMENT_SAVE_INSTANCE_STATE = new MethodInfoForLifecycle(PageType.FRAGMENT, "onSaveInstanceState", "(Landroid/os/Bundle;)V");
78+
private static MethodInfoForLifecycle ON_FRAGMENT_VIEW_DESTROY = new MethodInfoForLifecycle(PageType.FRAGMENT, "onDestroyView", "()V");
79+
private static MethodInfoForLifecycle ON_FRAGMENT_DESTROY = new MethodInfoForLifecycle(PageType.FRAGMENT, "onDestory", "()V");
80+
private static MethodInfoForLifecycle ON_FRAGMENT_DETACH = new MethodInfoForLifecycle(PageType.FRAGMENT, "onDetach", "()V");
81+
82+
private static Map<MethodInfoForLifecycle, LifecycleEvent> sPairsOfLifecycleAndMethods;
8383

8484
static {
8585
sPairsOfLifecycleAndMethods = new HashMap<>();

android-godeye/src/main/java/cn/hikyson/godeye/core/utils/ProcessUtils.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,6 @@ public class ProcessUtils {
2626
private static volatile String sProcessName;
2727
private final static Object sNameLock = new Object();
2828

29-
private ProcessUtils() {
30-
throw new InstantiationError("Must not instantiate this class");
31-
}
32-
3329
public static String myProcessName(Context context) {
3430
if (sProcessName != null) {
3531
return sProcessName;

android-godeye/src/test/java/cn/hikyson/godeye/core/GodEyeHelperTest.java

Lines changed: 107 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import cn.hikyson.godeye.core.helper.Test1Activity;
2727
import cn.hikyson.godeye.core.helper.Test1Fragment;
2828
import cn.hikyson.godeye.core.helper.Test2Activity;
29+
import cn.hikyson.godeye.core.helper.Test2NotV4FragmentActivity;
2930
import cn.hikyson.godeye.core.helper.TestPageEvent;
3031
import cn.hikyson.godeye.core.helper.ThreadHelper;
3132
import cn.hikyson.godeye.core.internal.modules.methodcanary.MethodCanaryConfig;
@@ -96,23 +97,34 @@ public void onPageLoadedNotInstalled() {
9697
}
9798

9899
@Test
99-
public void onPageLoadedNoResultWhenNotCreated() {
100+
public void onPageLoadedWhenNotCreated() {
100101
GodEye.instance().uninstall();
101102
GodEye.instance().install(GodEyeConfig.noneConfigBuilder().withPageloadConfig(new PageloadConfig()).build());
102103
try {
103-
TestObserver testObserver = GodEye.instance().<Pageload, PageLifecycleEventInfo>moduleObservable(GodEye.ModuleName.PAGELOAD).test();
104-
GodEyeHelper.onPageLoaded(new Activity());
105-
GodEyeHelper.onPageLoaded(new Fragment());
104+
TestObserver<PageLifecycleEventInfo> testObserver = GodEye.instance().<Pageload, PageLifecycleEventInfo>moduleObservable(GodEye.ModuleName.PAGELOAD).test();
105+
Activity activity = new Activity();
106+
GodEyeHelper.onPageLoaded(activity);
107+
Fragment fragment = new Fragment();
108+
GodEyeHelper.onPageLoaded(fragment);
106109
Shadows.shadowOf(ThreadUtil.obtainHandler("godeye-pageload").getLooper()).getScheduler().advanceToNextPostedRunnable();
107110
testObserver.await(1, TimeUnit.SECONDS);
108-
testObserver.assertNoValues();
109-
fail();
111+
testObserver.assertValueAt(0, new Predicate<PageLifecycleEventInfo>() {
112+
@Override
113+
public boolean test(PageLifecycleEventInfo o) throws Exception {
114+
return activity.hashCode() == o.pageInfo.pageHashCode
115+
&& ActivityLifecycleEvent.ON_LOAD.equals(o.currentEvent.lifecycleEvent);
116+
}
117+
}).assertValueAt(1, new Predicate<PageLifecycleEventInfo>() {
118+
@Override
119+
public boolean test(PageLifecycleEventInfo o) throws Exception {
120+
return fragment.hashCode() == o.pageInfo.pageHashCode
121+
&& FragmentLifecycleEvent.ON_LOAD.equals(o.currentEvent.lifecycleEvent);
122+
}
123+
});
110124
} catch (UninstallException e) {
111125
fail();
112126
} catch (InterruptedException e) {
113127
fail();
114-
} catch (IllegalStateException ignore) {
115-
// will throw new IllegalStateException(String.format("Page [%s] Lifecycle [%s] must start with ActivityLifecycleEvent.ON_CREATE or FragmentLifecycleEvent.ON_ATTACH", pageInfo, pageLifecycleEventLine.lifecycleEvent));
116128
}
117129
}
118130

@@ -227,6 +239,69 @@ public boolean test(PageLifecycleEventInfo o) throws Exception {
227239
}
228240
}
229241

242+
/**
243+
* Test2NotV4FragmentActivityON_CREATE
244+
* Test2NotV4FragmentActivityON_START
245+
* Test2NotV4FragmentActivityON_DRAW
246+
* Test2NotV4FragmentActivityON_RESUME
247+
* Test1NotV4FragmentON_SHOW
248+
* Test1NotV4FragmentON_HIDE
249+
* Test2NotV4FragmentActivityON_LOAD
250+
* Test1NotV4FragmentON_LOAD
251+
* Test2NotV4FragmentActivityON_PAUSE
252+
* Test2NotV4FragmentActivityON_STOP
253+
* Test2NotV4FragmentActivityON_DESTROY
254+
*/
255+
@Test
256+
public void onFragmentPageVisibilityChangeSuccess() {
257+
try {
258+
GodEye.instance().uninstall();
259+
GodEye.instance().install(GodEyeConfig.noneConfigBuilder().withPageloadConfig(new PageloadConfig()).build());
260+
Activity activity = null;
261+
Object fragment = null;
262+
TestObserver testObserver = null;
263+
ActivityController<Test2NotV4FragmentActivity> activityController = Robolectric.buildActivity(Test2NotV4FragmentActivity.class).create().start().resume();
264+
activity = activityController.get();
265+
testObserver = GodEye.instance().<Pageload, PageLifecycleEventInfo>moduleObservable(GodEye.ModuleName.PAGELOAD).test();
266+
fragment = ((Test2NotV4FragmentActivity) activity).getTest1NotV4Fragment();
267+
GodEyeHelper.onFragmentPageVisibilityChange(fragment, true);
268+
Thread.sleep(100);
269+
GodEyeHelper.onFragmentPageVisibilityChange(fragment, false);
270+
GodEyeHelper.onPageLoaded(activity);
271+
GodEyeHelper.onPageLoaded(fragment);
272+
activityController.pause().stop().destroy();
273+
Shadows.shadowOf(ThreadUtil.obtainHandler("godeye-pageload").getLooper()).getScheduler().advanceToNextPostedRunnable();
274+
List<TestPageEvent> testPageEvents = new ArrayList<>();
275+
testPageEvents.add(new TestPageEvent(activity.hashCode(), ActivityLifecycleEvent.ON_CREATE, 1));
276+
testPageEvents.add(new TestPageEvent(activity.hashCode(), ActivityLifecycleEvent.ON_START, 2));
277+
testPageEvents.add(new TestPageEvent(activity.hashCode(), ActivityLifecycleEvent.ON_DRAW, 3));
278+
testPageEvents.add(new TestPageEvent(activity.hashCode(), ActivityLifecycleEvent.ON_RESUME, 4));
279+
testPageEvents.add(new TestPageEvent(fragment.hashCode(), FragmentLifecycleEvent.ON_SHOW, 1));
280+
testPageEvents.add(new TestPageEvent(fragment.hashCode(), FragmentLifecycleEvent.ON_HIDE, 2));
281+
testPageEvents.add(new TestPageEvent(activity.hashCode(), ActivityLifecycleEvent.ON_LOAD, 5));
282+
testPageEvents.add(new TestPageEvent(fragment.hashCode(), FragmentLifecycleEvent.ON_LOAD, 3));
283+
testPageEvents.add(new TestPageEvent(activity.hashCode(), ActivityLifecycleEvent.ON_PAUSE, 6));
284+
testPageEvents.add(new TestPageEvent(activity.hashCode(), ActivityLifecycleEvent.ON_STOP, 7));
285+
testPageEvents.add(new TestPageEvent(activity.hashCode(), ActivityLifecycleEvent.ON_DESTROY, 8));
286+
testObserver.assertValueCount(11);
287+
for (int i = 0; i < 11; i++) {
288+
int finalI = i;
289+
testObserver.assertValueAt(i, new Predicate<PageLifecycleEventInfo>() {
290+
@Override
291+
public boolean test(PageLifecycleEventInfo o) throws Exception {
292+
return testPageEvents.get(finalI).pageHashCode == o.pageInfo.pageHashCode
293+
&& testPageEvents.get(finalI).allEventSize == o.allEvents.size()
294+
&& testPageEvents.get(finalI).lifecycleEvent.equals(o.currentEvent.lifecycleEvent);
295+
}
296+
});
297+
}
298+
} catch (UninstallException e) {
299+
fail();
300+
} catch (InterruptedException e) {
301+
fail();
302+
}
303+
}
304+
230305
/**
231306
* ACTIVITY,Test2Activity,ON_CREATE
232307
* FRAGMENT,Test1Fragment,ON_ATTACH
@@ -240,6 +315,8 @@ public boolean test(PageLifecycleEventInfo o) throws Exception {
240315
* FRAGMENT,Test1Fragment,ON_RESUME
241316
* FRAGMENT,Test1Fragment,ON_SHOW
242317
* FRAGMENT,Test1Fragment,ON_HIDE
318+
* ACTIVITY,Test2Activity,ON_LOAD
319+
* FRAGMENT,Test1Fragment,ON_LOAD
243320
* ACTIVITY,Test2Activity,ON_PAUSE
244321
* FRAGMENT,Test1Fragment,ON_PAUSE
245322
* ACTIVITY,Test2Activity,ON_STOP
@@ -250,22 +327,24 @@ public boolean test(PageLifecycleEventInfo o) throws Exception {
250327
* FRAGMENT,Test1Fragment,ON_DETACH
251328
*/
252329
@Test
253-
public void onFragmentPageVisibilityChangeSuccess() {
330+
public void onFragmentPageVisibilityChangeSuccessV4() {
254331
try {
255332
GodEye.instance().uninstall();
256333
GodEye.instance().install(GodEyeConfig.noneConfigBuilder().withPageloadConfig(new PageloadConfig()).build());
334+
Activity activity = null;
335+
Object fragment = null;
336+
TestObserver testObserver = null;
257337
ActivityController<Test2Activity> activityController = Robolectric.buildActivity(Test2Activity.class).create().start().resume();
258-
Test2Activity activity = activityController.get();
259-
TestObserver testObserver = GodEye.instance().<Pageload, PageLifecycleEventInfo>moduleObservable(GodEye.ModuleName.PAGELOAD).test();
260-
Test1Fragment fragment = activity.getTest1Fragment();
261-
338+
activity = activityController.get();
339+
testObserver = GodEye.instance().<Pageload, PageLifecycleEventInfo>moduleObservable(GodEye.ModuleName.PAGELOAD).test();
340+
fragment = ((Test2Activity) activity).getTest1Fragment();
262341
GodEyeHelper.onFragmentPageVisibilityChange(fragment, true);
263342
Thread.sleep(100);
264343
GodEyeHelper.onFragmentPageVisibilityChange(fragment, false);
265-
344+
GodEyeHelper.onPageLoaded(activity);
345+
GodEyeHelper.onPageLoaded(fragment);
266346
activityController.pause().stop().destroy();
267347
Shadows.shadowOf(ThreadUtil.obtainHandler("godeye-pageload").getLooper()).getScheduler().advanceToNextPostedRunnable();
268-
269348
List<TestPageEvent> testPageEvents = new ArrayList<>();
270349
testPageEvents.add(new TestPageEvent(activity.hashCode(), ActivityLifecycleEvent.ON_CREATE, 1));
271350
testPageEvents.add(new TestPageEvent(fragment.hashCode(), FragmentLifecycleEvent.ON_ATTACH, 1));
@@ -279,16 +358,18 @@ public void onFragmentPageVisibilityChangeSuccess() {
279358
testPageEvents.add(new TestPageEvent(fragment.hashCode(), FragmentLifecycleEvent.ON_RESUME, 6));
280359
testPageEvents.add(new TestPageEvent(fragment.hashCode(), FragmentLifecycleEvent.ON_SHOW, 7));
281360
testPageEvents.add(new TestPageEvent(fragment.hashCode(), FragmentLifecycleEvent.ON_HIDE, 8));
282-
testPageEvents.add(new TestPageEvent(activity.hashCode(), ActivityLifecycleEvent.ON_PAUSE, 5));
283-
testPageEvents.add(new TestPageEvent(fragment.hashCode(), FragmentLifecycleEvent.ON_PAUSE, 9));
284-
testPageEvents.add(new TestPageEvent(activity.hashCode(), ActivityLifecycleEvent.ON_STOP, 6));
285-
testPageEvents.add(new TestPageEvent(fragment.hashCode(), FragmentLifecycleEvent.ON_STOP, 10));
286-
testPageEvents.add(new TestPageEvent(activity.hashCode(), ActivityLifecycleEvent.ON_DESTROY, 7));
287-
testPageEvents.add(new TestPageEvent(fragment.hashCode(), FragmentLifecycleEvent.ON_VIEW_DESTROY, 11));
288-
testPageEvents.add(new TestPageEvent(fragment.hashCode(), FragmentLifecycleEvent.ON_DESTROY, 12));
289-
testPageEvents.add(new TestPageEvent(fragment.hashCode(), FragmentLifecycleEvent.ON_DETACH, 13));
290-
testObserver.assertValueCount(20);
291-
for (int i = 0; i < 20; i++) {
361+
testPageEvents.add(new TestPageEvent(activity.hashCode(), ActivityLifecycleEvent.ON_LOAD, 5));
362+
testPageEvents.add(new TestPageEvent(fragment.hashCode(), FragmentLifecycleEvent.ON_LOAD, 9));
363+
testPageEvents.add(new TestPageEvent(activity.hashCode(), ActivityLifecycleEvent.ON_PAUSE, 6));
364+
testPageEvents.add(new TestPageEvent(fragment.hashCode(), FragmentLifecycleEvent.ON_PAUSE, 10));
365+
testPageEvents.add(new TestPageEvent(activity.hashCode(), ActivityLifecycleEvent.ON_STOP, 7));
366+
testPageEvents.add(new TestPageEvent(fragment.hashCode(), FragmentLifecycleEvent.ON_STOP, 11));
367+
testPageEvents.add(new TestPageEvent(activity.hashCode(), ActivityLifecycleEvent.ON_DESTROY, 8));
368+
testPageEvents.add(new TestPageEvent(fragment.hashCode(), FragmentLifecycleEvent.ON_VIEW_DESTROY, 12));
369+
testPageEvents.add(new TestPageEvent(fragment.hashCode(), FragmentLifecycleEvent.ON_DESTROY, 13));
370+
testPageEvents.add(new TestPageEvent(fragment.hashCode(), FragmentLifecycleEvent.ON_DETACH, 14));
371+
testObserver.assertValueCount(22);
372+
for (int i = 0; i < 22; i++) {
292373
int finalI = i;
293374
testObserver.assertValueAt(i, new Predicate<PageLifecycleEventInfo>() {
294375
@Override
@@ -306,6 +387,7 @@ public boolean test(PageLifecycleEventInfo o) throws Exception {
306387
}
307388
}
308389

390+
309391
@Test
310392
public void onFragmentPageVisibilityChangeWhenIllegal() {
311393
try {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package cn.hikyson.godeye.core.helper;
2+
3+
import android.app.Fragment;
4+
import android.os.Bundle;
5+
import android.view.LayoutInflater;
6+
import android.view.View;
7+
import android.view.ViewGroup;
8+
9+
import androidx.annotation.NonNull;
10+
import androidx.annotation.Nullable;
11+
12+
public class Test1NotV4Fragment extends Fragment {
13+
@Override
14+
public void onCreate(@Nullable Bundle savedInstanceState) {
15+
super.onCreate(savedInstanceState);
16+
}
17+
18+
@Nullable
19+
@Override
20+
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
21+
View view = new View(inflater.getContext());
22+
return view;
23+
}
24+
25+
@Override
26+
public void onDestroyView() {
27+
super.onDestroyView();
28+
}
29+
30+
@Override
31+
public void onDestroy() {
32+
super.onDestroy();
33+
}
34+
}

0 commit comments

Comments
 (0)