blob: 5688d55081d78a6c6e724d762123691e09610265 [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.file.integration.downloader;
import static com.google.android.libraries.mobiledatadownload.file.common.testing.StreamUtils.makeArrayOfBytesContent;
import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.SECONDS;
import android.content.SharedPreferences;
import android.net.Uri;
import androidx.test.core.app.ApplicationProvider;
import com.google.android.downloader.DownloadDestination;
import com.google.android.downloader.DownloadMetadata;
import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage;
import com.google.android.libraries.mobiledatadownload.file.backends.JavaFileBackend;
import com.google.android.libraries.mobiledatadownload.file.common.testing.TemporaryUri;
import com.google.android.libraries.mobiledatadownload.file.openers.ReadByteArrayOpener;
import com.google.android.libraries.mobiledatadownload.file.openers.WriteByteArrayOpener;
import com.google.common.collect.ImmutableList;
import com.google.common.primitives.Bytes;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import java.nio.ByteBuffer;
import java.nio.channels.WritableByteChannel;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeoutException;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.ParameterizedRobolectricTestRunner;
import org.robolectric.ParameterizedRobolectricTestRunner.Parameters;
@RunWith(ParameterizedRobolectricTestRunner.class)
public final class DownloadDestinationOpenerTest {
private static final long TIMEOUT = 3L;
private static final byte[] CONTENT = makeArrayOfBytesContent();
private static final ListeningExecutorService EXECUTOR_SERVICE =
MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
private SynchronousFileStorage storage;
private final Implementation implUnderTest;
private SharedPreferences downloadMetadataSp;
@Rule public TemporaryUri tmpUri = new TemporaryUri();
/* Run the same test suite on multiple implementations of the same interface. */
private enum Implementation {
SHARED_PREFERENCES
}
@Parameters(name = "implementation={0}")
public static ImmutableList<Object[]> data() {
return ImmutableList.of(new Object[] {Implementation.SHARED_PREFERENCES});
}
public DownloadDestinationOpenerTest(Implementation impl) {
this.implUnderTest = impl;
}
@Before
public void setUp() throws Exception {
storage = new SynchronousFileStorage(ImmutableList.of(new JavaFileBackend()));
downloadMetadataSp =
ApplicationProvider.getApplicationContext().getSharedPreferences("prefs", 0);
}
@Test
public void opener_withNoExistingData_createsNewMetadata() throws Exception {
Uri fileUri = tmpUri.newUri();
DownloadMetadata emptyMetadata = DownloadMetadata.create("", 0);
// Create destination.
DownloadMetadataStore store = createMetadataStore();
DownloadDestinationOpener opener = DownloadDestinationOpener.create(store);
DownloadDestination destination = storage.open(fileUri, opener);
// Asset that destination has initial, empty values.
assertThat(destination.numExistingBytes()).isEqualTo(0);
assertThat(destination.readMetadata()).isEqualTo(emptyMetadata);
}
@Test
public void opener_withNoExistingData_writes() throws Exception {
Uri fileUri = tmpUri.newUri();
// Set up data and metadata to write.
ByteBuffer buffer = ByteBuffer.allocate(CONTENT.length);
buffer.put(CONTENT);
buffer.position(0);
DownloadMetadata metadataToWrite = DownloadMetadata.create("test", 10);
// Create destination and write data/metadata.
DownloadMetadataStore store = createMetadataStore();
DownloadDestinationOpener opener = DownloadDestinationOpener.create(store);
DownloadDestination destination = storage.open(fileUri, opener);
try (WritableByteChannel writeChannel = destination.openByteChannel(0, metadataToWrite)) {
writeChannel.write(buffer);
}
// Read back data to ensure the write completed properly.
ReadByteArrayOpener readOpener = ReadByteArrayOpener.create();
byte[] readContent = storage.open(fileUri, readOpener);
assertThat(readContent).hasLength(CONTENT.length);
assertThat(readContent).isEqualTo(CONTENT);
// Assert that destination now reflects the latest state.
assertThat(destination.numExistingBytes()).isEqualTo(CONTENT.length);
assertThat(destination.readMetadata()).isEqualTo(metadataToWrite);
}
@Test
public void opener_withExistingData_usesExistingMetadata() throws Exception {
Uri fileUri = tmpUri.newUri();
DownloadMetadata expectedMetadata = DownloadMetadata.create("test", 10);
// Set up file with existing data and existing metadata in store.
Void unused = storage.open(fileUri, WriteByteArrayOpener.create(CONTENT));
writeToMetadataStore(fileUri.toString(), expectedMetadata);
// Create destination.
DownloadMetadataStore store = createMetadataStore();
DownloadDestinationOpener opener = DownloadDestinationOpener.create(store);
DownloadDestination destination = storage.open(fileUri, opener);
// Assert that destination now reflects the latest state.
assertThat(destination.numExistingBytes()).isEqualTo(CONTENT.length);
assertThat(destination.readMetadata()).isEqualTo(expectedMetadata);
}
@Test
public void opener_withExistingData_writes() throws Exception {
Uri fileUri = tmpUri.newUri();
byte[] newContent = makeArrayOfBytesContent();
byte[] expectedContent = Bytes.concat(CONTENT, newContent);
// Set up file with existing data and existing metadata in store
Void unused = storage.open(fileUri, WriteByteArrayOpener.create(CONTENT));
writeToMetadataStore(fileUri.toString(), DownloadMetadata.create("initial", 5L));
// Set up data/metadata to write.
ByteBuffer buffer = ByteBuffer.allocate(newContent.length);
buffer.put(newContent);
buffer.position(0);
DownloadMetadata metadataToWrite = DownloadMetadata.create("test", 10);
// Create destination and write data/metadata.
DownloadMetadataStore store = createMetadataStore();
DownloadDestinationOpener opener = DownloadDestinationOpener.create(store);
DownloadDestination destination = storage.open(fileUri, opener);
try (WritableByteChannel writeChannel =
destination.openByteChannel(destination.numExistingBytes(), metadataToWrite)) {
writeChannel.write(buffer);
}
// Read back data to ensure the write completed properly.
byte[] readContent = storage.open(fileUri, ReadByteArrayOpener.create());
assertThat(readContent).hasLength(expectedContent.length);
assertThat(readContent).isEqualTo(expectedContent);
// Assert that destination now reflects the latest state.
assertThat(destination.numExistingBytes()).isEqualTo(expectedContent.length);
assertThat(destination.readMetadata()).isEqualTo(metadataToWrite);
}
@Test
public void opener_clearsMetadataAndData() throws Exception {
Uri fileUri = tmpUri.newUri();
DownloadMetadata emptyMetadata = DownloadMetadata.create("", 0);
// Set up file with existing data and existing metadata in store.
Void unused = storage.open(fileUri, WriteByteArrayOpener.create(CONTENT));
writeToMetadataStore(fileUri.toString(), DownloadMetadata.create("test", 10L));
// Create destination and clear.
DownloadMetadataStore store = createMetadataStore();
DownloadDestinationOpener opener = DownloadDestinationOpener.create(store);
DownloadDestination destination = storage.open(fileUri, opener);
destination.clear();
// Assert that destination now reflects the latest state.
assertThat(destination.numExistingBytes()).isEqualTo(0);
assertThat(destination.readMetadata()).isEqualTo(emptyMetadata);
}
private DownloadMetadataStore createMetadataStore() throws Exception {
switch (implUnderTest) {
case SHARED_PREFERENCES:
return new SharedPreferencesDownloadMetadata(downloadMetadataSp, EXECUTOR_SERVICE);
}
throw new AssertionError(); // Exhaustive switch
}
private void writeToMetadataStore(String fileUri, DownloadMetadata metadataToWrite)
throws InterruptedException, ExecutionException, TimeoutException {
switch (implUnderTest) {
case SHARED_PREFERENCES:
downloadMetadataSp.edit().clear().commit();
new SharedPreferencesDownloadMetadata(downloadMetadataSp, EXECUTOR_SERVICE)
.upsert(Uri.parse(fileUri), metadataToWrite)
.get(TIMEOUT, SECONDS);
break;
}
}
}