package com.android.server.backup;
+import static android.app.backup.ForwardingBackupAgent.forward;
+
+import static com.android.server.backup.BackupManagerService.PACKAGE_MANAGER_SENTINEL;
import static com.android.server.backup.testing.BackupManagerServiceTestUtils.createBackupWakeLock;
+import static com.android.server.backup.testing.BackupManagerServiceTestUtils.createInitializedBackupManagerService;
import static com.android.server.backup.testing.BackupManagerServiceTestUtils.setUpBackupManagerServiceBasics;
-import static com.android.server.backup.testing.BackupManagerServiceTestUtils.startBackupThreadAndGetLooper;
import static com.android.server.backup.testing.TestUtils.uncheck;
import static com.android.server.backup.testing.TransportData.backupTransport;
import static com.google.common.truth.Truth.assertThat;
+
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toList;
import static org.mockito.ArgumentMatchers.intThat;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import android.app.backup.IBackupManager;
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.IBackupObserver;
+import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.DeadObjectException;
import android.os.Handler;
-import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
import com.android.server.testing.SystemLoaderPackages;
import com.android.server.testing.shadows.ShadowBackupDataInput;
import com.android.server.testing.shadows.ShadowBackupDataOutput;
+
+import com.google.common.truth.IterableSubject;
+
+import java.io.ByteArrayOutputStream;
import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.OutputStream;
import java.nio.file.Files;
-import java.nio.file.Paths;
+import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
import java.util.stream.Stream;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.shadows.ShadowPackageManager;
import org.robolectric.shadows.ShadowQueuedWork;
+// TODO: Don't do backup for full-backup
+// TODO: Don't do backup for stopped
+// TODO: Don't do backup for non-eligible
+// TODO: (performBackup() => SUCCESS, finishBackup() => SUCCESS) => delete stage file, renames
+// state file
+// TODO: Check agent writes state file => check file content
+// TODO: Check agent writes new state file => next agent reads it correctly
+// TODO: Check non-incremental has empty state file
+// TODO: Check queue of 2, transport rejecting package but other package proceeds
+// TODO: Check queue in general, behavior w/ multiple packages
+// TODO: Check quota is passed from transport to agent
+// TODO: Check non-incremental and transport requests PM in queue
+// TODO: Verify initialization
@RunWith(FrameworkRobolectricTestRunner.class)
@Config(
manifest = Config.NONE,
private static final String PACKAGE_1 = "com.example.package1";
private static final String PACKAGE_2 = "com.example.package2";
- @Mock private BackupManagerService mBackupManagerService;
@Mock private TransportManager mTransportManager;
@Mock private DataChangedJournal mDataChangedJournal;
@Mock private IBackupObserver mObserver;
@Mock private IBackupManagerMonitor mMonitor;
@Mock private OnTaskFinishedListener mListener;
+ private BackupManagerService mBackupManagerService;
private TransportData mTransport;
private ShadowLooper mShadowBackupLooper;
- private BackupHandler mBackupHandler;
+ private Handler mBackupHandler;
private PowerManager.WakeLock mWakeLock;
private ShadowPackageManager mShadowPackageManager;
private FakeIBackupManager mBackupManager;
private File mBaseStateDir;
+ private File mDataDir;
private Application mApplication;
+ private Context mContext;
@Before
public void setUp() throws Exception {
mTransport = backupTransport();
mApplication = RuntimeEnvironment.application;
+ mContext = mApplication;
+
File cacheDir = mApplication.getCacheDir();
- mBaseStateDir = new File(cacheDir, "base_state_dir");
- File dataDir = new File(cacheDir, "data_dir");
- assertThat(mBaseStateDir.mkdir()).isTrue();
- assertThat(dataDir.mkdir()).isTrue();
+ // Corresponds to /data/backup
+ mBaseStateDir = new File(cacheDir, "base_state");
+ // Corresponds to /cache/backup_stage
+ mDataDir = new File(cacheDir, "data");
+ // We create here simulating init.rc
+ mDataDir.mkdirs();
+ assertThat(mDataDir.isDirectory()).isTrue();
PackageManager packageManager = mApplication.getPackageManager();
mShadowPackageManager = shadowOf(packageManager);
mWakeLock = createBackupWakeLock(mApplication);
- Looper backupLooper = startBackupThreadAndGetLooper();
- mShadowBackupLooper = shadowOf(backupLooper);
-
- Handler mainHandler = new Handler(Looper.getMainLooper());
- BackupAgentTimeoutParameters agentTimeoutParameters =
- new BackupAgentTimeoutParameters(mainHandler, mApplication.getContentResolver());
- agentTimeoutParameters.start();
-
- // We need to mock BMS timeout parameters before initializing the BackupHandler since
- // the constructor of BackupHandler relies on the timeout parameters.
- when(mBackupManagerService.getAgentTimeoutParameters()).thenReturn(agentTimeoutParameters);
- mBackupHandler = new BackupHandler(mBackupManagerService, backupLooper);
-
mBackupManager = spy(FakeIBackupManager.class);
- BackupManagerConstants constants =
- new BackupManagerConstants(mainHandler, mApplication.getContentResolver());
- constants.start();
-
+ mBackupManagerService =
+ spy(
+ createInitializedBackupManagerService(
+ mContext, mBaseStateDir, mDataDir, mTransportManager));
setUpBackupManagerServiceBasics(
mBackupManagerService,
mApplication,
mTransportManager,
packageManager,
- mBackupHandler,
+ mBackupManagerService.getBackupHandler(),
mWakeLock,
- agentTimeoutParameters);
+ mBackupManagerService.getAgentTimeoutParameters());
when(mBackupManagerService.getBaseStateDir()).thenReturn(mBaseStateDir);
- when(mBackupManagerService.getDataDir()).thenReturn(dataDir);
+ when(mBackupManagerService.getDataDir()).thenReturn(mDataDir);
when(mBackupManagerService.getBackupManagerBinder()).thenReturn(mBackupManager);
- when(mBackupManagerService.getConstants()).thenReturn(constants);
- when(mBackupManagerService.makeMetadataAgent()).thenAnswer(invocation -> createPmAgent());
+
+ mBackupHandler = mBackupManagerService.getBackupHandler();
+ mShadowBackupLooper = shadowOf(mBackupHandler.getLooper());
+ }
+
+ @Test
+ public void testRunTask_whenQueueEmpty() throws Exception {
+ when(mBackupManagerService.getCurrentToken()).thenReturn(0L);
+ TransportMock transportMock = setUpTransport(mTransport);
+ PerformBackupTask task =
+ createPerformBackupTask(
+ transportMock.transportClient, mTransport.transportDirName, true);
+
+ runTask(task);
+
+ assertThat(mBackupManagerService.getPendingInits()).isEmpty();
+ assertThat(mBackupManagerService.isBackupRunning()).isFalse();
+ assertThat(mBackupManagerService.getCurrentOperations().size()).isEqualTo(0);
+ assertThat(mWakeLock.isHeld()).isFalse();
+ assertDirectory(getStateDirectory(mTransport)).isEmpty();
+ assertDirectory(mDataDir.toPath()).isEmpty();
+ verify(transportMock.transport, never()).initializeDevice();
+ verify(transportMock.transport, never()).performBackup(any(), any(), anyInt());
+ verify(transportMock.transport, never()).finishBackup();
+ verify(mDataChangedJournal).delete();
+ verify(mListener).onFinished(any());
+ verify(mObserver, never()).onResult(any(), anyInt());
+ verify(mObserver).backupFinished(BackupManager.SUCCESS);
+ // TODO: Verify set current token?
+ }
+
+ @Test
+ public void testRunTask_whenQueueEmpty_doesNotChangeStateFiles() throws Exception {
+ TransportMock transportMock = setUpTransport(mTransport);
+ PerformBackupTask task =
+ createPerformBackupTask(
+ transportMock.transportClient, mTransport.transportDirName, true);
+ createPmStateFile();
+ Files.write(getStateFile(mTransport, PACKAGE_1), "packageState".getBytes());
+
+ runTask(task);
+
+ assertThat(Files.readAllBytes(getStateFile(mTransport, PACKAGE_MANAGER_SENTINEL))).isEqualTo("pmState".getBytes());
+ assertThat(Files.readAllBytes(getStateFile(mTransport, PACKAGE_1))).isEqualTo("packageState".getBytes());
+ }
+
+ @Test
+ public void testRunTask_whenSinglePackage_aboutAgent() throws Exception {
+ TransportMock transportMock = setUpTransport(mTransport);
+ AgentMock agentMock = setUpAgent(PACKAGE_1);
+ agentOnBackupDo(
+ agentMock,
+ (oldState, dataOutput, newState) -> {
+ writeData(dataOutput, "key", "data".getBytes());
+ writeState(newState, "newState".getBytes());
+ });
+ PerformBackupTask task =
+ createPerformBackupTask(
+ transportMock.transportClient, mTransport.transportDirName, PACKAGE_1);
+
+ runTask(task);
+
+ verify(agentMock.agent).onBackup(any(), any(), any());
+ assertThat(Files.readAllBytes(getStateFile(mTransport, PACKAGE_1)))
+ .isEqualTo("newState".getBytes());
+ }
+
+ @Test
+ public void testRunTask_whenSinglePackage_notifiesCorrectly() throws Exception {
+ TransportMock transportMock = setUpTransport(mTransport);
+ setUpAgentWithData(PACKAGE_1);
+ PerformBackupTask task =
+ createPerformBackupTask(
+ transportMock.transportClient, mTransport.transportDirName, PACKAGE_1);
+
+ runTask(task);
+
+ verify(mBackupManagerService).logBackupComplete(PACKAGE_1);
+ verify(mObserver).onResult(PACKAGE_1, BackupManager.SUCCESS);
+ verify(mListener).onFinished(any());
+ verify(mObserver).backupFinished(BackupManager.SUCCESS);
+ }
+
+ @Test
+ public void testRunTask_whenSinglePackage_releasesWakeLock() throws Exception {
+ TransportMock transportMock = setUpTransport(mTransport);
+ setUpAgentWithData(PACKAGE_1);
+ PerformBackupTask task =
+ createPerformBackupTask(
+ transportMock.transportClient, mTransport.transportDirName, PACKAGE_1);
+
+ runTask(task);
+
+ assertThat(mWakeLock.isHeld()).isFalse();
+ }
+
+ @Test
+ public void testRunTask_whenSinglePackage_updatesBookkeeping() throws Exception {
+ TransportMock transportMock = setUpTransport(mTransport);
+ setUpAgentWithData(PACKAGE_1);
+ PerformBackupTask task =
+ createPerformBackupTask(
+ transportMock.transportClient, mTransport.transportDirName, PACKAGE_1);
+
+ runTask(task);
+
+ assertThat(mBackupManagerService.getPendingInits()).isEmpty();
+ assertThat(mBackupManagerService.isBackupRunning()).isFalse();
+ assertThat(mBackupManagerService.getCurrentOperations().size()).isEqualTo(0);
+ verify(mDataChangedJournal).delete();
+ }
+
+ @Test
+ public void testRunTask_whenSinglePackageIncremental_passesOldStateToAgent() throws Exception {
+ TransportMock transportMock = setUpTransport(mTransport);
+ AgentMock agentMock = setUpAgentWithData(PACKAGE_1);
+ PerformBackupTask task =
+ createPerformBackupTask(
+ transportMock.transportClient,
+ mTransport.transportDirName,
+ false,
+ PACKAGE_1);
+ createPmStateFile();
+ Files.write(getStateFile(mTransport, PACKAGE_1), "oldState".getBytes());
+
+ runTask(task);
+
+ assertThat(agentMock.oldState).isEqualTo("oldState".getBytes());
+ }
+
+ @Test
+ public void testRunTask_whenSinglePackageNonIncremental_passesEmptyOldStateToAgent() throws Exception {
+ TransportMock transportMock = setUpTransport(mTransport);
+ AgentMock agentMock = setUpAgentWithData(PACKAGE_1);
+ PerformBackupTask task =
+ createPerformBackupTask(
+ transportMock.transportClient,
+ mTransport.transportDirName,
+ true,
+ PACKAGE_1);
+ createPmStateFile();
+ Files.write(getStateFile(mTransport, PACKAGE_1), "oldState".getBytes());
+
+ runTask(task);
+
+ assertThat(agentMock.oldState).isEqualTo(new byte[0]);
+ }
+
+ @Test
+ public void testRunTask_whenSinglePackageNonIncremental_doesNotBackUpPm() throws Exception {
+ PackageManagerBackupAgent pmAgent = spy(createPmAgent());
+ when(mBackupManagerService.makeMetadataAgent()).thenReturn(forward(pmAgent));
+ TransportMock transportMock = setUpTransport(mTransport);
+ setUpAgentWithData(PACKAGE_1);
+ PerformBackupTask task =
+ createPerformBackupTask(
+ transportMock.transportClient,
+ mTransport.transportDirName,
+ true,
+ PACKAGE_1);
+
+ runTask(task);
+
+ verify(pmAgent, never()).onBackup(any(), any(), any());
+ }
+
+ @Test
+ public void testRunTask_whenPackageAndPmNonIncremental_backsUpPm() throws Exception {
+ PackageManagerBackupAgent pmAgent = spy(createPmAgent());
+ when(mBackupManagerService.makeMetadataAgent()).thenReturn(forward(pmAgent));
+ TransportMock transportMock = setUpTransport(mTransport);
+ setUpAgentWithData(PACKAGE_1);
+ PerformBackupTask task =
+ createPerformBackupTask(
+ transportMock.transportClient,
+ mTransport.transportDirName,
+ true,
+ PACKAGE_1,
+ PACKAGE_MANAGER_SENTINEL);
+
+ runTask(task);
+
+ verify(pmAgent).onBackup(any(), any(), any());
+ }
+
+ @Test
+ public void testRunTask_whenSinglePackageIncremental_backsUpPm() throws Exception {
+ PackageManagerBackupAgent pmAgent = spy(createPmAgent());
+ when(mBackupManagerService.makeMetadataAgent()).thenReturn(forward(pmAgent));
+ TransportMock transportMock = setUpTransport(mTransport);
+ setUpAgentWithData(PACKAGE_1);
+ PerformBackupTask task =
+ createPerformBackupTask(
+ transportMock.transportClient,
+ mTransport.transportDirName,
+ false,
+ PACKAGE_1);
+
+ runTask(task);
+
+ verify(pmAgent).onBackup(any(), any(), any());
+ }
+
+ @Test
+ public void testRunTask_whenSinglePackageNoPmState_initializesTransport() throws Exception {
+ TransportMock transportMock = setUpTransport(mTransport);
+ setUpAgentWithData(PACKAGE_1);
+ PerformBackupTask task =
+ createPerformBackupTask(
+ transportMock.transportClient, mTransport.transportDirName, PACKAGE_1);
+ Files.deleteIfExists(getStateFile(mTransport, PACKAGE_MANAGER_SENTINEL));
+
+ runTask(task);
+
+ verify(transportMock.transport).initializeDevice();
+ }
+
+ @Test
+ public void testRunTask_whenSinglePackageWithPmState_doesNotInitializeTransport()
+ throws Exception {
+ TransportMock transportMock = setUpTransport(mTransport);
+ setUpAgentWithData(PACKAGE_1);
+ PerformBackupTask task =
+ createPerformBackupTask(
+ transportMock.transportClient, mTransport.transportDirName, PACKAGE_1);
+ createPmStateFile();
+
+ runTask(task);
+
+ verify(transportMock.transport, never()).initializeDevice();
}
@Test
}
@Test
- public void testRunTask_callsListenerAndObserver() throws Exception {
- TransportMock transportMock = setUpTransport(mTransport);
- setUpAgent(PACKAGE_1);
- PerformBackupTask task =
- createPerformBackupTask(
- transportMock.transportClient, mTransport.transportDirName, PACKAGE_1);
-
- runTask(task);
-
- verify(mListener).onFinished(any());
- verify(mObserver).backupFinished(eq(BackupManager.SUCCESS));
- }
-
- @Test
public void testRunTask_releasesWakeLock() throws Exception {
TransportMock transportMock = setUpTransport(mTransport);
setUpAgent(PACKAGE_1);
IBackupTransport transportBinder = transportMock.transport;
AgentMock agentMock = setUpAgent(PACKAGE_1);
agentOnBackupDo(
- agentMock.agent,
+ agentMock,
(oldState, dataOutput, newState) -> {
writeData(dataOutput, "key1", "foo".getBytes());
writeData(dataOutput, "key2", "bar".getBytes());
PerformBackupTask task =
createPerformBackupTask(
transportMock.transportClient, mTransport.transportDirName, PACKAGE_1);
- // We need to verify at call time because the file is deleted right after
+ Path backupDataPath =
+ Files.createTempFile(mContext.getCacheDir().toPath(), "backup", ".tmp");
when(transportBinder.performBackup(argThat(packageInfo(PACKAGE_1)), any(), anyInt()))
- .then(this::mockAndVerifyTransportPerformBackupData);
+ .then(
+ invocation -> {
+ ParcelFileDescriptor backupDataParcelFd = invocation.getArgument(1);
+ FileDescriptor backupDataFd = backupDataParcelFd.getFileDescriptor();
+ Files.copy(
+ new FileInputStream(backupDataFd),
+ backupDataPath,
+ REPLACE_EXISTING);
+ backupDataParcelFd.close();
+ return BackupTransport.TRANSPORT_OK;
+ });
runTask(task);
- // Already verified data in mockAndVerifyPerformBackupData
verify(transportBinder).performBackup(argThat(packageInfo(PACKAGE_1)), any(), anyInt());
- }
-
- private int mockAndVerifyTransportPerformBackupData(InvocationOnMock invocation)
- throws IOException {
- ParcelFileDescriptor data = invocation.getArgument(1);
- // Verifying that what we passed to the transport is what the agent wrote
- BackupDataInput dataInput = new BackupDataInput(data.getFileDescriptor());
+ // Now verify data sent
+ FileInputStream inputStream = new FileInputStream(backupDataPath.toFile());
+ BackupDataInput backupData = new BackupDataInput(inputStream.getFD());
// "key1" => "foo"
- assertThat(dataInput.readNextHeader()).isTrue();
- assertThat(dataInput.getKey()).isEqualTo("key1");
- int size1 = dataInput.getDataSize();
+ assertThat(backupData.readNextHeader()).isTrue();
+ assertThat(backupData.getKey()).isEqualTo("key1");
+ int size1 = backupData.getDataSize();
byte[] data1 = new byte[size1];
- dataInput.readEntityData(data1, 0, size1);
+ backupData.readEntityData(data1, 0, size1);
assertThat(data1).isEqualTo("foo".getBytes());
// "key2" => "bar"
- assertThat(dataInput.readNextHeader()).isTrue();
- assertThat(dataInput.getKey()).isEqualTo("key2");
- int size2 = dataInput.getDataSize();
+ assertThat(backupData.readNextHeader()).isTrue();
+ assertThat(backupData.getKey()).isEqualTo("key2");
+ int size2 = backupData.getDataSize();
byte[] data2 = new byte[size2];
- dataInput.readEntityData(data2, 0, size2);
+ backupData.readEntityData(data2, 0, size2);
assertThat(data2).isEqualTo("bar".getBytes());
// No more
- assertThat(dataInput.readNextHeader()).isFalse();
-
- return BackupTransport.TRANSPORT_OK;
+ assertThat(backupData.readNextHeader()).isFalse();
+ inputStream.close();
}
@Test
TransportMock transportMock = setUpTransport(mTransport);
AgentMock agentMock = setUpAgent(PACKAGE_1);
agentOnBackupDo(
- agentMock.agent,
+ agentMock,
(oldState, dataOutput, newState) -> {
char prohibitedChar = 0xff00;
writeData(dataOutput, prohibitedChar + "key", "foo".getBytes());
AgentMock agentMock1 = agentMocks.get(0);
AgentMock agentMock2 = agentMocks.get(1);
agentOnBackupDo(
- agentMock1.agent,
+ agentMock1,
(oldState, dataOutput, newState) -> {
char prohibitedChar = 0xff00;
writeData(dataOutput, prohibitedChar + "key", "foo".getBytes());
});
agentOnBackupDo(
- agentMock2.agent,
+ agentMock2,
(oldState, dataOutput, newState) -> {
writeData(dataOutput, "key", "bar".getBytes());
});
runTask(task);
+ // Error because it was non-incremental already, so transport can't request it
verify(mObserver).onResult(PACKAGE_1, BackupManager.ERROR_TRANSPORT_ABORTED);
verify(mObserver).backupFinished(BackupManager.ERROR_TRANSPORT_ABORTED);
}
mTransport.transportDirName,
false,
PACKAGE_1);
- // Write content to be incremental
- Files.write(
- Paths.get(mBaseStateDir.getAbsolutePath(), mTransport.transportDirName, PACKAGE_1),
- "existent".getBytes());
+ createPmStateFile();
+ // Write state to be incremental
+ Files.write(getStateFile(mTransport, PACKAGE_1), "oldState".getBytes());
runTask(task);
}
@Test
- public void testRunTask_whenQueueEmpty() throws Exception {
- TransportMock transportMock = setUpTransport(mTransport);
- PerformBackupTask task =
- createPerformBackupTask(
- transportMock.transportClient, mTransport.transportDirName, true);
-
- runTask(task);
-
- verify(mObserver).backupFinished(eq(BackupManager.SUCCESS));
- }
-
- @Test
public void testRunTask_whenIncrementalAndTransportUnavailableDuringPmBackup()
throws Exception {
TransportMock transportMock = setUpTransport(mTransport);
IBackupTransport transportBinder = transportMock.transport;
setUpAgent(PACKAGE_1);
- when(transportBinder.getBackupQuota(
- eq(BackupManagerService.PACKAGE_MANAGER_SENTINEL), anyBoolean()))
+ when(transportBinder.getBackupQuota(eq(PACKAGE_MANAGER_SENTINEL), anyBoolean()))
.thenThrow(DeadObjectException.class);
PerformBackupTask task =
createPerformBackupTask(
}
@Test
- public void testRunTask_whenIncrementalAndAgentFails() throws Exception {
+ public void testRunTask_whenIncrementalAndPmAgentFails() throws Exception {
TransportMock transportMock = setUpTransport(mTransport);
- createFakePmAgent();
+ PackageManagerBackupAgent pmAgent = createThrowingPmAgent();
+ when(mBackupManagerService.makeMetadataAgent()).thenReturn(pmAgent);
PerformBackupTask task =
createPerformBackupTask(
transportMock.transportClient,
private TransportMock setUpTransport(TransportData transport) throws Exception {
TransportMock transportMock =
TransportTestUtils.setUpTransport(mTransportManager, transport);
- File stateDir = new File(mBaseStateDir, transport.transportDirName);
- assertThat(stateDir.mkdir()).isTrue();
+ Files.createDirectories(getStateDirectory(transport));
return transportMock;
}
+ private Path getStateDirectory(TransportData transport) {
+ return mBaseStateDir.toPath().resolve(transport.transportDirName);
+ }
+
+ private Path getStateFile(TransportData transport, String packageName) {
+ return getStateDirectory(transport).resolve(packageName);
+ }
+
private List<AgentMock> setUpAgents(String... packageNames) {
return Stream.of(packageNames).map(this::setUpAgent).collect(toList());
}
spy(IBackupAgent.Stub.asInterface(backupAgent.onBind()));
// Don't crash our only process (in production code this would crash the app, not us)
doNothing().when(backupAgentBinder).fail(any());
- when(mBackupManagerService.bindToAgentSynchronous(
- eq(packageInfo.applicationInfo), anyInt()))
- .thenReturn(backupAgentBinder);
+ doReturn(backupAgentBinder)
+ .when(mBackupManagerService)
+ .bindToAgentSynchronous(eq(packageInfo.applicationInfo), anyInt());
return new AgentMock(backupAgentBinder, backupAgent);
} catch (RemoteException e) {
// Never happens, compiler happy
private AgentMock setUpAgentWithData(String packageName) {
AgentMock agentMock = setUpAgent(packageName);
+
uncheck(
() ->
agentOnBackupDo(
- agentMock.agent,
- (oldState, dataOutput, newState) ->
- writeData(dataOutput, "key", packageName.getBytes())));
+ agentMock,
+ (oldState, dataOutput, newState) -> {
+ writeData(dataOutput, "key", ("data" + packageName).getBytes());
+ writeState(newState, ("state" + packageName).getBytes());
+ }));
return agentMock;
}
* Returns an implementation of PackageManagerBackupAgent that throws RuntimeException in {@link
* BackupAgent#onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor)}
*/
- private PackageManagerBackupAgent createFakePmAgent() {
- PackageManagerBackupAgent fakePmAgent =
- new FakePackageManagerBackupAgent(mApplication.getPackageManager());
- fakePmAgent.attach(mApplication);
- fakePmAgent.onCreate();
- when(mBackupManagerService.makeMetadataAgent()).thenReturn(fakePmAgent);
- return fakePmAgent;
+ private PackageManagerBackupAgent createThrowingPmAgent() {
+ PackageManagerBackupAgent pmAgent =
+ new ThrowingPackageManagerBackupAgent(mApplication.getPackageManager());
+ pmAgent.attach(mApplication);
+ pmAgent.onCreate();
+ return pmAgent;
}
/** Matches {@link PackageInfo} whose package name is {@code packageName}. */
dataOutput.writeEntityData(data, data.length);
}
- private static void agentOnBackupDo(BackupAgent agent, BackupAgentOnBackup function)
+ private static void writeState(ParcelFileDescriptor newState, byte[] state) throws IOException {
+ OutputStream outputStream = new FileOutputStream(newState.getFileDescriptor());
+ outputStream.write(state);
+ outputStream.flush();
+ }
+
+ /** Prevents the states from being reset and transport initialization. */
+ private void createPmStateFile() throws IOException {
+ Files.write(getStateFile(mTransport, PACKAGE_MANAGER_SENTINEL), "pmState".getBytes());
+ }
+
+ /**
+ * Implements {@code function} for {@link BackupAgent#onBackup(ParcelFileDescriptor,
+ * BackupDataOutput, ParcelFileDescriptor)} of {@code agentMock} and populates {@link
+ * AgentMock#oldState}.
+ */
+ private static void agentOnBackupDo(AgentMock agentMock, BackupAgentOnBackup function)
throws Exception {
- doAnswer(function).when(agent).onBackup(any(), any(), any());
+ doAnswer(
+ (BackupAgentOnBackup)
+ (oldState, dataOutput, newState) -> {
+ ByteArrayOutputStream outputStream =
+ new ByteArrayOutputStream();
+ Utils.transferStreamedData(
+ new FileInputStream(oldState.getFileDescriptor()),
+ outputStream);
+ agentMock.oldState = outputStream.toByteArray();
+ function.onBackup(oldState, dataOutput, newState);
+ })
+ .when(agentMock.agent)
+ .onBackup(any(), any(), any());
+ }
+
+ // TODO: Find some implementation? Extract?
+ private static <T> Iterable<T> oneTimeIterable(Iterator<T> iterator) {
+ return () -> iterator;
+ }
+
+ private static IterableSubject<
+ ? extends IterableSubject<?, Path, Iterable<Path>>, Path, Iterable<Path>>
+ assertDirectory(Path directory) throws IOException {
+ return assertThat(oneTimeIterable(Files.newDirectoryStream(directory).iterator()))
+ .named("directory " + directory);
}
@FunctionalInterface
private static class AgentMock {
private final IBackupAgent agentBinder;
private final BackupAgent agent;
+ private byte[] oldState;
private AgentMock(IBackupAgent agentBinder, BackupAgent agent) {
this.agentBinder = agentBinder;
}
}
- private static class FakePackageManagerBackupAgent extends PackageManagerBackupAgent {
- public FakePackageManagerBackupAgent(PackageManager packageMgr) {
+ private static class ThrowingPackageManagerBackupAgent extends PackageManagerBackupAgent {
+ ThrowingPackageManagerBackupAgent(PackageManager packageMgr) {
super(packageMgr);
}
package com.android.server.backup.testing;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
+import android.annotation.Nullable;
import android.app.Application;
import android.app.IActivityManager;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.PowerManager;
import com.android.server.backup.BackupAgentTimeoutParameters;
import com.android.server.backup.BackupManagerService;
+import com.android.server.backup.Trampoline;
import com.android.server.backup.TransportManager;
-import com.android.server.backup.internal.BackupHandler;
+import com.android.server.backup.internal.Operation;
+import org.mockito.stubbing.Answer;
+import org.robolectric.shadows.ShadowLog;
+import org.robolectric.shadows.ShadowLooper;
+import org.robolectric.shadows.ShadowSystemClock;
+
+import java.io.File;
import java.lang.Thread.UncaughtExceptionHandler;
+import java.util.concurrent.atomic.AtomicReference;
/** Test utils for {@link BackupManagerService} and friends. */
public class BackupManagerServiceTestUtils {
- /** Sets up basic mocks for {@link BackupManagerService}. */
+ public static BackupManagerService createInitializedBackupManagerService(
+ Context context, File baseStateDir, File dataDir, TransportManager transportManager) {
+ return createInitializedBackupManagerService(
+ context,
+ startBackupThread(null),
+ baseStateDir,
+ dataDir,
+ transportManager);
+ }
+
+ public static BackupManagerService createInitializedBackupManagerService(
+ Context context,
+ HandlerThread backupThread,
+ File baseStateDir,
+ File dataDir,
+ TransportManager transportManager) {
+ BackupManagerService backupManagerService =
+ new BackupManagerService(
+ context,
+ new Trampoline(context),
+ backupThread,
+ baseStateDir,
+ dataDir,
+ transportManager);
+ ShadowLooper shadowBackupLooper = shadowOf(backupThread.getLooper());
+ shadowBackupLooper.runToEndOfTasks();
+ // Handler instances have their own clock, so advancing looper (with runToEndOfTasks())
+ // above does NOT advance the handlers' clock, hence whenever a handler post messages with
+ // specific time to the looper the time of those messages will be before the looper's time.
+ // To fix this we advance SystemClock as well since that is from where the handlers read
+ // time.
+ ShadowSystemClock.setCurrentTimeMillis(shadowBackupLooper.getScheduler().getCurrentTime());
+ return backupManagerService;
+ }
+
+ /** Sets up basic mocks for {@link BackupManagerService} mock. */
+ @SuppressWarnings("ResultOfMethodCallIgnored")
public static void setUpBackupManagerServiceBasics(
BackupManagerService backupManagerService,
Context context,
TransportManager transportManager,
PackageManager packageManager,
- BackupHandler backupHandler,
+ Handler backupHandler,
PowerManager.WakeLock wakeLock,
BackupAgentTimeoutParameters agentTimeoutParameters) {
+ SparseArray<Operation> operations = new SparseArray<>();
+
when(backupManagerService.getContext()).thenReturn(context);
when(backupManagerService.getTransportManager()).thenReturn(transportManager);
when(backupManagerService.getPackageManager()).thenReturn(packageManager);
when(backupManagerService.getBackupHandler()).thenReturn(backupHandler);
when(backupManagerService.getCurrentOpLock()).thenReturn(new Object());
when(backupManagerService.getQueueLock()).thenReturn(new Object());
- when(backupManagerService.getCurrentOperations()).thenReturn(new SparseArray<>());
+ when(backupManagerService.getCurrentOperations()).thenReturn(operations);
when(backupManagerService.getActivityManager()).thenReturn(mock(IActivityManager.class));
when(backupManagerService.getWakelock()).thenReturn(wakeLock);
when(backupManagerService.getAgentTimeoutParameters()).thenReturn(agentTimeoutParameters);
+
+ AccessorMock backupEnabled = mockAccessor(false);
+ doAnswer(backupEnabled.getter).when(backupManagerService).isBackupEnabled();
+ doAnswer(backupEnabled.setter).when(backupManagerService).setBackupEnabled(anyBoolean());
+
+ AccessorMock backupRunning = mockAccessor(false);
+ doAnswer(backupEnabled.getter).when(backupManagerService).isBackupRunning();
+ doAnswer(backupRunning.setter).when(backupManagerService).setBackupRunning(anyBoolean());
+
+ doAnswer(
+ invocation -> {
+ operations.put(invocation.getArgument(0), invocation.getArgument(1));
+ return null;
+ })
+ .when(backupManagerService)
+ .putOperation(anyInt(), any());
+ doAnswer(
+ invocation -> {
+ int token = invocation.getArgument(0);
+ operations.remove(token);
+ return null;
+ })
+ .when(backupManagerService)
+ .removeOperation(anyInt());
+ }
+
+ /**
+ * Returns one getter {@link Answer<T>} and one setter {@link Answer<T>} to be easily passed to
+ * Mockito mocking facilities.
+ *
+ * @param defaultValue Value returned by the getter if there was no setter call until then.
+ */
+ public static <T> AccessorMock<T> mockAccessor(T defaultValue) {
+ AtomicReference<T> holder = new AtomicReference<>(defaultValue);
+ return new AccessorMock<>(
+ invocation -> holder.get(),
+ invocation -> {
+ holder.set(invocation.getArgument(0));
+ return null;
+ });
}
public static PowerManager.WakeLock createBackupWakeLock(Application application) {
* @return The backup thread.
* @see #startBackupThreadAndGetLooper()
*/
- public static HandlerThread startBackupThread(UncaughtExceptionHandler exceptionHandler) {
+ public static HandlerThread startBackupThread(
+ @Nullable UncaughtExceptionHandler exceptionHandler) {
HandlerThread backupThread = new HandlerThread("backup");
backupThread.setUncaughtExceptionHandler(exceptionHandler);
backupThread.start();
return backupThread;
}
+ /**
+ * Similar to {@link #startBackupThread(UncaughtExceptionHandler)} but logging uncaught
+ * exceptions to logcat.
+ *
+ * @param tag Tag used for logging exceptions.
+ * @return The backup thread.
+ * @see #startBackupThread(UncaughtExceptionHandler)
+ */
+ public static HandlerThread startSilentBackupThread(String tag) {
+ return startBackupThread(
+ (thread, e) ->
+ ShadowLog.e(
+ tag, "Uncaught exception in test thread " + thread.getName(), e));
+ }
+
private BackupManagerServiceTestUtils() {}
+
+ public static class AccessorMock<T> {
+ public Answer<T> getter;
+ public Answer<T> setter;
+
+ private AccessorMock(Answer<T> getter, Answer<T> setter) {
+ this.getter = getter;
+ this.setter = setter;
+ }
+ }
}