Index: /trunk/src/org/openstreetmap/josm/data/validation/OsmValidator.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/validation/OsmValidator.java	(revision 9932)
+++ /trunk/src/org/openstreetmap/josm/data/validation/OsmValidator.java	(revision 9933)
@@ -47,4 +47,5 @@
 import org.openstreetmap.josm.data.validation.tests.OverlappingWays;
 import org.openstreetmap.josm.data.validation.tests.PowerLines;
+import org.openstreetmap.josm.data.validation.tests.PublicTransportRouteTest;
 import org.openstreetmap.josm.data.validation.tests.RelationChecker;
 import org.openstreetmap.josm.data.validation.tests.SelfIntersectingWay;
@@ -127,4 +128,5 @@
         ApiCapabilitiesTest.class, // 3400 .. 3499
         LongSegment.class, // 3500 .. 3599
+        PublicTransportRouteTest.class, // 3600 .. 3699
     };
 
Index: /trunk/src/org/openstreetmap/josm/data/validation/tests/PublicTransportRouteTest.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/validation/tests/PublicTransportRouteTest.java	(revision 9933)
+++ /trunk/src/org/openstreetmap/josm/data/validation/tests/PublicTransportRouteTest.java	(revision 9933)
@@ -0,0 +1,68 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.validation.tests;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.RelationMember;
+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.gui.dialogs.relation.sort.WayConnectionType;
+import org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionTypeCalculator;
+
+/**
+ * Tests for <a href="https://wiki.openstreetmap.org/wiki/Proposed_features/Public_Transport">public transport routes</a>.
+ */
+public class PublicTransportRouteTest extends Test {
+
+    private final WayConnectionTypeCalculator connectionTypeCalculator = new WayConnectionTypeCalculator();
+
+    /**
+     * Constructs a new {@code PublicTransportRouteTest}.
+     */
+    public PublicTransportRouteTest() {
+        super(tr("Public Transport Route"));
+    }
+
+    @Override
+    public void visit(Relation r) {
+        final boolean skip = r.hasIncompleteMembers()
+                || !r.hasTag("type", "route")
+                || !r.hasKey("route")
+                || !r.hasTag("public_transport:version", "2");
+        if (skip) {
+            return;
+        }
+
+        final List<RelationMember> membersToCheck = new ArrayList<>();
+        for (RelationMember member : r.getMembers()) {
+            if (member.hasRole("forward", "backward")) {
+                errors.add(new TestError(this, Severity.WARNING, tr("Route relation contains a ''{0}'' role", "forward/backward"), 3601, r));
+                return;
+            } else if (member.hasRole("")) {
+                membersToCheck.add(member);
+            }
+        }
+        if (membersToCheck.isEmpty()) {
+            return;
+        }
+
+        final List<WayConnectionType> links = connectionTypeCalculator.updateLinks(membersToCheck);
+        for (int i = 0; i < links.size(); i++) {
+            final WayConnectionType link = links.get(i);
+            final boolean hasError = !(i == 0 || link.linkPrev)
+                    || !(i == links.size() - 1 || link.linkNext)
+                    || !EnumSet.of(WayConnectionType.Direction.FORWARD, WayConnectionType.Direction.BACKWARD).contains(link.direction);
+            if (hasError) {
+                errors.add(new TestError(this, Severity.WARNING, tr("Route relation contains a gap"), 3602, r));
+                return;
+            }
+        }
+
+    }
+}
Index: /trunk/test/unit/org/openstreetmap/josm/TestUtils.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/TestUtils.java	(revision 9932)
+++ /trunk/test/unit/org/openstreetmap/josm/TestUtils.java	(revision 9933)
@@ -36,4 +36,9 @@
 import java.util.Map;
 
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmUtils;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.RelationMember;
+import org.openstreetmap.josm.data.osm.Way;
 import org.openstreetmap.josm.gui.progress.AbstractProgressMonitor;
 import org.openstreetmap.josm.gui.progress.CancelHandler;
@@ -545,3 +550,33 @@
     // CHECKSTYLE.ON: MethodLength
     // CHECKSTYLE.ON: AnonInnerLength
+
+    /**
+     * Creates a new way with the given tags (see {@link OsmUtils#createPrimitive(java.lang.String)}) and the nodes added
+     *
+     * @param tags  the tags to set
+     * @param nodes the nodes to add
+     * @return a new way
+     */
+    public static Way newWay(String tags, Node... nodes) {
+        final Way way = (Way) OsmUtils.createPrimitive("way " + tags);
+        for (Node node : nodes) {
+            way.addNode(node);
+        }
+        return way;
+    }
+
+    /**
+     * Creates a new relation with the given tags (see {@link OsmUtils#createPrimitive(java.lang.String)}) and the members added
+     *
+     * @param tags  the tags to set
+     * @param members the members to add
+     * @return a new relation
+     */
+    public static Relation newRelation(String tags, RelationMember... members) {
+        final Relation relation = (Relation) OsmUtils.createPrimitive("relation " + tags);
+        for (RelationMember member : members) {
+            relation.addMember(member);
+        }
+        return relation;
+    }
 }
Index: /trunk/test/unit/org/openstreetmap/josm/data/validation/tests/PublicTransportRouteTestTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/data/validation/tests/PublicTransportRouteTestTest.java	(revision 9933)
+++ /trunk/test/unit/org/openstreetmap/josm/data/validation/tests/PublicTransportRouteTestTest.java	(revision 9933)
@@ -0,0 +1,70 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.validation.tests;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.openstreetmap.josm.JOSMFixture;
+import org.openstreetmap.josm.TestUtils;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.RelationMember;
+import org.openstreetmap.josm.data.osm.Way;
+
+/**
+ * JUnit Test of "Public Transport Route" validation test.
+ */
+public class PublicTransportRouteTestTest {
+
+    final PublicTransportRouteTest test = new PublicTransportRouteTest();
+
+    /**
+     * Setup test.
+     */
+    @Before
+    public void setUp() {
+        JOSMFixture.createUnitTestFixture().init();
+    }
+
+    /**
+     * Performs various tests.
+     */
+    @Test
+    public void test() {
+        final List<Node> nodes = Arrays.asList(new Node(), new Node(), new Node(), new Node(), new Node(), new Node());
+        final Way w1 = TestUtils.newWay("", nodes.get(0), nodes.get(1));
+        final Way w2 = TestUtils.newWay("", nodes.get(1), nodes.get(2));
+        final Way w3 = TestUtils.newWay("", nodes.get(3), nodes.get(2));
+        final Way w4 = TestUtils.newWay("", nodes.get(3), nodes.get(4));
+
+        test.startTest(null);
+        test.visit(TestUtils.newRelation("type=route route=tram public_transport:version=2"));
+        test.visit(TestUtils.newRelation("type=unknown"));
+        assertEquals(0, test.getErrors().size());
+
+        final Relation r2 = TestUtils.newRelation("type=route route=tram public_transport:version=2",
+                new RelationMember("", w1), new RelationMember("", w2), new RelationMember("", w3), new RelationMember("", w4));
+        test.startTest(null);
+        test.visit(r2);
+        assertEquals(0, test.getErrors().size());
+
+        final Relation r3 = TestUtils.newRelation("type=route route=tram public_transport:version=2",
+                new RelationMember("forward", w1));
+        test.startTest(null);
+        test.visit(r3);
+        assertEquals(1, test.getErrors().size());
+        assertEquals("Route relation contains a 'forward/backward' role", test.getErrors().get(0).getMessage());
+
+        final Relation r4 = TestUtils.newRelation("type=route route=tram public_transport:version=2",
+                new RelationMember("", w1), new RelationMember("", w3), new RelationMember("", w2));
+        test.startTest(null);
+        test.visit(r4);
+        assertEquals(1, test.getErrors().size());
+        assertEquals("Route relation contains a gap", test.getErrors().get(0).getMessage());
+
+    }
+}
