source: josm/trunk/test/unit/org/openstreetmap/josm/actions/SimplifyWayActionTest.java

Last change on this file was 18935, checked in by taylor.smock, 4 months ago

Fix #23399: Simplify way crashes

This was primarily caused by selection updates occurring while the commands were generated. The fixes could be any of the following:

  • Synchronized collections
  • New list
  • Avoiding selection updates

The last option was taken for the following reasons:

  • Avoiding a StackOverflow exception when many ways are being simplified
  • Reduced memory allocations (>11GB to <250MB)
  • Reduced CPU cycles
  • Property svn:eol-style set to native
File size: 6.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.actions;
3
4import static org.junit.jupiter.api.Assertions.assertAll;
5import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
6import static org.junit.jupiter.api.Assertions.assertEquals;
7import static org.junit.jupiter.api.Assertions.assertFalse;
8import static org.junit.jupiter.api.Assertions.assertNotNull;
9import static org.openstreetmap.josm.tools.I18n.tr;
10
11import java.io.IOException;
12import java.nio.file.Files;
13import java.nio.file.Paths;
14import java.util.ArrayList;
15import java.util.Collection;
16import java.util.Collections;
17import java.util.Comparator;
18import java.util.List;
19import java.util.stream.Collectors;
20import java.util.stream.Stream;
21
22import org.junit.jupiter.api.BeforeEach;
23import org.junit.jupiter.api.Test;
24import org.openstreetmap.josm.TestUtils;
25import org.openstreetmap.josm.command.DeleteCommand;
26import org.openstreetmap.josm.command.SequenceCommand;
27import org.openstreetmap.josm.data.coor.LatLon;
28import org.openstreetmap.josm.data.osm.DataSet;
29import org.openstreetmap.josm.data.osm.Node;
30import org.openstreetmap.josm.data.osm.Way;
31import org.openstreetmap.josm.gui.ExtendedDialog;
32import org.openstreetmap.josm.gui.MainApplication;
33import org.openstreetmap.josm.gui.layer.OsmDataLayer;
34import org.openstreetmap.josm.gui.util.GuiHelper;
35import org.openstreetmap.josm.io.IllegalDataException;
36import org.openstreetmap.josm.io.OsmReader;
37import org.openstreetmap.josm.testutils.annotations.Main;
38import org.openstreetmap.josm.testutils.annotations.Projection;
39import org.openstreetmap.josm.testutils.mockers.ExtendedDialogMocker;
40import org.openstreetmap.josm.testutils.mockers.HelpAwareOptionPaneMocker;
41import org.openstreetmap.josm.tools.Utils;
42
43/**
44 * Unit tests for class {@link SimplifyWayAction}.
45 */
46@Main
47@Projection
48final class SimplifyWayActionTest {
49
50 /** Class under test. */
51 private static SimplifyWayAction action;
52
53 /**
54 * Setup test.
55 */
56 @BeforeEach
57 public void setUp() {
58 if (action == null) {
59 action = MainApplication.getMenu().simplifyWay;
60 action.setEnabled(true);
61 }
62 }
63
64 private DataSet getDs(String file) throws IllegalDataException, IOException {
65 return OsmReader.parseDataSet(Files.newInputStream(Paths.get(TestUtils.getTestDataRoot(), "tracks/" + file + ".osm")), null);
66 }
67
68 /**
69 * Tests simplification
70 * @throws Exception in case of error
71 */
72 @Test
73 void testSimplify() throws Exception {
74 DataSet DsSimplify = getDs("tracks");
75 DataSet DsExpected = getDs("tracks-simplify15");
76 SimplifyWayAction.simplifyWays(new ArrayList<>(DsSimplify.getWays()), 15);
77 DsSimplify.cleanupDeletedPrimitives();
78 //compare sorted Coordinates and total amount of primitives, because IDs and order will vary after reload
79 List<LatLon> CoorSimplify = DsSimplify.getNodes().stream()
80 .map(Node::getCoor)
81 .sorted(Comparator.comparing(LatLon::hashCode))
82 .collect(Collectors.toList());
83 List<LatLon> CoorExpected = DsExpected.getNodes().stream()
84 .map(Node::getCoor)
85 .sorted(Comparator.comparing(LatLon::hashCode))
86 .collect(Collectors.toList());
87 assertEquals(CoorExpected, CoorSimplify);
88 assertEquals(DsExpected.allPrimitives().size(), DsSimplify.allPrimitives().size());
89 }
90
91 /**
92 * Tests that also the first node may be simplified, see #13094.
93 */
94 @Test
95 void testSimplifyFirstNode() {
96 final DataSet ds = new DataSet();
97 final Node n1 = new Node(new LatLon(47.26269614984, 11.34044231149));
98 final Node n2 = new Node(new LatLon(47.26274590831, 11.34053120859));
99 final Node n3 = new Node(new LatLon(47.26276562382, 11.34034715039));
100 final Node n4 = new Node(new LatLon(47.26264639132, 11.34035341438));
101 final Way w = new Way();
102 Stream.of(n1, n2, n3, n4, w).forEach(ds::addPrimitive);
103 Stream.of(n1, n2, n3, n4, n1).forEach(w::addNode);
104 final SequenceCommand command = SimplifyWayAction.createSimplifyCommand(w, 3);
105 assertNotNull(command);
106 assertEquals(2, command.getChildren().size());
107 final Collection<DeleteCommand> deleteCommands = Utils.filteredCollection(command.getChildren(), DeleteCommand.class);
108 assertEquals(1, deleteCommands.size());
109 assertEquals(Collections.singleton(n1), deleteCommands.iterator().next().getParticipatingPrimitives());
110 }
111
112 /**
113 * Non-regression test for #23399
114 */
115 @Test
116 void testNonRegression23399() {
117 TestUtils.assumeWorkingJMockit();
118 new ExtendedDialogMocker(Collections.singletonMap("Simplify way", "Simplify")) {
119 @Override
120 protected String getString(ExtendedDialog instance) {
121 return instance.getTitle();
122 }
123 };
124 new HelpAwareOptionPaneMocker(Collections.singletonMap(
125 tr("The selection contains {0} ways. Are you sure you want to simplify them all?", 1000), "Yes"));
126 final ArrayList<Way> ways = new ArrayList<>(1000);
127 final DataSet ds = new DataSet();
128 for (int i = 0; i < 1000; i++) {
129 final Way way = TestUtils.newWay("", new Node(new LatLon(0, 0)), new Node(new LatLon(0, 0.001)),
130 new Node(new LatLon(0, 0.002)));
131 ways.add(way);
132 ds.addPrimitiveRecursive(way);
133 }
134 MainApplication.getLayerManager().addLayer(new OsmDataLayer(ds, "SimplifyWayActionTest#testNonRegression23399", null));
135 GuiHelper.runInEDTAndWait(() -> ds.setSelected(ds.allPrimitives()));
136 assertEquals(ds.allPrimitives().size(), ds.getAllSelected().size());
137 assertDoesNotThrow(() -> GuiHelper.runInEDTAndWaitWithException(() -> action.actionPerformed(null)));
138 assertAll(ways.stream().map(way -> () -> assertEquals(2, way.getNodesCount())));
139 assertAll(ds.getAllSelected().stream().map(p -> () -> assertFalse(p.isDeleted())));
140 assertEquals(3000, ds.getAllSelected().size());
141 }
142}
Note: See TracBrowser for help on using the repository browser.