| /* |
| * 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.server.timezone; |
| |
| import static com.android.server.timezone.RulesManagerService.REQUIRED_QUERY_PERMISSION; |
| import static com.android.server.timezone.RulesManagerService.REQUIRED_UPDATER_PERMISSION; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertNull; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| import static org.mockito.ArgumentMatchers.any; |
| import static org.mockito.Mockito.doNothing; |
| import static org.mockito.Mockito.doReturn; |
| import static org.mockito.Mockito.doThrow; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.reset; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.verifyNoMoreInteractions; |
| import static org.mockito.Mockito.verifyZeroInteractions; |
| import static org.mockito.Mockito.when; |
| |
| import android.app.timezone.Callback; |
| import android.app.timezone.DistroRulesVersion; |
| import android.app.timezone.ICallback; |
| import android.app.timezone.RulesManager; |
| import android.app.timezone.RulesState; |
| import android.os.ParcelFileDescriptor; |
| |
| import com.android.timezone.distro.DistroVersion; |
| import com.android.timezone.distro.StagedDistroOperation; |
| import com.android.timezone.distro.TimeZoneDistro; |
| import com.android.timezone.distro.installer.TimeZoneDistroInstaller; |
| |
| import libcore.io.IoUtils; |
| import libcore.timezone.TzDataSetVersion; |
| |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| import java.io.File; |
| import java.io.FileDescriptor; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.util.concurrent.Executor; |
| |
| import javax.annotation.Nullable; |
| |
| /** |
| * White box interaction / unit testing of the {@link RulesManagerService}. |
| */ |
| public class RulesManagerServiceTest { |
| |
| private static final int CURRENT_FORMAT_MAJOR_VERSION = |
| TzDataSetVersion.currentFormatMajorVersion(); |
| private static final int CURRENT_FORMAT_MINOR_VERSION = |
| TzDataSetVersion.currentFormatMinorVersion(); |
| |
| private RulesManagerService mRulesManagerService; |
| |
| private FakeExecutor mFakeExecutor; |
| private PermissionHelper mMockPermissionHelper; |
| private RulesManagerIntentHelper mMockIntentHelper; |
| private PackageTracker mMockPackageTracker; |
| private TimeZoneDistroInstaller mMockTimeZoneDistroInstaller; |
| |
| @Before |
| public void setUp() { |
| mFakeExecutor = new FakeExecutor(); |
| |
| mMockPackageTracker = mock(PackageTracker.class); |
| mMockPermissionHelper = mock(PermissionHelper.class); |
| mMockIntentHelper = mock(RulesManagerIntentHelper.class); |
| mMockTimeZoneDistroInstaller = mock(TimeZoneDistroInstaller.class); |
| |
| mRulesManagerService = new RulesManagerService( |
| mMockPermissionHelper, |
| mFakeExecutor, |
| mMockIntentHelper, |
| mMockPackageTracker, |
| mMockTimeZoneDistroInstaller); |
| } |
| |
| @Test(expected = SecurityException.class) |
| public void getRulesState_noCallerPermission() throws Exception { |
| configureCallerDoesNotHaveQueryPermission(); |
| mRulesManagerService.getRulesState(); |
| } |
| |
| @Test(expected = SecurityException.class) |
| public void requestInstall_noCallerPermission() throws Exception { |
| configureCallerDoesNotHaveUpdatePermission(); |
| mRulesManagerService.requestInstall(null, null, null); |
| } |
| |
| @Test(expected = SecurityException.class) |
| public void requestUninstall_noCallerPermission() throws Exception { |
| configureCallerDoesNotHaveUpdatePermission(); |
| mRulesManagerService.requestUninstall(null, null); |
| } |
| |
| @Test(expected = SecurityException.class) |
| public void requestNothing_noCallerPermission() throws Exception { |
| configureCallerDoesNotHaveUpdatePermission(); |
| mRulesManagerService.requestNothing(null, true); |
| } |
| |
| @Test |
| public void getRulesState_baseVersionError() throws Exception { |
| configureDeviceCannotReadBaseVersion(); |
| |
| assertNull(mRulesManagerService.getRulesState()); |
| } |
| |
| @Test |
| public void getRulesState_stagedInstall() throws Exception { |
| configureCallerHasPermission(); |
| |
| configureDeviceBaseVersion("2016a"); |
| |
| DistroVersion stagedDistroVersion = new DistroVersion( |
| CURRENT_FORMAT_MAJOR_VERSION, |
| CURRENT_FORMAT_MINOR_VERSION - 1, |
| "2016c", |
| 3 /* revision */); |
| configureStagedInstall(stagedDistroVersion); |
| |
| DistroVersion installedDistroVersion = new DistroVersion( |
| CURRENT_FORMAT_MAJOR_VERSION, |
| CURRENT_FORMAT_MINOR_VERSION - 1, |
| "2016b", |
| 4); |
| configureInstalledDistroVersion(installedDistroVersion); |
| |
| DistroRulesVersion stagedDistroRulesVersion = new DistroRulesVersion( |
| stagedDistroVersion.rulesVersion, stagedDistroVersion.revision); |
| DistroRulesVersion installedDistroRulesVersion = new DistroRulesVersion( |
| installedDistroVersion.rulesVersion, installedDistroVersion.revision); |
| RulesState expectedRuleState = new RulesState( |
| "2016a", RulesManagerService.DISTRO_FORMAT_VERSION_SUPPORTED, |
| false /* operationInProgress */, |
| RulesState.STAGED_OPERATION_INSTALL, stagedDistroRulesVersion, |
| RulesState.DISTRO_STATUS_INSTALLED, installedDistroRulesVersion); |
| assertEquals(expectedRuleState, mRulesManagerService.getRulesState()); |
| } |
| |
| @Test |
| public void getRulesState_nothingStaged() throws Exception { |
| configureCallerHasPermission(); |
| |
| configureDeviceBaseVersion("2016a"); |
| |
| configureNoStagedOperation(); |
| |
| DistroVersion installedDistroVersion = new DistroVersion( |
| CURRENT_FORMAT_MAJOR_VERSION, |
| CURRENT_FORMAT_MINOR_VERSION - 1, |
| "2016b", |
| 4); |
| configureInstalledDistroVersion(installedDistroVersion); |
| |
| DistroRulesVersion installedDistroRulesVersion = new DistroRulesVersion( |
| installedDistroVersion.rulesVersion, installedDistroVersion.revision); |
| RulesState expectedRuleState = new RulesState( |
| "2016a", RulesManagerService.DISTRO_FORMAT_VERSION_SUPPORTED, |
| false /* operationInProgress */, |
| RulesState.STAGED_OPERATION_NONE, null /* stagedDistroRulesVersion */, |
| RulesState.DISTRO_STATUS_INSTALLED, installedDistroRulesVersion); |
| assertEquals(expectedRuleState, mRulesManagerService.getRulesState()); |
| } |
| |
| @Test |
| public void getRulesState_uninstallStaged() throws Exception { |
| configureCallerHasPermission(); |
| |
| configureDeviceBaseVersion("2016a"); |
| |
| configureStagedUninstall(); |
| |
| DistroVersion installedDistroVersion = new DistroVersion( |
| CURRENT_FORMAT_MAJOR_VERSION, |
| CURRENT_FORMAT_MINOR_VERSION - 1, |
| "2016b", |
| 4); |
| configureInstalledDistroVersion(installedDistroVersion); |
| |
| DistroRulesVersion installedDistroRulesVersion = new DistroRulesVersion( |
| installedDistroVersion.rulesVersion, installedDistroVersion.revision); |
| RulesState expectedRuleState = new RulesState( |
| "2016a", RulesManagerService.DISTRO_FORMAT_VERSION_SUPPORTED, |
| false /* operationInProgress */, |
| RulesState.STAGED_OPERATION_UNINSTALL, null /* stagedDistroRulesVersion */, |
| RulesState.DISTRO_STATUS_INSTALLED, installedDistroRulesVersion); |
| assertEquals(expectedRuleState, mRulesManagerService.getRulesState()); |
| } |
| |
| @Test |
| public void getRulesState_installedRulesError() throws Exception { |
| configureCallerHasPermission(); |
| |
| String baseRulesVersion = "2016a"; |
| configureDeviceBaseVersion(baseRulesVersion); |
| |
| configureStagedUninstall(); |
| configureDeviceCannotReadInstalledDistroVersion(); |
| |
| RulesState expectedRuleState = new RulesState( |
| "2016a", RulesManagerService.DISTRO_FORMAT_VERSION_SUPPORTED, |
| false /* operationInProgress */, |
| RulesState.STAGED_OPERATION_UNINSTALL, null /* stagedDistroRulesVersion */, |
| RulesState.DISTRO_STATUS_UNKNOWN, null /* installedDistroRulesVersion */); |
| assertEquals(expectedRuleState, mRulesManagerService.getRulesState()); |
| } |
| |
| @Test |
| public void getRulesState_stagedRulesError() throws Exception { |
| configureCallerHasPermission(); |
| |
| String baseRulesVersion = "2016a"; |
| configureDeviceBaseVersion(baseRulesVersion); |
| |
| configureDeviceCannotReadStagedDistroOperation(); |
| |
| DistroVersion installedDistroVersion = new DistroVersion( |
| CURRENT_FORMAT_MAJOR_VERSION, |
| CURRENT_FORMAT_MINOR_VERSION - 1, |
| "2016b", |
| 4); |
| configureInstalledDistroVersion(installedDistroVersion); |
| |
| DistroRulesVersion installedDistroRulesVersion = new DistroRulesVersion( |
| installedDistroVersion.rulesVersion, installedDistroVersion.revision); |
| RulesState expectedRuleState = new RulesState( |
| "2016a", RulesManagerService.DISTRO_FORMAT_VERSION_SUPPORTED, |
| false /* operationInProgress */, |
| RulesState.STAGED_OPERATION_UNKNOWN, null /* stagedDistroRulesVersion */, |
| RulesState.DISTRO_STATUS_INSTALLED, installedDistroRulesVersion); |
| assertEquals(expectedRuleState, mRulesManagerService.getRulesState()); |
| } |
| |
| @Test |
| public void getRulesState_noInstalledRules() throws Exception { |
| configureCallerHasPermission(); |
| |
| String baseRulesVersion = "2016a"; |
| configureDeviceBaseVersion(baseRulesVersion); |
| configureNoStagedOperation(); |
| configureInstalledDistroVersion(null); |
| |
| RulesState expectedRuleState = new RulesState( |
| baseRulesVersion, RulesManagerService.DISTRO_FORMAT_VERSION_SUPPORTED, |
| false /* operationInProgress */, |
| RulesState.STAGED_OPERATION_NONE, null /* stagedDistroRulesVersion */, |
| RulesState.DISTRO_STATUS_NONE, null /* installedDistroRulesVersion */); |
| assertEquals(expectedRuleState, mRulesManagerService.getRulesState()); |
| } |
| |
| @Test |
| public void getRulesState_operationInProgress() throws Exception { |
| configureCallerHasPermission(); |
| |
| String baseRulesVersion = "2016a"; |
| String installedRulesVersion = "2016b"; |
| int revision = 3; |
| |
| configureDeviceBaseVersion(baseRulesVersion); |
| |
| DistroVersion installedDistroVersion = new DistroVersion( |
| CURRENT_FORMAT_MAJOR_VERSION, |
| CURRENT_FORMAT_MINOR_VERSION - 1, |
| installedRulesVersion, |
| revision); |
| configureInstalledDistroVersion(installedDistroVersion); |
| |
| ParcelFileDescriptor parcelFileDescriptor = |
| createParcelFileDescriptor(createArbitraryBytes(1000)); |
| |
| // Start an async operation so there is one in progress. The mFakeExecutor won't actually |
| // execute it. |
| byte[] tokenBytes = createArbitraryTokenBytes(); |
| ICallback callback = new StubbedCallback(); |
| |
| mRulesManagerService.requestInstall(parcelFileDescriptor, tokenBytes, callback); |
| |
| // Request the rules state while the async operation is "happening". |
| RulesState actualRulesState = mRulesManagerService.getRulesState(); |
| DistroRulesVersion expectedInstalledDistroRulesVersion = |
| new DistroRulesVersion(installedRulesVersion, revision); |
| RulesState expectedRuleState = new RulesState( |
| baseRulesVersion, RulesManagerService.DISTRO_FORMAT_VERSION_SUPPORTED, |
| true /* operationInProgress */, |
| RulesState.STAGED_OPERATION_UNKNOWN, null /* stagedDistroRulesVersion */, |
| RulesState.DISTRO_STATUS_INSTALLED, expectedInstalledDistroRulesVersion); |
| assertEquals(expectedRuleState, actualRulesState); |
| } |
| |
| @Test |
| public void requestInstall_operationInProgress() throws Exception { |
| configureCallerHasPermission(); |
| |
| ParcelFileDescriptor parcelFileDescriptor1 = |
| createParcelFileDescriptor(createArbitraryBytes(1000)); |
| |
| byte[] tokenBytes = createArbitraryTokenBytes(); |
| ICallback callback = new StubbedCallback(); |
| |
| // First request should succeed. |
| assertEquals(RulesManager.SUCCESS, |
| mRulesManagerService.requestInstall(parcelFileDescriptor1, tokenBytes, callback)); |
| |
| // Something async should be enqueued. Clear it but do not execute it so we can detect the |
| // second request does nothing. |
| mFakeExecutor.getAndResetLastCommand(); |
| |
| // Second request should fail. |
| ParcelFileDescriptor parcelFileDescriptor2 = |
| createParcelFileDescriptor(createArbitraryBytes(1000)); |
| assertEquals(RulesManager.ERROR_OPERATION_IN_PROGRESS, |
| mRulesManagerService.requestInstall(parcelFileDescriptor2, tokenBytes, callback)); |
| |
| assertClosed(parcelFileDescriptor2); |
| |
| // Assert nothing async was enqueued. |
| mFakeExecutor.assertNothingQueued(); |
| verifyNoInstallerCallsMade(); |
| verifyNoPackageTrackerCallsMade(); |
| verifyNoIntentsSent(); |
| } |
| |
| @Test |
| public void requestInstall_badToken() throws Exception { |
| configureCallerHasPermission(); |
| |
| ParcelFileDescriptor parcelFileDescriptor = |
| createParcelFileDescriptor(createArbitraryBytes(1000)); |
| |
| byte[] badTokenBytes = new byte[2]; |
| ICallback callback = new StubbedCallback(); |
| |
| try { |
| mRulesManagerService.requestInstall(parcelFileDescriptor, badTokenBytes, callback); |
| fail(); |
| } catch (IllegalArgumentException expected) { |
| } |
| |
| assertClosed(parcelFileDescriptor); |
| |
| // Assert nothing async was enqueued. |
| mFakeExecutor.assertNothingQueued(); |
| verifyNoInstallerCallsMade(); |
| verifyNoPackageTrackerCallsMade(); |
| verifyNoIntentsSent(); |
| } |
| |
| @Test |
| public void requestInstall_nullParcelFileDescriptor() throws Exception { |
| configureCallerHasPermission(); |
| |
| ParcelFileDescriptor parcelFileDescriptor = null; |
| byte[] tokenBytes = createArbitraryTokenBytes(); |
| ICallback callback = new StubbedCallback(); |
| |
| try { |
| mRulesManagerService.requestInstall(parcelFileDescriptor, tokenBytes, callback); |
| fail(); |
| } catch (NullPointerException expected) {} |
| |
| // Assert nothing async was enqueued. |
| mFakeExecutor.assertNothingQueued(); |
| verifyNoInstallerCallsMade(); |
| verifyNoPackageTrackerCallsMade(); |
| verifyNoIntentsSent(); |
| } |
| |
| @Test |
| public void requestInstall_nullCallback() throws Exception { |
| configureCallerHasPermission(); |
| |
| ParcelFileDescriptor parcelFileDescriptor = |
| createParcelFileDescriptor(createArbitraryBytes(1000)); |
| byte[] tokenBytes = createArbitraryTokenBytes(); |
| ICallback callback = null; |
| |
| try { |
| mRulesManagerService.requestInstall(parcelFileDescriptor, tokenBytes, callback); |
| fail(); |
| } catch (NullPointerException expected) {} |
| |
| assertClosed(parcelFileDescriptor); |
| |
| // Assert nothing async was enqueued. |
| mFakeExecutor.assertNothingQueued(); |
| verifyNoInstallerCallsMade(); |
| verifyNoPackageTrackerCallsMade(); |
| verifyNoIntentsSent(); |
| } |
| |
| @Test |
| public void requestInstall_asyncSuccess() throws Exception { |
| configureCallerHasPermission(); |
| |
| ParcelFileDescriptor parcelFileDescriptor = |
| createParcelFileDescriptor(createArbitraryBytes(1000)); |
| |
| CheckToken token = createArbitraryToken(); |
| byte[] tokenBytes = token.toByteArray(); |
| |
| TestCallback callback = new TestCallback(); |
| |
| // Request the install. |
| assertEquals(RulesManager.SUCCESS, |
| mRulesManagerService.requestInstall(parcelFileDescriptor, tokenBytes, callback)); |
| |
| // Assert nothing has happened yet. |
| callback.assertNoResultReceived(); |
| verifyNoInstallerCallsMade(); |
| verifyNoPackageTrackerCallsMade(); |
| verifyNoIntentsSent(); |
| |
| // Set up the installer. |
| configureStageInstallExpectation(TimeZoneDistroInstaller.INSTALL_SUCCESS); |
| |
| // Simulate the async execution. |
| mFakeExecutor.simulateAsyncExecutionOfLastCommand(); |
| |
| assertClosed(parcelFileDescriptor); |
| |
| // Verify the expected calls were made to other components. |
| verifyStageInstallCalled(); |
| verifyPackageTrackerCalled(token, true /* success */); |
| verifyStagedOperationIntentSent(); |
| |
| // Check the callback was called. |
| callback.assertResultReceived(Callback.SUCCESS); |
| } |
| |
| @Test |
| public void requestInstall_nullTokenBytes() throws Exception { |
| configureCallerHasPermission(); |
| |
| ParcelFileDescriptor parcelFileDescriptor = |
| createParcelFileDescriptor(createArbitraryBytes(1000)); |
| |
| TestCallback callback = new TestCallback(); |
| |
| // Request the install. |
| assertEquals(RulesManager.SUCCESS, |
| mRulesManagerService.requestInstall( |
| parcelFileDescriptor, null /* tokenBytes */, callback)); |
| |
| // Assert nothing has happened yet. |
| verifyNoInstallerCallsMade(); |
| callback.assertNoResultReceived(); |
| verifyNoIntentsSent(); |
| |
| // Set up the installer. |
| configureStageInstallExpectation(TimeZoneDistroInstaller.INSTALL_SUCCESS); |
| |
| // Simulate the async execution. |
| mFakeExecutor.simulateAsyncExecutionOfLastCommand(); |
| |
| assertClosed(parcelFileDescriptor); |
| |
| // Verify the expected calls were made to other components. |
| verifyStageInstallCalled(); |
| verifyPackageTrackerCalled(null /* expectedToken */, true /* success */); |
| verifyStagedOperationIntentSent(); |
| |
| // Check the callback was received. |
| callback.assertResultReceived(Callback.SUCCESS); |
| } |
| |
| @Test |
| public void requestInstall_asyncInstallFail() throws Exception { |
| configureCallerHasPermission(); |
| |
| ParcelFileDescriptor parcelFileDescriptor = |
| createParcelFileDescriptor(createArbitraryBytes(1000)); |
| |
| CheckToken token = createArbitraryToken(); |
| byte[] tokenBytes = token.toByteArray(); |
| |
| TestCallback callback = new TestCallback(); |
| |
| // Request the install. |
| assertEquals(RulesManager.SUCCESS, |
| mRulesManagerService.requestInstall(parcelFileDescriptor, tokenBytes, callback)); |
| |
| // Assert nothing has happened yet. |
| verifyNoInstallerCallsMade(); |
| callback.assertNoResultReceived(); |
| verifyNoIntentsSent(); |
| |
| // Set up the installer. |
| configureStageInstallExpectation(TimeZoneDistroInstaller.INSTALL_FAIL_VALIDATION_ERROR); |
| |
| // Simulate the async execution. |
| mFakeExecutor.simulateAsyncExecutionOfLastCommand(); |
| |
| assertClosed(parcelFileDescriptor); |
| |
| // Verify the expected calls were made to other components. |
| verifyStageInstallCalled(); |
| |
| // Validation failure is treated like a successful check: repeating it won't improve things. |
| boolean expectedSuccess = true; |
| verifyPackageTrackerCalled(token, expectedSuccess); |
| |
| // Nothing should be staged, so no intents sent. |
| verifyNoIntentsSent(); |
| |
| // Check the callback was received. |
| callback.assertResultReceived(Callback.ERROR_INSTALL_VALIDATION_ERROR); |
| } |
| |
| @Test |
| public void requestUninstall_operationInProgress() throws Exception { |
| configureCallerHasPermission(); |
| |
| byte[] tokenBytes = createArbitraryTokenBytes(); |
| ICallback callback = new StubbedCallback(); |
| |
| // First request should succeed. |
| assertEquals(RulesManager.SUCCESS, |
| mRulesManagerService.requestUninstall(tokenBytes, callback)); |
| |
| // Something async should be enqueued. Clear it but do not execute it so we can detect the |
| // second request does nothing. |
| mFakeExecutor.getAndResetLastCommand(); |
| |
| // Second request should fail. |
| assertEquals(RulesManager.ERROR_OPERATION_IN_PROGRESS, |
| mRulesManagerService.requestUninstall(tokenBytes, callback)); |
| |
| // Assert nothing async was enqueued. |
| mFakeExecutor.assertNothingQueued(); |
| verifyNoInstallerCallsMade(); |
| verifyNoPackageTrackerCallsMade(); |
| verifyNoIntentsSent(); |
| } |
| |
| @Test |
| public void requestUninstall_badToken() throws Exception { |
| configureCallerHasPermission(); |
| |
| byte[] badTokenBytes = new byte[2]; |
| ICallback callback = new StubbedCallback(); |
| |
| try { |
| mRulesManagerService.requestUninstall(badTokenBytes, callback); |
| fail(); |
| } catch (IllegalArgumentException expected) { |
| } |
| |
| // Assert nothing async was enqueued. |
| mFakeExecutor.assertNothingQueued(); |
| verifyNoInstallerCallsMade(); |
| verifyNoPackageTrackerCallsMade(); |
| verifyNoIntentsSent(); |
| } |
| |
| @Test |
| public void requestUninstall_nullCallback() throws Exception { |
| configureCallerHasPermission(); |
| |
| byte[] tokenBytes = createArbitraryTokenBytes(); |
| ICallback callback = null; |
| |
| try { |
| mRulesManagerService.requestUninstall(tokenBytes, callback); |
| fail(); |
| } catch (NullPointerException expected) {} |
| |
| // Assert nothing async was enqueued. |
| mFakeExecutor.assertNothingQueued(); |
| verifyNoInstallerCallsMade(); |
| verifyNoPackageTrackerCallsMade(); |
| verifyNoIntentsSent(); |
| } |
| |
| @Test |
| public void requestUninstall_asyncSuccess() throws Exception { |
| configureCallerHasPermission(); |
| |
| CheckToken token = createArbitraryToken(); |
| byte[] tokenBytes = token.toByteArray(); |
| |
| TestCallback callback = new TestCallback(); |
| |
| // Request the uninstall. |
| assertEquals(RulesManager.SUCCESS, |
| mRulesManagerService.requestUninstall(tokenBytes, callback)); |
| |
| // Assert nothing has happened yet. |
| callback.assertNoResultReceived(); |
| verifyNoInstallerCallsMade(); |
| verifyNoPackageTrackerCallsMade(); |
| verifyNoIntentsSent(); |
| |
| // Set up the installer. |
| configureStageUninstallExpectation(TimeZoneDistroInstaller.UNINSTALL_SUCCESS); |
| |
| // Simulate the async execution. |
| mFakeExecutor.simulateAsyncExecutionOfLastCommand(); |
| |
| // Verify the expected calls were made to other components. |
| verifyStageUninstallCalled(); |
| verifyPackageTrackerCalled(token, true /* success */); |
| verifyStagedOperationIntentSent(); |
| |
| // Check the callback was called. |
| callback.assertResultReceived(Callback.SUCCESS); |
| } |
| |
| @Test |
| public void requestUninstall_asyncNothingInstalled() throws Exception { |
| configureCallerHasPermission(); |
| |
| CheckToken token = createArbitraryToken(); |
| byte[] tokenBytes = token.toByteArray(); |
| |
| TestCallback callback = new TestCallback(); |
| |
| // Request the uninstall. |
| assertEquals(RulesManager.SUCCESS, |
| mRulesManagerService.requestUninstall(tokenBytes, callback)); |
| |
| // Assert nothing has happened yet. |
| callback.assertNoResultReceived(); |
| verifyNoInstallerCallsMade(); |
| verifyNoPackageTrackerCallsMade(); |
| verifyNoIntentsSent(); |
| |
| // Set up the installer. |
| configureStageUninstallExpectation(TimeZoneDistroInstaller.UNINSTALL_NOTHING_INSTALLED); |
| |
| // Simulate the async execution. |
| mFakeExecutor.simulateAsyncExecutionOfLastCommand(); |
| |
| // Verify the expected calls were made to other components. |
| verifyStageUninstallCalled(); |
| verifyPackageTrackerCalled(token, true /* success */); |
| verifyUnstagedOperationIntentSent(); |
| |
| // Check the callback was called. |
| callback.assertResultReceived(Callback.SUCCESS); |
| } |
| |
| @Test |
| public void requestUninstall_nullTokenBytes() throws Exception { |
| configureCallerHasPermission(); |
| |
| TestCallback callback = new TestCallback(); |
| |
| // Request the uninstall. |
| assertEquals(RulesManager.SUCCESS, |
| mRulesManagerService.requestUninstall(null /* tokenBytes */, callback)); |
| |
| // Assert nothing has happened yet. |
| verifyNoInstallerCallsMade(); |
| callback.assertNoResultReceived(); |
| verifyNoIntentsSent(); |
| |
| // Set up the installer. |
| configureStageUninstallExpectation(TimeZoneDistroInstaller.UNINSTALL_SUCCESS); |
| |
| // Simulate the async execution. |
| mFakeExecutor.simulateAsyncExecutionOfLastCommand(); |
| |
| // Verify the expected calls were made to other components. |
| verifyStageUninstallCalled(); |
| verifyPackageTrackerCalled(null /* expectedToken */, true /* success */); |
| verifyStagedOperationIntentSent(); |
| |
| // Check the callback was received. |
| callback.assertResultReceived(Callback.SUCCESS); |
| } |
| |
| @Test |
| public void requestUninstall_asyncUninstallFail() throws Exception { |
| configureCallerHasPermission(); |
| |
| CheckToken token = createArbitraryToken(); |
| byte[] tokenBytes = token.toByteArray(); |
| |
| TestCallback callback = new TestCallback(); |
| |
| // Request the uninstall. |
| assertEquals(RulesManager.SUCCESS, |
| mRulesManagerService.requestUninstall(tokenBytes, callback)); |
| |
| // Assert nothing has happened yet. |
| verifyNoInstallerCallsMade(); |
| callback.assertNoResultReceived(); |
| verifyNoIntentsSent(); |
| |
| // Set up the installer. |
| configureStageUninstallExpectation(TimeZoneDistroInstaller.UNINSTALL_FAIL); |
| |
| // Simulate the async execution. |
| mFakeExecutor.simulateAsyncExecutionOfLastCommand(); |
| |
| // Verify the expected calls were made to other components. |
| verifyStageUninstallCalled(); |
| verifyPackageTrackerCalled(token, false /* success */); |
| verifyNoIntentsSent(); |
| |
| // Check the callback was received. |
| callback.assertResultReceived(Callback.ERROR_UNKNOWN_FAILURE); |
| } |
| |
| @Test |
| public void requestNothing_operationInProgressOk() throws Exception { |
| configureCallerHasPermission(); |
| |
| // Set up a parallel operation. |
| assertEquals(RulesManager.SUCCESS, |
| mRulesManagerService.requestUninstall(null, new StubbedCallback())); |
| // Something async should be enqueued. Clear it but do not execute it to simulate it still |
| // being in progress. |
| mFakeExecutor.getAndResetLastCommand(); |
| |
| CheckToken token = createArbitraryToken(); |
| byte[] tokenBytes = token.toByteArray(); |
| |
| // Make the call. |
| mRulesManagerService.requestNothing(tokenBytes, true /* success */); |
| |
| // Assert nothing async was enqueued. |
| mFakeExecutor.assertNothingQueued(); |
| |
| // Verify the expected calls were made to other components. |
| verifyPackageTrackerCalled(token, true /* success */); |
| verifyNoInstallerCallsMade(); |
| verifyNoIntentsSent(); |
| } |
| |
| @Test |
| public void requestNothing_badToken() throws Exception { |
| configureCallerHasPermission(); |
| |
| byte[] badTokenBytes = new byte[2]; |
| |
| try { |
| mRulesManagerService.requestNothing(badTokenBytes, true /* success */); |
| fail(); |
| } catch (IllegalArgumentException expected) { |
| } |
| |
| // Assert nothing async was enqueued. |
| mFakeExecutor.assertNothingQueued(); |
| |
| // Assert no other calls were made. |
| verifyNoInstallerCallsMade(); |
| verifyNoPackageTrackerCallsMade(); |
| verifyNoIntentsSent(); |
| } |
| |
| @Test |
| public void requestNothing() throws Exception { |
| configureCallerHasPermission(); |
| |
| CheckToken token = createArbitraryToken(); |
| byte[] tokenBytes = token.toByteArray(); |
| |
| // Make the call. |
| mRulesManagerService.requestNothing(tokenBytes, false /* success */); |
| |
| // Assert everything required was done. |
| verifyNoInstallerCallsMade(); |
| verifyPackageTrackerCalled(token, false /* success */); |
| verifyNoIntentsSent(); |
| } |
| |
| @Test |
| public void requestNothing_nullTokenBytes() throws Exception { |
| configureCallerHasPermission(); |
| |
| // Make the call. |
| mRulesManagerService.requestNothing(null /* tokenBytes */, true /* success */); |
| |
| // Assert everything required was done. |
| verifyNoInstallerCallsMade(); |
| verifyPackageTrackerCalled(null /* token */, true /* success */); |
| verifyNoIntentsSent(); |
| } |
| |
| @Test |
| public void dump_noPermission() throws Exception { |
| when(mMockPermissionHelper.checkDumpPermission(any(String.class), any(PrintWriter.class))) |
| .thenReturn(false); |
| |
| doDumpCallAndCapture(mRulesManagerService, null); |
| verifyZeroInteractions(mMockPackageTracker, mMockTimeZoneDistroInstaller); |
| } |
| |
| @Test |
| public void dump_emptyArgs() throws Exception { |
| doSuccessfulDumpCall(mRulesManagerService, new String[0]); |
| |
| // Verify the package tracker was consulted. |
| verify(mMockPackageTracker).dump(any(PrintWriter.class)); |
| } |
| |
| @Test |
| public void dump_nullArgs() throws Exception { |
| doSuccessfulDumpCall(mRulesManagerService, null); |
| // Verify the package tracker was consulted. |
| verify(mMockPackageTracker).dump(any(PrintWriter.class)); |
| } |
| |
| @Test |
| public void dump_unknownArgs() throws Exception { |
| String dumpedTextUnknownArgs = doSuccessfulDumpCall( |
| mRulesManagerService, new String[] { "foo", "bar"}); |
| |
| // Verify the package tracker was consulted. |
| verify(mMockPackageTracker).dump(any(PrintWriter.class)); |
| |
| String dumpedTextZeroArgs = doSuccessfulDumpCall(mRulesManagerService, null); |
| assertEquals(dumpedTextZeroArgs, dumpedTextUnknownArgs); |
| } |
| |
| @Test |
| public void dump_formatState() throws Exception { |
| // Just expect these to not throw exceptions, not return nothing, and not interact with the |
| // package tracker. |
| doSuccessfulDumpCall(mRulesManagerService, dumpFormatArgs("p")); |
| doSuccessfulDumpCall(mRulesManagerService, dumpFormatArgs("s")); |
| doSuccessfulDumpCall(mRulesManagerService, dumpFormatArgs("c")); |
| doSuccessfulDumpCall(mRulesManagerService, dumpFormatArgs("i")); |
| doSuccessfulDumpCall(mRulesManagerService, dumpFormatArgs("o")); |
| doSuccessfulDumpCall(mRulesManagerService, dumpFormatArgs("t")); |
| doSuccessfulDumpCall(mRulesManagerService, dumpFormatArgs("a")); |
| doSuccessfulDumpCall(mRulesManagerService, dumpFormatArgs("z" /* Unknown */)); |
| doSuccessfulDumpCall(mRulesManagerService, dumpFormatArgs("piscotz")); |
| |
| verifyZeroInteractions(mMockPackageTracker); |
| } |
| |
| private static String[] dumpFormatArgs(String argsString) { |
| return new String[] { "-format_state", argsString}; |
| } |
| |
| private String doSuccessfulDumpCall(RulesManagerService rulesManagerService, String[] args) |
| throws Exception { |
| when(mMockPermissionHelper.checkDumpPermission(any(String.class), any(PrintWriter.class))) |
| .thenReturn(true); |
| |
| // Set up the mocks to return (arbitrary) information about the current device state. |
| TzDataSetVersion baseVersion = new TzDataSetVersion( |
| CURRENT_FORMAT_MAJOR_VERSION, CURRENT_FORMAT_MINOR_VERSION, "2017a", |
| 1 /* revision */); |
| when(mMockTimeZoneDistroInstaller.readBaseVersion()).thenReturn(baseVersion); |
| DistroVersion installedDistroVersion = new DistroVersion( |
| CURRENT_FORMAT_MAJOR_VERSION, CURRENT_FORMAT_MINOR_VERSION, "2017b", |
| 4 /* revision */); |
| when(mMockTimeZoneDistroInstaller.getInstalledDistroVersion()) |
| .thenReturn(installedDistroVersion); |
| DistroVersion stagedDistroVersion = new DistroVersion( |
| CURRENT_FORMAT_MAJOR_VERSION, CURRENT_FORMAT_MINOR_VERSION, "2017c", |
| 7 /* revision */); |
| when(mMockTimeZoneDistroInstaller.getStagedDistroOperation()).thenReturn( |
| StagedDistroOperation.install(stagedDistroVersion)); |
| |
| // Do the dump call. |
| String dumpedOutput = doDumpCallAndCapture(rulesManagerService, args); |
| |
| assertFalse(dumpedOutput.isEmpty()); |
| |
| return dumpedOutput; |
| } |
| |
| private static String doDumpCallAndCapture( |
| RulesManagerService rulesManagerService, String[] args) throws IOException { |
| File file = File.createTempFile("dump", null); |
| try { |
| try (FileOutputStream fos = new FileOutputStream(file)) { |
| FileDescriptor fd = fos.getFD(); |
| rulesManagerService.dump(fd, args); |
| } |
| return IoUtils.readFileAsString(file.getAbsolutePath()); |
| } finally { |
| file.delete(); |
| } |
| } |
| |
| private void verifyNoPackageTrackerCallsMade() { |
| verifyNoMoreInteractions(mMockPackageTracker); |
| reset(mMockPackageTracker); |
| } |
| |
| private void verifyPackageTrackerCalled( |
| CheckToken expectedCheckToken, boolean expectedSuccess) { |
| verify(mMockPackageTracker).recordCheckResult(expectedCheckToken, expectedSuccess); |
| reset(mMockPackageTracker); |
| } |
| |
| private void verifyNoIntentsSent() { |
| verifyNoMoreInteractions(mMockIntentHelper); |
| reset(mMockIntentHelper); |
| } |
| |
| private void verifyStagedOperationIntentSent() { |
| verify(mMockIntentHelper).sendTimeZoneOperationStaged(); |
| reset(mMockIntentHelper); |
| } |
| |
| private void verifyUnstagedOperationIntentSent() { |
| verify(mMockIntentHelper).sendTimeZoneOperationUnstaged(); |
| reset(mMockIntentHelper); |
| } |
| |
| private void configureCallerHasPermission() throws Exception { |
| doNothing() |
| .when(mMockPermissionHelper) |
| .enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION); |
| } |
| |
| private void configureCallerDoesNotHaveUpdatePermission() { |
| doThrow(new SecurityException("Simulated permission failure")) |
| .when(mMockPermissionHelper) |
| .enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION); |
| } |
| |
| private void configureCallerDoesNotHaveQueryPermission() { |
| doThrow(new SecurityException("Simulated permission failure")) |
| .when(mMockPermissionHelper) |
| .enforceCallerHasPermission(REQUIRED_QUERY_PERMISSION); |
| } |
| |
| private void configureStageInstallExpectation(int resultCode) |
| throws Exception { |
| when(mMockTimeZoneDistroInstaller.stageInstallWithErrorCode(any(TimeZoneDistro.class))) |
| .thenReturn(resultCode); |
| } |
| |
| private void configureStageUninstallExpectation(int resultCode) throws Exception { |
| doReturn(resultCode).when(mMockTimeZoneDistroInstaller).stageUninstall(); |
| } |
| |
| private void verifyStageInstallCalled() throws Exception { |
| verify(mMockTimeZoneDistroInstaller).stageInstallWithErrorCode(any(TimeZoneDistro.class)); |
| verifyNoMoreInteractions(mMockTimeZoneDistroInstaller); |
| reset(mMockTimeZoneDistroInstaller); |
| } |
| |
| private void verifyStageUninstallCalled() throws Exception { |
| verify(mMockTimeZoneDistroInstaller).stageUninstall(); |
| verifyNoMoreInteractions(mMockTimeZoneDistroInstaller); |
| reset(mMockTimeZoneDistroInstaller); |
| } |
| |
| private void verifyNoInstallerCallsMade() { |
| verifyNoMoreInteractions(mMockTimeZoneDistroInstaller); |
| reset(mMockTimeZoneDistroInstaller); |
| } |
| |
| private static byte[] createArbitraryBytes(int length) { |
| byte[] bytes = new byte[length]; |
| for (int i = 0; i < length; i++) { |
| bytes[i] = (byte) i; |
| } |
| return bytes; |
| } |
| |
| private byte[] createArbitraryTokenBytes() { |
| return createArbitraryToken().toByteArray(); |
| } |
| |
| private CheckToken createArbitraryToken() { |
| return new CheckToken(1, new PackageVersions(1, 1)); |
| } |
| |
| private void configureDeviceBaseVersion(String baseRulesVersion) throws Exception { |
| TzDataSetVersion tzDataSetVersion = new TzDataSetVersion( |
| CURRENT_FORMAT_MAJOR_VERSION, CURRENT_FORMAT_MINOR_VERSION, baseRulesVersion, |
| 1 /* revision */); |
| when(mMockTimeZoneDistroInstaller.readBaseVersion()).thenReturn(tzDataSetVersion); |
| } |
| |
| private void configureInstalledDistroVersion(@Nullable DistroVersion installedDistroVersion) |
| throws Exception { |
| when(mMockTimeZoneDistroInstaller.getInstalledDistroVersion()) |
| .thenReturn(installedDistroVersion); |
| } |
| |
| private void configureStagedInstall(DistroVersion stagedDistroVersion) throws Exception { |
| when(mMockTimeZoneDistroInstaller.getStagedDistroOperation()) |
| .thenReturn(StagedDistroOperation.install(stagedDistroVersion)); |
| } |
| |
| private void configureStagedUninstall() throws Exception { |
| when(mMockTimeZoneDistroInstaller.getStagedDistroOperation()) |
| .thenReturn(StagedDistroOperation.uninstall()); |
| } |
| |
| private void configureNoStagedOperation() throws Exception { |
| when(mMockTimeZoneDistroInstaller.getStagedDistroOperation()).thenReturn(null); |
| } |
| |
| private void configureDeviceCannotReadStagedDistroOperation() throws Exception { |
| when(mMockTimeZoneDistroInstaller.getStagedDistroOperation()) |
| .thenThrow(new IOException("Simulated failure")); |
| } |
| |
| private void configureDeviceCannotReadBaseVersion() throws Exception { |
| when(mMockTimeZoneDistroInstaller.readBaseVersion()) |
| .thenThrow(new IOException("Simulated failure")); |
| } |
| |
| private void configureDeviceCannotReadInstalledDistroVersion() throws Exception { |
| when(mMockTimeZoneDistroInstaller.getInstalledDistroVersion()) |
| .thenThrow(new IOException("Simulated failure")); |
| } |
| |
| private static void assertClosed(ParcelFileDescriptor parcelFileDescriptor) { |
| assertFalse(parcelFileDescriptor.getFileDescriptor().valid()); |
| } |
| |
| private static class FakeExecutor implements Executor { |
| |
| private Runnable mLastCommand; |
| |
| @Override |
| public void execute(Runnable command) { |
| assertNull(mLastCommand); |
| assertNotNull(command); |
| mLastCommand = command; |
| } |
| |
| public Runnable getAndResetLastCommand() { |
| assertNotNull(mLastCommand); |
| Runnable toReturn = mLastCommand; |
| mLastCommand = null; |
| return toReturn; |
| } |
| |
| public void simulateAsyncExecutionOfLastCommand() { |
| Runnable toRun = getAndResetLastCommand(); |
| toRun.run(); |
| } |
| |
| public void assertNothingQueued() { |
| assertNull(mLastCommand); |
| } |
| } |
| |
| private static class TestCallback extends ICallback.Stub { |
| |
| private boolean mOnFinishedCalled; |
| private int mLastError; |
| |
| @Override |
| public void onFinished(int error) { |
| assertFalse(mOnFinishedCalled); |
| mOnFinishedCalled = true; |
| mLastError = error; |
| } |
| |
| public void assertResultReceived(int expectedResult) { |
| assertTrue(mOnFinishedCalled); |
| assertEquals(expectedResult, mLastError); |
| } |
| |
| public void assertNoResultReceived() { |
| assertFalse(mOnFinishedCalled); |
| } |
| } |
| |
| private static class StubbedCallback extends ICallback.Stub { |
| @Override |
| public void onFinished(int error) { |
| fail("Unexpected call"); |
| } |
| } |
| |
| private static ParcelFileDescriptor createParcelFileDescriptor(byte[] bytes) |
| throws IOException { |
| File file = File.createTempFile("pfd", null); |
| try (FileOutputStream fos = new FileOutputStream(file)) { |
| fos.write(bytes); |
| } |
| ParcelFileDescriptor pfd = |
| ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); |
| // This should now be safe to delete. The ParcelFileDescriptor has an open fd. |
| file.delete(); |
| return pfd; |
| } |
| } |