Create a System health checker for system_server

We want to capture if a system_server occurred during
a module run.

Test: unit tests
Change-Id: Iccf31c43669b5d40f9340d52325564861ecbacb8
diff --git a/src/com/android/tradefed/suite/checker/SystemServerStatusChecker.java b/src/com/android/tradefed/suite/checker/SystemServerStatusChecker.java
new file mode 100644
index 0000000..d8844dd
--- /dev/null
+++ b/src/com/android/tradefed/suite/checker/SystemServerStatusChecker.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tradefed.suite.checker;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+
+/**
+ * Check if the pid of system_server has changed from before and after a module run. A new pid would
+ * mean a runtime restart occurred during the module run.
+ */
+public class SystemServerStatusChecker implements ISystemStatusChecker {
+
+    private String mSystemServerPid = null;
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean preExecutionCheck(ITestDevice device) throws DeviceNotAvailableException {
+        mSystemServerPid = null;
+        mSystemServerPid = device.executeShellCommand("pidof system_server");
+        if (mSystemServerPid == null) {
+            CLog.w("Failed to get system_server pid.");
+            return false;
+        }
+        if (!checkValidPid(mSystemServerPid.trim())) {
+            CLog.w(
+                    "Invalid pid response found: '%s'. Skipping the system checker.",
+                    mSystemServerPid);
+            mSystemServerPid = null;
+            return true;
+        }
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean postExecutionCheck(ITestDevice device) throws DeviceNotAvailableException {
+        if (mSystemServerPid == null) {
+            CLog.d("No valid known value of system_server pid, skipping system checker.");
+            return true;
+        }
+        String tmpSystemServerPid = device.executeShellCommand("pidof system_server").trim();
+        if (mSystemServerPid.equals(tmpSystemServerPid)) {
+            return true;
+        }
+        CLog.w(
+                "system_server has a different pid after the module run. from %s to %s",
+                mSystemServerPid, tmpSystemServerPid);
+        return false;
+    }
+
+    /** Validate that pid is an integer and not empty. */
+    private boolean checkValidPid(String output) {
+        if (output.isEmpty()) {
+            return false;
+        }
+        try {
+            Integer.parseInt(output);
+        } catch (NumberFormatException e) {
+            return false;
+        }
+
+        return true;
+    }
+}
diff --git a/tests/src/com/android/tradefed/UnitTests.java b/tests/src/com/android/tradefed/UnitTests.java
index 33eba48..0d9dae7 100644
--- a/tests/src/com/android/tradefed/UnitTests.java
+++ b/tests/src/com/android/tradefed/UnitTests.java
@@ -88,6 +88,7 @@
 import com.android.tradefed.result.TestSummaryTest;
 import com.android.tradefed.result.XmlResultReporterTest;
 import com.android.tradefed.suite.checker.KeyguardStatusCheckerTest;
+import com.android.tradefed.suite.checker.SystemServerStatusCheckerTest;
 import com.android.tradefed.targetprep.AllTestAppsInstallSetupTest;
 import com.android.tradefed.targetprep.AppSetupTest;
 import com.android.tradefed.targetprep.BuildInfoAttributePreparerTest;
@@ -320,6 +321,7 @@
 
     // suite/checker
     KeyguardStatusCheckerTest.class,
+    SystemServerStatusCheckerTest.class,
 
     // testtype
     AndroidJUnitTestTest.class,
diff --git a/tests/src/com/android/tradefed/suite/checker/SystemServerStatusCheckerTest.java b/tests/src/com/android/tradefed/suite/checker/SystemServerStatusCheckerTest.java
new file mode 100644
index 0000000..19fa443
--- /dev/null
+++ b/tests/src/com/android/tradefed/suite/checker/SystemServerStatusCheckerTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tradefed.suite.checker;
+
+import static org.junit.Assert.*;
+
+import com.android.tradefed.device.ITestDevice;
+
+import org.easymock.EasyMock;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link SystemServerStatusChecker} */
+@RunWith(JUnit4.class)
+public class SystemServerStatusCheckerTest {
+
+    private SystemServerStatusChecker mChecker;
+    private ITestDevice mMockDevice;
+
+    @Before
+    public void setUp() {
+        mMockDevice = EasyMock.createMock(ITestDevice.class);
+        mChecker = new SystemServerStatusChecker();
+    }
+
+    /** Test that system checker pass if the pid of system checker does not change. */
+    @Test
+    public void testPidRemainUnchanged() throws Exception {
+        EasyMock.expect(mMockDevice.executeShellCommand(EasyMock.eq("pidof system_server")))
+                .andReturn("914")
+                .times(2);
+        EasyMock.replay(mMockDevice);
+        assertTrue(mChecker.preExecutionCheck(mMockDevice));
+        assertTrue(mChecker.postExecutionCheck(mMockDevice));
+        EasyMock.verify(mMockDevice);
+    }
+
+    /** Test that system checker fail if the pid of system checker does change. */
+    @Test
+    public void testPidChanged() throws Exception {
+        EasyMock.expect(mMockDevice.executeShellCommand(EasyMock.eq("pidof system_server")))
+                .andReturn("914\n");
+        EasyMock.expect(mMockDevice.executeShellCommand(EasyMock.eq("pidof system_server")))
+                .andReturn("1024\n");
+        EasyMock.replay(mMockDevice);
+        assertTrue(mChecker.preExecutionCheck(mMockDevice));
+        assertFalse(mChecker.postExecutionCheck(mMockDevice));
+        EasyMock.verify(mMockDevice);
+    }
+
+    /** Test that if the format of the pid is unexpected, we skip the system checker. */
+    @Test
+    public void testFailToGetPid() throws Exception {
+        EasyMock.expect(mMockDevice.executeShellCommand(EasyMock.eq("pidof system_server")))
+                .andReturn("not found\n");
+        EasyMock.replay(mMockDevice);
+        assertTrue(mChecker.preExecutionCheck(mMockDevice));
+        assertTrue(mChecker.postExecutionCheck(mMockDevice));
+        EasyMock.verify(mMockDevice);
+    }
+
+    /**
+     * Test that if the pid output is null, we fail the current preExecution but skip post
+     * execution.
+     */
+    @Test
+    public void testPid_null() throws Exception {
+        EasyMock.expect(mMockDevice.executeShellCommand(EasyMock.eq("pidof system_server")))
+                .andReturn(null);
+        EasyMock.replay(mMockDevice);
+        assertFalse(mChecker.preExecutionCheck(mMockDevice));
+        assertTrue(mChecker.postExecutionCheck(mMockDevice));
+        EasyMock.verify(mMockDevice);
+    }
+}