source: josm/trunk/test/unit/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerTest.java@ 17916

Last change on this file since 17916 was 17916, checked in by simon04, 3 years ago

fix #20957, see #20744 - Fix MapCSS function round

  • Property svn:eol-style set to native
File size: 19.7 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.validation.tests;
3
4import static org.junit.jupiter.api.Assertions.assertEquals;
5import static org.junit.jupiter.api.Assertions.assertFalse;
6import static org.junit.jupiter.api.Assertions.assertNotNull;
7import static org.junit.jupiter.api.Assertions.assertTrue;
8
9import java.io.InputStream;
10import java.io.StringReader;
11import java.util.Collection;
12import java.util.HashSet;
13import java.util.Iterator;
14import java.util.LinkedHashSet;
15import java.util.List;
16import java.util.Set;
17
18import org.junit.jupiter.api.BeforeEach;
19import org.junit.jupiter.api.Test;
20import org.junit.jupiter.api.extension.RegisterExtension;
21import org.openstreetmap.josm.TestUtils;
22import org.openstreetmap.josm.command.ChangePropertyCommand;
23import org.openstreetmap.josm.command.ChangePropertyKeyCommand;
24import org.openstreetmap.josm.command.Command;
25import org.openstreetmap.josm.command.PseudoCommand;
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.IPrimitive;
30import org.openstreetmap.josm.data.osm.Node;
31import org.openstreetmap.josm.data.osm.OsmPrimitive;
32import org.openstreetmap.josm.data.osm.OsmUtils;
33import org.openstreetmap.josm.data.preferences.sources.ExtendedSourceEntry;
34import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper;
35import org.openstreetmap.josm.data.validation.Severity;
36import org.openstreetmap.josm.data.validation.Test.TagTest;
37import org.openstreetmap.josm.data.validation.TestError;
38import org.openstreetmap.josm.data.validation.tests.MapCSSTagChecker.ParseResult;
39import org.openstreetmap.josm.gui.mappaint.Environment;
40import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
41import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource;
42import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.ParseException;
43import org.openstreetmap.josm.io.OsmReader;
44import org.openstreetmap.josm.testutils.JOSMTestRules;
45import org.openstreetmap.josm.tools.Logging;
46
47import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
48
49/**
50 * JUnit Test of {@link MapCSSTagChecker}.
51 */
52class MapCSSTagCheckerTest {
53
54 /**
55 * Setup test.
56 */
57 @RegisterExtension
58 @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
59 public JOSMTestRules test = new JOSMTestRules().projection().territories().preferences();
60
61 /**
62 * Setup test.
63 */
64 @BeforeEach
65 public void setUp() {
66 MapCSSTagCheckerAsserts.clear();
67 }
68
69 static MapCSSTagChecker buildTagChecker(String css) throws ParseException {
70 final MapCSSTagChecker test = new MapCSSTagChecker();
71 Set<String> errors = new HashSet<>();
72 test.checks.putAll("test", MapCSSTagCheckerRule.readMapCSS(new StringReader(css), errors::add).parseChecks);
73 assertTrue(errors.isEmpty(), errors::toString);
74 return test;
75 }
76
77 /**
78 * Test {@code natural=marsh}.
79 * @throws ParseException if a parsing error occurs
80 */
81 @Test
82 void testNaturalMarsh() throws ParseException {
83 ParseResult result = MapCSSTagCheckerRule.readMapCSS(new StringReader(
84 "*[natural=marsh] {\n" +
85 " group: tr(\"deprecated\");\n" +
86 " throwWarning: tr(\"{0}={1} is deprecated\", \"{0.key}\", tag(\"natural\"));\n" +
87 " fixRemove: \"{0.key}\";\n" +
88 " fixAdd: \"natural=wetland\";\n" +
89 " fixAdd: \"wetland=marsh\";\n" +
90 "}"));
91 final List<MapCSSTagCheckerRule> checks = result.parseChecks;
92 assertEquals(1, checks.size());
93 assertTrue(result.parseErrors.isEmpty());
94 final MapCSSTagCheckerRule check = checks.get(0);
95 assertNotNull(check);
96 assertEquals("{0.key}=null is deprecated", check.getDescription(null));
97 assertEquals("fixRemove: {0.key}", check.fixCommands.get(0).toString());
98 assertEquals("fixAdd: natural=wetland", check.fixCommands.get(1).toString());
99 assertEquals("fixAdd: wetland=marsh", check.fixCommands.get(2).toString());
100 final OsmPrimitive n1 = OsmUtils.createPrimitive("node natural=marsh");
101 final OsmPrimitive n2 = OsmUtils.createPrimitive("node natural=wood");
102 new DataSet(n1, n2);
103 assertTrue(check.test(n1));
104
105 final Collection<TestError> errors = check.getErrorsForPrimitive(n1, check.whichSelectorMatchesPrimitive(n1), new Environment(), null);
106 assertEquals(1, errors.size());
107 TestError err = errors.iterator().next();
108 assertEquals("deprecated", err.getMessage());
109 assertEquals("natural=marsh is deprecated", err.getDescription());
110 assertEquals(Severity.WARNING, err.getSeverity());
111 assertEquals("Sequence: Fix of natural=marsh is deprecated", check.fixPrimitive(n1).getDescriptionText());
112 assertEquals("{natural=}", ((ChangePropertyCommand) check.fixPrimitive(n1).getChildren().iterator().next()).getTags().toString());
113 assertFalse(check.test(n2));
114 assertEquals("The key is natural and the value is marsh",
115 MapCSSTagCheckerRule.insertArguments(check.rule.selectors.get(0), "The key is {0.key} and the value is {0.value}", null));
116 }
117
118 /**
119 * Non-regression test for <a href="https://josm.openstreetmap.de/ticket/10913">Bug #10913</a>.
120 * @throws ParseException if a parsing error occurs
121 */
122 @Test
123 void testTicket10913() throws ParseException {
124 final OsmPrimitive p = TestUtils.addFakeDataSet(TestUtils.newWay("highway=tertiary construction=yes"));
125 final MapCSSTagCheckerRule check = MapCSSTagCheckerRule.readMapCSS(new StringReader("way {" +
126 "throwError: \"error\";" +
127 "fixChangeKey: \"highway => construction\";\n" +
128 "fixAdd: \"highway=construction\";\n" +
129 "}")).parseChecks.get(0);
130 final Command command = check.fixPrimitive(p);
131 assertTrue(command instanceof SequenceCommand);
132 final Iterator<PseudoCommand> it = command.getChildren().iterator();
133 assertTrue(it.next() instanceof ChangePropertyKeyCommand);
134 assertTrue(it.next() instanceof ChangePropertyCommand);
135 }
136
137 /**
138 * Non-regression test for <a href="https://josm.openstreetmap.de/ticket/9782">Bug #9782</a>.
139 * @throws ParseException if a parsing error occurs
140 */
141 @Test
142 void testTicket9782() throws ParseException {
143 final MapCSSTagChecker test = buildTagChecker("*[/.+_name/][!name] {" +
144 "throwWarning: tr(\"has {0} but not {1}\", \"{0.key}\", \"{1.key}\");}");
145 final OsmPrimitive p = OsmUtils.createPrimitive("way alt_name=Foo");
146 final Collection<TestError> errors = test.getErrorsForPrimitive(p, false);
147 assertEquals(1, errors.size());
148 assertEquals("has alt_name but not name", errors.iterator().next().getMessage());
149 assertEquals("3000_has alt_name but not name", errors.iterator().next().getIgnoreSubGroup());
150 }
151
152 /**
153 * Non-regression test for <a href="https://josm.openstreetmap.de/ticket/10859">Bug #10859</a>.
154 * @throws ParseException if a parsing error occurs
155 */
156 @Test
157 void testTicket10859() throws ParseException {
158 final MapCSSTagChecker test = buildTagChecker("way[highway=footway][foot?!] {\n" +
159 " throwWarning: tr(\"{0} used with {1}\", \"{0.value}\", \"{1.tag}\");}");
160 final OsmPrimitive p = OsmUtils.createPrimitive("way highway=footway foot=no");
161 final Collection<TestError> errors = test.getErrorsForPrimitive(p, false);
162 assertEquals(1, errors.size());
163 assertEquals("footway used with foot=no", errors.iterator().next().getMessage());
164 assertEquals("3000_footway used with foot=no", errors.iterator().next().getIgnoreSubGroup());
165 }
166
167 /**
168 * Non-regression test for <a href="https://josm.openstreetmap.de/ticket/13630">Bug #13630</a>.
169 * @throws ParseException if a parsing error occurs
170 */
171 @Test
172 void testTicket13630() throws ParseException {
173 ParseResult result = MapCSSTagCheckerRule.readMapCSS(new StringReader(
174 "node[crossing=zebra] {fixRemove: \"crossing=zebra\";}"));
175 assertTrue(result.parseChecks.isEmpty());
176 assertEquals(1, result.parseErrors.size());
177 }
178
179 /**
180 * Unit test of {@code min-josm-version} processing.
181 * @throws ParseException if a parsing error occurs
182 */
183 @Test
184 void testPreprocessing() throws ParseException {
185 final MapCSSTagChecker test = buildTagChecker(
186 "@supports (min-josm-version: 0) { *[foo] { throwWarning: \"!\"; } }\n" +
187 "@supports (min-josm-version: 2147483647) { *[bar] { throwWarning: \"!\"; } }");
188 assertEquals(1, test.getErrorsForPrimitive(OsmUtils.createPrimitive("way foo=1"), false).size());
189 assertEquals(0, test.getErrorsForPrimitive(OsmUtils.createPrimitive("way bar=1"), false).size());
190 }
191
192 /**
193 * Unit test of {@link MapCSSTagChecker#initialize}.
194 * @throws Exception if an error occurs
195 */
196 @Test
197 void testInit() throws Exception {
198 Logging.clearLastErrorAndWarnings();
199 MapCSSTagChecker c = new MapCSSTagChecker();
200 c.initialize();
201
202 assertTrue(Logging.getLastErrorAndWarnings().isEmpty(), "no warnings/errors are logged");
203
204 // to trigger MapCSSStyleIndex code
205 Node node = new Node(new LatLon(12, 34));
206 node.put("amenity", "drinking_water");
207 assertTrue(c.getErrorsForPrimitive(node, false).isEmpty());
208 }
209
210 /**
211 * Unit test for all {@link TagTest} assertions.
212 * @throws Exception if an error occurs
213 */
214 @Test
215 void testAssertions() throws Exception {
216 MapCSSTagChecker c = new MapCSSTagChecker();
217 Set<String> assertionErrors = new LinkedHashSet<>();
218
219 // initialize
220 for (ExtendedSourceEntry entry : ValidatorPrefHelper.INSTANCE.getDefault()) {
221 c.addMapCSS(entry.url, assertionErrors::add);
222 }
223
224 for (String msg : assertionErrors) {
225 Logging.error(msg);
226 }
227 assertTrue(assertionErrors.isEmpty(), "not all assertions included in the tests are met");
228 }
229
230 /**
231 * Checks that assertions work for country-specific checks.
232 * @throws ParseException if a parsing error occurs
233 */
234 @Test
235 void testAssertInsideCountry() throws ParseException {
236 final MapCSSTagChecker test = buildTagChecker(
237 "node[amenity=parking][inside(\"BR\")] {\n" +
238 " throwWarning: \"foo\";\n" +
239 " assertMatch: \"node amenity=parking\";\n" +
240 " assertNoMatch: \"node amenity=restaurant\";\n" +
241 "}");
242 assertNotNull(test);
243 }
244
245 /**
246 * Non-regression test for <a href="https://josm.openstreetmap.de/ticket/17058">Bug #17058</a>.
247 * @throws ParseException if a parsing error occurs
248 */
249 @Test
250 void testTicket17058() throws ParseException {
251 final MapCSSTagChecker test = buildTagChecker(
252 "*[name =~ /(?i).*Straße.*/][inside(\"LI,CH\")] {\n" +
253 " throwError: tr(\"street name contains ß\");\n" +
254 " assertMatch: \"way name=Hauptstraße\";\n" +
255 " assertNoMatch: \"way name=Hauptstrasse\";\n" +
256 "}");
257 assertNotNull(test);
258 }
259
260 /**
261 * Non-regression test for <a href="https://josm.openstreetmap.de/ticket/13762">Bug #13762</a>.
262 * @throws ParseException if a parsing error occurs
263 */
264 @Test
265 void testTicket13762() throws ParseException {
266 final ParseResult parseResult = MapCSSTagCheckerRule.readMapCSS(new StringReader("" +
267 "meta[lang=de] {\n" +
268 " title: \"Deutschlandspezifische Regeln\";" +
269 "}"));
270 assertTrue(parseResult.parseErrors.isEmpty());
271 }
272
273 /**
274 * Non-regression test for <a href="https://josm.openstreetmap.de/ticket/14287">Bug #14287</a>.
275 * @throws Exception if an error occurs
276 */
277 @Test
278 void testTicket14287() throws Exception {
279 final MapCSSTagChecker test = buildTagChecker(
280 "node[amenity=parking] ∈ *[amenity=parking] {" +
281 " throwWarning: tr(\"{0} inside {1}\", \"amenity=parking\", \"amenity=parking\");" +
282 "}");
283 try (InputStream is = TestUtils.getRegressionDataStream(14287, "example.osm")) {
284 test.visit(OsmReader.parseDataSet(is, null).allPrimitives());
285 assertEquals(6, test.getErrors().size());
286 }
287 }
288
289 /**
290 * Non-regression test for <a href="https://josm.openstreetmap.de/ticket/17053">Bug #17053</a>.
291 * @throws ParseException if a parsing error occurs
292 */
293 @Test
294 void testTicket17053() throws ParseException {
295 final MapCSSTagChecker test = buildTagChecker(
296 "way[highway=cycleway][cycleway=track] {\n" +
297 " throwWarning: tr(\"{0} with {1}\", \"{0.tag}\", \"{1.tag}\");\n" +
298 " -osmoseItemClassLevel: \"3032/30328/2\";\n" +
299 " -osmoseTags: list(\"tag\", \"highway\", \"cycleway\");\n" +
300 " fixRemove: \"cycleway\";\n" +
301 "}");
302 assertEquals(1, test.checks.size());
303 MapCSSTagCheckerRule check = test.checks.get("test").iterator().next();
304 assertEquals(1, check.fixCommands.size());
305 assertEquals(2, check.rule.declaration.instructions.size());
306 }
307
308 private void doTestNaturalWood(int ticket, String filename, int errorsCount, int setsCount) throws Exception {
309 final MapCSSTagChecker test = buildTagChecker(
310 "area:closed:areaStyle[tag(\"natural\") = parent_tag(\"natural\")] ⧉ area:closed:areaStyle[natural] {" +
311 " throwWarning: tr(\"Overlapping Identical Natural Areas\");" +
312 "}");
313 final MapCSSStyleSource style = new MapCSSStyleSource(
314 "area[natural=wood] {" +
315 " fill-color: woodarea#008000;" +
316 "}");
317 MapPaintStyles.addStyle(style);
318 try (InputStream is = TestUtils.getRegressionDataStream(ticket, filename)) {
319 test.visit(OsmReader.parseDataSet(is, null).allPrimitives());
320 List<TestError> errors = test.getErrors();
321 assertEquals(errorsCount, errors.size());
322 Set<Set<IPrimitive>> primitives = new HashSet<>();
323 for (TestError e : errors) {
324 primitives.add(new HashSet<>(e.getPrimitives()));
325 }
326 assertEquals(setsCount, primitives.size());
327 } finally {
328 MapPaintStyles.removeStyle(style);
329 }
330 }
331
332 /**
333 * Non-regression test for <a href="https://josm.openstreetmap.de/ticket/12627">Bug #12627</a>.
334 * @throws Exception if an error occurs
335 */
336 @Test
337 void testTicket12627() throws Exception {
338 doTestNaturalWood(12627, "overlapping.osm", 1, 1);
339 }
340
341 /**
342 * Non-regression test for <a href="https://josm.openstreetmap.de/ticket/14289">Bug #14289</a>.
343 * @throws Exception if an error occurs
344 */
345 @Test
346 void testTicket14289() throws Exception {
347 doTestNaturalWood(14289, "example2.osm", 3, 3);
348 }
349
350 /**
351 * Non-regression test for <a href="https://josm.openstreetmap.de/ticket/15641">Bug #15641</a>.
352 * @throws ParseException if an error occurs
353 */
354 @Test
355 void testTicket15641() throws ParseException {
356 assertNotNull(buildTagChecker(
357 "relation[type=public_transport][public_transport=stop_area_group] > way {" +
358 " throwWarning: eval(count(parent_tags(public_transport)));" +
359 "}"));
360 }
361
362 /**
363 * Non-regression test for <a href="https://josm.openstreetmap.de/ticket/17358">Bug #17358</a>.
364 * @throws ParseException if an error occurs
365 */
366 @Test
367 void testTicket17358() throws ParseException {
368 final Collection<TestError> errors = buildTagChecker(
369 "*[/^name/=~/Test/]{" +
370 " throwWarning: \"Key value match\";" +
371 "}").getErrorsForPrimitive(OsmUtils.createPrimitive("way name=Test St"), false);
372 assertEquals(1, errors.size());
373 }
374
375 /**
376 * Non-regression test for <a href="https://josm.openstreetmap.de/ticket/17695">Bug #17695</a>.
377 * @throws Exception if an error occurs
378 */
379 @Test
380 void testTicket17695() throws Exception {
381 final MapCSSTagChecker test = buildTagChecker(
382 "*[building] ∈ *[building] {" +
383 "throwWarning: tr(\"Building inside building\");" +
384 "}");
385 try (InputStream is = TestUtils.getRegressionDataStream(17695, "bib2.osm")) {
386 test.visit(OsmReader.parseDataSet(is, null).allPrimitives());
387 List<TestError> errors = test.getErrors();
388 assertEquals(6, errors.size());
389 }
390 }
391
392 /**
393 * Non-regression test for <a href="https://josm.openstreetmap.de/ticket/13165">Bug #13165</a>.
394 * @throws Exception if an error occurs
395 */
396 @Test
397 void testTicket13165() throws Exception {
398 final MapCSSTagChecker test = buildTagChecker(
399 "area:closed[tag(\"landuse\") = parent_tag(\"landuse\")] ⧉ area:closed[landuse] {"
400 + "throwWarning: tr(\"Overlapping Identical Landuses\");"
401 + "}");
402 try (InputStream is = TestUtils.getRegressionDataStream(13165, "13165.osm")) {
403 test.visit(OsmReader.parseDataSet(is, null).allPrimitives());
404 List<TestError> errors = test.getErrors();
405 assertEquals(3, errors.size());
406 }
407 }
408
409 /**
410 * Non-regression test for <a href="https://josm.openstreetmap.de/ticket/13165">Bug #13165</a>.
411 * @throws Exception if an error occurs
412 */
413 @Test
414 void testTicket13165IncompleteMP() throws Exception {
415 final MapCSSTagChecker test = buildTagChecker(
416 "area:closed[tag(\"landuse\") = parent_tag(\"landuse\")] ⧉ area:closed[landuse] {"
417 + "throwWarning: tr(\"Overlapping Identical Landuses\");"
418 + "}");
419 try (InputStream is = TestUtils.getRegressionDataStream(13165, "13165-incomplete.osm.bz2")) {
420 test.visit(OsmReader.parseDataSet(is, null).allPrimitives());
421 List<TestError> errors = test.getErrors();
422 assertEquals(3, errors.size());
423 }
424 }
425
426 /**
427 * Non-regression test for <a href="https://josm.openstreetmap.de/ticket/19053">Bug #19053</a> and
428 * <a href="https://josm.openstreetmap.de/ticket/20957">Bug #20957</a>
429 * - MapCSS rule with group.
430 * - MapCSS functions round, tag, *, /
431 * @throws ParseException if a parsing error occurs
432 */
433 @Test
434 void testTicket19053() throws ParseException {
435 final MapCSSTagChecker test = buildTagChecker(
436 "*[ele][ele =~ /^-?[0-9]+\\.[0-9][0-9][0-9]+$/] {"
437 + "throwWarning: tr(\"{0}\",\"{0.tag}\");"
438 + "fixAdd: concat(\"ele=\", round(tag(\"ele\")*100)/100);"
439 + "group: tr(\"Unnecessary amount of decimal places\");" + "}");
440 final OsmPrimitive p = OsmUtils.createPrimitive("node ele=12.123456");
441 new DataSet(p);
442 final Collection<TestError> errors = test.getErrorsForPrimitive(p, false);
443 assertEquals(1, errors.size());
444 assertEquals("Unnecessary amount of decimal places", errors.iterator().next().getMessage());
445 assertEquals("3000_ele=12.123456", errors.iterator().next().getIgnoreSubGroup());
446 assertEquals("3000_Unnecessary amount of decimal places", errors.iterator().next().getIgnoreGroup());
447 Command fix = errors.iterator().next().getFix();
448 assertNotNull(fix);
449 assertEquals("12.123456", p.get("ele"));
450 fix.executeCommand();
451 assertEquals("12.12", p.get("ele"));
452 }
453
454}
Note: See TracBrowser for help on using the repository browser.