source: josm/trunk/test/unit/org/openstreetmap/josm/command/SplitWayCommandTest.java

Last change on this file was 18870, checked in by taylor.smock, 6 months ago

See #16567: Update to JUnit 5

This converts most tests to use @Annotations. There are also some performance
improvements as it relates to tests.

File size: 23.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.command;
3
4import static org.junit.jupiter.api.Assertions.assertAll;
5import static org.junit.jupiter.api.Assertions.assertEquals;
6import static org.junit.jupiter.api.Assertions.assertFalse;
7import static org.junit.jupiter.api.Assertions.assertTrue;
8
9import java.io.IOException;
10import java.io.InputStream;
11import java.util.ArrayList;
12import java.util.Arrays;
13import java.util.Collections;
14import java.util.Iterator;
15import java.util.List;
16import java.util.Optional;
17import java.util.stream.Stream;
18
19import org.junit.jupiter.api.Test;
20import org.junit.jupiter.params.ParameterizedTest;
21import org.junit.jupiter.params.provider.Arguments;
22import org.junit.jupiter.params.provider.MethodSource;
23import org.junit.jupiter.params.provider.ValueSource;
24import org.openstreetmap.josm.TestUtils;
25import org.openstreetmap.josm.command.SplitWayCommand.Strategy;
26import org.openstreetmap.josm.data.UndoRedoHandler;
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.OsmPrimitive;
31import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
32import org.openstreetmap.josm.data.osm.Relation;
33import org.openstreetmap.josm.data.osm.RelationMember;
34import org.openstreetmap.josm.data.osm.Way;
35import org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionType;
36import org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionTypeCalculator;
37import org.openstreetmap.josm.io.IllegalDataException;
38import org.openstreetmap.josm.io.OsmReader;
39import org.openstreetmap.josm.testutils.annotations.BasicPreferences;
40import org.openstreetmap.josm.testutils.annotations.Main;
41import org.openstreetmap.josm.testutils.annotations.Projection;
42
43/**
44 * Unit tests for class {@link SplitWayCommand}.
45 */
46@BasicPreferences
47@Main
48@Projection
49final class SplitWayCommandTest {
50 /**
51 * Unit test of {@link SplitWayCommand#findVias}.
52 */
53 @Test
54 void testFindVias() {
55 // empty relation
56 assertTrue(SplitWayCommand.findVias(new Relation(), null).isEmpty());
57 // restriction relation without via member
58 Relation r = new Relation();
59 r.addMember(new RelationMember("", new Node()));
60 assertTrue(SplitWayCommand.findVias(r, "restriction").isEmpty());
61 // restriction relation with via member
62 r = new Relation();
63 OsmPrimitive via = new Node();
64 r.addMember(new RelationMember("via", via));
65 assertEquals(Collections.singletonList(via), SplitWayCommand.findVias(r, "restriction"));
66 // destination_sign relation without sign nor intersection
67 r = new Relation();
68 r.addMember(new RelationMember("", new Node()));
69 assertTrue(SplitWayCommand.findVias(r, "destination_sign").isEmpty());
70 // destination_sign with sign
71 r = new Relation();
72 via = new Node();
73 r.addMember(new RelationMember("sign", via));
74 assertEquals(Collections.singletonList(via), SplitWayCommand.findVias(r, "destination_sign"));
75 // destination_sign with intersection
76 r = new Relation();
77 via = new Node();
78 r.addMember(new RelationMember("intersection", via));
79 assertEquals(Collections.singletonList(via), SplitWayCommand.findVias(r, "destination_sign"));
80 }
81
82 static Stream<Arguments> testRouteRelation() {
83 Stream.Builder<Arguments> builder = Stream.builder();
84 for (int i = 0; i < 4; i++) {
85 builder.add(Arguments.of(false, i));
86 builder.add(Arguments.of(true, i));
87 }
88 return builder.build();
89 }
90
91 /**
92 * Unit tests of route relations.
93 */
94 @ParameterizedTest
95 @MethodSource
96 void testRouteRelation(final boolean wayIsReversed, final int indexOfWayToKeep) {
97 final DataSet dataSet = new DataSet();
98 final Node n1 = new Node(new LatLon(1, 0));
99 final Node n2 = new Node(new LatLon(2, 0));
100 final Node n3 = new Node(new LatLon(3, 0));
101 final Node n4 = new Node(new LatLon(4, 0));
102 final Node n5 = new Node(new LatLon(5, 0));
103 final Node n6 = new Node(new LatLon(6, 0));
104 final Node n7 = new Node(new LatLon(7, 0));
105 final Way w1 = new Way();
106 final Way w2 = new Way();
107 final Way w3 = new Way();
108 final Relation route = new Relation();
109 for (OsmPrimitive p : Arrays.asList(n1, n2, n3, n4, n5, n6, n7, w1, w2, w3, route)) {
110 dataSet.addPrimitive(p);
111 }
112 w1.setNodes(Arrays.asList(n1, n2));
113 w2.setNodes(wayIsReversed
114 ? Arrays.asList(n6, n5, n4, n3, n2)
115 : Arrays.asList(n2, n3, n4, n5, n6)
116 );
117 w3.setNodes(Arrays.asList(n6, n7));
118 route.put("type", "route");
119 route.addMember(new RelationMember("", w1));
120 route.addMember(new RelationMember("", w2));
121 route.addMember(new RelationMember("", w3));
122 dataSet.setSelected(Arrays.asList(w2, n3, n4, n5));
123
124 final Strategy strategy = wayChunks -> {
125 final Iterator<Way> it = wayChunks.iterator();
126 for (int i = 0; i < indexOfWayToKeep; i++) {
127 it.next();
128 }
129 return it.next();
130 };
131 final SplitWayCommand result = SplitWayCommand.splitWay(
132 w2, SplitWayCommand.buildSplitChunks(w2, Arrays.asList(n3, n4, n5)), new ArrayList<>(), strategy);
133 UndoRedoHandler.getInstance().add(result);
134
135 assertEquals(6, route.getMembersCount());
136 assertEquals(w1, route.getMemberPrimitivesList().get(0));
137 assertEquals(w3, route.getMemberPrimitivesList().get(5));
138 assertFirstLastNodeIs(((Way) route.getMemberPrimitivesList().get(0)), n1);
139 assertFirstLastNodeIs(((Way) route.getMemberPrimitivesList().get(0)), n2);
140 assertFirstLastNodeIs(((Way) route.getMemberPrimitivesList().get(1)), n2);
141 assertFirstLastNodeIs(((Way) route.getMemberPrimitivesList().get(1)), n3);
142 assertFirstLastNodeIs(((Way) route.getMemberPrimitivesList().get(2)), n3);
143 assertFirstLastNodeIs(((Way) route.getMemberPrimitivesList().get(2)), n4);
144 assertFirstLastNodeIs(((Way) route.getMemberPrimitivesList().get(3)), n4);
145 assertFirstLastNodeIs(((Way) route.getMemberPrimitivesList().get(3)), n5);
146 assertFirstLastNodeIs(((Way) route.getMemberPrimitivesList().get(4)), n5);
147 assertFirstLastNodeIs(((Way) route.getMemberPrimitivesList().get(4)), n6);
148 assertFirstLastNodeIs(((Way) route.getMemberPrimitivesList().get(5)), n6);
149 assertFirstLastNodeIs(((Way) route.getMemberPrimitivesList().get(5)), n7);
150 }
151
152 @Test
153 void testOneMemberOrderedRelationShowsWarningTest() {
154 final DataSet dataSet = new DataSet();
155
156 // Positive IDs to mark that these ways are incomplete (i.e., no nodes loaded).
157 final Way w1 = new Way(1);
158 final Way w3 = new Way(3);
159
160 // The way we are going to split is complete of course.
161 final Node n1 = new Node(new LatLon(1, 0));
162 final Node n2 = new Node(new LatLon(2, 0));
163 final Node n3 = new Node(new LatLon(3, 0));
164 final Way w2 = new Way();
165
166 final Relation route = new Relation();
167 for (OsmPrimitive p : Arrays.asList(n1, n2, n3, w1, w2, w3, route)) {
168 dataSet.addPrimitive(p);
169 }
170 w2.setNodes(Arrays.asList(n1, n2, n3));
171
172 route.put("type", "route");
173 route.addMember(new RelationMember("", w1));
174 route.addMember(new RelationMember("", w2));
175 route.addMember(new RelationMember("", w3));
176 dataSet.setSelected(Arrays.asList(w2, n2));
177
178 // This split cannot be safely performed without downloading extra relation members.
179 // Here we ask the split method to abort if it needs more information.
180 final Optional<SplitWayCommand> result = SplitWayCommand.splitWay(
181 w2,
182 SplitWayCommand.buildSplitChunks(w2, Collections.singletonList(n2)),
183 new ArrayList<>(),
184 Strategy.keepLongestChunk(),
185 SplitWayCommand.WhenRelationOrderUncertain.ABORT
186 );
187
188 assertFalse(result.isPresent());
189 }
190
191 static Stream<Arguments> testIncompleteMembersOrderedRelationCorrectOrderTest() {
192 Stream.Builder<Arguments> builder = Stream.builder();
193 for (int i = 0; i < 2; i++) {
194 // All these permutations should result in a split that keeps the new parts in order.
195 builder.add(Arguments.of(false, false, i));
196 builder.add(Arguments.of(true, false, i));
197 builder.add(Arguments.of(true, true, i));
198 builder.add(Arguments.of(false, true, i));
199 }
200 return builder.build();
201 }
202
203 @ParameterizedTest
204 @MethodSource
205 void testIncompleteMembersOrderedRelationCorrectOrderTest(final boolean reverseWayOne,
206 final boolean reverseWayTwo,
207 final int indexOfWayToKeep) {
208 final DataSet dataSet = new DataSet();
209
210 // Positive IDs to mark that these ways are incomplete (i.e., no nodes loaded).
211 final Way w1 = new Way(1);
212 final Way w4 = new Way(3);
213
214 // The ways we are going to split are complete of course.
215 final Node n1 = new Node(new LatLon(1, 0));
216 final Node n2 = new Node(new LatLon(2, 0));
217 final Node n3 = new Node(new LatLon(3, 0));
218 final Node n4 = new Node(new LatLon(4, 0));
219 final Node n5 = new Node(new LatLon(5, 0));
220 final Way w2 = new Way();
221 final Way w3 = new Way();
222
223 final Relation route = new Relation();
224 for (OsmPrimitive p : Arrays.asList(n1, n2, n3, n4, n5, w1, w2, w3, w4, route)) {
225 dataSet.addPrimitive(p);
226 }
227 w2.setNodes(reverseWayOne ? Arrays.asList(n3, n2, n1) : Arrays.asList(n1, n2, n3));
228 w3.setNodes(reverseWayTwo ? Arrays.asList(n5, n4, n3) : Arrays.asList(n3, n4, n5));
229
230 route.put("type", "route");
231 route.addMember(new RelationMember("", w1));
232 route.addMember(new RelationMember("", w2));
233 route.addMember(new RelationMember("", w3));
234 route.addMember(new RelationMember("", w4));
235
236 Way splitWay = indexOfWayToKeep == 0 ? w2 : w3;
237 Node splitNode = indexOfWayToKeep == 0 ? n2 : n4;
238
239 dataSet.setSelected(Arrays.asList(splitWay, splitNode));
240
241 final SplitWayCommand result = SplitWayCommand.splitWay(
242 splitWay, SplitWayCommand.buildSplitChunks(splitWay, Collections.singletonList(splitNode)), new ArrayList<>());
243 UndoRedoHandler.getInstance().add(result);
244
245 assertEquals(5, route.getMembersCount());
246 assertConnectedAtEnds(route.getMember(1).getWay(), route.getMember(2).getWay());
247 assertConnectedAtEnds(route.getMember(2).getWay(), route.getMember(3).getWay());
248 }
249
250 static void assertFirstLastNodeIs(Way way, Node node) {
251 assertTrue(node.equals(way.firstNode()) || node.equals(way.lastNode()),
252 "First/last node of " + way + " should be " + node);
253 }
254
255 static void assertConnectedAtEnds(Way one, Way two) {
256 Node first1 = one.firstNode();
257 Node last1 = one.lastNode();
258 Node first2 = two.firstNode();
259 Node last2 = two.lastNode();
260
261 assertTrue(first1 == first2 || first1 == last2 || last1 == first2 || last1 == last2,
262 "Ways expected to be connected at their ends.");
263 }
264
265 /**
266 * Non-regression test for patch #18596 (Fix relation ordering after split-way)
267 * @throws IOException if any I/O error occurs
268 * @throws IllegalDataException if OSM parsing fails
269 */
270 @Test
271 void testTicket18596() throws IOException, IllegalDataException {
272 try (InputStream is = TestUtils.getRegressionDataStream(18596, "data.osm")) {
273 DataSet ds = OsmReader.parseDataSet(is, null);
274
275 Way splitWay = (Way) ds.getPrimitiveById(5, OsmPrimitiveType.WAY);
276 Node splitNode = (Node) ds.getPrimitiveById(100002, OsmPrimitiveType.NODE);
277
278 final SplitWayCommand result = SplitWayCommand.splitWay(
279 splitWay,
280 SplitWayCommand.buildSplitChunks(splitWay, Collections.singletonList(splitNode)),
281 new ArrayList<>()
282 );
283
284 UndoRedoHandler.getInstance().add(result);
285
286 Relation relation = (Relation) ds.getPrimitiveById(8888, OsmPrimitiveType.RELATION);
287
288 assertEquals(8, relation.getMembersCount());
289
290 // Before the patch introduced in #18596, these asserts would fail. The two parts of
291 // way '5' would be in the wrong order, breaking the boundary relation in this test.
292 assertConnectedAtEnds(relation.getMember(4).getWay(), relation.getMember(5).getWay());
293 assertConnectedAtEnds(relation.getMember(5).getWay(), relation.getMember(6).getWay());
294 }
295 }
296
297 /**
298 * Non-regression test for issue #17400 (Warn when splitting way in not fully downloaded region)
299 * <p>
300 * Bus route 190 gets broken when the split occurs, because the two new way parts are inserted in the relation in
301 * the wrong order.
302 *
303 * @throws IOException if any I/O error occurs
304 * @throws IllegalDataException if OSM parsing fails
305 */
306 @Test
307 void testTicket17400() throws IOException, IllegalDataException {
308 try (InputStream is = TestUtils.getRegressionDataStream(17400, "data.osm")) {
309 DataSet ds = OsmReader.parseDataSet(is, null);
310
311 Way splitWay = (Way) ds.getPrimitiveById(253731928, OsmPrimitiveType.WAY);
312 Node splitNode = (Node) ds.getPrimitiveById(29830834, OsmPrimitiveType.NODE);
313
314 final Optional<SplitWayCommand> result = SplitWayCommand.splitWay(
315 splitWay,
316 SplitWayCommand.buildSplitChunks(splitWay, Collections.singletonList(splitNode)),
317 new ArrayList<>(),
318 Strategy.keepLongestChunk(),
319 // This split requires no additional downloads.
320 SplitWayCommand.WhenRelationOrderUncertain.ABORT
321 );
322
323 assertTrue(result.isPresent());
324
325 UndoRedoHandler.getInstance().add(result.get());
326
327 // 190 Hormersdorf-Thalheim-Stollberg.
328 Relation relation = (Relation) ds.getPrimitiveById(2873422, OsmPrimitiveType.RELATION);
329
330 // One more than the original 161.
331 assertEquals(162, relation.getMembersCount());
332
333 // Before the patch introduced in #18596, these asserts would fail. The new parts of
334 // the Hauptstraße would be in the wrong order, breaking the bus route relation.
335 // These parts should be connected, in their relation sequence: 74---75---76.
336 // Before #18596 this would have been a broken connection: 74---75-x-76.
337 assertConnectedAtEnds(relation.getMember(74).getWay(), relation.getMember(75).getWay());
338 assertConnectedAtEnds(relation.getMember(75).getWay(), relation.getMember(76).getWay());
339 }
340 }
341
342 /**
343 * Non-regression test for issue #18863 (Asking for download of missing members when not needed)
344 * <p>
345 * A split on node 4518025255 caused the 'download missing members?' dialog to pop up for relation 68745 (CB 2),
346 * even though the way members next to the split way were already downloaded. This happened because this relation
347 * does not have its members connected at all.
348 * <p>
349 * This split should not trigger any download action at all.
350 *
351 * @throws IOException if any I/O error occurs
352 * @throws IllegalDataException if OSM parsing fails
353 */
354 @Test
355 void testTicket18863() throws IOException, IllegalDataException {
356 try (InputStream is = TestUtils.getRegressionDataStream(18863, "data.osm.bz2")) {
357 DataSet ds = OsmReader.parseDataSet(is, null);
358
359 Way splitWay = (Way) ds.getPrimitiveById(290581177L, OsmPrimitiveType.WAY);
360 Node splitNode = (Node) ds.getPrimitiveById(4518025255L, OsmPrimitiveType.NODE);
361
362 final Optional<SplitWayCommand> result = SplitWayCommand.splitWay(
363 splitWay,
364 SplitWayCommand.buildSplitChunks(splitWay, Collections.singletonList(splitNode)),
365 new ArrayList<>(),
366 Strategy.keepLongestChunk(),
367 // This split requires no additional downloads. If any are needed, this command will fail.
368 SplitWayCommand.WhenRelationOrderUncertain.ABORT
369 );
370
371 // Should not result in aborting the split.
372 assertTrue(result.isPresent());
373 }
374 }
375
376 /**
377 * Non-regression test for issue #19432 (AIOOB: Problem with member check with duplicate members)
378 *
379 * @throws IOException if any I/O error occurs
380 * @throws IllegalDataException if OSM parsing fails
381 */
382 @Test
383 void testTicket19432() throws IOException, IllegalDataException {
384 try (InputStream is = TestUtils.getRegressionDataStream(19432, "josm_split_way_exception_example.osm.bz2")) {
385 DataSet ds = OsmReader.parseDataSet(is, null);
386
387 Way splitWay = (Way) ds.getPrimitiveById(632576744L, OsmPrimitiveType.WAY);
388 Node splitNode = (Node) ds.getPrimitiveById(1523436358L, OsmPrimitiveType.NODE);
389
390 final Optional<SplitWayCommand> result = SplitWayCommand.splitWay(
391 splitWay,
392 SplitWayCommand.buildSplitChunks(splitWay, Collections.singletonList(splitNode)),
393 new ArrayList<>(),
394 Strategy.keepLongestChunk(),
395 // This split requires additional downloads but problem occured before the download
396 SplitWayCommand.WhenRelationOrderUncertain.SPLIT_ANYWAY
397 );
398
399 // Should not result in aborting the split.
400 assertTrue(result.isPresent());
401 }
402 }
403
404 /**
405 * Non-regression test for issue #20163 (Split way corrupts relation when splitting via way)
406 *
407 * @throws IOException if any I/O error occurs
408 * @throws IllegalDataException if OSM parsing fails
409 */
410 @Test
411 void testTicket20163() throws IOException, IllegalDataException {
412 try (InputStream is = TestUtils.getRegressionDataStream(20163, "data-20163.osm")) {
413 DataSet ds = OsmReader.parseDataSet(is, null);
414
415 Way splitWay = (Way) ds.getPrimitiveById(757606841L, OsmPrimitiveType.WAY);
416 Node splitNode = splitWay.getNode(1);
417 Relation r = (Relation) ds.getPrimitiveById(10452821L, OsmPrimitiveType.RELATION);
418 assertEquals(3, r.getMembersCount());
419 assertFalse(r.getMembersFor(Collections.singleton(splitWay)).isEmpty());
420 assertEquals(1, r.getMembers().stream().filter(rm -> "via".equals(rm.getRole())).count());
421 assertEquals("via", r.getMembersFor(Collections.singleton(splitWay)).iterator().next().getRole());
422 final Optional<SplitWayCommand> result = SplitWayCommand.splitWay(
423 splitWay,
424 SplitWayCommand.buildSplitChunks(splitWay, Collections.singletonList(splitNode)),
425 new ArrayList<>(),
426 Strategy.keepLongestChunk(),
427 // This split requires additional downloads but problem occured before the download
428 SplitWayCommand.WhenRelationOrderUncertain.SPLIT_ANYWAY
429 );
430
431 // Should not result in aborting the split.
432 assertTrue(result.isPresent());
433 result.get().executeCommand();
434
435 assertTrue(r.isModified());
436 assertEquals(4, r.getMembersCount());
437 assertEquals(2, r.getMembers().stream().filter(rm -> "via".equals(rm.getRole())).count());
438 }
439 }
440
441 /**
442 * Test case: smart ordering in routes
443 * See #21856
444 */
445 @ParameterizedTest
446 @ValueSource(booleans = {false, true})
447 void testTicket21856(boolean reverse) {
448 Way way1 = TestUtils.newWay("highway=residential", TestUtils.newNode(""), TestUtils.newNode(""));
449 way1.setOsmId(23_968_090, 1);
450 way1.lastNode().setOsmId(6_823_898_683L, 1);
451 Way way2 = TestUtils.newWay("highway=residential", way1.lastNode(), TestUtils.newNode(""));
452 way2.setOsmId(728_199_307, 1);
453 way2.lastNode().setOsmId(6_823_898_684L, 1);
454 Node splitNode = TestUtils.newNode("");
455 splitNode.setOsmId(6_823_906_290L, 1);
456 Way splitWay = TestUtils.newWay("highway=service", way2.firstNode(), splitNode, TestUtils.newNode(""), way2.lastNode());
457 // The behavior should be the same regardless of the direction of the way
458 if (reverse) {
459 List<Node> nodes = new ArrayList<>(splitWay.getNodes());
460 Collections.reverse(nodes);
461 splitWay.setNodes(nodes);
462 }
463 splitWay.setOsmId(728_199_306, 1);
464 Relation route = TestUtils.newRelation("type=route route=bus", new RelationMember("", way1), new RelationMember("", splitWay),
465 new RelationMember("", way2), new RelationMember("", way1));
466 DataSet dataSet = new DataSet();
467 dataSet.addPrimitiveRecursive(route);
468 dataSet.setSelected(splitNode);
469 // Sanity check (preconditions -- the route should be well-formed already)
470 WayConnectionTypeCalculator connectionTypeCalculator = new WayConnectionTypeCalculator();
471 List<WayConnectionType> links = connectionTypeCalculator.updateLinks(route, route.getMembers());
472 assertAll("All links should be connected (forward)",
473 links.subList(0, links.size() - 2).stream().map(link -> () -> assertTrue(link.linkNext)));
474 assertAll("All links should be connected (backward)",
475 links.subList(1, links.size() - 1).stream().map(link -> () -> assertTrue(link.linkPrev)));
476 final Optional<SplitWayCommand> result = SplitWayCommand.splitWay(
477 splitWay,
478 SplitWayCommand.buildSplitChunks(splitWay, Collections.singletonList(splitNode)),
479 new ArrayList<>(),
480 Strategy.keepLongestChunk(),
481 // This split requires additional downloads but problem occured before the download
482 SplitWayCommand.WhenRelationOrderUncertain.SPLIT_ANYWAY
483 );
484 assertTrue(result.isPresent());
485 result.get().executeCommand();
486 // Actual check
487 connectionTypeCalculator = new WayConnectionTypeCalculator();
488 links = connectionTypeCalculator.updateLinks(route, route.getMembers());
489 assertAll("All links should be connected (forward)",
490 links.subList(0, links.size() - 2).stream().map(link -> () -> assertTrue(link.linkNext)));
491 assertAll("All links should be connected (backward)",
492 links.subList(1, links.size() - 1).stream().map(link -> () -> assertTrue(link.linkPrev)));
493 }
494}
Note: See TracBrowser for help on using the repository browser.