Index: trunk/src/org/openstreetmap/josm/data/validation/OsmValidator.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/validation/OsmValidator.java	(revision 6591)
+++ trunk/src/org/openstreetmap/josm/data/validation/OsmValidator.java	(revision 6592)
@@ -34,4 +34,5 @@
 import org.openstreetmap.josm.data.validation.tests.DuplicatedWayNodes;
 import org.openstreetmap.josm.data.validation.tests.Highways;
+import org.openstreetmap.josm.data.validation.tests.Lanes;
 import org.openstreetmap.josm.data.validation.tests.MapCSSTagChecker;
 import org.openstreetmap.josm.data.validation.tests.MultipolygonTest;
@@ -120,4 +121,5 @@
         OpeningHourTest.class, // 2901 .. 2999
         MapCSSTagChecker.class, // 3000 .. 3099
+        Lanes.class, // 3100 .. 3199
     };
     
Index: trunk/src/org/openstreetmap/josm/data/validation/tests/Lanes.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/validation/tests/Lanes.java	(revision 6592)
+++ trunk/src/org/openstreetmap/josm/data/validation/tests/Lanes.java	(revision 6592)
@@ -0,0 +1,50 @@
+package org.openstreetmap.josm.data.validation.tests;
+
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.validation.Severity;
+import org.openstreetmap.josm.data.validation.Test;
+import org.openstreetmap.josm.data.validation.TestError;
+import org.openstreetmap.josm.tools.Predicates;
+import org.openstreetmap.josm.tools.Utils;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.regex.Pattern;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+public class Lanes extends Test.TagTest {
+
+    public Lanes() {
+        super(tr("Lane tags"));
+    }
+
+    static int getLanesCount(String value) {
+        return value.isEmpty() ? 0 : value.split("\\|").length;
+    }
+
+    protected void checkEqualNumberOfLanes(final OsmPrimitive p, Pattern keyPattern, String message) {
+        final Collection<String> keysForPattern = Utils.filter(p.keySet(), Predicates.stringContainsPattern(keyPattern));
+        if (keysForPattern.size() < 2) {
+            // nothing to check
+            return;
+        }
+        final Collection<Integer> lanesCount = Utils.transform(keysForPattern, new Utils.Function<String, Integer>() {
+            @Override
+            public Integer apply(String key) {
+                return getLanesCount(p.get(key));
+            }
+        });
+        // if not all numbers are the same
+        if (new HashSet<Integer>(lanesCount).size() > 1) {
+            errors.add(new TestError(this, Severity.WARNING, message, 3100, p));
+        }
+    }
+
+    @Override
+    public void check(OsmPrimitive p) {
+        checkEqualNumberOfLanes(p, Pattern.compile(":lanes$"), tr("Number of lane dependent values inconsistent"));
+        checkEqualNumberOfLanes(p, Pattern.compile(":lanes:forward"), tr("Number of lane dependent values inconsistent in forward direction"));
+        checkEqualNumberOfLanes(p, Pattern.compile(":lanes:backward"), tr("Number of lane dependent values inconsistent in backward direction"));
+    }
+}
Index: trunk/src/org/openstreetmap/josm/tools/Predicates.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/Predicates.java	(revision 6591)
+++ trunk/src/org/openstreetmap/josm/tools/Predicates.java	(revision 6592)
@@ -11,4 +11,28 @@
 
     private Predicates() {
+    }
+
+    /**
+     * Returns the negation of {@code predicate}.
+     */
+    public static <T> Predicate<T> not(final Predicate<T> predicate) {
+        return new Predicate<T>() {
+            @Override
+            public boolean evaluate(T obj) {
+                return !predicate.evaluate(obj);
+            }
+        };
+    }
+
+    /**
+     * Returns a {@link Predicate} executing {@link Utils#equal}.
+     */
+    public static <T> Predicate<T> equalTo(final T ref) {
+        return new Predicate<T>() {
+            @Override
+            public boolean evaluate(T obj) {
+                return Utils.equal(obj, ref);
+            }
+        };
     }
 
Index: trunk/test/unit/org/openstreetmap/TestUtils.java
===================================================================
--- trunk/test/unit/org/openstreetmap/TestUtils.java	(revision 6591)
+++ trunk/test/unit/org/openstreetmap/TestUtils.java	(revision 6592)
@@ -2,10 +2,20 @@
 package org.openstreetmap;
 
-import org.junit.Ignore;
+import org.junit.Before;
+import org.junit.Test;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.tools.TextTagParser;
 
-@Ignore
+import java.util.Map;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
 public class TestUtils {
-    private TestUtils() {
-    }
 
     /**
@@ -20,3 +30,33 @@
         return testDataRoot.endsWith("/") ? testDataRoot : testDataRoot + "/";
     }
+
+    public static OsmPrimitive createPrimitive(String assertion) {
+        if (Main.pref == null) {
+            Main.initApplicationPreferences();
+        }
+        final String[] x = assertion.split("\\s+", 2);
+        final OsmPrimitive p = "n".equals(x[0]) || "node".equals(x[0])
+                ? new Node()
+                : "w".equals(x[0]) || "way".equals(x[0])
+                ? new Way()
+                : "r".equals(x[0]) || "relation".equals(x[0])
+                ? new Relation()
+                : null;
+        if (p == null) {
+            throw new IllegalArgumentException("Expecting n/node/w/way/r/relation, but got " + x[0]);
+        }
+        for (final Map.Entry<String, String> i : TextTagParser.readTagsFromText(x[1]).entrySet()) {
+            p.put(i.getKey(), i.getValue());
+        }
+        return p;
+    }
+
+    @Test
+    public void testCreatePrimitive() throws Exception {
+        final OsmPrimitive p = createPrimitive("way name=Foo railway=rail");
+        assertTrue(p instanceof Way);
+        assertThat(p.keySet().size(), is(2));
+        assertThat(p.get("name"), is("Foo"));
+        assertThat(p.get("railway"), is("rail"));
+    }
 }
Index: trunk/test/unit/org/openstreetmap/josm/data/validation/tests/LanesTest.groovy
===================================================================
--- trunk/test/unit/org/openstreetmap/josm/data/validation/tests/LanesTest.groovy	(revision 6592)
+++ trunk/test/unit/org/openstreetmap/josm/data/validation/tests/LanesTest.groovy	(revision 6592)
@@ -0,0 +1,41 @@
+package org.openstreetmap.josm.data.validation.tests
+
+import org.openstreetmap.TestUtils
+
+class LanesTest extends GroovyTestCase {
+
+    def lanes = new Lanes()
+
+    @Override
+    void setUp() {
+        lanes.initialize()
+        lanes.startTest(null)
+    }
+
+    void testLanesCount() {
+        assert lanes.getLanesCount("") == 0
+        assert lanes.getLanesCount("left") == 1
+        assert lanes.getLanesCount("left|right") == 2
+        assert lanes.getLanesCount("yes|no|yes") == 3
+    }
+
+    void test1() {
+        lanes.check(TestUtils.createPrimitive("way turn:lanes=left|right change:lanes=only_left|not_right|yes"))
+        assert lanes.errors.get(0).getMessage() == "Number of lane dependent values inconsistent"
+    }
+
+    void test2() {
+        lanes.check(TestUtils.createPrimitive("way width:lanes:forward=1|2|3 psv:lanes:forward=no|designated"))
+        assert lanes.errors.get(0).getMessage() == "Number of lane dependent values inconsistent in forward direction"
+    }
+
+    void test3() {
+        lanes.check(TestUtils.createPrimitive("way change:lanes:forward=yes|no turn:lanes:backward=left|right|left"))
+        assert lanes.errors.isEmpty()
+    }
+
+    void test4() {
+        lanes.check(TestUtils.createPrimitive("way turn:lanes:forward=left|right change:lanes:forward=yes|no|yes width:backward=1|2|3"))
+        assert lanes.errors.get(0).getMessage() == "Number of lane dependent values inconsistent in forward direction"
+    }
+}
Index: trunk/test/unit/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerTest.java
===================================================================
--- trunk/test/unit/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerTest.java	(revision 6591)
+++ trunk/test/unit/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerTest.java	(revision 6592)
@@ -3,4 +3,5 @@
 import org.junit.Before;
 import org.junit.Test;
+import org.openstreetmap.TestUtils;
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.command.ChangePropertyCommand;
@@ -65,34 +66,7 @@
     }
 
-    OsmPrimitive createPrimitiveForAssertion(String assertion) {
-        final String[] x = assertion.split("\\s+", 2);
-        final OsmPrimitive p = "n".equals(x[0]) || "node".equals(x[0])
-                ? new Node()
-                : "w".equals(x[0]) || "way".equals(x[0])
-                ? new Way()
-                : "r".equals(x[0]) || "relation".equals(x[0])
-                ? new Relation()
-                : null;
-        if (p == null) {
-            throw new IllegalArgumentException("Expecting n/node/w/way/r/relation, but got " + x[0]);
-        }
-        for (final Map.Entry<String, String> i : TextTagParser.readTagsFromText(x[1]).entrySet()) {
-            p.put(i.getKey(), i.getValue());
-        }
-        return p;
-    }
-
-    @Test
-    public void testCreatePrimitiveForAssertion() throws Exception {
-        final OsmPrimitive p = createPrimitiveForAssertion("way name=Foo railway=rail");
-        assertTrue(p instanceof Way);
-        assertThat(p.keySet().size(), is(2));
-        assertThat(p.get("name"), is("Foo"));
-        assertThat(p.get("railway"), is("rail"));
-    }
-
     @Test(expected = IllegalArgumentException.class)
     public void testCreatePrimitiveForAssertionFail() throws Exception {
-        final OsmPrimitive p = createPrimitiveForAssertion("noway name=Foo");
+        final OsmPrimitive p = TestUtils.createPrimitive("noway name=Foo");
     }
 
@@ -105,5 +79,5 @@
         for (final MapCSSTagChecker.TagCheck check : c.checks) {
             for (final Map.Entry<String, Boolean> i : check.assertions.entrySet()) {
-                final OsmPrimitive p = createPrimitiveForAssertion(i.getKey());
+                final OsmPrimitive p = TestUtils.createPrimitive(i.getKey());
                 if (check.matchesPrimitive(p) != i.getValue()) {
                     final String error = MessageFormat.format("Expecting test ''{0}'' (i.e., {1}) to {2} {3} (i.e., {4})",
