// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.data.validation.tests;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.InputStream;
import java.io.StringReader;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.TestUtils;
import org.openstreetmap.josm.command.ChangePropertyCommand;
import org.openstreetmap.josm.command.ChangePropertyKeyCommand;
import org.openstreetmap.josm.command.Command;
import org.openstreetmap.josm.command.PseudoCommand;
import org.openstreetmap.josm.command.SequenceCommand;
import org.openstreetmap.josm.data.osm.Node;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.data.osm.OsmUtils;
import org.openstreetmap.josm.data.validation.Severity;
import org.openstreetmap.josm.data.validation.TestError;
import org.openstreetmap.josm.data.validation.tests.MapCSSTagChecker.ParseResult;
import org.openstreetmap.josm.data.validation.tests.MapCSSTagChecker.TagCheck;
import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource;
import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.ParseException;
import org.openstreetmap.josm.io.OsmReader;
import org.openstreetmap.josm.testutils.JOSMTestRules;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
/**
* JUnit Test of {@link MapCSSTagChecker}.
*/
public class MapCSSTagCheckerTest {
/**
* Setup test.
*/
@Rule
@SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
public JOSMTestRules test = new JOSMTestRules().projection().platform();
static MapCSSTagChecker buildTagChecker(String css) throws ParseException {
final MapCSSTagChecker test = new MapCSSTagChecker();
test.checks.putAll("test", TagCheck.readMapCSS(new StringReader(css)).parseChecks);
return test;
}
/**
* Test {@code natural=marsh}.
* @throws ParseException if a parsing error occurs
*/
@Test
public void testNaturalMarsh() throws ParseException {
ParseResult result = TagCheck.readMapCSS(new StringReader(
"*[natural=marsh] {\n" +
" group: tr(\"deprecated\");\n" +
" throwWarning: tr(\"{0}={1} is deprecated\", \"{0.key}\", tag(\"natural\"));\n" +
" fixRemove: \"{0.key}\";\n" +
" fixAdd: \"natural=wetland\";\n" +
" fixAdd: \"wetland=marsh\";\n" +
"}"));
final List<TagCheck> checks = result.parseChecks;
assertEquals(1, checks.size());
assertTrue(result.parseErrors.isEmpty());
final TagCheck check = checks.get(0);
assertNotNull(check);
assertEquals("{0.key}=null is deprecated", check.getDescription(null));
assertEquals("fixRemove: {0.key}", check.fixCommands.get(0).toString());
assertEquals("fixAdd: natural=wetland", check.fixCommands.get(1).toString());
assertEquals("fixAdd: wetland=marsh", check.fixCommands.get(2).toString());
final Node n1 = new Node();
n1.put("natural", "marsh");
assertTrue(check.test(n1));
assertEquals("deprecated", check.getErrorForPrimitive(n1).getMessage());
assertEquals("natural=marsh is deprecated", check.getErrorForPrimitive(n1).getDescription());
assertEquals(Severity.WARNING, check.getErrorForPrimitive(n1).getSeverity());
assertEquals("Sequence: Fix of natural=marsh is deprecated", check.fixPrimitive(n1).getDescriptionText());
assertEquals("{natural=}", ((ChangePropertyCommand) check.fixPrimitive(n1).getChildren().iterator().next()).getTags().toString());
final Node n2 = new Node();
n2.put("natural", "wood");
assertFalse(check.test(n2));
assertEquals("The key is natural and the value is marsh",
TagCheck.insertArguments(check.rule.selectors.get(0), "The key is {0.key} and the value is {0.value}", null));
}
/**
* Non-regression test for <a href="https://josm.openstreetmap.de/ticket/10913">Bug #10913</a>.
* @throws ParseException if a parsing error occurs
*/
@Test
public void testTicket10913() throws ParseException {
final OsmPrimitive p = OsmUtils.createPrimitive("way highway=tertiary construction=yes");
final TagCheck check = TagCheck.readMapCSS(new StringReader("way {" +
"throwError: \"error\";" +
"fixChangeKey: \"highway => construction\";\n" +
"fixAdd: \"highway=construction\";\n" +
"}")).parseChecks.get(0);
final Command command = check.fixPrimitive(p);
assertTrue(command instanceof SequenceCommand);
final Iterator<PseudoCommand> it = command.getChildren().iterator();
assertTrue(it.next() instanceof ChangePropertyKeyCommand);
assertTrue(it.next() instanceof ChangePropertyCommand);
}
/**
* Non-regression test for <a href="https://josm.openstreetmap.de/ticket/9782">Bug #9782</a>.
* @throws ParseException if a parsing error occurs
*/
@Test
public void testTicket9782() throws ParseException {
final MapCSSTagChecker test = buildTagChecker("*[/.+_name/][!name] {" +
"throwWarning: tr(\"has {0} but not {1}\", \"{0.key}\", \"{1.key}\");}");
final OsmPrimitive p = OsmUtils.createPrimitive("way alt_name=Foo");
final Collection<TestError> errors = test.getErrorsForPrimitive(p, false);
assertEquals(1, errors.size());
assertEquals("has alt_name but not name", errors.iterator().next().getMessage());
assertEquals("3000_*[.+_name][!name]", errors.iterator().next().getIgnoreSubGroup());
}
/**
* Non-regression test for <a href="https://josm.openstreetmap.de/ticket/10859">Bug #10859</a>.
* @throws ParseException if a parsing error occurs
*/
@Test
public void testTicket10859() throws ParseException {
final MapCSSTagChecker test = buildTagChecker("way[highway=footway][foot?!] {\n" +
" throwWarning: tr(\"{0} used with {1}\", \"{0.value}\", \"{1.tag}\");}");
final OsmPrimitive p = OsmUtils.createPrimitive("way highway=footway foot=no");
final Collection<TestError> errors = test.getErrorsForPrimitive(p, false);
assertEquals(1, errors.size());
assertEquals("footway used with foot=no", errors.iterator().next().getMessage());
assertEquals("3000_way[highway=footway][foot]", errors.iterator().next().getIgnoreSubGroup());
}
/**
* Non-regression test for <a href="https://josm.openstreetmap.de/ticket/13630">Bug #13630</a>.
* @throws ParseException if a parsing error occurs
*/
@Test
public void testTicket13630() throws ParseException {
ParseResult result = TagCheck.readMapCSS(new StringReader(
"node[crossing=zebra] {fixRemove: \"crossing=zebra\";}"));
assertTrue(result.parseChecks.isEmpty());
assertEquals(1, result.parseErrors.size());
}
/**
* Unit test of {@code min-josm-version} processing.
* @throws ParseException if a parsing error occurs
*/
@Test
public void testPreprocessing() throws ParseException {
final MapCSSTagChecker test = buildTagChecker("" +
"@supports (min-josm-version: 1) { *[foo] { throwWarning: \"!\"; } }\n" +
"@supports (min-josm-version: 2147483647) { *[bar] { throwWarning: \"!\"; } }\n");
assertEquals(1, test.getErrorsForPrimitive(OsmUtils.createPrimitive("way foo=1"), false).size());
assertEquals(0, test.getErrorsForPrimitive(OsmUtils.createPrimitive("way bar=1"), false).size());
}
/**
* Unit test of {@link MapCSSTagChecker#initialize}.
* @throws Exception if an error occurs
*/
@Test
public void testInit() throws Exception {
MapCSSTagChecker c = new MapCSSTagChecker();
c.initialize();
Set<String> assertionErrors = new LinkedHashSet<>();
for (Set<TagCheck> schecks : c.checks.values()) {
assertionErrors.addAll(c.checkAsserts(schecks));
}
for (String msg : assertionErrors) {
Main.error(msg);
}
assertTrue("not all assertions included in the tests are met", assertionErrors.isEmpty());
}
/**
* Non-regression test for <a href="https://josm.openstreetmap.de/ticket/13762">Bug #13762</a>.
* @throws ParseException if a parsing error occurs
*/
@Test
public void testTicket13762() throws ParseException {
final ParseResult parseResult = TagCheck.readMapCSS(new StringReader("" +
"meta[lang=de] {\n" +
" title: \"Deutschlandspezifische Regeln\";" +
"}"));
assertTrue(parseResult.parseErrors.isEmpty());
}
/**
* Non-regression test for <a href="https://josm.openstreetmap.de/ticket/14287">Bug #14287</a>.
* @throws Exception if an error occurs
*/
@Test
@Ignore("not fixed yet")
public void testTicket14287() throws Exception {
final MapCSSTagChecker test = buildTagChecker(
"node[amenity=parking] ∈ *[amenity=parking] {" +
" throwWarning: tr(\"{0} inside {1}\", \"amenity=parking\", \"amenity=parking\");" +
"}");
try (InputStream is = TestUtils.getRegressionDataStream(14287, "example.osm")) {
test.visit(OsmReader.parseDataSet(is, null).allPrimitives());
assertEquals(6, test.getErrors().size());
}
}
private void doTestNaturalWood(int ticket, String filename, int errorsCount, int setsCount) throws Exception {
final MapCSSTagChecker test = buildTagChecker(
"area:closed:areaStyle[tag(\"natural\") = parent_tag(\"natural\")] ⧉ area:closed:areaStyle[natural] {" +
" throwWarning: tr(\"Overlapping Identical Natural Areas\");" +
"}");
final MapCSSStyleSource style = new MapCSSStyleSource(
"area[natural=wood] {" +
" fill-color: woodarea#008000;" +
"}");
MapPaintStyles.addStyle(style);
try (InputStream is = TestUtils.getRegressionDataStream(ticket, filename)) {
test.visit(OsmReader.parseDataSet(is, null).allPrimitives());
List<TestError> errors = test.getErrors();
assertEquals(errorsCount, errors.size());
Set<Set<OsmPrimitive>> primitives = new HashSet<>();
for (TestError e : errors) {
primitives.add(new HashSet<>(e.getPrimitives()));
}
assertEquals(setsCount, primitives.size());
} finally {
MapPaintStyles.removeStyle(style);
}
}
/**
* Non-regression test for <a href="https://josm.openstreetmap.de/ticket/12627">Bug #12627</a>.
* @throws Exception if an error occurs
*/
@Test
@Ignore("not fixed yet")
public void testTicket12627() throws Exception {
doTestNaturalWood(12627, "overlapping.osm", 1, 1);
}
/**
* Non-regression test for <a href="https://josm.openstreetmap.de/ticket/14289">Bug #14289</a>.
* @throws Exception if an error occurs
*/
@Test
@Ignore("not fixed yet")
public void testTicket14289() throws Exception {
doTestNaturalWood(14289, "example2.osm", 3, 3);
}
}