blob: 582f412b4bb5aed850fbdcb6cf44b80275d165cb [file] [log] [blame]
/*
* Copyright 2022 Google LLC
*
* 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.google.android.libraries.mobiledatadownload.internal;
import static com.google.common.truth.Truth.assertThat;
import static java.nio.charset.StandardCharsets.UTF_16;
import android.content.Context;
import android.net.Uri;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.libraries.mobiledatadownload.SilentFeedback;
import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage;
import com.google.android.libraries.mobiledatadownload.file.backends.AndroidFileBackend;
import com.google.android.libraries.mobiledatadownload.file.backends.AndroidUri;
import com.google.android.libraries.mobiledatadownload.file.common.testing.FakeFileBackend;
import com.google.android.libraries.mobiledatadownload.file.common.testing.TemporaryUri;
import com.google.android.libraries.mobiledatadownload.file.openers.WriteByteArrayOpener;
import com.google.android.libraries.mobiledatadownload.internal.downloader.MddFileDownloader;
import com.google.android.libraries.mobiledatadownload.internal.experimentation.NoOpDownloadStageManager;
import com.google.android.libraries.mobiledatadownload.internal.logging.EventLogger;
import com.google.android.libraries.mobiledatadownload.internal.util.SymlinkUtil;
import com.google.android.libraries.mobiledatadownload.testing.FakeTimeSource;
import com.google.android.libraries.mobiledatadownload.testing.TestFlags;
import com.google.common.base.Optional;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.mobiledatadownload.internal.MetadataProto.DataFile;
import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal;
import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal.AllowedReaders;
import com.google.mobiledatadownload.internal.MetadataProto.DownloadConditions;
import com.google.mobiledatadownload.internal.MetadataProto.FileStatus;
import com.google.mobiledatadownload.internal.MetadataProto.GroupKey;
import com.google.mobiledatadownload.internal.MetadataProto.NewFileKey;
import com.google.mobiledatadownload.internal.MetadataProto.SharedFile;
import java.util.Arrays;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
/**
* Emulator tests for MDD isolated structures support. This is separate from the other robolectric
* tests because android.os.symlink and android.os.readlink do not work with robolectric.
*/
@RunWith(AndroidJUnit4.class)
public final class MddIsolatedStructuresTest {
private static final String TEST_GROUP = "test-group";
@Rule public TemporaryUri tempUri = new TemporaryUri();
private Context context;
private FileGroupManager fileGroupManager;
private FileGroupsMetadata fileGroupsMetadata;
private SharedFileManager sharedFileManager;
private SharedFilesMetadata sharedFilesMetadata;
private FakeTimeSource testClock;
private SynchronousFileStorage fileStorage;
private FakeFileBackend fakeAndroidFileBackend;
@Mock SilentFeedback mockSilentFeedback;
GroupKey defaultGroupKey;
DataFileGroupInternal defaultFileGroup;
DataFile file;
NewFileKey newFileKey;
SharedFile existingSharedFile;
@Mock MddFileDownloader mockDownloader;
@Mock EventLogger mockLogger;
@Rule public final MockitoRule mockito = MockitoJUnit.rule();
private static final Executor SEQUENTIAL_CONTROL_EXECUTOR =
Executors.newSingleThreadScheduledExecutor();
@Before
public void setUp() throws Exception {
context = ApplicationProvider.getApplicationContext();
testClock = new FakeTimeSource();
TestFlags flags = new TestFlags();
fakeAndroidFileBackend = new FakeFileBackend(AndroidFileBackend.builder(context).build());
fileStorage = new SynchronousFileStorage(Arrays.asList(fakeAndroidFileBackend));
fileGroupsMetadata =
new SharedPreferencesFileGroupsMetadata(
context,
testClock,
mockSilentFeedback,
Optional.absent(),
MoreExecutors.directExecutor());
sharedFilesMetadata =
new SharedPreferencesSharedFilesMetadata(
context, mockSilentFeedback, Optional.absent(), flags);
sharedFileManager =
new SharedFileManager(
context,
mockSilentFeedback,
sharedFilesMetadata,
fileStorage,
mockDownloader,
Optional.absent(),
Optional.absent(),
mockLogger,
flags,
fileGroupsMetadata,
Optional.absent(),
MoreExecutors.directExecutor());
fileGroupManager =
new FileGroupManager(
context,
mockLogger,
mockSilentFeedback,
fileGroupsMetadata,
sharedFileManager,
new FakeTimeSource(),
Optional.absent(),
SEQUENTIAL_CONTROL_EXECUTOR,
Optional.absent(),
fileStorage,
new NoOpDownloadStageManager(),
flags);
defaultGroupKey =
GroupKey.newBuilder()
.setGroupName(TEST_GROUP)
.setOwnerPackage(context.getPackageName())
.build();
defaultFileGroup =
MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder()
.setPreserveFilenamesAndIsolateFiles(true)
.build();
file = defaultFileGroup.getFile(0);
newFileKey = SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS);
existingSharedFile =
SharedFile.newBuilder()
.setFileStatus(FileStatus.DOWNLOAD_COMPLETE)
.setFileName("fileName")
.setAndroidShared(false)
.build();
}
@Test
public void testSymlinkUtil() throws Exception {
Uri targetUri = AndroidUri.builder(context).setRelativePath("targetFile").build();
// Write some data so the target file exists.
fileStorage.open(targetUri, WriteByteArrayOpener.create("some bytes".getBytes(UTF_16)));
Uri linkUri = AndroidUri.builder(context).setRelativePath("linkFile").build();
SymlinkUtil.createSymlink(context, linkUri, targetUri);
// Make sure the symlink points to the original target
assertThat(SymlinkUtil.readSymlink(context, linkUri)).isEqualTo(targetUri);
}
@Test
public void testFileGroupManager_createsIsolatedStructures() throws Exception {
writePendingFileGroup(defaultGroupKey, defaultFileGroup);
sharedFilesMetadata.write(newFileKey, existingSharedFile).get();
Uri onDeviceUri = fileGroupManager.getOnDeviceUri(file, defaultFileGroup).get();
// Actually write something to disk so the symlink points to something.
fileStorage.open(onDeviceUri, WriteByteArrayOpener.create("some content".getBytes(UTF_16)));
// Download the file group so MDD creates the structures
fileGroupManager
.downloadFileGroup(
defaultGroupKey, DownloadConditions.getDefaultInstance(), noCustomValidation())
.get();
Uri isolatedFileUri =
fileGroupManager.getAndVerifyIsolatedFileUri(onDeviceUri, file, defaultFileGroup);
assertThat(SymlinkUtil.readSymlink(context, isolatedFileUri)).isEqualTo(onDeviceUri);
}
@Test
public void testFileGroupManager_getDownloadedFileGroup_returnsNullIfIsolatedStructuresDontExist()
throws Exception {
writePendingFileGroup(defaultGroupKey, defaultFileGroup);
sharedFilesMetadata.write(newFileKey, existingSharedFile).get();
fileGroupManager
.downloadFileGroup(
defaultGroupKey, DownloadConditions.getDefaultInstance(), noCustomValidation())
.get();
Uri onDeviceUri = fileGroupManager.getOnDeviceUri(file, defaultFileGroup).get();
Uri isolatedFileUri =
fileGroupManager.getAndVerifyIsolatedFileUri(onDeviceUri, file, defaultFileGroup);
fileStorage.deleteFile(isolatedFileUri);
assertThat(fileGroupManager.getFileGroup(defaultGroupKey, true).get()).isNull();
}
@Test
public void testFileGroupManager_repairsIsolatedStructuresOnMaintenance() throws Exception {
writePendingFileGroup(defaultGroupKey, defaultFileGroup);
sharedFilesMetadata.write(newFileKey, existingSharedFile).get();
fileGroupManager
.downloadFileGroup(
defaultGroupKey, DownloadConditions.getDefaultInstance(), noCustomValidation())
.get();
Uri onDeviceUri = fileGroupManager.getOnDeviceUri(file, defaultFileGroup).get();
Uri isolatedFileUri =
fileGroupManager.getAndVerifyIsolatedFileUri(onDeviceUri, file, defaultFileGroup);
assertThat(fileGroupManager.getFileGroup(defaultGroupKey, true).get()).isNotNull();
fileStorage.deleteFile(isolatedFileUri);
assertThat(fileGroupManager.getFileGroup(defaultGroupKey, true).get()).isNull();
fileGroupManager.verifyAndAttemptToRepairIsolatedFiles().get();
assertThat(fileGroupManager.getFileGroup(defaultGroupKey, true).get()).isNotNull();
}
private void writePendingFileGroup(GroupKey key, DataFileGroupInternal group) throws Exception {
GroupKey duplicateGroupKey = key.toBuilder().setDownloaded(false).build();
fileGroupsMetadata.write(duplicateGroupKey, group).get();
}
private AsyncFunction<DataFileGroupInternal, Boolean> noCustomValidation() {
return unused -> Futures.immediateFuture(true);
}
}