source: josm/trunk/test/unit/org/openstreetmap/josm/gui/dialogs/relation/actions/RelationEditorActionsTest.java@ 19014

Last change on this file since 19014 was 19014, checked in by GerdP, 2 months ago

fix #23527: Memory leak in relation editor

  • add new method wouldRelationBeUseful() in IRelationEditorActionAccess which doesn't call the problematic method getChangedRelation() to evaluate whether a relation would be useful if saved.
  • add comments and code in unit test for the method that shows how to handle the result of getChangedRelation() in case it is not needed any longer to avoid the memory leak.
  • Property svn:eol-style set to native
File size: 7.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.dialogs.relation.actions;
3
4import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
5import static org.junit.jupiter.api.Assertions.assertEquals;
6import static org.junit.jupiter.api.Assertions.assertTrue;
7
8import java.awt.Component;
9import java.awt.Container;
10
11import javax.swing.Icon;
12import javax.swing.JOptionPane;
13import javax.swing.text.JTextComponent;
14
15import org.junit.jupiter.api.Test;
16import org.openstreetmap.josm.TestUtils;
17import org.openstreetmap.josm.data.coor.LatLon;
18import org.openstreetmap.josm.data.osm.DataSet;
19import org.openstreetmap.josm.data.osm.IRelation;
20import org.openstreetmap.josm.data.osm.Node;
21import org.openstreetmap.josm.data.osm.Relation;
22import org.openstreetmap.josm.data.osm.RelationMember;
23import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
24import org.openstreetmap.josm.gui.MainApplication;
25import org.openstreetmap.josm.gui.layer.OsmDataLayer;
26import org.openstreetmap.josm.testutils.mockers.JOptionPaneSimpleMocker;
27
28import mockit.Mock;
29import mockit.MockUp;
30
31/**
32 * Unit tests for relation editor actions.
33 */
34class RelationEditorActionsTest extends AbstractRelationEditorActionTest {
35
36 /**
37 * Check that all dialog-less actions do not crash.
38 */
39 @Test
40 void testNoDialogActions() {
41 new AddSelectedAfterSelection(relationEditorAccess).actionPerformed(null);
42 new AddSelectedBeforeSelection(relationEditorAccess).actionPerformed(null);
43 new AddSelectedAtStartAction(relationEditorAccess).actionPerformed(null);
44 new AddSelectedAtEndAction(relationEditorAccess).actionPerformed(null);
45
46 new ApplyAction(relationEditorAccess).actionPerformed(null);
47 new RefreshAction(relationEditorAccess).actionPerformed(null);
48 new OKAction(relationEditorAccess).actionPerformed(null);
49 new CancelAction(relationEditorAccess).actionPerformed(null);
50
51 new CopyMembersAction(relationEditorAccess).actionPerformed(null);
52 new PasteMembersAction(relationEditorAccess).actionPerformed(null);
53
54 new SelectAction(relationEditorAccess).actionPerformed(null);
55
56 new DownloadIncompleteMembersAction(relationEditorAccess, "downloadincomplete").actionPerformed(null);
57 new DownloadSelectedIncompleteMembersAction(relationEditorAccess).actionPerformed(null);
58
59 new DuplicateRelationAction(relationEditorAccess).actionPerformed(null);
60 new EditAction(relationEditorAccess).actionPerformed(null);
61
62 new MoveDownAction(relationEditorAccess, "movedown").actionPerformed(null);
63 new MoveUpAction(relationEditorAccess, "moveup").actionPerformed(null);
64 new RemoveAction(relationEditorAccess, "remove").actionPerformed(null);
65
66 new RemoveSelectedAction(relationEditorAccess).actionPerformed(null);
67 new SelectedMembersForSelectionAction(relationEditorAccess).actionPerformed(null);
68
69 new SelectPrimitivesForSelectedMembersAction(relationEditorAccess).actionPerformed(null);
70
71 new SortAction(relationEditorAccess).actionPerformed(null);
72 new SortBelowAction(relationEditorAccess).actionPerformed(null);
73 new ReverseAction(relationEditorAccess).actionPerformed(null);
74 }
75
76 /**
77 * Test DeleteCurrentRelationAction
78 */
79 @Test
80 void testDeleteCurrentRelationAction() {
81 TestUtils.assumeWorkingJMockit();
82 final JOptionPaneSimpleMocker jopsMocker = new JOptionPaneSimpleMocker() {
83 @Override
84 public String getStringFromOriginalMessage(Object originalMessage) {
85 return ((JTextComponent) ((Container) originalMessage).getComponent(0)).getText();
86 }
87 };
88 jopsMocker.getMockResultMap().put(
89 "<html>\n <head>\n \n </head>\n <body>\n You are about to delete 1 "
90 + "relation:\n\n "
91 + "<ul>\n <li>\n incomplete\n </li>\n </ul>\n <br>\n "
92 + "This step is rarely necessary and cannot be undone easily after being \n "
93 + "uploaded to the server.<br>Do you really want to delete?\n </body>\n</html>\n", JOptionPane.YES_OPTION);
94 jopsMocker.getMockResultMap().put(
95 "<html>\n <head>\n \n </head>\n <body>\n You are about to delete incomplete "
96 + "objects.<br>This will cause problems \n because you don\'t see the real object.<br>"
97 + "Do you really want to delete?\n </body>\n</html>\n",
98 JOptionPane.YES_OPTION);
99
100 new DeleteCurrentRelationAction(relationEditorAccess).actionPerformed(null);
101
102 assertEquals(2, jopsMocker.getInvocationLog().size());
103
104 Object[] invocationLogEntry = jopsMocker.getInvocationLog().get(0);
105 assertEquals(JOptionPane.YES_OPTION, (int) invocationLogEntry[0]);
106 assertEquals("Delete relation?", invocationLogEntry[2]);
107
108 invocationLogEntry = jopsMocker.getInvocationLog().get(1);
109 assertEquals(JOptionPane.YES_OPTION, (int) invocationLogEntry[0]);
110 assertEquals("Delete confirmation", invocationLogEntry[2]);
111 }
112
113 /**
114 * Test SetRoleAction
115 */
116 @Test
117 void testSetRoleAction() {
118 TestUtils.assumeWorkingJMockit();
119 final JOptionPaneSimpleMocker.MessagePanelMocker mpMocker = new JOptionPaneSimpleMocker.MessagePanelMocker();
120 // JOptionPaneSimpleMocker doesn't handle showOptionDialog calls because of their potential
121 // complexity, but this is quite a simple use of showOptionDialog which we can mock from scratch.
122 final boolean[] jopMockerCalled = new boolean[] {false};
123 new MockUp<JOptionPane>() {
124 @Mock
125 public int showOptionDialog(
126 Component parentComponent,
127 Object message,
128 String title,
129 int optionType,
130 int messageType,
131 Icon icon,
132 Object[] options,
133 Object initialValue
134 ) {
135 assertEquals(
136 "<html>You are setting an empty role on 0 objects.<br>This is equal to deleting the "
137 + "roles of these objects.<br>Do you really want to apply the new role?</html>",
138 mpMocker.getOriginalMessage((ConditionalOptionPaneUtil.MessagePanel) message).toString()
139 );
140 assertEquals(
141 "Confirm empty role",
142 title
143 );
144 jopMockerCalled[0] = true;
145 return JOptionPane.YES_OPTION;
146 }
147 };
148
149 new SetRoleAction(relationEditorAccess).actionPerformed(null);
150
151 assertTrue(jopMockerCalled[0]);
152 }
153
154 /**
155 * Non-regression test for JOSM #22024.
156 * This is due to a race condition between uploading and refreshing the relation in the editor.
157 */
158 @Test
159 void testNonRegression22024() {
160 final DataSet ds = new DataSet();
161 final Node node = new Node(LatLon.ZERO);
162 Relation relation = TestUtils.newRelation("type=restriction", new RelationMember("", node));
163 ds.addPrimitive(node);
164 ds.addPrimitive(relation);
165 MainApplication.getLayerManager().prepareLayerForUpload(new OsmDataLayer(ds, "testNonRegression22024", null));
166 // Sanity check that behavior hasn't changed
167 assertTrue(ds.isLocked(), "The dataset should be locked when it is being uploaded.");
168 relationEditorAccess.getEditor().setRelation(relation);
169 relationEditorAccess.getMemberTableModel().populate(relation);
170 relationEditorAccess.getTagModel().initFromPrimitive(relation);
171 relationEditorAccess.getEditor().reloadDataFromRelation();
172
173 assertDoesNotThrow(() -> {
174 IRelation<?> tempRelation = relationEditorAccess.getChangedRelation();
175 if (tempRelation.getDataSet() == null)
176 tempRelation.setMembers(null); // see #19885
177 });
178 }
179}
Note: See TracBrowser for help on using the repository browser.