blob: 2598b7531da18cfc63b43e2a48be3a8cb76e876b [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.android_webview.test;
import static org.chromium.android_webview.test.OnlyRunIn.ProcessMode.SINGLE_PROCESS;
import android.os.ParcelFileDescriptor;
import androidx.test.filters.MediumTest;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.android_webview.common.variations.VariationsUtils;
import org.chromium.android_webview.services.VariationsSeedHolder;
import org.chromium.android_webview.test.util.VariationsTestUtils;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.components.variations.firstrun.VariationsSeedFetcher.SeedInfo;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* Test VariationsSeedHolder.
*/
@RunWith(AwJUnit4ClassRunner.class)
@OnlyRunIn(SINGLE_PROCESS)
public class VariationsSeedHolderTest {
private class TestHolder extends VariationsSeedHolder {
private final CallbackHelper mWriteFinished; // notified after each writeSeedIfNewer
private final CallbackHelper mUpdateFinished; // notified after each updateSeed
public TestHolder() {
mWriteFinished = new CallbackHelper();
mUpdateFinished = new CallbackHelper();
}
public TestHolder(CallbackHelper writeFinished) {
mWriteFinished = writeFinished;
mUpdateFinished = new CallbackHelper();
}
// Don't schedule the seed download job.
@Override
public void scheduleFetchIfNeeded() {}
@Override
public void onWriteFinished() {
mWriteFinished.notifyCalled();
}
public void writeSeedIfNewerBlocking(File destination, long date)
throws IOException, TimeoutException {
ParcelFileDescriptor fd = null;
try {
fd = ParcelFileDescriptor.open(destination, ParcelFileDescriptor.MODE_WRITE_ONLY);
int calls = mWriteFinished.getCallCount();
writeSeedIfNewer(fd, date);
mWriteFinished.waitForCallback(calls);
} finally {
if (fd != null) fd.close();
}
}
public void updateSeedBlocking(SeedInfo newSeed) throws TimeoutException {
int calls = mUpdateFinished.getCallCount();
updateSeed(newSeed, /*onFinished=*/() -> mUpdateFinished.notifyCalled());
mUpdateFinished.waitForCallback(calls);
}
}
@Before
public void setUp() throws IOException {
VariationsTestUtils.deleteSeeds();
}
@After
public void tearDown() throws IOException {
VariationsTestUtils.deleteSeeds();
}
// Request that the seed holder write its current seed to a file when the holder has no seed. No
// write should happen.
@Test
@MediumTest
public void testWriteNoSeed() throws IOException, TimeoutException {
TestHolder holder = new TestHolder();
File file = null;
try {
file = File.createTempFile("seed", null, null);
holder.writeSeedIfNewerBlocking(file, Long.MIN_VALUE);
Assert.assertEquals(0L, file.length());
} finally {
if (file != null) file.delete();
}
}
// Test updating the holder with the mock seed, and then request the holder writes that seed to
// an empty file. The written seed should match the mock seed.
@Test
@MediumTest
public void testUpdateAndWriteToEmptySeed() throws IOException, TimeoutException {
try {
TestHolder holder = new TestHolder();
holder.updateSeedBlocking(VariationsTestUtils.createMockSeed());
File file = null;
try {
file = File.createTempFile("seed", null, null);
holder.writeSeedIfNewerBlocking(file, Long.MIN_VALUE);
SeedInfo readSeed = VariationsUtils.readSeedFile(file);
VariationsTestUtils.assertSeedsEqual(
VariationsTestUtils.createMockSeed(), readSeed);
} finally {
if (file != null) file.delete();
}
} finally {
VariationsTestUtils.deleteSeeds(); // Remove the holder's saved seed.
}
}
// Test updating the holder with the mock seed, and then request the holder writes that seed to
// a file. Pretend the file already contains a seed, but it's out of date, so writing should
// proceed. The written seed should match the mock seed.
@Test
@MediumTest
public void testUpdateAndWriteToStaleSeed() throws IOException, TimeoutException {
try {
SeedInfo mockSeed = VariationsTestUtils.createMockSeed();
long mockDateMinusOneDay = mockSeed.date - TimeUnit.DAYS.toMillis(1);
TestHolder holder = new TestHolder();
holder.updateSeedBlocking(mockSeed);
File file = null;
try {
file = File.createTempFile("seed", null, null);
holder.writeSeedIfNewerBlocking(file, mockDateMinusOneDay);
SeedInfo readSeed = VariationsUtils.readSeedFile(file);
VariationsTestUtils.assertSeedsEqual(mockSeed, readSeed);
} finally {
if (file != null) file.delete();
}
} finally {
VariationsTestUtils.deleteSeeds(); // Remove the holder's saved seed.
}
}
// Test updating the holder with the mock seed, and then request the holder writes that seed to
// a file. Pretend the file already contains an up-to-date seed, so no write should happen.
@Test
@MediumTest
public void testUpdateAndWriteToFreshSeed() throws IOException, TimeoutException {
try {
SeedInfo mockSeed = VariationsTestUtils.createMockSeed();
TestHolder holder = new TestHolder();
holder.updateSeedBlocking(mockSeed);
File file = null;
try {
file = File.createTempFile("seed", null, null);
holder.writeSeedIfNewerBlocking(file, mockSeed.date);
Assert.assertEquals(0L, file.length());
} finally {
if (file != null) file.delete();
}
} finally {
VariationsTestUtils.deleteSeeds(); // Remove the holder's saved seed.
}
}
@Test
@MediumTest
public void testConcurrentUpdatesAndWrites()
throws IOException, FileNotFoundException, TimeoutException {
ArrayList<File> files = new ArrayList<>();
try {
ArrayList<ParcelFileDescriptor> fds = new ArrayList<>();
try {
// Create a series of mock seeds (5 chosen arbitrarily) which will be "downloaded"
// to the holder via updateSeed. Differentiate the seeds by filling each seedData
// field with the index of the seed.
SeedInfo[] mockSeeds = new SeedInfo[5];
for (int i = 0; i < mockSeeds.length; i++) {
mockSeeds[i] = VariationsTestUtils.createMockSeed();
mockSeeds[i].seedData = new byte[100];
Arrays.fill(mockSeeds[i].seedData, (byte) i);
}
// Used to track the completion of every updateSeed and writeSeedIfNewer call.
CallbackHelper callbackHelper = new CallbackHelper();
int callbacksExpected = 0;
// TestHolder will notify callbackHelper whenever a writeSeedIfNewer request
// completes.
TestHolder holder = new TestHolder(callbackHelper);
// "Download" each mock seed to the holder.
for (int i = 0; i < mockSeeds.length; i++) {
callbacksExpected++;
holder.updateSeed(
mockSeeds[i], /*onFinished=*/() -> callbackHelper.notifyCalled());
// Between each "download", schedule a few (3 chosen arbitrarily) requests for
// the seed, creating a new file to receive each request.
for (int write = 0; write < 3; write++) {
File file = File.createTempFile("seed", null, null);
files.add(file);
ParcelFileDescriptor fd = ParcelFileDescriptor.open(
file, ParcelFileDescriptor.MODE_WRITE_ONLY);
fds.add(fd);
callbacksExpected++;
holder.writeSeedIfNewer(fd, Long.MIN_VALUE);
}
}
// Wait for all updateSeed and writeSeedIfNewer calls to finish.
callbackHelper.waitForCallback(0, callbacksExpected);
// Read each requested seed and ensure it's either empty (if the request was
// scheduled before any "downloads") or it matches one of the mock seeds. For the
// requests that got a mock seed, there's no guarantee as to which seed they got.
for (int i = 0; i < files.size(); i++) {
if (files.get(i).length() == 0) continue;
SeedInfo readSeed = VariationsUtils.readSeedFile(files.get(i));
Assert.assertNotNull("Failed reading seed index " + i, readSeed);
boolean match = false;
for (SeedInfo mockSeed : mockSeeds) {
if (Arrays.equals(readSeed.seedData, mockSeed.seedData)) {
match = true;
break;
}
}
Assert.assertTrue("Seed data " + Arrays.toString(readSeed.seedData)
+ " read from seed index " + i
+ " does not match any written data",
match);
}
} finally {
for (ParcelFileDescriptor fd : fds) {
fd.close();
}
}
} finally {
for (File file : files) {
if (!file.delete()) {
throw new IOException("Failed to delete " + file);
}
}
VariationsTestUtils.deleteSeeds(); // Remove the holder's saved seed.
}
}
}