blob: 137c8abe72bb7400c681ea0f74cada5be85a5d2f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2020 Paul Pazderski and others.
*
* 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:
* Paul Pazderski - initial API and implementation
*******************************************************************************/
package org.eclipse.swt.tests.win32.snippets;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSource;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.DropTargetListener;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.internal.ole.win32.IDataObject;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.tests.win32.SwtWin32TestUtil;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Shell;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
/**
* This one is primary a manual test because it is platform dependent, kind of
* slow, a bit unreliable (can succeed even if bug exist; see dragDropCount) and
* grabs the mouse while testing. Apart from that the test runs automatically.
* Just start and see if the JVM crashes.
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class Bug567422_DNDCrash {
enum TestMode {
EXPECTED,
UNCOMMON,
EVIL,
}
public static void main(String[] args) throws InterruptedException {
for (TestMode mode : TestMode.values()) {
new Bug567422_DNDCrash().testBug567422(mode);
}
}
/**
* Simulate drag and drop on a target with expected behaviour which is that the
* target release all references on the COM objects it acquires but not more and
* therefore is actually finished once the drop is performed.
*/
@Test
public void testBug567422_1_expectedDropTarget() throws InterruptedException {
testBug567422(TestMode.EXPECTED);
}
/**
* Simulate drag and drop on a target with uncommon behaviour which is that the
* target does not release all references on the COM objects once the drop is
* performed. Holding a reference to the IDataObject after the drop is done or
* canceled is not wrong but uncommon.
*/
@Test
public void testBug567422_2_uncommonDropTarget() throws InterruptedException {
testBug567422(TestMode.UNCOMMON);
}
/**
* Simulate drag and drop on a target with evil or faulty behaviour which is that the
* target releases more references on the COM objects as it has acquired previously.
*/
@Test
public void testBug567422_3_evilDropTarget() throws InterruptedException {
testBug567422(TestMode.EVIL);
}
public void testBug567422(TestMode mode) throws InterruptedException {
// More DND operations increase the test time but also the chance to trigger a
// crash.
int dragDropCount = 5;
// Decrease to potential speed up test or increase for a little higher chance to
// trigger crash if bug exist.
// Must be at least 2. This number of DND operations are performed before there
// is a chance to find a crash.
int testBatchSize = 5;
String data = "Test data";
Shell shell = new Shell();
shell.setLayout(new RowLayout());
shell.setSize(300, 300);
shell.open();
try {
try {
SwtWin32TestUtil.processEvents(shell.getDisplay(), 1000, shell::isVisible);
} catch (InterruptedException e) {
fail("Initialization interrupted");
}
assertTrue("Shell not visible.", shell.isVisible());
Transfer transfer = TextTransfer.getInstance();
AtomicBoolean dropped = new AtomicBoolean(false);
AtomicReference<String> droppedData = new AtomicReference<>(null);
List<IDataObject> dataObjects = new ArrayList<>();
DragSource source = new DragSource(shell, DND.DROP_LINK | DND.DROP_COPY | DND.DROP_MOVE);
source.setTransfer(transfer);
source.addListener(DND.DragSetData, event -> event.data = data);
DropTarget target = new DropTarget(shell, DND.DROP_DEFAULT | DND.DROP_COPY | DND.DROP_LINK | DND.DROP_MOVE);
target.setTransfer(transfer);
target.addDropListener(new DropTargetListener() {
@Override
public void drop(DropTargetEvent event) {
try {
String o = (String) event.data;
droppedData.set(o);
} catch (ClassCastException ex) {
}
dropped.set(true);
if (mode == TestMode.UNCOMMON) {
// keep reference on data object and release it eventually later
IDataObject dataObject = new IDataObject(event.currentDataType.pIDataObject);
dataObject.AddRef();
dataObjects.add(dataObject);
}
if (mode == TestMode.EVIL) {
// release more references on data object as acquired
IDataObject dataObject = new IDataObject(event.currentDataType.pIDataObject);
for (int i = 0; i < 3; i++) {
dataObject.Release();
}
}
}
@Override
public void dropAccept(DropTargetEvent event) {
}
@Override
public void dragOver(DropTargetEvent event) {
}
@Override
public void dragOperationChanged(DropTargetEvent event) {
}
@Override
public void dragLeave(DropTargetEvent event) {
}
@Override
public void dragEnter(DropTargetEvent event) {
}
});
for (int i = 0; i < dragDropCount; i++) {
dropped.set(false);
droppedData.set(null);
int dragDropTries = 3;
do {
shell.setText(i + "/" + dragDropCount);
postDragAndDropEvents(shell);
SwtWin32TestUtil.processEvents(shell.getDisplay(), 1000, dropped::get);
} while (!dropped.get() && --dragDropTries > 0);
assertTrue("No drop received.", dropped.get());
assertNotNull("No data was dropped.", droppedData.get());
if (mode == TestMode.UNCOMMON && (i % testBatchSize) == 0) {
for (IDataObject dataObject : dataObjects) {
dataObject.Release();
}
dataObjects.clear();
}
}
if (mode == TestMode.UNCOMMON) {
for (IDataObject dataObject : dataObjects) {
dataObject.Release();
}
}
} finally {
Display display = shell.getDisplay();
display.dispose();
assertTrue(display.isDisposed());
}
}
/**
* Posts the events required to do a drag and drop. (i.e. click and hold mouse
* button and move mouse)
* <p>
* The caller is responsible to ensure the generated events are processed by the
* event loop.
* </p>
*/
private static void postDragAndDropEvents(Shell shell) {
shell.forceActive();
assertTrue("Test shell requires input focus.", shell.forceFocus());
Event event = new Event();
Point pt = shell.toDisplay(50, 50);
event.x = pt.x;
event.y = pt.y;
event.type = SWT.MouseMove;
shell.getDisplay().post(event);
event.button = 1;
event.count = 1;
event.type = SWT.MouseDown;
shell.getDisplay().post(event);
event.x += 30;
event.type = SWT.MouseMove;
shell.getDisplay().post(event);
event.type = SWT.MouseUp;
shell.getDisplay().post(event);
}
}