blob: 49e96174b275759e51050c4c20bdbfb4bfad724a [file] [log] [blame]
/*
* Copyright (C) 2018 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.tools.idea.gradle.project.sync.ng;
import com.android.builder.model.*;
import com.android.java.model.GradlePluginModel;
import com.android.tools.idea.flags.StudioFlags;
import com.android.tools.idea.gradle.project.sync.common.CommandLineArgs;
import com.android.tools.idea.gradle.project.sync.errors.SyncErrorHandlerManager;
import com.android.tools.idea.gradle.project.sync.ng.variantonly.VariantOnlyProjectModels;
import com.android.tools.idea.gradle.project.sync.ng.variantonly.VariantOnlyProjectModels.VariantOnlyModuleModel;
import com.android.tools.idea.gradle.project.sync.ng.variantonly.VariantOnlySyncOptions;
import com.android.tools.idea.testing.AndroidGradleTestCase;
import com.android.tools.idea.testing.BuildEnvironment;
import com.google.common.base.Charsets;
import com.google.common.io.Files;
import com.intellij.mock.MockProgressIndicator;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.gradle.settings.DistributionType;
import org.jetbrains.plugins.gradle.settings.GradleProjectSettings;
import org.jetbrains.plugins.gradle.settings.GradleSettings;
import java.io.File;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import static com.android.SdkConstants.FN_BUILD_GRADLE;
import static com.android.SdkConstants.FN_SETTINGS_GRADLE;
import static com.android.tools.idea.gradle.project.sync.Modules.createUniqueModuleId;
import static com.android.tools.idea.testing.AndroidGradleTests.replaceRegexGroup;
import static com.android.tools.idea.testing.TestProjectPaths.*;
import static com.android.utils.FileUtils.join;
import static com.android.utils.FileUtils.writeToFile;
import static com.google.common.truth.Truth.assertThat;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
/**
* Tests for {@link SyncExecutor}.
*/
public class SyncExecutorIntegrationTest extends AndroidGradleTestCase {
@Override
public void setUp() throws Exception {
super.setUp();
StudioFlags.NEW_SYNC_INFRA_ENABLED.override(true);
setUpIdeGradleSettings();
}
private void setUpIdeGradleSettings() {
GradleProjectSettings settings = new GradleProjectSettings();
settings.setDistributionType(DistributionType.DEFAULT_WRAPPED);
settings.setExternalProjectPath(getProjectFolderPath().getPath());
GradleSettings.getInstance(getProject()).setLinkedProjectsSettings(Collections.singleton(settings));
}
@Override
protected void tearDown() throws Exception {
try {
StudioFlags.SINGLE_VARIANT_SYNC_ENABLED.clearOverride();
StudioFlags.NEW_SYNC_INFRA_ENABLED.clearOverride();
StudioFlags.COMPOUND_SYNC_ENABLED.clearOverride();
}
finally {
super.tearDown();
}
}
public void testSyncProjectWithSingleVariantSync() throws Throwable {
StudioFlags.SINGLE_VARIANT_SYNC_ENABLED.override(true);
prepareProjectForImport(SIMPLE_APPLICATION);
Project project = getProject();
// Simulate that "release" variant is selected in "app" module.
SelectedVariantCollectorMock variantCollector = new SelectedVariantCollectorMock(project);
variantCollector.setSelectedVariants("app", "release");
SyncExecutor syncExecutor = new SyncExecutor(project, ExtraGradleSyncModelsManager.getInstance(),
new CommandLineArgs(true /* apply Java library plugin */),
new SyncErrorHandlerManager(project), variantCollector);
SyncListener syncListener = new SyncListener();
syncExecutor.syncProject(new MockProgressIndicator(), syncListener);
syncListener.await();
syncListener.propagateFailureIfAny();
SyncProjectModels models = syncListener.getSyncModels();
Map<String, SyncModuleModels> modelsByModule = indexByModuleName(models.getModuleModels());
assertThat(modelsByModule).hasSize(2);
verifyRequestedVariants(modelsByModule.get("app"), singletonList("release"));
}
public void testSyncProjectWithSingleVariantSyncOnFirstTime() throws Throwable {
StudioFlags.SINGLE_VARIANT_SYNC_ENABLED.override(true);
prepareProjectForImport(TRANSITIVE_DEPENDENCIES);
Project project = getProject();
SyncExecutor syncExecutor = new SyncExecutor(project);
SyncListener syncListener = new SyncListener();
syncExecutor.syncProject(new MockProgressIndicator(), syncListener);
syncListener.await();
syncListener.propagateFailureIfAny();
SyncProjectModels models = syncListener.getSyncModels();
Map<String, SyncModuleModels> modelsByModule = indexByModuleName(models.getModuleModels());
verifyRequestedVariants(modelsByModule.get("app"), singletonList("debug"));
verifyRequestedVariants(modelsByModule.get("library1"), singletonList("debug"));
verifyRequestedVariants(modelsByModule.get("library2"), singletonList("debug"));
}
public void testSyncProjectWithSingleVariantSyncWithRecursiveSelection() throws Throwable {
StudioFlags.SINGLE_VARIANT_SYNC_ENABLED.override(true);
prepareProjectForImport(TRANSITIVE_DEPENDENCIES);
Project project = getProject();
// Simulate that "debug" variant is selected in "app" module.
// "release" is selected in library1 and library2.
SelectedVariantCollectorMock variantCollector = new SelectedVariantCollectorMock(project);
variantCollector.setSelectedVariants("app", "debug");
variantCollector.setSelectedVariants("library1", "release");
variantCollector.setSelectedVariants("library2", "release");
SyncExecutor syncExecutor = new SyncExecutor(project, ExtraGradleSyncModelsManager.getInstance(),
new CommandLineArgs(true /* apply Java library plugin */),
new SyncErrorHandlerManager(project), variantCollector);
SyncListener syncListener = new SyncListener();
syncExecutor.syncProject(new MockProgressIndicator(), syncListener);
syncListener.await();
syncListener.propagateFailureIfAny();
SyncProjectModels models = syncListener.getSyncModels();
Map<String, SyncModuleModels> modelsByModule = indexByModuleName(models.getModuleModels());
// app -> library2 -> library1
// Verify that the variant of library1 and library2 are selected based on app.
verifyRequestedVariants(modelsByModule.get("app"), singletonList("debug"));
verifyRequestedVariants(modelsByModule.get("library1"), singletonList("debug"));
verifyRequestedVariants(modelsByModule.get("library2"), singletonList("debug"));
}
public void testSyncProjectWithSingleVariantSyncWithSelectionConflict() throws Throwable {
StudioFlags.SINGLE_VARIANT_SYNC_ENABLED.override(true);
prepareProjectForImport(TRANSITIVE_DEPENDENCIES);
Project project = getProject();
// Create a new module library3, so that library3 -> library2 -> library1.
File projectFolderPath = getProjectFolderPath();
File buildFile = new File(projectFolderPath, join("library3", FN_BUILD_GRADLE));
String text = "apply plugin: 'com.android.library'\n" +
"android {\n" +
" compileSdkVersion " + BuildEnvironment.getInstance().getCompileSdkVersion() + "\n" +
"}\n" +
"dependencies {\n" +
" api project(':library2')\n" +
"}";
writeToFile(buildFile, text);
File settingsFile = new File(projectFolderPath, FN_SETTINGS_GRADLE);
String contents = Files.asCharSource(settingsFile, Charsets.UTF_8).read().trim();
contents += ", ':library3'";
writeToFile(settingsFile, contents);
// Simulate that "debug" variant is selected in "app" module.
// "release" is selected in "library3" module.
// This will cause variant conflict because app -> library2, and library3 -> library2.
SelectedVariantCollectorMock variantCollector = new SelectedVariantCollectorMock(project);
variantCollector.setSelectedVariants("app", "debug");
variantCollector.setSelectedVariants("library3", "release");
SyncExecutor syncExecutor = new SyncExecutor(project, ExtraGradleSyncModelsManager.getInstance(),
new CommandLineArgs(true /* apply Java library plugin */),
new SyncErrorHandlerManager(project), variantCollector);
SyncListener syncListener = new SyncListener();
syncExecutor.syncProject(new MockProgressIndicator(), syncListener);
syncListener.await();
syncListener.propagateFailureIfAny();
SyncProjectModels models = syncListener.getSyncModels();
Map<String, SyncModuleModels> modelsByModule = indexByModuleName(models.getModuleModels());
// app -> library2 -> library1
// library3 -> library2 -> library1
// Verify that library1 and library2 have both of debug and release variants requested.
verifyRequestedVariants(modelsByModule.get("app"), singletonList("debug"));
verifyRequestedVariants(modelsByModule.get("library3"), singletonList("release"));
verifyRequestedVariants(modelsByModule.get("library1"), asList("debug", "release"));
verifyRequestedVariants(modelsByModule.get("library2"), asList("debug", "release"));
}
public void testVariantOnlySyncWithDynamicApp() throws Throwable {
StudioFlags.SINGLE_VARIANT_SYNC_ENABLED.override(true);
prepareProjectForImport(DYNAMIC_APP);
Project project = getProject();
// Simulate that "release" variant is selected in both modules.
SelectedVariantCollectorMock variantCollector = new SelectedVariantCollectorMock(project);
variantCollector.setSelectedVariants("app", "release");
variantCollector.setSelectedVariants("feature1", "release");
// Request variant only sync for "app" -> "debug".
File buildId = getProjectFolderPath();
VariantOnlySyncOptions options = new VariantOnlySyncOptions(buildId, ":app", "debug");
SyncExecutor syncExecutor = new SyncExecutor(project, ExtraGradleSyncModelsManager.getInstance(),
new CommandLineArgs(false /* don't apply Java library plugin */),
new SyncErrorHandlerManager(project), variantCollector);
SyncListener syncListener = new SyncListener();
syncExecutor.syncProject(new MockProgressIndicator(), syncListener, options);
syncListener.await();
syncListener.propagateFailureIfAny();
VariantOnlyProjectModels models = syncListener.getVariantOnlyModels();
Map<String, VariantOnlyModuleModel> modelsByModuleId =
models.getModuleModels().stream().collect(toMap(VariantOnlyModuleModel::getModuleId, m -> m));
// Verify that feature module was also requested.
verifyRequestedVariants(modelsByModuleId.get(createUniqueModuleId(buildId, ":app")), singletonList("debug"));
verifyRequestedVariants(modelsByModuleId.get(createUniqueModuleId(buildId, ":feature1")), singletonList("debug"));
}
private static void verifyRequestedVariants(@NotNull SyncModuleModels moduleModels, @NotNull List<String> requestedVariants) {
AndroidProject androidProject = moduleModels.findModel(AndroidProject.class);
assertNotNull(androidProject);
assertThat(androidProject.getVariants()).isEmpty();
List<Variant> variants = moduleModels.findModels(Variant.class);
assertNotNull(variants);
assertThat(variants.stream().map(Variant::getName).collect(toList())).containsExactlyElementsIn(requestedVariants);
}
public void testSingleVariantSyncWithOldGradleVersion() throws Throwable {
StudioFlags.SINGLE_VARIANT_SYNC_ENABLED.override(true);
// Use plugin 1.5.0 and Gradle 2.4.0
prepareProjectForImport(PROJECT_WITH1_DOT5);
File projectFolderPath = getProjectFolderPath();
createGradleWrapper(projectFolderPath, "2.4");
File topBuildFilePath = new File(projectFolderPath, "build.gradle");
String contents = Files.asCharSource(topBuildFilePath, Charsets.UTF_8).read();
contents = replaceRegexGroup(contents, "classpath ['\"]com.android.tools.build:gradle:(.+)['\"]", "1.5.0");
// Remove constraint-layout, which was not supported by old plugins.
contents = replaceRegexGroup(contents, "(compile 'com.android.support.constraint:constraint-layout:\\+')", "");
Files.asCharSink(topBuildFilePath, Charsets.UTF_8).write(contents);
Project project = getProject();
// Simulate that "release" variant is selected in "app" module.
SelectedVariantCollectorMock variantCollector = new SelectedVariantCollectorMock(project);
variantCollector.setSelectedVariants("app", "release");
SyncExecutor syncExecutor = new SyncExecutor(project, ExtraGradleSyncModelsManager.getInstance(),
new CommandLineArgs(true /* apply Java library plugin */),
new SyncErrorHandlerManager(project), variantCollector);
SyncListener syncListener = new SyncListener();
syncExecutor.syncProject(new MockProgressIndicator(), syncListener);
syncListener.await();
syncListener.propagateFailureIfAny();
SyncProjectModels models = syncListener.getSyncModels();
Map<String, SyncModuleModels> modelsByModule = indexByModuleName(models.getModuleModels());
assertThat(modelsByModule).hasSize(2);
SyncModuleModels appModels = modelsByModule.get("app");
AndroidProject androidProject = appModels.findModel(AndroidProject.class);
assertNotNull(androidProject);
Collection<Variant> variants = androidProject.getVariants();
assertThat(variants).isNotEmpty();
assertNull(appModels.findModel(Variant.class));
}
@NotNull
private static Map<String, SyncModuleModels> indexByModuleName(@NotNull List<SyncModuleModels> allModuleModels) {
Map<String, SyncModuleModels> modelsByModuleName = new HashMap<>();
for (SyncModuleModels moduleModels : allModuleModels) {
modelsByModuleName.put(moduleModels.getModuleName(), moduleModels);
}
return modelsByModuleName;
}
public void testVariantOnlySyncWithRecursiveSelection() throws Throwable {
prepareProjectForImport(TRANSITIVE_DEPENDENCIES);
Project project = getProject();
SyncExecutor syncExecutor = new SyncExecutor(project, ExtraGradleSyncModelsManager.getInstance(),
new CommandLineArgs(true /* apply Java library plugin */),
new SyncErrorHandlerManager(project), new SelectedVariantCollectorMock(project));
SyncListener syncListener = new SyncListener();
// Request for single-variant sync for library2 with variant "release".
File buildId = getProjectFolderPath();
VariantOnlySyncOptions options = new VariantOnlySyncOptions(buildId, ":library2", "release");
syncExecutor.syncProject(new MockProgressIndicator(), syncListener, options);
syncListener.await();
syncListener.propagateFailureIfAny();
VariantOnlyProjectModels models = syncListener.getVariantOnlyModels();
Map<String, VariantOnlyModuleModel> modelsByModuleId =
models.getModuleModels().stream().collect(toMap(VariantOnlyModuleModel::getModuleId, m -> m));
// app -> library2 -> library1
// Verify that models for app was not requested.
assertNull(modelsByModuleId.get(createUniqueModuleId(buildId, ":app")));
// Verify that models for library1 and library2 are requested based on user selection.
verifyRequestedVariants(modelsByModuleId.get(createUniqueModuleId(buildId, ":library1")), singletonList("release"));
verifyRequestedVariants(modelsByModuleId.get(createUniqueModuleId(buildId, ":library2")), singletonList("release"));
}
public void testGradlePluginModel() throws Throwable {
prepareProjectForImport(SIMPLE_APPLICATION);
Project project = getProject();
SelectedVariantCollectorMock variantCollector = new SelectedVariantCollectorMock(project);
SyncExecutor syncExecutor = new SyncExecutor(project, ExtraGradleSyncModelsManager.getInstance(),
new CommandLineArgs(true /* apply Java library plugin */),
new SyncErrorHandlerManager(project), variantCollector);
SyncListener syncListener = new SyncListener();
syncExecutor.syncProject(new MockProgressIndicator(), syncListener);
syncListener.await();
syncListener.propagateFailureIfAny();
SyncProjectModels models = syncListener.getSyncModels();
for (SyncModuleModels moduleModels : models.getModuleModels()) {
// Verify that GradlePluginModel is available for all Gradle modules.
GradlePluginModel pluginModel = moduleModels.findModel(GradlePluginModel.class);
assertNotNull(pluginModel);
Collection<String> plugins = pluginModel.getGradlePluginList();
// Verify that Java Library Plugin is in the plugin list of each module.
assertThat(plugins).contains("com.android.java.model.builder.JavaLibraryPlugin");
if ("app".equals(moduleModels.getModuleName())) {
// Verify that Android plugin is in the plugin list of app module.
assertThat(plugins).contains("com.android.build.gradle.AppPlugin");
}
}
}
public void testSyncProjectWithCompoundSync() throws Throwable {
StudioFlags.SINGLE_VARIANT_SYNC_ENABLED.override(true);
StudioFlags.COMPOUND_SYNC_ENABLED.override(true);
prepareProjectForImport(SIMPLE_APPLICATION);
Project project = getProject();
// Simulate that "release" variant is selected in "app" module.
SelectedVariantCollectorMock variantCollector = new SelectedVariantCollectorMock(project);
variantCollector.setSelectedVariants("app", "release");
SyncExecutor syncExecutor = new SyncExecutor(project, ExtraGradleSyncModelsManager.getInstance(),
new CommandLineArgs(true /* apply Java library plugin */),
new SyncErrorHandlerManager(project), variantCollector);
SyncListener syncListener = new SyncListener();
syncExecutor.syncProject(new MockProgressIndicator(), syncListener, null, null, null, null, true);
syncListener.await();
syncListener.propagateFailureIfAny();
SyncProjectModels models = syncListener.getSyncModels();
Map<String, SyncModuleModels> modelsByModule = indexByModuleName(models.getModuleModels());
assertThat(modelsByModule).hasSize(2);
verifyRequestedVariants(modelsByModule.get("app"), singletonList("release"));
}
private static void verifyRequestedVariants(@NotNull VariantOnlyModuleModel moduleModels, @NotNull List<String> requestedVariants) {
AndroidProject androidProject = moduleModels.getAndroidProject();
assertNotNull(androidProject);
assertThat(androidProject.getVariants()).isEmpty();
for (String variant : requestedVariants) {
assertTrue(moduleModels.containsVariant(variant));
}
}
public void testSyncProjectWithSingleVariantSyncWithNdkProject() throws Throwable {
StudioFlags.SINGLE_VARIANT_SYNC_ENABLED.override(true);
prepareProjectForImport(HELLO_JNI);
Project project = getProject();
// Simulate that "release" variant is selected in "app" module.
String variant = "x86Release";
String abi = "x86";
SelectedVariantCollectorMock variantCollector = new SelectedVariantCollectorMock(project);
variantCollector.setSelectedVariants("app", variant, abi);
SyncExecutor syncExecutor = new SyncExecutor(project, ExtraGradleSyncModelsManager.getInstance(),
new CommandLineArgs(true /* apply Java library plugin */),
new SyncErrorHandlerManager(project), variantCollector);
SyncListener syncListener = new SyncListener();
syncExecutor.syncProject(new MockProgressIndicator(), syncListener);
syncListener.await();
syncListener.propagateFailureIfAny();
SyncProjectModels models = syncListener.getSyncModels();
Map<String, SyncModuleModels> modelsByModule = indexByModuleName(models.getModuleModels());
assertThat(modelsByModule).hasSize(2);
SyncModuleModels moduleModels = modelsByModule.get("app");
verifyRequestedVariants(moduleModels, singletonList(variant));
NativeAndroidProject nativeProject = moduleModels.findModel(NativeAndroidProject.class);
assertNotNull(nativeProject);
assertThat(nativeProject.getArtifacts()).isEmpty();
List<NativeVariantAbi> variants = moduleModels.findModels(NativeVariantAbi.class);
assertNotNull(variants);
List<NativeArtifact> artifacts = variants.stream()
.flatMap(it -> it.getArtifacts().stream())
.filter(it -> variant.equals(it.getGroupName()) && abi.equals(it.getAbi()))
.collect(toList());
assertNotEmpty(artifacts);
}
public void testVariantOnlySyncWithNdkProject() throws Throwable {
prepareProjectForImport(HELLO_JNI);
Project project = getProject();
SyncExecutor syncExecutor = new SyncExecutor(project, ExtraGradleSyncModelsManager.getInstance(),
new CommandLineArgs(true /* apply Java library plugin */),
new SyncErrorHandlerManager(project), new SelectedVariantCollectorMock(project));
SyncListener syncListener = new SyncListener();
// Request for single-variant sync for app module with variant "x86Release", abi "x86".
String variant = "x86Release";
String abi = "x86";
File buildId = getProjectFolderPath();
VariantOnlySyncOptions options = new VariantOnlySyncOptions(buildId, ":app", variant, abi, false);
syncExecutor.syncProject(new MockProgressIndicator(), syncListener, options);
syncListener.await();
syncListener.propagateFailureIfAny();
VariantOnlyProjectModels models = syncListener.getVariantOnlyModels();
Map<String, VariantOnlyModuleModel> modelsByModuleId =
models.getModuleModels().stream().collect(toMap(VariantOnlyModuleModel::getModuleId, m -> m));
VariantOnlyModuleModel moduleModels = modelsByModuleId.get(createUniqueModuleId(buildId, ":app"));
// Verify that variant x86Release is requested for Android variant.
verifyRequestedVariants(moduleModels, singletonList(variant));
// Verify that variant x86Release, and abi x86 is requested for NativeVariantAbi.
NativeVariantAbi nativeVariantAbi = moduleModels.getNativeVariantAbi().model;
assertNotNull(nativeVariantAbi);
List<NativeArtifact> artifacts = nativeVariantAbi.getArtifacts().stream()
.filter(it -> variant.equals(it.getGroupName()) && abi.equals(it.getAbi()))
.collect(toList());
assertNotEmpty(artifacts);
}
public void testFetchGradleModelsWithSingleVariantSync() throws Throwable {
StudioFlags.SINGLE_VARIANT_SYNC_ENABLED.override(true);
prepareProjectForImport(SIMPLE_APPLICATION);
Project project = getProject();
// Simulate that "release" variant is selected in "app" module.
SelectedVariantCollectorMock variantCollector = new SelectedVariantCollectorMock(project);
variantCollector.setSelectedVariants("app", "release");
SyncExecutor syncExecutor = new SyncExecutor(project, ExtraGradleSyncModelsManager.getInstance(),
new CommandLineArgs(true /* apply Java library plugin */),
new SyncErrorHandlerManager(project), variantCollector);
Map<String, SyncModuleModels> modelsByName = indexByModuleName(syncExecutor.fetchGradleModels(new MockProgressIndicator()));
assertThat(modelsByName).hasSize(2);
SyncModuleModels appModuleModels = modelsByName.get("app");
// Verify that full variants are requested even single-variant sync is enabled.
AndroidProject androidProject = appModuleModels.findModel(AndroidProject.class);
assertNotNull(androidProject);
assertThat(androidProject.getVariants()).hasSize(2);
assertNull(appModuleModels.findModels(Variant.class));
}
private static class SelectedVariantCollectorMock extends SelectedVariantCollector {
@NotNull private final SelectedVariants mySelectedVariants = new SelectedVariants();
@NotNull private final File myProjectFolderPath;
SelectedVariantCollectorMock(@NotNull Project project) {
super(project);
myProjectFolderPath = new File(project.getBasePath());
}
@Override
@NotNull
public SelectedVariants collectSelectedVariants() {
return mySelectedVariants;
}
void setSelectedVariants(@NotNull String moduleName, @NotNull String selectedVariant) {
setSelectedVariants(moduleName, selectedVariant, null);
}
void setSelectedVariants(@NotNull String moduleName, @NotNull String selectedVariant, @Nullable String selectedAbi) {
String moduleId = createUniqueModuleId(myProjectFolderPath, ":" + moduleName);
mySelectedVariants.addSelectedVariant(moduleId, selectedVariant, selectedAbi);
}
}
private static class SyncListener extends SyncExecutionCallback {
@NotNull private final CountDownLatch myCountDownLatch = new CountDownLatch(1);
private boolean myFailed;
SyncListener() {
doWhenDone(() -> myCountDownLatch.countDown());
doWhenRejected(s -> {
myFailed = true;
myCountDownLatch.countDown();
});
}
void await() throws InterruptedException {
myCountDownLatch.await(5, MINUTES);
}
void propagateFailureIfAny() throws Throwable {
if (myFailed) {
Throwable error = getSyncError();
if (error != null) {
throw error;
}
throw new AssertionError("Sync failed - unknown cause");
}
}
}
}