Added Yang validator. 09/209/2
authorMaros Marsalek <mmarsale@cisco.com>
Tue, 16 Apr 2013 15:05:00 +0000 (17:05 +0200)
committerGerrit Code Review <gerrit@opendaylight.org>
Mon, 22 Apr 2013 14:47:35 +0000 (14:47 +0000)
Initial implementation committed that validates Module and Submodule statements.
It also validates a few additional yang statements e.g. revision, import.

Rules that must be obeyed are documented in code as javadoc.
Added tests to test implemented rules.

Signed-off-by: Maros Marsalek <mmarsale@cisco.com>
opendaylight/sal/yang-prototype/code-generator/yang-model-parser-impl/src/main/java/org/opendaylight/controller/yang/model/parser/impl/YangModelParserImpl.java
opendaylight/sal/yang-prototype/code-generator/yang-model-parser-impl/src/main/java/org/opendaylight/controller/yang/model/parser/impl/YangModelParserListenerImpl.java
opendaylight/sal/yang-prototype/code-generator/yang-model-parser-impl/src/main/java/org/opendaylight/controller/yang/model/parser/impl/YangModelValidationListener.java [new file with mode: 0644]
opendaylight/sal/yang-prototype/code-generator/yang-model-parser-impl/src/main/java/org/opendaylight/controller/yang/model/parser/impl/YangValidationException.java [new file with mode: 0644]
opendaylight/sal/yang-prototype/code-generator/yang-model-parser-impl/src/test/java/org/opendaylight/controller/yang/model/parser/impl/YangModelValidationListenerTest_Module.java [new file with mode: 0644]
opendaylight/sal/yang-prototype/code-generator/yang-model-parser-impl/src/test/java/org/opendaylight/controller/yang/model/parser/impl/YangModelValidationListenerTest_SubModule.java [new file with mode: 0644]
opendaylight/sal/yang-prototype/code-generator/yang-model-parser-impl/src/test/resources/types/custom-types-test@2012-4-4.yang

index 27a65c1..5490223 100644 (file)
@@ -119,6 +119,21 @@ public class YangModelParserImpl implements YangModelParser {
         final List<ParseTree> trees = parseStreams(yangFiles);
         final ModuleBuilder[] builders = new ModuleBuilder[trees.size()];
 
+       // validation\r
+        // if validation fails with any file, do not continue and throw\r
+        // exception\r
+        for (int i = 0; i < trees.size(); i++) {\r
+            try {\r
+                final YangModelValidationListener yangModelParser = new YangModelValidationListener();\r
+                walker.walk(yangModelParser, trees.get(i));\r
+            } catch (IllegalStateException e) {\r
+                // wrap exception to add information about which file failed\r
+                throw new YangValidationException(\r
+                        "Yang validation failed for file" + yangFiles[i], e);\r
+            }\r
+        }\r
+
+
         YangModelParserListenerImpl yangModelParser = null;
         for (int i = 0; i < trees.size(); i++) {
             yangModelParser = new YangModelParserListenerImpl();
index 6d51868..765220b 100644 (file)
@@ -82,7 +82,7 @@ final class YangModelParserListenerImpl extends YangParserBaseListener {
     private String yangModelPrefix;\r
     private Date revision = new Date(0L);\r
 \r
-    private final DateFormat simpleDateFormat = new SimpleDateFormat(\r
+    final static DateFormat simpleDateFormat = new SimpleDateFormat(\r
             "yyyy-mm-dd");\r
     private final Stack<String> actualPath = new Stack<String>();\r
 \r
diff --git a/opendaylight/sal/yang-prototype/code-generator/yang-model-parser-impl/src/main/java/org/opendaylight/controller/yang/model/parser/impl/YangModelValidationListener.java b/opendaylight/sal/yang-prototype/code-generator/yang-model-parser-impl/src/main/java/org/opendaylight/controller/yang/model/parser/impl/YangModelValidationListener.java
new file mode 100644 (file)
index 0000000..b8a5cf3
--- /dev/null
@@ -0,0 +1,414 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.yang.model.parser.impl;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import org.antlr.v4.runtime.tree.ParseTree;
+import org.opendaylight.controller.antlrv4.code.gen.YangParser;
+import org.opendaylight.controller.antlrv4.code.gen.YangParser.Belongs_to_stmtContext;
+import org.opendaylight.controller.antlrv4.code.gen.YangParser.Import_stmtContext;
+import org.opendaylight.controller.antlrv4.code.gen.YangParser.Include_stmtContext;
+import org.opendaylight.controller.antlrv4.code.gen.YangParser.Module_header_stmtsContext;
+import org.opendaylight.controller.antlrv4.code.gen.YangParser.Namespace_stmtContext;
+import org.opendaylight.controller.antlrv4.code.gen.YangParser.Prefix_stmtContext;
+import org.opendaylight.controller.antlrv4.code.gen.YangParser.Revision_date_stmtContext;
+import org.opendaylight.controller.antlrv4.code.gen.YangParser.Revision_stmtContext;
+import org.opendaylight.controller.antlrv4.code.gen.YangParser.Revision_stmtsContext;
+import org.opendaylight.controller.antlrv4.code.gen.YangParser.Submodule_header_stmtsContext;
+import org.opendaylight.controller.antlrv4.code.gen.YangParser.Submodule_stmtContext;
+import org.opendaylight.controller.antlrv4.code.gen.YangParser.Yang_version_stmtContext;
+import org.opendaylight.controller.antlrv4.code.gen.YangParserBaseListener;
+import org.opendaylight.controller.yang.model.parser.util.YangModelBuilderUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Validation listener that validates yang statements according to RFC-6020.
+ * This validator expects only one module or submodule per file.
+ */
+
+/*
+ * TODO is this assumption(module per file) correct ? if so, should a check be
+ * performed ?
+ * 
+ * TODO break into smaller classes e.g. class for header statements, body
+ * statements...
+ */
+final class YangModelValidationListener extends YangParserBaseListener {
+
+    private static final Logger logger = LoggerFactory
+            .getLogger(YangModelValidationListener.class);
+
+    private final Set<String> uniquePrefixes;
+    private final Set<String> uniqueImports;
+    private final Set<String> uniqueIncludes;
+
+    public YangModelValidationListener() {
+        super();
+        uniquePrefixes = new HashSet<String>();
+        uniqueImports = new HashSet<String>();
+        uniqueIncludes = new HashSet<String>();
+    }
+
+    /**
+     * Rules:
+     * <ol>
+     * <li>Identifier contains only permitted characters</li>
+     * <li>One revision statements present</li>
+     * <li>One header statements present</li>
+     * </ol>
+     */
+    @Override
+    public void enterModule_stmt(YangParser.Module_stmtContext ctx) {
+        String moduleName = getName(ctx);
+
+        checkIdentifier(moduleName, "Module");
+
+        checkPresentChildOfType(ctx, Revision_stmtsContext.class,
+                f("Missing revision statements in module:%s", moduleName), true);
+
+        checkPresentChildOfType(ctx, Module_header_stmtsContext.class,
+                f("Missing header statements in module:%s", moduleName), true);
+    }
+
+    /**
+     * Rules:
+     * <ol>
+     * <li>Identifier contains only permitted characters</li>
+     * <li>One revision statements present</li>
+     * <li>One header statements present</li>
+     * </ol>
+     */
+    @Override
+    public void enterSubmodule_stmt(Submodule_stmtContext ctx) {
+        String submoduleName = getName(ctx);
+
+        checkIdentifier(submoduleName, "Submodule");
+
+        checkPresentChildOfType(
+                ctx,
+                Revision_stmtsContext.class,
+                f("Missing revision statements in submodule:%s", submoduleName),
+                true);
+
+        checkPresentChildOfType(ctx, Submodule_header_stmtsContext.class,
+                f("Missing header statements in submodule:%s", submoduleName),
+                true);
+    }
+
+    /**
+     * Rules:
+     * <ol>
+     * <li>One Belongs-to statement present</li>
+     * </ol>
+     */
+    @Override
+    public void enterSubmodule_header_stmts(Submodule_header_stmtsContext ctx) {
+        String submoduleName = getRootParentName(ctx);
+
+        checkPresentChildOfType(
+                ctx,
+                Belongs_to_stmtContext.class,
+                f("Missing belongs-to statement in submodule:%s", submoduleName),
+                true);
+
+        // check Yang version present, if not issue warning
+        checkYangVersion(ctx, submoduleName);
+    }
+
+    /**
+     * Rules:
+     * <ol>
+     * <li>Identifier contains only permitted characters</li>
+     * <li>One Prefix statement child</li>
+     * </ol>
+     */
+    @Override
+    public void enterBelongs_to_stmt(Belongs_to_stmtContext ctx) {
+        String belongToName = getName(ctx);
+        String rootParentName = getRootParentName(ctx);
+
+        checkIdentifier(belongToName,
+                f("In (sub)module:%s , Belongs-to statement", rootParentName));
+
+        checkPresentChildOfType(
+                ctx,
+                Prefix_stmtContext.class,
+                f("Missing prefix statement in belongs-to:%s, in (sub)module:%s",
+                        belongToName, rootParentName), true);
+    }
+
+    /**
+     * Rules:
+     * <ol>
+     * <li>At least one Revision statement present</li>
+     * </ol>
+     */
+    @Override
+    public void enterRevision_stmts(Revision_stmtsContext ctx) {
+        String rootParentName = getRootParentName(ctx);
+
+        checkPresentChildOfType(
+                ctx,
+                Revision_stmtContext.class,
+                f("Missing at least one revision statement in (sub)module:%s",
+                        rootParentName), false);
+    }
+
+    /**
+     * Rules:
+     * <ol>
+     * <li>One Namespace statement present</li>
+     * </ol>
+     */
+    @Override
+    public void enterModule_header_stmts(Module_header_stmtsContext ctx) {
+        String moduleName = getRootParentName(ctx);
+
+        checkPresentChildOfType(ctx, Namespace_stmtContext.class,
+                f("Missing namespace statement in module:%s", moduleName), true);
+
+        // check Yang version present, if not issue warning
+        checkYangVersion(ctx, moduleName);
+    }
+
+    /**
+     * Rules:
+     * <ol>
+     * <li>Namespace string can be parsed as URI</li>
+     * </ol>
+     */
+    @Override
+    public void enterNamespace_stmt(Namespace_stmtContext ctx) {
+        String namespaceName = getName(ctx);
+        String rootParentName = getRootParentName(ctx);
+
+        try {
+            new URI(namespaceName);
+        } catch (URISyntaxException e) {
+            throw new YangValidationException(f(
+                    "Namespace:%s in module:%s cannot be parsed as URI",
+                    namespaceName, rootParentName));
+        }
+    }
+
+    /**
+     * Rules:
+     * <ol>
+     * <li>Identifier contains only permitted characters</li>
+     * <li>Every import(identified by identifier) within a module/submodule is
+     * present only once</li>
+     * <li>One prefix statement child</li>
+     * <li>One revision-date statement child</li>
+     * </ol>
+     */
+    @Override
+    public void enterImport_stmt(Import_stmtContext ctx) {
+        String importName = getName(ctx);
+        String rootParentName = getRootParentName(ctx);
+
+        checkIdentifier(importName,
+                f("In (sub)module:%s , Import statement", rootParentName));
+
+        if (uniqueImports.contains(importName))
+            throw new YangValidationException(f(
+                    "Module:%s imported twice in (sub)module:%s", importName,
+                    rootParentName));
+        uniqueImports.add(importName);
+
+        checkPresentChildOfType(
+                ctx,
+                Prefix_stmtContext.class,
+                f("Missing prefix statement in import:%s, in (sub)module:%s",
+                        importName, rootParentName), true);
+        checkPresentChildOfType(
+                ctx,
+                Revision_date_stmtContext.class,
+                f("Missing revision-date statement in import:%s, in (sub)module:%s",
+                        importName, rootParentName), true);
+    }
+
+    /**
+     * Rules:
+     * <ol>
+     * <li>Date is in valid format</li>
+     * </ol>
+     */
+    @Override
+    public void enterRevision_date_stmt(Revision_date_stmtContext ctx) {
+        String rootParentName = getRootParentName(ctx);
+        String exceptionMessage = f(
+                "Invalid date format for revision-date:%s in import/include statement:%s, in (sub)module:%s , expected date format is:%s",
+                getName(ctx), getRootParentName(ctx), rootParentName,
+                YangModelParserListenerImpl.simpleDateFormat.format(new Date()));
+
+        validateDateFormat(getName(ctx),
+                YangModelParserListenerImpl.simpleDateFormat, exceptionMessage);
+    }
+
+    /**
+     * Rules:
+     * <ol>
+     * <li>Identifier contains only permitted characters</li>
+     * <li>Every include(identified by identifier) within a module/submodule is
+     * present only once</li>
+     * <li>One Revision-date statement child</li>
+     * </ol>
+     */
+    @Override
+    public void enterInclude_stmt(Include_stmtContext ctx) {
+        String includeName = getName(ctx);
+        String rootParentName = getRootParentName(ctx);
+
+        checkIdentifier(includeName,
+                f("In (sub)module:%s , Include statement", rootParentName));
+
+        if (uniqueIncludes.contains(includeName))
+            throw new YangValidationException(f(
+                    "Submodule:%s included twice in (sub)module:%s",
+                    includeName, rootParentName));
+        uniqueIncludes.add(includeName);
+
+        checkPresentChildOfType(
+                ctx,
+                Revision_date_stmtContext.class,
+                f("Missing revision-date statement in include:%s, in (sub)module:%s",
+                        includeName, rootParentName), true);
+    }
+
+    static final String SUPPORTED_YANG_VERSION = "1";
+
+    /**
+     * Rules:
+     * <ol>
+     * <li>Yang-version is specified as 1</li>
+     * </ol>
+     */
+    @Override
+    public void enterYang_version_stmt(YangParser.Yang_version_stmtContext ctx) {
+        String version = getName(ctx);
+        String rootParentName = getRootParentName(ctx);
+        if (!version.equals(SUPPORTED_YANG_VERSION)) {
+            throw new YangValidationException(
+                    f("Unsupported yang version:%s, in (sub)module:%s, supported version:%s",
+                            version, rootParentName, SUPPORTED_YANG_VERSION));
+        }
+    }
+
+    /**
+     * Rules:
+     * <ol>
+     * <li>Date is in valid format</li>
+     * </ol>
+     */
+    @Override
+    public void enterRevision_stmt(YangParser.Revision_stmtContext ctx) {
+        String parentName = getRootParentName(ctx);
+        String exceptionMessage = f(
+                "Invalid date format for revision:%s in (sub)module:%s, expected date format is:%s",
+                getName(ctx), parentName,
+                YangModelParserListenerImpl.simpleDateFormat.format(new Date()));
+
+        validateDateFormat(getName(ctx),
+                YangModelParserListenerImpl.simpleDateFormat, exceptionMessage);
+    }
+
+    /**
+     * Rules:
+     * <ol>
+     * <li>Identifier contains only permitted characters</li>
+     * <li>Every prefix(identified by identifier) within a module/submodule is
+     * presented only once</li>
+     * </ol>
+     */
+    @Override
+    public void enterPrefix_stmt(Prefix_stmtContext ctx) {
+        String name = getName(ctx);
+        checkIdentifier(
+                name,
+                f("In module or import statement:%s , Prefix",
+                        getRootParentName(ctx)));
+
+        if (uniquePrefixes.contains(name))
+            throw new YangValidationException(f(
+                    "Not a unique prefix:%s, in (sub)module:%s", name,
+                    getRootParentName(ctx)));
+        uniquePrefixes.add(name);
+    }
+
+    private String getRootParentName(ParseTree ctx) {
+        ParseTree root = ctx;
+        while (root.getParent() != null) {
+            root = root.getParent();
+        }
+        return getName(root);
+    }
+
+    private static String getName(ParseTree child) {
+        return YangModelBuilderUtil.stringFromNode(child);
+    }
+
+    private static String f(String base, Object... args) {
+        return String.format(base, args);
+    }
+
+    private static void checkYangVersion(ParseTree ctx, String moduleName) {
+        if (!checkPresentChildOfType(ctx, Yang_version_stmtContext.class, true))
+            logger.warn(f(
+                    "Yang version statement not present in module:%s, Validating as yang version:%s",
+                    moduleName, SUPPORTED_YANG_VERSION));
+    }
+
+    private static void validateDateFormat(String string, DateFormat format,
+            String message) {
+        try {
+            format.parse(string);
+        } catch (ParseException e) {
+            throw new YangValidationException(message);
+        }
+    }
+
+    private static Pattern identifierPattern = Pattern
+            .compile("[a-zA-Z_][a-zA-Z0-9_.-]*");
+
+    static void checkIdentifier(String name, String messagePrefix) {
+        if (!identifierPattern.matcher(name).matches())
+            throw new YangValidationException(f(
+                    "%s identifier:%s is not in required format:%s",
+                    messagePrefix, name, identifierPattern.toString()));
+    }
+
+    private static void checkPresentChildOfType(ParseTree ctx,
+            Class<?> expectedChildType, String message, boolean atMostOne) {
+        if (!checkPresentChildOfType(ctx, expectedChildType, atMostOne))
+            throw new YangValidationException(message);
+    }
+
+    private static boolean checkPresentChildOfType(ParseTree ctx,
+            Class<?> expectedChildType, boolean atMostOne) {
+
+        int count = 0;
+
+        for (int i = 0; i < ctx.getChildCount(); i++) {
+            ParseTree child = ctx.getChild(i);
+            if (expectedChildType.isInstance(child))
+                count++;
+        }
+
+        return atMostOne ? count == 1 ? true : false : count != 0 ? true
+                : false;
+    }
+}
diff --git a/opendaylight/sal/yang-prototype/code-generator/yang-model-parser-impl/src/main/java/org/opendaylight/controller/yang/model/parser/impl/YangValidationException.java b/opendaylight/sal/yang-prototype/code-generator/yang-model-parser-impl/src/main/java/org/opendaylight/controller/yang/model/parser/impl/YangValidationException.java
new file mode 100644 (file)
index 0000000..43d3940
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.yang.model.parser.impl;
+
+/**
+ * Unchecked exception thrown if yang definition is not valid according to
+ * {@link YangModelValidationListener}
+ */
+public class YangValidationException extends RuntimeException {
+
+    private static final long serialVersionUID = 7414330400390825381L;
+
+    public YangValidationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public YangValidationException(String message) {
+        super(message);
+    }
+
+}
\ No newline at end of file
diff --git a/opendaylight/sal/yang-prototype/code-generator/yang-model-parser-impl/src/test/java/org/opendaylight/controller/yang/model/parser/impl/YangModelValidationListenerTest_Module.java b/opendaylight/sal/yang-prototype/code-generator/yang-model-parser-impl/src/test/java/org/opendaylight/controller/yang/model/parser/impl/YangModelValidationListenerTest_Module.java
new file mode 100644 (file)
index 0000000..76715e8
--- /dev/null
@@ -0,0 +1,315 @@
+package org.opendaylight.controller.yang.model.parser.impl;
+
+import static org.hamcrest.core.Is.*;
+import static org.junit.Assert.*;
+import static org.junit.matchers.JUnitMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.util.Date;
+
+import org.antlr.v4.runtime.tree.ParseTree;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.controller.antlrv4.code.gen.YangParser.Import_stmtContext;
+import org.opendaylight.controller.antlrv4.code.gen.YangParser.Include_stmtContext;
+import org.opendaylight.controller.antlrv4.code.gen.YangParser.Module_header_stmtsContext;
+import org.opendaylight.controller.antlrv4.code.gen.YangParser.Module_stmtContext;
+import org.opendaylight.controller.antlrv4.code.gen.YangParser.Namespace_stmtContext;
+import org.opendaylight.controller.antlrv4.code.gen.YangParser.Prefix_stmtContext;
+import org.opendaylight.controller.antlrv4.code.gen.YangParser.Revision_date_stmtContext;
+import org.opendaylight.controller.antlrv4.code.gen.YangParser.Revision_stmtContext;
+import org.opendaylight.controller.antlrv4.code.gen.YangParser.Revision_stmtsContext;
+import org.opendaylight.controller.antlrv4.code.gen.YangParser.StringContext;
+import org.opendaylight.controller.antlrv4.code.gen.YangParser.Yang_version_stmtContext;
+
+public class YangModelValidationListenerTest_Module {
+
+    private YangModelValidationListener valid;
+    private Module_stmtContext ctx;
+
+    @Before
+    public void setUp() {
+        valid = new YangModelValidationListener();
+    }
+
+    @Test(expected = YangValidationException.class)
+    public void testRevisionInvalidDateFormat() {
+        Revision_stmtContext mockedRev = mockModuleWithRevision(2, "badFormat");
+
+        try {
+            valid.enterRevision_stmt(mockedRev);
+        } catch (YangValidationException e) {
+            assertThat(
+                    e.getMessage(),
+                    containsString("Invalid date format for revision:badFormat in (sub)module:module1, expected date format is:"));
+            throw e;
+        }
+    }
+
+    private Revision_stmtContext mockModuleWithRevision(int moduleChildren,
+            String date) {
+        Revision_stmtContext mockedRev = mock(Revision_stmtContext.class);
+        doReturn(1).when(mockedRev).getChildCount();
+        mockName(mockedRev, date);
+
+        Revision_stmtsContext revs = mockRevisionsParent(2, mockedRev);
+
+        mockModuleParent(moduleChildren, revs, "module1");
+        return mockedRev;
+    }
+
+    @Test
+    public void testRevisionValidDateFormat() {
+        Revision_stmtContext mockedRev = mockModuleWithRevision(2,
+                getFormattedDate());
+
+        valid.enterRevision_stmt(mockedRev);
+    }
+
+    private String getFormattedDate() {
+        return YangModelParserListenerImpl.simpleDateFormat.format(new Date());
+    }
+
+    @Test(expected = YangValidationException.class)
+    public void testNoRevision() {
+
+        Module_stmtContext ctx = mock(Module_stmtContext.class);
+        doReturn(1).when(ctx).getChildCount();
+        mockName(ctx, "module1");
+
+        try {
+            valid.enterModule_stmt(ctx);
+        } catch (YangValidationException e) {
+            assertThat(
+                    e.getMessage(),
+                    containsString("Missing revision statements in module:module1"));
+            throw e;
+        }
+    }
+
+    @Test(expected = YangValidationException.class)
+    public void testNoHeaderStmts() {
+        mockModuleWithRevision(2, "1999-4-5");
+
+        try {
+            valid.enterModule_stmt(ctx);
+        } catch (YangValidationException e) {
+            assertThat(
+                    e.getMessage(),
+                    containsString("Missing header statements in module:module1"));
+            throw e;
+        }
+    }
+
+    @Test(expected = YangValidationException.class)
+    public void testNoNamespace() {
+        Module_header_stmtsContext header = mock(Module_header_stmtsContext.class);
+        mockModuleParent(2, header, "module1");
+
+        try {
+            valid.enterModule_header_stmts(header);
+        } catch (YangValidationException e) {
+            assertThat(
+                    e.getMessage(),
+                    containsString("Missing namespace statement in module:module1"));
+            throw e;
+        }
+    }
+
+    @Test
+    public void testPrefixes() {
+        Prefix_stmtContext pref = mock(Prefix_stmtContext.class);
+        doReturn(1).when(pref).getChildCount();
+        mockName(pref, "unique1");
+        mockModuleParent(2, pref, "module1");
+        valid.enterPrefix_stmt(pref);
+
+        pref = mock(Prefix_stmtContext.class);
+        doReturn(1).when(pref).getChildCount();
+        mockName(pref, "unique1");
+        mockModuleParent(2, pref, "module1");
+
+        try {
+            valid.enterPrefix_stmt(pref);
+        } catch (Exception e) {
+            return;
+        }
+
+        fail("Validation Exception should have occured");
+    }
+
+    @Test
+    public void testNamespace() {
+        Namespace_stmtContext namespace = mock(Namespace_stmtContext.class);
+        doReturn(1).when(namespace).getChildCount();
+        mockName(namespace, "http://test.parsing.uri.com");
+        mockModuleParent(2, namespace, "module1");
+        valid.enterNamespace_stmt(namespace);
+
+        namespace = mock(Namespace_stmtContext.class);
+        doReturn(1).when(namespace).getChildCount();
+        mockName(namespace, "invalid uri");
+        mockModuleParent(2, namespace, "module1");
+        try {
+            valid.enterNamespace_stmt(namespace);
+        } catch (YangValidationException e) {
+            assertThat(
+                    e.getMessage(),
+                    containsString("Namespace:invalid uri in module:module1 cannot be parsed as URI"));
+            return;
+        }
+
+        fail("Validation Exception should have occured");
+    }
+
+    @Test
+    public void testImports() {
+        Import_stmtContext impor = mockImport("unique1", "p1");
+        mockModuleParent(2, impor, "module1");
+        valid.enterImport_stmt(impor);
+
+        impor = mockImport("unique1", "p2");
+        mockModuleParent(2, impor, "module1");
+        mockName(impor, "unique1");
+
+        try {
+            valid.enterImport_stmt(impor);
+        } catch (YangValidationException e) {
+            assertThat(
+                    e.getMessage(),
+                    containsString("Module:unique1 imported twice in (sub)module:module1"));
+            return;
+        }
+
+        fail("Validation Exception should have occured");
+    }
+
+    @Test
+    public void testIncludes() {
+        Include_stmtContext impor = mockInclude("unique1");
+        mockModuleParent(2, impor, "module1");
+        valid.enterInclude_stmt(impor);
+
+        impor = mockInclude("unique1");
+        mockModuleParent(2, impor, "module1");
+        mockName(impor, "unique1");
+
+        try {
+            valid.enterInclude_stmt(impor);
+        } catch (YangValidationException e) {
+            assertThat(
+                    e.getMessage(),
+                    containsString("Submodule:unique1 included twice in (sub)module:module1"));
+            return;
+        }
+
+        fail("Validation Exception should have occured");
+    }
+
+    private Import_stmtContext mockImport(String name, String prefixName) {
+        Import_stmtContext impor = mock(Import_stmtContext.class);
+        doReturn(3).when(impor).getChildCount();
+        Prefix_stmtContext prefix = mock(Prefix_stmtContext.class);
+        mockName(prefix, prefixName);
+        doReturn(prefix).when(impor).getChild(1);
+        Revision_date_stmtContext revDate = mock(Revision_date_stmtContext.class);
+        mockName(revDate, getFormattedDate());
+        doReturn(revDate).when(impor).getChild(2);
+        mockName(impor, name);
+        return impor;
+    }
+
+    private Include_stmtContext mockInclude(String name) {
+        Include_stmtContext impor = mock(Include_stmtContext.class);
+        doReturn(2).when(impor).getChildCount();
+        Revision_date_stmtContext revDate = mock(Revision_date_stmtContext.class);
+        mockName(revDate, getFormattedDate());
+        doReturn(revDate).when(impor).getChild(1);
+        mockName(impor, name);
+        return impor;
+    }
+
+    @Test(expected = YangValidationException.class)
+    public void testInvalidYangVersion() {
+
+        Yang_version_stmtContext yangVersion = mock(Yang_version_stmtContext.class);
+        doReturn(1).when(yangVersion).getChildCount();
+        mockName(yangVersion, "55Unsup");
+
+        mockModuleParent(2, yangVersion, "module1");
+
+        try {
+            valid.enterYang_version_stmt(yangVersion);
+        } catch (YangValidationException e) {
+            assertThat(
+                    e.getMessage(),
+                    containsString("Unsupported yang version:55Unsup, in (sub)module:module1, supported version:"
+                            + YangModelValidationListener.SUPPORTED_YANG_VERSION));
+            throw e;
+        }
+    }
+
+    private void mockModuleParent(int moduleChildren, ParseTree child,
+            String moduleName) {
+        ctx = mock(Module_stmtContext.class);
+        doReturn(moduleChildren).when(ctx).getChildCount();
+        mockName(ctx, moduleName);
+        doReturn(child).when(ctx).getChild(1);
+        doReturn(ctx).when(child).getParent();
+    }
+
+    static Revision_stmtsContext mockRevisionsParent(int moduleChildren,
+            Revision_stmtContext mockedRev) {
+        Revision_stmtsContext revs = mock(Revision_stmtsContext.class);
+        doReturn(moduleChildren).when(revs).getChildCount();
+        doReturn(mockedRev).when(revs).getChild(1);
+        doReturn(revs).when(mockedRev).getParent();
+        return revs;
+    }
+
+    @Test
+    public void testValidYangVersion() {
+
+        Yang_version_stmtContext ctx = mock(Yang_version_stmtContext.class);
+        doReturn(1).when(ctx).getChildCount();
+        mockName(ctx, "1");
+
+        valid.enterYang_version_stmt(ctx);
+    }
+
+    @Test
+    public void testIdentifierMatching() {
+        YangModelValidationListener.checkIdentifier("_ok98-.87.-.8...88-asdAD",
+                null);
+        YangModelValidationListener.checkIdentifier("AA.bcd", null);
+        YangModelValidationListener.checkIdentifier("a", null);
+
+        int thrown = 0;
+
+        try {
+            YangModelValidationListener.checkIdentifier("9aa", null);
+        } catch (YangValidationException e) {
+            thrown++;
+        }
+        try {
+            YangModelValidationListener.checkIdentifier("-", null);
+        } catch (YangValidationException e) {
+            thrown++;
+        }
+        try {
+            YangModelValidationListener.checkIdentifier(".", null);
+        } catch (YangValidationException e) {
+            thrown++;
+        }
+
+        assertThat(thrown, is(3));
+    }
+
+    static void mockName(ParseTree mockedRev, String name) {
+        StringContext nameCtx = mock(StringContext.class);
+        ParseTree internalName = mock(ParseTree.class);
+        doReturn(name).when(internalName).getText();
+        doReturn(internalName).when(nameCtx).getChild(0);
+        doReturn(nameCtx).when(mockedRev).getChild(0);
+    }
+}
diff --git a/opendaylight/sal/yang-prototype/code-generator/yang-model-parser-impl/src/test/java/org/opendaylight/controller/yang/model/parser/impl/YangModelValidationListenerTest_SubModule.java b/opendaylight/sal/yang-prototype/code-generator/yang-model-parser-impl/src/test/java/org/opendaylight/controller/yang/model/parser/impl/YangModelValidationListenerTest_SubModule.java
new file mode 100644 (file)
index 0000000..056e869
--- /dev/null
@@ -0,0 +1,129 @@
+package org.opendaylight.controller.yang.model.parser.impl;
+
+import static org.junit.Assert.*;
+import static org.junit.matchers.JUnitMatchers.*;
+import static org.mockito.Mockito.*;
+
+import org.antlr.v4.runtime.tree.ParseTree;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.controller.antlrv4.code.gen.YangParser.Belongs_to_stmtContext;
+import org.opendaylight.controller.antlrv4.code.gen.YangParser.Prefix_stmtContext;
+import org.opendaylight.controller.antlrv4.code.gen.YangParser.Revision_stmtContext;
+import org.opendaylight.controller.antlrv4.code.gen.YangParser.Revision_stmtsContext;
+import org.opendaylight.controller.antlrv4.code.gen.YangParser.Submodule_header_stmtsContext;
+import org.opendaylight.controller.antlrv4.code.gen.YangParser.Submodule_stmtContext;
+
+public class YangModelValidationListenerTest_SubModule {
+
+    private YangModelValidationListener valid;
+    private Submodule_stmtContext ctx;
+
+    @Before
+    public void setUp() {
+        valid = new YangModelValidationListener();
+    }
+
+    @Test(expected = YangValidationException.class)
+    public void testNoRevision() {
+
+        Submodule_stmtContext ctx = mock(Submodule_stmtContext.class);
+        doReturn(1).when(ctx).getChildCount();
+        YangModelValidationListenerTest_Module.mockName(ctx, "submodule1");
+
+        try {
+            valid.enterSubmodule_stmt(ctx);
+        } catch (YangValidationException e) {
+            assertThat(
+                    e.getMessage(),
+                    containsString("Missing revision statements in submodule:submodule1"));
+            throw e;
+        }
+    }
+
+    @Test(expected = YangValidationException.class)
+    public void testNoHeaderStmts() {
+        mockSubmoduleWithRevision(2, "1999-4-5", "submodule");
+
+        try {
+            valid.enterSubmodule_stmt(ctx);
+        } catch (YangValidationException e) {
+            assertThat(
+                    e.getMessage(),
+                    containsString("Missing header statements in submodule:submodule"));
+            throw e;
+        }
+    }
+
+    @Test(expected = YangValidationException.class)
+    public void testNoBelongsTo() {
+        Submodule_header_stmtsContext header = mock(Submodule_header_stmtsContext.class);
+        mockSubmoduleParent(2, header, "submodule");
+
+        try {
+            valid.enterSubmodule_header_stmts(header);
+        } catch (YangValidationException e) {
+            assertThat(
+                    e.getMessage(),
+                    containsString("Missing belongs-to statement in submodule:submodule"));
+            throw e;
+        }
+    }
+
+    @Test(expected = YangValidationException.class)
+    public void testBelongsToNoPrefix() {
+        Belongs_to_stmtContext belongsTo = mock(Belongs_to_stmtContext.class);
+        doReturn(1).when(belongsTo).getChildCount();
+        YangModelValidationListenerTest_Module.mockName(belongsTo,
+                "supermodule");
+
+        mockSubmoduleParent(2, belongsTo, "submodule");
+
+        try {
+            valid.enterBelongs_to_stmt(belongsTo);
+        } catch (YangValidationException e) {
+            assertThat(
+                    e.getMessage(),
+                    containsString("Missing prefix statement in belongs-to:supermodule, in (sub)module:submodule"));
+            throw e;
+        }
+    }
+
+    @Test
+    public void testBelongsTo() {
+        Belongs_to_stmtContext belongsTo = mock(Belongs_to_stmtContext.class);
+        doReturn(2).when(belongsTo).getChildCount();
+        YangModelValidationListenerTest_Module.mockName(belongsTo,
+                "supermodule");
+
+        Prefix_stmtContext prefix = mock(Prefix_stmtContext.class);
+        doReturn(prefix).when(belongsTo).getChild(1);
+        doReturn(belongsTo).when(prefix).getParent();
+
+        mockSubmoduleParent(2, belongsTo, "submodule");
+        valid.enterBelongs_to_stmt(belongsTo);
+
+    }
+
+    private Revision_stmtContext mockSubmoduleWithRevision(int moduleChildren,
+            String date, String nameOfSubmodule) {
+        Revision_stmtContext mockedRev = mock(Revision_stmtContext.class);
+        doReturn(1).when(mockedRev).getChildCount();
+        YangModelValidationListenerTest_Module.mockName(mockedRev, date);
+
+        Revision_stmtsContext revs = YangModelValidationListenerTest_Module
+                .mockRevisionsParent(2, mockedRev);
+
+        mockSubmoduleParent(moduleChildren, revs, nameOfSubmodule);
+        return mockedRev;
+    }
+
+    private void mockSubmoduleParent(int moduleChildren, ParseTree child,
+            String moduleName) {
+        ctx = mock(Submodule_stmtContext.class);
+        doReturn(moduleChildren).when(ctx).getChildCount();
+        YangModelValidationListenerTest_Module.mockName(ctx, moduleName);
+        doReturn(child).when(ctx).getChild(1);
+        doReturn(ctx).when(child).getParent();
+    }
+}