mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-390110 [external-system] new: add API for partial resolve
With resilient sync a project resolve could produce partial project data but finish with the failure status. As a result every status-driven handler (auto-reload, build output, sync status, etc.) treated a sync that actually failed as a successful one. (cherry picked from commit 095c8651c6fbf5b8e160e9a81fa177f69e30fe03) IJ-CR-214557 GitOrigin-RevId: c316a7a7928adf77f1938cc5bf5280e1b3141e08
This commit is contained in:
committed by
intellij-monorepo-bot
parent
7a605c737f
commit
3f12e6c578
+21
-2
@@ -1,10 +1,13 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.platform.externalSystem.testFramework
|
||||
|
||||
import com.intellij.openapi.externalSystem.importing.ImportSpecBuilder
|
||||
import com.intellij.openapi.externalSystem.model.DataNode
|
||||
import com.intellij.openapi.externalSystem.model.ProjectSystemId
|
||||
import com.intellij.openapi.externalSystem.model.project.ProjectData
|
||||
import com.intellij.openapi.externalSystem.service.project.ProjectDataManager
|
||||
import com.intellij.openapi.externalSystem.settings.ExternalProjectSettings
|
||||
import com.intellij.openapi.externalSystem.util.ExternalSystemUtil
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.testFramework.common.DEFAULT_TEST_TIMEOUT
|
||||
import com.intellij.testFramework.common.timeoutRunBlocking
|
||||
@@ -12,11 +15,12 @@ import com.intellij.util.messages.Topic
|
||||
import kotlin.time.Duration
|
||||
|
||||
object ExternalSystemTestUtil {
|
||||
|
||||
@JvmField
|
||||
val TEST_EXTERNAL_SYSTEM_ID: ProjectSystemId = ProjectSystemId("TEST_EXTERNAL_SYSTEM_ID")
|
||||
|
||||
@JvmField
|
||||
val SETTINGS_TOPIC: Topic<TestExternalSystemSettingsListener?> = Topic.create<TestExternalSystemSettingsListener?>(
|
||||
val SETTINGS_TOPIC: Topic<TestExternalSystemSettingsListener?> = Topic.create(
|
||||
"TEST_EXTERNAL_SYSTEM_SETTINGS", TestExternalSystemSettingsListener::class.java
|
||||
)
|
||||
}
|
||||
@@ -24,6 +28,21 @@ object ExternalSystemTestUtil {
|
||||
@JvmOverloads
|
||||
fun importData(dataNode: DataNode<ProjectData>, project: Project, timeout: Duration = DEFAULT_TEST_TIMEOUT) {
|
||||
timeoutRunBlocking(timeout) {
|
||||
ProjectDataManager.getInstance().importData<ProjectData?>(dataNode, project)
|
||||
ProjectDataManager.getInstance().importData(dataNode, project)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun linkProject(
|
||||
project: Project,
|
||||
systemId: ProjectSystemId,
|
||||
projectSettings: ExternalProjectSettings,
|
||||
configure: ImportSpecBuilder.() -> Unit = {},
|
||||
) {
|
||||
ExternalSystemTestObservation.awaitProjectActivity(project) {
|
||||
ExternalSystemUtil.linkExternalProject(
|
||||
projectSettings,
|
||||
ImportSpecBuilder(project, systemId)
|
||||
.apply(configure)
|
||||
)
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.openapi.externalSystem.service.internal;
|
||||
|
||||
import com.intellij.openapi.externalSystem.model.ExternalSystemException;
|
||||
import com.intellij.openapi.externalSystem.model.DataNode;
|
||||
import com.intellij.openapi.externalSystem.model.project.ProjectData;
|
||||
import org.jetbrains.annotations.ApiStatus.Internal;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Marker exception to signal that the resolve failed but still produced result.
|
||||
*
|
||||
* @see ExternalSystemResolveProjectTask#doExecute
|
||||
*/
|
||||
@Internal
|
||||
public class ExternalSystemPartialResolutionException extends ExternalSystemException {
|
||||
|
||||
private final @NotNull DataNode<ProjectData> myPartialProjectData;
|
||||
|
||||
public ExternalSystemPartialResolutionException(@NotNull DataNode<ProjectData> partialProjectData) {
|
||||
myPartialProjectData = partialProjectData;
|
||||
}
|
||||
|
||||
public @NotNull DataNode<ProjectData> getPartialProjectData() {
|
||||
return myPartialProjectData;
|
||||
}
|
||||
}
|
||||
+41
-27
@@ -124,24 +124,11 @@ public class ExternalSystemResolveProjectTask extends AbstractExternalSystemTask
|
||||
}
|
||||
});
|
||||
|
||||
if (projectNode != null) {
|
||||
myExternalProject.set(projectNode);
|
||||
|
||||
var externalSystemManager = ExternalSystemApiUtil.getManager(projectSystemId);
|
||||
assert externalSystemManager != null;
|
||||
|
||||
var externalModulePaths = new HashSet<String>();
|
||||
var moduleNodes = ExternalSystemApiUtil.findAll(projectNode, ProjectKeys.MODULE);
|
||||
for (DataNode<ModuleData> node : moduleNodes) {
|
||||
externalModulePaths.add(node.getData().getLinkedExternalProjectPath());
|
||||
}
|
||||
var ExternalProjectPath = projectNode.getData().getLinkedExternalProjectPath();
|
||||
var externalSystemSettings = externalSystemManager.getSettingsProvider().fun(project);
|
||||
var externalProjectSettings = externalSystemSettings.getLinkedProjectSettings(ExternalProjectPath);
|
||||
if (externalProjectSettings != null && !externalModulePaths.isEmpty()) {
|
||||
externalProjectSettings.setModules(externalModulePaths);
|
||||
}
|
||||
}
|
||||
myExternalProject.set(projectNode);
|
||||
}
|
||||
catch (ExternalSystemPartialResolutionException e) {
|
||||
myExternalProject.set(e.getPartialProjectData());
|
||||
throw e;
|
||||
}
|
||||
finally {
|
||||
activity.finished();
|
||||
@@ -201,15 +188,42 @@ public class ExternalSystemResolveProjectTask extends AbstractExternalSystemTask
|
||||
@Override
|
||||
protected void setState(@NotNull ExternalSystemTaskState state) {
|
||||
super.setState(state);
|
||||
if (state.isStopped() &&
|
||||
// merging existing cache data with the new partial data is not supported yet
|
||||
!(myResolverPolicy != null && myResolverPolicy.isPartialDataResolveAllowed())) {
|
||||
InternalExternalProjectInfo projectInfo =
|
||||
new InternalExternalProjectInfo(getExternalSystemId(), getExternalProjectPath(), myExternalProject.getAndSet(null));
|
||||
final long currentTimeMillis = System.currentTimeMillis();
|
||||
projectInfo.setLastImportTimestamp(currentTimeMillis);
|
||||
projectInfo.setLastSuccessfulImportTimestamp(state == ExternalSystemTaskState.FAILED ? -1 : currentTimeMillis);
|
||||
ProjectDataManagerImpl.getInstance().updateExternalProjectData(getIdeProject(), projectInfo);
|
||||
if (!state.isStopped()) return;
|
||||
// merging existing cache data with the new partial data is not supported yet
|
||||
if (myResolverPolicy != null && myResolverPolicy.isPartialDataResolveAllowed()) return;
|
||||
|
||||
var projectNode = myExternalProject.getAndSet(null);
|
||||
|
||||
setExternalProjectInfo(state, projectNode);
|
||||
if (projectNode != null) {
|
||||
setExternalModulePaths(projectNode);
|
||||
}
|
||||
}
|
||||
|
||||
private void setExternalProjectInfo(@NotNull ExternalSystemTaskState state, @Nullable DataNode<ProjectData> projectNode) {
|
||||
var projectInfo = new InternalExternalProjectInfo(getExternalSystemId(), getExternalProjectPath(), projectNode);
|
||||
var currentTimeMillis = System.currentTimeMillis();
|
||||
projectInfo.setLastImportTimestamp(currentTimeMillis);
|
||||
projectInfo.setLastSuccessfulImportTimestamp(state == ExternalSystemTaskState.FAILED ? -1 : currentTimeMillis);
|
||||
ProjectDataManagerImpl.getInstance().updateExternalProjectData(getIdeProject(), projectInfo);
|
||||
}
|
||||
|
||||
private void setExternalModulePaths(@NotNull DataNode<ProjectData> projectNode) {
|
||||
var projectSystemId = getExternalSystemId();
|
||||
var project = getIdeProject();
|
||||
var externalSystemManager = ExternalSystemApiUtil.getManager(projectSystemId);
|
||||
assert externalSystemManager != null;
|
||||
|
||||
var externalModulePaths = new HashSet<String>();
|
||||
var moduleNodes = ExternalSystemApiUtil.findAll(projectNode, ProjectKeys.MODULE);
|
||||
for (DataNode<ModuleData> node : moduleNodes) {
|
||||
externalModulePaths.add(node.getData().getLinkedExternalProjectPath());
|
||||
}
|
||||
var externalProjectPath = projectNode.getData().getLinkedExternalProjectPath();
|
||||
var externalSystemSettings = externalSystemManager.getSettingsProvider().fun(project);
|
||||
var externalProjectSettings = externalSystemSettings.getLinkedProjectSettings(externalProjectPath);
|
||||
if (externalProjectSettings != null && !externalModulePaths.isEmpty()) {
|
||||
externalProjectSettings.setModules(externalModulePaths);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-2
@@ -21,6 +21,7 @@ import com.intellij.openapi.application.ModalityState;
|
||||
import com.intellij.openapi.components.Service;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.externalSystem.issue.BuildIssueException;
|
||||
import com.intellij.openapi.externalSystem.service.internal.ExternalSystemPartialResolutionException;
|
||||
import com.intellij.openapi.externalSystem.model.LocationAwareExternalSystemException;
|
||||
import com.intellij.openapi.externalSystem.model.ProjectSystemId;
|
||||
import com.intellij.openapi.externalSystem.service.project.manage.ExternalProjectsManagerImpl;
|
||||
@@ -48,6 +49,7 @@ import com.intellij.ui.content.MessageView;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.SmartList;
|
||||
import com.intellij.util.concurrency.ThreadingAssertions;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import com.intellij.util.ui.accessibility.ScreenReader;
|
||||
import com.intellij.util.ui.update.DebouncedUpdates;
|
||||
@@ -189,8 +191,9 @@ public final class ExternalSystemNotificationManager implements Disposable {
|
||||
private static boolean isInternalError(@NotNull Throwable error,
|
||||
@NotNull ProjectSystemId externalSystemId) {
|
||||
if (RemoteUtil.unwrap(error) instanceof BuildIssueException) return false;
|
||||
return ExternalSystemNotificationExtension.EP_NAME.getExtensionList().stream()
|
||||
.anyMatch(extension -> externalSystemId.equals(extension.getTargetExternalSystemId()) && extension.isInternalError(error));
|
||||
if (RemoteUtil.unwrap(error) instanceof ExternalSystemPartialResolutionException) return true;
|
||||
return ContainerUtil.exists(ExternalSystemNotificationExtension.EP_NAME.getExtensionList(), it ->
|
||||
externalSystemId.equals(it.getTargetExternalSystemId()) && it.isInternalError(error));
|
||||
}
|
||||
|
||||
public boolean isNotificationActive(@NotNull Key<String> notificationKey) {
|
||||
|
||||
+31
-14
@@ -50,11 +50,14 @@ import com.intellij.openapi.externalSystem.execution.ExternalSystemExecutionCons
|
||||
import com.intellij.openapi.externalSystem.importing.ImportSpec;
|
||||
import com.intellij.openapi.externalSystem.importing.ImportSpecBuilder;
|
||||
import com.intellij.openapi.externalSystem.issue.BuildIssueException;
|
||||
import com.intellij.openapi.externalSystem.model.DataNode;
|
||||
import com.intellij.openapi.externalSystem.model.ExternalProjectInfo;
|
||||
import com.intellij.openapi.externalSystem.model.ExternalSystemDataKeys;
|
||||
import com.intellij.openapi.externalSystem.model.ExternalSystemException;
|
||||
import com.intellij.openapi.externalSystem.service.internal.ExternalSystemPartialResolutionException;
|
||||
import com.intellij.openapi.externalSystem.model.ProjectSystemId;
|
||||
import com.intellij.openapi.externalSystem.model.execution.ExternalSystemTaskExecutionSettings;
|
||||
import com.intellij.openapi.externalSystem.model.project.ProjectData;
|
||||
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTask;
|
||||
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId;
|
||||
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationEvent;
|
||||
@@ -603,20 +606,7 @@ public final class ExternalSystemUtil {
|
||||
try {
|
||||
var error = resolveProjectTask.getError();
|
||||
if (error == null) {
|
||||
var projectDataManager = ProjectDataManager.getInstance();
|
||||
var externalProjectData = projectDataManager.getExternalProjectData(project, externalSystemId, externalProjectPath);
|
||||
var externalProject = ObjectUtils.doIfNotNull(externalProjectData, it -> it.getExternalProjectStructure());
|
||||
if (externalProject != null) {
|
||||
if (importSpec.shouldCreateDirectoriesForEmptyContentRoots()) {
|
||||
externalProject.putUserData(ContentRootDataService.CREATE_EMPTY_DIRECTORIES, Boolean.TRUE);
|
||||
}
|
||||
if (importSpec.shouldImportProjectData()) {
|
||||
if (importSpec.shouldSelectProjectDataToImport()) {
|
||||
selectProjectDataToImport(project, externalProjectData);
|
||||
}
|
||||
projectDataManager.importData(externalProject, project);
|
||||
}
|
||||
}
|
||||
var externalProject = importProjectData(project, importSpec, externalSystemId, externalProjectPath);
|
||||
if (callback != null) {
|
||||
callback.onSuccess(taskId, externalProject);
|
||||
}
|
||||
@@ -626,6 +616,10 @@ public final class ExternalSystemUtil {
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (error instanceof ExternalSystemPartialResolutionException) {
|
||||
// The resolve failed but produced partial project data.
|
||||
importProjectData(project, importSpec, externalSystemId, externalProjectPath);
|
||||
}
|
||||
if (error instanceof ImportCanceledException) {
|
||||
// stop refresh task
|
||||
return;
|
||||
@@ -667,6 +661,29 @@ public final class ExternalSystemUtil {
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable DataNode<ProjectData> importProjectData(
|
||||
@NotNull Project project,
|
||||
@NotNull ImportSpec importSpec,
|
||||
@NotNull ProjectSystemId externalSystemId,
|
||||
@NotNull String externalProjectPath
|
||||
) {
|
||||
var projectDataManager = ProjectDataManager.getInstance();
|
||||
var externalProjectData = projectDataManager.getExternalProjectData(project, externalSystemId, externalProjectPath);
|
||||
if (externalProjectData == null) return null;
|
||||
var externalProject = externalProjectData.getExternalProjectStructure();
|
||||
if (externalProject == null) return null;
|
||||
if (importSpec.shouldCreateDirectoriesForEmptyContentRoots()) {
|
||||
externalProject.putUserData(ContentRootDataService.CREATE_EMPTY_DIRECTORIES, Boolean.TRUE);
|
||||
}
|
||||
if (importSpec.shouldImportProjectData()) {
|
||||
if (importSpec.shouldSelectProjectDataToImport()) {
|
||||
selectProjectDataToImport(project, externalProjectData);
|
||||
}
|
||||
projectDataManager.importData(externalProject, project);
|
||||
}
|
||||
return externalProject;
|
||||
}
|
||||
|
||||
private static void selectProjectDataToImport(
|
||||
@NotNull Project project,
|
||||
@NotNull ExternalProjectInfo projectInfo
|
||||
|
||||
+383
@@ -0,0 +1,383 @@
|
||||
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.openapi.externalSystem.service.internal
|
||||
|
||||
import com.intellij.execution.configurations.ConfigurationType
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.externalSystem.ExternalSystemManager
|
||||
import com.intellij.openapi.externalSystem.importing.ProjectResolverPolicy
|
||||
import com.intellij.openapi.externalSystem.model.DataNode
|
||||
import com.intellij.openapi.externalSystem.model.ExternalSystemException
|
||||
import com.intellij.openapi.externalSystem.model.ProjectKeys
|
||||
import com.intellij.openapi.externalSystem.model.project.ProjectData
|
||||
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId
|
||||
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListener
|
||||
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskType
|
||||
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskType.EXECUTE_TASK
|
||||
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskType.RESOLVE_PROJECT
|
||||
import com.intellij.openapi.externalSystem.service.execution.AbstractExternalSystemTaskConfigurationType
|
||||
import com.intellij.openapi.externalSystem.service.notification.ExternalSystemProgressNotificationManager
|
||||
import com.intellij.openapi.externalSystem.service.project.ExternalProjectRefreshCallback
|
||||
import com.intellij.openapi.externalSystem.service.project.ExternalSystemProjectResolver
|
||||
import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProvider
|
||||
import com.intellij.openapi.externalSystem.service.project.manage.ExternalProjectsManagerImpl
|
||||
import com.intellij.openapi.externalSystem.service.project.manage.ExternalSystemTaskActivator
|
||||
import com.intellij.openapi.externalSystem.service.project.manage.ProjectDataService
|
||||
import com.intellij.openapi.externalSystem.task.ExternalSystemTaskManager
|
||||
import com.intellij.openapi.externalSystem.testFramework.fixtures.multiProjectFixture
|
||||
import com.intellij.openapi.externalSystem.testFramework.fixtures.projectFixture
|
||||
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
|
||||
import com.intellij.openapi.externalSystem.util.ExternalSystemConstants
|
||||
import com.intellij.openapi.observable.util.whenDisposed
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Computable
|
||||
import com.intellij.openapi.util.io.toCanonicalPath
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.platform.externalSystem.testFramework.ExternalSystemTestUtil.TEST_EXTERNAL_SYSTEM_ID
|
||||
import com.intellij.platform.externalSystem.testFramework.TestExternalProjectSettings
|
||||
import com.intellij.platform.externalSystem.testFramework.TestExternalSystemExecutionSettings
|
||||
import com.intellij.platform.externalSystem.testFramework.TestExternalSystemManager
|
||||
import com.intellij.platform.externalSystem.testFramework.linkProject
|
||||
import com.intellij.platform.externalSystem.testFramework.project
|
||||
import com.intellij.platform.externalSystem.testFramework.toDataNode
|
||||
import com.intellij.platform.testFramework.assertion.collectionAssertion.CollectionAssertions.assertEmpty
|
||||
import com.intellij.platform.testFramework.assertion.collectionAssertion.CollectionAssertions.assertSingle
|
||||
import com.intellij.testFramework.assertions.Assertions.assertThat
|
||||
import com.intellij.testFramework.common.mock.requireImplemented
|
||||
import com.intellij.testFramework.junit5.SystemProperty
|
||||
import com.intellij.testFramework.junit5.TestApplication
|
||||
import com.intellij.testFramework.junit5.TestDisposable
|
||||
import com.intellij.testFramework.junit5.fixture.tempPathFixture
|
||||
import com.intellij.testFramework.junit5.fixture.testFixture
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.assertNotNull
|
||||
import org.junit.jupiter.api.assertNull
|
||||
import java.nio.file.Path
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
@TestApplication
|
||||
@SystemProperty("force.execute.activated.tasks", true.toString())
|
||||
internal class ExternalSystemPartialResolveTest {
|
||||
|
||||
private val projectRootFixture = tempPathFixture()
|
||||
private val projectRoot by projectRootFixture
|
||||
|
||||
private val projectsFixture = multiProjectFixture()
|
||||
|
||||
private val projectFixture = projectsFixture.projectFixture(projectRootFixture)
|
||||
private val project by projectFixture
|
||||
|
||||
private val recordingManager by testFixture {
|
||||
val project = projectFixture.init()
|
||||
val manager = RecordingExternalSystemManager(project).apply {
|
||||
addSyncTask(projectRoot, BEFORE_SYNC_TASK, ExternalSystemTaskActivator.Phase.BEFORE_SYNC)
|
||||
addSyncTask(projectRoot, AFTER_SYNC_TASK, ExternalSystemTaskActivator.Phase.AFTER_SYNC)
|
||||
}
|
||||
initialized(manager) {}
|
||||
}
|
||||
|
||||
private val recordingDataService by testFixture {
|
||||
initialized(RecordingProjectDataService()) {}
|
||||
}
|
||||
|
||||
private val recordingCallback by testFixture {
|
||||
initialized(RecordingRefreshCallback()) {}
|
||||
}
|
||||
|
||||
private val recordingListener by testFixture {
|
||||
val listener = RecordingTaskNotificationListener()
|
||||
val manager = ExternalSystemProgressNotificationManager.getInstance()
|
||||
manager.addNotificationListener(listener)
|
||||
initialized(listener) {
|
||||
manager.removeNotificationListener(listener)
|
||||
}
|
||||
}
|
||||
|
||||
private val projectSettings by testFixture {
|
||||
val projectRoot = projectRootFixture.init()
|
||||
val projectSettings = TestExternalProjectSettings().apply {
|
||||
externalProjectPath = projectRoot.toCanonicalPath()
|
||||
}
|
||||
initialized(projectSettings) {}
|
||||
}
|
||||
|
||||
private val projectData by testFixture {
|
||||
val projectRoot = projectRootFixture.init()
|
||||
val projectData = project(PROJECT_NAME, projectRoot.toCanonicalPath()) {
|
||||
module(MODULE_NAME, projectRoot.resolve(MODULE_NAME).toCanonicalPath())
|
||||
}
|
||||
initialized(projectData.toDataNode()) {}
|
||||
}
|
||||
|
||||
private val projectPath get() = projectRoot.toCanonicalPath()
|
||||
private val modulePath get() = projectRoot.resolve(MODULE_NAME).toCanonicalPath()
|
||||
|
||||
@BeforeEach
|
||||
fun setUp(@TestDisposable disposable: Disposable) {
|
||||
setRegistry(TEST_EXTERNAL_SYSTEM_ID.id + ExternalSystemConstants.USE_IN_PROCESS_COMMUNICATION_REGISTRY_KEY_SUFFIX, true, disposable)
|
||||
ExternalSystemManager.EP_NAME.point.registerExtension(recordingManager, disposable)
|
||||
ProjectDataService.EP_NAME.point.registerExtension(recordingDataService, disposable)
|
||||
ConfigurationType.CONFIGURATION_TYPE_EP.point.registerExtension(TestExternalSystemConfigurationType(), disposable)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test resolve without issues`(): Unit = runBlocking {
|
||||
|
||||
recordingManager.setResolveProjectInfo { projectData }
|
||||
|
||||
linkProject(project, TEST_EXTERNAL_SYSTEM_ID, projectSettings) {
|
||||
withCallback(recordingCallback)
|
||||
}
|
||||
|
||||
val actualProjectData = ExternalSystemApiUtil.findProjectInfo(project, TEST_EXTERNAL_SYSTEM_ID, projectPath)
|
||||
assertNotNull(actualProjectData)
|
||||
assertEquals(projectData, actualProjectData.externalProjectStructure)
|
||||
assertNotEquals(NO_TIMESTAMP, actualProjectData.lastImportTimestamp)
|
||||
assertNotEquals(NO_TIMESTAMP, actualProjectData.lastSuccessfulImportTimestamp)
|
||||
assertSingle(modulePath, projectSettings.modules)
|
||||
recordingDataService.assertCounters(numImports = 1)
|
||||
recordingManager.assertCounters(numBeforeSync = 1, numAfterSync = 1)
|
||||
recordingCallback.assertCounters(numSuccesses = 1, numFailures = 0)
|
||||
recordingListener.assertCounters(RESOLVE_PROJECT, numSuccesses = 1, numFailures = 0)
|
||||
recordingListener.assertCounters(EXECUTE_TASK, numSuccesses = 2, numFailures = 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test resolve with exception`(): Unit = runBlocking {
|
||||
|
||||
recordingManager.setResolveProjectInfo { throw ExternalSystemException() }
|
||||
|
||||
linkProject(project, TEST_EXTERNAL_SYSTEM_ID, projectSettings) {
|
||||
withCallback(recordingCallback)
|
||||
}
|
||||
|
||||
val actualProjectData = ExternalSystemApiUtil.findProjectInfo(project, TEST_EXTERNAL_SYSTEM_ID, projectPath)
|
||||
assertNotNull(actualProjectData)
|
||||
assertNull(actualProjectData.externalProjectStructure)
|
||||
assertNotEquals(NO_TIMESTAMP, actualProjectData.lastImportTimestamp)
|
||||
assertEquals(NO_TIMESTAMP, actualProjectData.lastSuccessfulImportTimestamp)
|
||||
assertEmpty(projectSettings.modules)
|
||||
recordingDataService.assertCounters(numImports = 0)
|
||||
recordingManager.assertCounters(numBeforeSync = 1, numAfterSync = 0)
|
||||
recordingCallback.assertCounters(numSuccesses = 0, numFailures = 1)
|
||||
recordingListener.assertCounters(RESOLVE_PROJECT, numSuccesses = 0, numFailures = 1)
|
||||
recordingListener.assertCounters(EXECUTE_TASK, numSuccesses = 1, numFailures = 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test resolve with partial data policy`(): Unit = runBlocking {
|
||||
|
||||
recordingManager.setResolveProjectInfo { projectData }
|
||||
|
||||
linkProject(project, TEST_EXTERNAL_SYSTEM_ID, projectSettings) {
|
||||
withCallback(recordingCallback)
|
||||
projectResolverPolicy(ProjectResolverPolicy { true })
|
||||
}
|
||||
|
||||
val actualProjectData = ExternalSystemApiUtil.findProjectInfo(project, TEST_EXTERNAL_SYSTEM_ID, projectPath)
|
||||
assertNull(actualProjectData)
|
||||
assertEmpty(projectSettings.modules)
|
||||
recordingDataService.assertCounters(numImports = 0)
|
||||
recordingManager.assertCounters(numBeforeSync = 1, numAfterSync = 1)
|
||||
recordingCallback.assertCounters(numSuccesses = 1, numFailures = 0)
|
||||
recordingListener.assertCounters(RESOLVE_PROJECT, numSuccesses = 1, numFailures = 0)
|
||||
recordingListener.assertCounters(EXECUTE_TASK, numSuccesses = 2, numFailures = 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test resolve with partial resolve exception`(): Unit = runBlocking {
|
||||
|
||||
recordingManager.setResolveProjectInfo { throw ExternalSystemPartialResolutionException(projectData) }
|
||||
|
||||
linkProject(project, TEST_EXTERNAL_SYSTEM_ID, projectSettings) {
|
||||
withCallback(recordingCallback)
|
||||
}
|
||||
|
||||
val actualProjectData = ExternalSystemApiUtil.findProjectInfo(project, TEST_EXTERNAL_SYSTEM_ID, projectPath)
|
||||
assertNotNull(actualProjectData)
|
||||
assertEquals(projectData, actualProjectData.externalProjectStructure)
|
||||
assertNotEquals(NO_TIMESTAMP, actualProjectData.lastImportTimestamp)
|
||||
assertEquals(NO_TIMESTAMP, actualProjectData.lastSuccessfulImportTimestamp)
|
||||
assertSingle(modulePath, projectSettings.modules)
|
||||
recordingDataService.assertCounters(numImports = 1)
|
||||
recordingManager.assertCounters(numBeforeSync = 1, numAfterSync = 0)
|
||||
recordingCallback.assertCounters(numSuccesses = 0, numFailures = 1)
|
||||
recordingListener.assertCounters(RESOLVE_PROJECT, numSuccesses = 0, numFailures = 1)
|
||||
recordingListener.assertCounters(EXECUTE_TASK, numSuccesses = 1, numFailures = 0)
|
||||
|
||||
}
|
||||
|
||||
class RecordingExternalSystemManager(private val project: Project) : TestExternalSystemManager(project) {
|
||||
|
||||
private var resolveProjectInfo: (() -> DataNode<ProjectData>)? = null
|
||||
|
||||
private val beforeSyncTasks = AtomicInteger(0)
|
||||
private val afterSyncTasks = AtomicInteger(0)
|
||||
|
||||
fun setResolveProjectInfo(task: () -> DataNode<ProjectData>) {
|
||||
resolveProjectInfo = task
|
||||
}
|
||||
|
||||
fun assertCounters(numBeforeSync: Int, numAfterSync: Int) {
|
||||
assertEquals(numBeforeSync, beforeSyncTasks.get())
|
||||
assertEquals(numAfterSync, afterSyncTasks.get())
|
||||
}
|
||||
|
||||
fun addSyncTask(projectRoot: Path, taskName: String, phase: ExternalSystemTaskActivator.Phase) {
|
||||
ExternalProjectsManagerImpl.getInstance(project).taskActivator.addTask(
|
||||
ExternalSystemTaskActivator.TaskActivationEntry(TEST_EXTERNAL_SYSTEM_ID, phase, projectRoot.toCanonicalPath(), taskName)
|
||||
)
|
||||
}
|
||||
|
||||
override fun getProjectResolverClass(): Class<out ExternalSystemProjectResolver<TestExternalSystemExecutionSettings>> =
|
||||
RecordingExternalSystemProjectResolver::class.java
|
||||
|
||||
override fun getTaskManagerClass(): Class<out ExternalSystemTaskManager<TestExternalSystemExecutionSettings>> =
|
||||
RecordingExternalSystemTaskManager::class.java
|
||||
|
||||
class RecordingExternalSystemProjectResolver : ExternalSystemProjectResolver<TestExternalSystemExecutionSettings> {
|
||||
|
||||
// @formatter:off
|
||||
override fun resolveProjectInfo(id: ExternalSystemTaskId, projectPath: String, isPreviewMode: Boolean, settings: TestExternalSystemExecutionSettings?, resolverPolicy: ProjectResolverPolicy?, listener: ExternalSystemTaskNotificationListener): DataNode<ProjectData> {
|
||||
// @formatter:on
|
||||
val manager = ExternalSystemApiUtil.getManager(id.projectSystemId) as RecordingExternalSystemManager
|
||||
return manager.requireImplemented(RecordingExternalSystemManager::resolveProjectInfo)()
|
||||
}
|
||||
|
||||
override fun cancelTask(taskId: ExternalSystemTaskId, listener: ExternalSystemTaskNotificationListener): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
class RecordingExternalSystemTaskManager : ExternalSystemTaskManager<TestExternalSystemExecutionSettings> {
|
||||
|
||||
// @formatter:off
|
||||
override fun executeTasks(projectPath: String, id: ExternalSystemTaskId, settings: TestExternalSystemExecutionSettings, listener: ExternalSystemTaskNotificationListener) {
|
||||
// @formatter:on
|
||||
val manager = ExternalSystemApiUtil.getManager(id.projectSystemId) as RecordingExternalSystemManager
|
||||
if (BEFORE_SYNC_TASK in settings.tasks) {
|
||||
manager.beforeSyncTasks.incrementAndGet()
|
||||
}
|
||||
if (AFTER_SYNC_TASK in settings.tasks) {
|
||||
manager.afterSyncTasks.incrementAndGet()
|
||||
}
|
||||
}
|
||||
|
||||
override fun cancelTask(id: ExternalSystemTaskId, listener: ExternalSystemTaskNotificationListener): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class RecordingProjectDataService : ProjectDataService<ProjectData, Any> {
|
||||
|
||||
private val importCounter = AtomicInteger(0)
|
||||
|
||||
fun assertCounters(numImports: Int) {
|
||||
assertEquals(numImports, importCounter.get())
|
||||
}
|
||||
|
||||
override fun getTargetDataKey() = ProjectKeys.PROJECT
|
||||
|
||||
// @formatter:off
|
||||
override fun importData(toImport: Collection<DataNode<ProjectData>>, projectData: ProjectData?, project: Project, modelsProvider: IdeModifiableModelsProvider) {
|
||||
// @formatter:on
|
||||
importCounter.addAndGet(toImport.size)
|
||||
}
|
||||
|
||||
// @formatter:off
|
||||
override fun computeOrphanData(toImport: Collection<DataNode<ProjectData>>, projectData: ProjectData, project: Project, modelsProvider: IdeModifiableModelsProvider): Computable<Collection<Any>> {
|
||||
// @formatter:on
|
||||
return Computable { emptyList() }
|
||||
}
|
||||
|
||||
// @formatter:off
|
||||
override fun removeData(toRemove: Computable<out Collection<Any>>, toIgnore: Collection<DataNode<ProjectData>>, projectData: ProjectData, project: Project, modelsProvider: IdeModifiableModelsProvider) {
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
||||
|
||||
private class RecordingRefreshCallback : ExternalProjectRefreshCallback {
|
||||
|
||||
private val successCounter = AtomicInteger(0)
|
||||
private val failureCounter = AtomicInteger(0)
|
||||
|
||||
fun assertCounters(numSuccesses: Int, numFailures: Int) {
|
||||
assertEquals(numSuccesses, successCounter.get())
|
||||
assertEquals(numFailures, failureCounter.get())
|
||||
}
|
||||
|
||||
override fun onSuccess(externalProject: DataNode<ProjectData>?) {
|
||||
successCounter.incrementAndGet()
|
||||
}
|
||||
|
||||
override fun onFailure(errorMessage: String, errorDetails: String?) {
|
||||
failureCounter.incrementAndGet()
|
||||
}
|
||||
}
|
||||
|
||||
private class RecordingTaskNotificationListener : ExternalSystemTaskNotificationListener {
|
||||
|
||||
private val counters = ConcurrentHashMap<ExternalSystemTaskType, TaskNotificationCounter>()
|
||||
|
||||
private fun counter(type: ExternalSystemTaskType): TaskNotificationCounter {
|
||||
return counters.computeIfAbsent(type) { TaskNotificationCounter() }
|
||||
}
|
||||
|
||||
fun assertCounters(type: ExternalSystemTaskType, numSuccesses: Int, numFailures: Int) {
|
||||
counter(type).assertCounters(numSuccesses, numFailures)
|
||||
}
|
||||
|
||||
override fun onSuccess(projectPath: String, id: ExternalSystemTaskId) {
|
||||
counter(id.type).onSuccess()
|
||||
}
|
||||
|
||||
override fun onFailure(projectPath: String, id: ExternalSystemTaskId, exception: Exception) {
|
||||
counter(id.type).onFailure()
|
||||
}
|
||||
|
||||
private class TaskNotificationCounter {
|
||||
|
||||
private val successCounter = AtomicInteger(0)
|
||||
private val failureCounter = AtomicInteger(0)
|
||||
|
||||
fun assertCounters(numSuccesses: Int, numFailures: Int) {
|
||||
assertEquals(numSuccesses, successCounter.get())
|
||||
assertEquals(numFailures, failureCounter.get())
|
||||
}
|
||||
|
||||
fun onSuccess() {
|
||||
successCounter.incrementAndGet()
|
||||
}
|
||||
|
||||
fun onFailure() {
|
||||
failureCounter.incrementAndGet()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class TestExternalSystemConfigurationType : AbstractExternalSystemTaskConfigurationType(TEST_EXTERNAL_SYSTEM_ID) {
|
||||
override fun getConfigurationFactoryId(): String = "Test_external_system_id"
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PROJECT_NAME = "project"
|
||||
private const val MODULE_NAME = "module"
|
||||
private const val BEFORE_SYNC_TASK = "beforeSyncTask"
|
||||
private const val AFTER_SYNC_TASK = "afterSyncTask"
|
||||
private const val NO_TIMESTAMP = -1L
|
||||
|
||||
private fun setRegistry(name: String, value: Boolean, disposable: Disposable) {
|
||||
val registryValue = Registry.get(name)
|
||||
registryValue.setValue(value)
|
||||
disposable.whenDisposed { registryValue.resetToDefault() }
|
||||
}
|
||||
|
||||
private fun assertNotEquals(unexpected: Any?, actual: Any?) {
|
||||
assertThat(actual).isNotEqualTo(unexpected)
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
@@ -2,9 +2,23 @@
|
||||
package com.intellij.openapi.externalSystem.testFramework.fixtures
|
||||
|
||||
import com.intellij.openapi.externalSystem.testFramework.fixtures.impl.MultiProjectTestFixtureImpl
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.testFramework.closeProjectAsync
|
||||
import com.intellij.testFramework.junit5.fixture.TestFixture
|
||||
import com.intellij.testFramework.junit5.fixture.testFixture
|
||||
import java.nio.file.Path
|
||||
|
||||
fun multiProjectFixture(): TestFixture<MultiProjectTestFixture> = testFixture {
|
||||
initialized(MultiProjectTestFixtureImpl()) {}
|
||||
}
|
||||
|
||||
fun TestFixture<MultiProjectTestFixture>.projectFixture(
|
||||
projectRootFixture: TestFixture<Path>,
|
||||
): TestFixture<Project> = testFixture {
|
||||
val gradle = this@projectFixture.init()
|
||||
val projectRoot = projectRootFixture.init()
|
||||
val project = gradle.openProject(projectRoot)
|
||||
initialized(project) {
|
||||
project.closeProjectAsync()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user