| /******************************************************************************* |
| * 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); |
| } |
| } |