Skip to content

Commit

Permalink
Add options to the UI to disable and remove update sites for cases wh…
Browse files Browse the repository at this point in the history
…ere update sites are not found (#207)

Previously, if accumulation of load failures was enabled, all failures were put into a MultiStatus and displayed to the user. This has been changed to use more specific dialogs if possible. If all load failures were caused by repositories not being found, dialogs that allow removing or disabling the affected sites are used. If there is only a single load failure and it was caused by a repository not being found, the "Error Contacting Site" dialog is used.

This commit extends the "Error Contacting Site" dialog with buttons for editing, removing or disabling a single site.
  • Loading branch information
erik-brangs authored and laeubi committed Apr 25, 2024
1 parent e1d3ea9 commit 0e4707e
Show file tree
Hide file tree
Showing 15 changed files with 728 additions and 69 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*******************************************************************************
* Copyright (c) 2023 Erik Brangs.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Erik Brangs - initial implementation
*******************************************************************************/
package org.eclipse.equinox.internal.p2.operations;

import java.net.URI;
import org.eclipse.equinox.p2.core.ProvisionException;

public class LoadFailure {

private final URI location;
private final ProvisionException provisionException;

public LoadFailure(URI location, ProvisionException provisionException) {
this.location = location;
this.provisionException = provisionException;
}

public URI getLocation() {
return location;
}

public ProvisionException getProvisionException() {
return provisionException;
}

@Override
public String toString() {
return "LoadFailure [location=" + location + ", provisionException=" + provisionException + "]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}

public static boolean failureRepresentsBadRepositoryLocation(ProvisionException exception) {
int code = exception.getStatus().getCode();
return code == IStatusCodes.INVALID_REPOSITORY_LOCATION
|| code == ProvisionException.REPOSITORY_INVALID_LOCATION
|| code == ProvisionException.REPOSITORY_NOT_FOUND;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*******************************************************************************
* Copyright (c) 2023 Erik Brangs.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Erik Brangs - initial implementation
*******************************************************************************/
package org.eclipse.equinox.internal.p2.operations;

import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.equinox.p2.core.ProvisionException;
import org.eclipse.equinox.p2.operations.RepositoryTracker;

/**
* Accumulates information about load failures caused by bad locations.
* <p>
* This class is designed to support the UI in enabling the user to deal with
* bad locations, e.g. by disabling or removing them. Therefore, it does not
* save all informations about the failures.
*/
public final class LoadFailureAccumulator {

private RepositoryTracker repositoryTracker;
private int loadFailuresNotCausedByBadRepoLocation;
private List<LoadFailure> loadFailuresCausedByBadRepoLocation;

public LoadFailureAccumulator(RepositoryTracker repositoryTracker) {
this.repositoryTracker = repositoryTracker;
this.loadFailuresCausedByBadRepoLocation = new ArrayList<>();
}

public void recordLoadFailure(ProvisionException e, URI location) {
if (LoadFailure.failureRepresentsBadRepositoryLocation(e)) {
loadFailuresCausedByBadRepoLocation.add(new LoadFailure(location, e));
repositoryTracker.addNotFound(location);
} else {
loadFailuresNotCausedByBadRepoLocation++;
}
}

public boolean hasSingleFailureCausedByBadLocation() {
return loadFailuresCausedByBadRepoLocation.size() == 1 && loadFailuresNotCausedByBadRepoLocation == 0;
}

public boolean allFailuresCausedByBadLocation() {
return loadFailuresCausedByBadRepoLocation.size() >= 1 && loadFailuresNotCausedByBadRepoLocation == 0;
}

public List<LoadFailure> getLoadFailuresCausedByBadRepoLocation() {
return loadFailuresCausedByBadRepoLocation;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,8 @@ public void setMetadataRepositoryFlags(int flags) {
*/
public void reportLoadFailure(final URI location, ProvisionException exception) {
// special handling when the repo location is bad. We don't want to continually report it
int code = exception.getStatus().getCode();
if (code == IStatusCodes.INVALID_REPOSITORY_LOCATION || code == ProvisionException.REPOSITORY_INVALID_LOCATION || code == ProvisionException.REPOSITORY_NOT_FOUND) {
boolean repoLocationIsBad = LoadFailure.failureRepresentsBadRepositoryLocation(exception);
if (repoLocationIsBad) {
if (hasNotFoundStatusBeenReported(location))
return;
addNotFound(location);
Expand Down
4 changes: 3 additions & 1 deletion bundles/org.eclipse.equinox.p2.tests.ui/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ Require-Bundle: org.eclipse.core.runtime;bundle-version="3.29.0",
org.eclipse.equinox.p2.ui.importexport;bundle-version="1.0.1"
Bundle-RequiredExecutionEnvironment: JavaSE-17
Import-Package: org.eclipse.equinox.internal.p2.operations,
org.eclipse.equinox.p2.operations;version="[2.0.0,3.0.0)"
org.eclipse.equinox.p2.operations;version="[2.0.0,3.0.0)",
org.mockito,
org.mockito.stubbing
Eclipse-BundleShape: dir
Automatic-Module-Name: org.eclipse.equinox.p2.tests.ui
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
*/
@RunWith(Suite.class)
@Suite.SuiteClasses({ ColocatedRepositoryTrackerTest.class, SizingTest.class, InstallOperationTests.class,
UpdateOperationTests.class, UninstallOperationTests.class })
UpdateOperationTests.class, UninstallOperationTests.class, LoadFailureTest.class,
LoadFailureAccumulatorTest.class, LocationNotFoundDialogTest.class, MultipleLocationsNotFoundDialogTest.class })
public class AllTests {
// test suite
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*******************************************************************************
* Copyright (c) 2023 Erik Brangs.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Erik Brangs - initial implementation
*******************************************************************************/
package org.eclipse.equinox.p2.tests.ui.operations;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.mockito.Mockito.*;

import java.net.URI;
import junit.framework.TestCase;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.equinox.internal.p2.operations.LoadFailureAccumulator;
import org.eclipse.equinox.p2.core.ProvisionException;
import org.eclipse.equinox.p2.operations.RepositoryTracker;
import org.mockito.InOrder;
import org.mockito.Mockito;

public class LoadFailureAccumulatorTest extends TestCase {

public void testRecordFailureForSingleBadLocation() throws Exception {
RepositoryTracker repositoryTracker = mock(RepositoryTracker.class);
LoadFailureAccumulator loadFailureAccumulator = new LoadFailureAccumulator(repositoryTracker);
ProvisionException exception = buildProvisionExceptionWithCode(ProvisionException.REPOSITORY_INVALID_LOCATION);
URI invalidLocation = new URI("https://example.com/invalid");
loadFailureAccumulator.recordLoadFailure(exception, invalidLocation);
assertThat(loadFailureAccumulator.allFailuresCausedByBadLocation(), is(true));
assertThat(loadFailureAccumulator.hasSingleFailureCausedByBadLocation(), is(true));
assertThat(loadFailureAccumulator.getLoadFailuresCausedByBadRepoLocation(), hasSize(1));
verify(repositoryTracker, times(1)).addNotFound(invalidLocation);
}

public void testRecordFailureForMultipleBadLocations() throws Exception {
RepositoryTracker repositoryTracker = mock(RepositoryTracker.class);
LoadFailureAccumulator loadFailureAccumulator = new LoadFailureAccumulator(repositoryTracker);
ProvisionException firstException = buildProvisionExceptionWithCode(
ProvisionException.REPOSITORY_INVALID_LOCATION);
URI firstInvalidLocation = new URI("https://example.com/invalid");
ProvisionException secondException = buildProvisionExceptionWithCode(
ProvisionException.REPOSITORY_INVALID_LOCATION);
URI secondInvalidLocation = new URI("https://example.com/invalidTwo");
loadFailureAccumulator.recordLoadFailure(firstException, firstInvalidLocation);
loadFailureAccumulator.recordLoadFailure(secondException, secondInvalidLocation);
assertThat(loadFailureAccumulator.allFailuresCausedByBadLocation(), is(true));
assertThat(loadFailureAccumulator.hasSingleFailureCausedByBadLocation(), is(false));
assertThat(loadFailureAccumulator.getLoadFailuresCausedByBadRepoLocation(), hasSize(2));
InOrder inOrder = Mockito.inOrder(repositoryTracker);
inOrder.verify(repositoryTracker, times(1)).addNotFound(firstInvalidLocation);
inOrder.verify(repositoryTracker, times(1)).addNotFound(secondInvalidLocation);
}

public void testRecordFailureForMultipleBadLocationsAndOneFailureCausedBySomethingElse() throws Exception {
RepositoryTracker repositoryTracker = mock(RepositoryTracker.class);
LoadFailureAccumulator loadFailureAccumulator = new LoadFailureAccumulator(repositoryTracker);
ProvisionException firstException = buildProvisionExceptionWithCode(
ProvisionException.REPOSITORY_INVALID_LOCATION);
URI firstInvalidLocation = new URI("https://example.com/invalid");
ProvisionException secondException = buildProvisionExceptionWithCode(
ProvisionException.REPOSITORY_INVALID_LOCATION);
URI secondInvalidLocation = new URI("https://example.com/invalidTwo");
ProvisionException thirdException = buildProvisionExceptionWithCode(
ProvisionException.REPOSITORY_FAILED_AUTHENTICATION);
URI thirdLocation = new URI("https://example.com/requiresAuthentication");
loadFailureAccumulator.recordLoadFailure(firstException, firstInvalidLocation);
loadFailureAccumulator.recordLoadFailure(secondException, secondInvalidLocation);
loadFailureAccumulator.recordLoadFailure(thirdException, thirdLocation);
assertThat(loadFailureAccumulator.allFailuresCausedByBadLocation(), is(false));
assertThat(loadFailureAccumulator.hasSingleFailureCausedByBadLocation(), is(false));
assertThat(loadFailureAccumulator.getLoadFailuresCausedByBadRepoLocation(), hasSize(2));
InOrder inOrder = Mockito.inOrder(repositoryTracker);
inOrder.verify(repositoryTracker, times(1)).addNotFound(firstInvalidLocation);
inOrder.verify(repositoryTracker, times(1)).addNotFound(secondInvalidLocation);
verify(repositoryTracker, never()).addNotFound(thirdLocation);
}

private ProvisionException buildProvisionExceptionWithCode(int code) {
IStatus status = new Status(IStatus.ERROR, "pluginId", code, "message", new RuntimeException());
return new ProvisionException(status);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*******************************************************************************
* Copyright (c) 2023 Erik Brangs.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Erik Brangs - initial implementation
*******************************************************************************/
package org.eclipse.equinox.p2.tests.ui.operations;

import junit.framework.TestCase;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.equinox.internal.p2.operations.IStatusCodes;
import org.eclipse.equinox.internal.p2.operations.LoadFailure;
import org.eclipse.equinox.p2.core.ProvisionException;

public class LoadFailureTest extends TestCase {

public void testFailureRepresentsBadRepositoryLocationForCodeInvalidRepositoryLocation() throws Exception {
int code = IStatusCodes.INVALID_REPOSITORY_LOCATION;
ProvisionException provisionException = buildProvisionExceptionWithCode(code);
boolean isBadLocation = LoadFailure.failureRepresentsBadRepositoryLocation(provisionException);
assertTrue(isBadLocation);
}

public void testFailureRepresentsBadRepositoryLocationForCodeRepositoryInvalidLocation() throws Exception {
int code = ProvisionException.REPOSITORY_INVALID_LOCATION;
ProvisionException provisionException = buildProvisionExceptionWithCode(code);
boolean isBadLocation = LoadFailure.failureRepresentsBadRepositoryLocation(provisionException);
assertTrue(isBadLocation);
}

public void testFailureRepresentsBadRepositoryLocationForCodeRepositoryNotFound() throws Exception {
int code = ProvisionException.REPOSITORY_INVALID_LOCATION;
ProvisionException provisionException = buildProvisionExceptionWithCode(code);
boolean isBadLocation = LoadFailure.failureRepresentsBadRepositoryLocation(provisionException);
assertTrue(isBadLocation);
}

public void testFailureRepresentsBadRepositoryLocationForOtherCode() throws Exception {
int code = ProvisionException.REPOSITORY_FAILED_READ;
ProvisionException provisionException = buildProvisionExceptionWithCode(code);
boolean isBadLocation = LoadFailure.failureRepresentsBadRepositoryLocation(provisionException);
assertFalse(isBadLocation);
}

private ProvisionException buildProvisionExceptionWithCode(int code) {
IStatus status = new Status(IStatus.ERROR, "pluginId", code, "message", new RuntimeException());
return new ProvisionException(status);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*******************************************************************************
* Copyright (c) 2023 Erik Brangs.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Erik Brangs - initial implementation
*******************************************************************************/
package org.eclipse.equinox.p2.tests.ui.operations;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.Mockito.*;

import java.net.URI;
import org.eclipse.equinox.internal.p2.ui.*;
import org.eclipse.equinox.p2.operations.ProvisioningSession;
import org.eclipse.equinox.p2.tests.AbstractProvisioningTest;
import org.eclipse.equinox.p2.ui.ProvisioningUI;
import org.mockito.InOrder;
import org.mockito.Mockito;

public class LocationNotFoundDialogTest extends AbstractProvisioningTest {

public void testCorrectLocation() {
// Set up a composite repo. This was copied from
// ColocatedRepositoryTrackerTest.
final String compositeRepo = "testData/bug338495/good.local";
final URI compositeRepoURI = getTestData("composite repo", compositeRepo).toURI();
final String childRepo = "testData/bug338495/good.local/one";
final URI childRepoOneURI = getTestData("composite repo", childRepo).toURI();

ProvisioningUI provUI = mock(ProvisioningUI.class);
ProvisioningSession provisioningSession = mock(ProvisioningSession.class);
when(provUI.getSession()).thenReturn(provisioningSession);
ColocatedRepositoryTracker tracker = mock(ColocatedRepositoryTracker.class);
URI location = compositeRepoURI;
LocationNotFoundDialog dialog = new LocationNotFoundDialog(tracker, provUI, location);
URI correctedLocation = childRepoOneURI;
String repositoryName = "repositoryName";
InOrder inOrder = Mockito.inOrder(tracker, provUI);
dialog.correctLocation(correctedLocation, repositoryName);
inOrder.verify(provUI, times(1)).signalRepositoryOperationStart();
inOrder.verify(tracker, times(1)).removeRepositories(new URI[] { location }, provisioningSession);
inOrder.verify(tracker, times(1)).addRepository(correctedLocation, repositoryName, provisioningSession);
inOrder.verify(provUI, times(1)).signalRepositoryOperationComplete(null, true);
}

public void testRemoveRepository() {
// Set up a composite repo. This was copied from
// ColocatedRepositoryTrackerTest.
final String compositeRepo = "testData/bug338495/good.local";
final URI compositeRepoURI = getTestData("composite repo", compositeRepo).toURI();

ProvisioningUI provUI = mock(ProvisioningUI.class);
ProvisioningSession provisioningSession = mock(ProvisioningSession.class);
when(provUI.getSession()).thenReturn(provisioningSession);
ColocatedRepositoryTracker tracker = mock(ColocatedRepositoryTracker.class);
URI location = compositeRepoURI;
LocationNotFoundDialog dialog = new LocationNotFoundDialog(tracker, provUI, location);
dialog.removeRepository();
verify(tracker, times(1)).removeRepositories(new URI[] { location }, provisioningSession);
}

public void testDisableRepository() {
// Set up a composite repo. This was adapted from
// ColocatedRepositoryTrackerTest.
final String compositeRepo = "testData/bug338495/good.local";
final URI compositeRepoURI = getTestData("composite repo", compositeRepo).toURI();
final String childRepo = "testData/bug338495/good.local/one";
final URI childRepoOneURI = getTestData("composite repo", childRepo).toURI();

ProvisioningUI provUI = ProvisioningUI.getDefaultUI();
ProvisioningSession provSession = provUI.getSession();

ColocatedRepositoryTracker tracker = new ColocatedRepositoryTracker(provUI);
tracker.addRepository(compositeRepoURI, "main", provSession);
tracker.addRepository(childRepoOneURI, "child", provSession);

URI location = compositeRepoURI;
LocationNotFoundDialog dialog = new LocationNotFoundDialog(tracker, provUI, location);
dialog.disableRepository();
assertThat(ProvUI.getMetadataRepositoryManager(provSession).isEnabled(location), is(false));
assertThat(ProvUI.getArtifactRepositoryManager(provSession).isEnabled(location), is(false));
}

}
Loading

0 comments on commit 0e4707e

Please sign in to comment.