From: Maros Marsalek Date: Tue, 16 Apr 2013 15:05:00 +0000 (+0200) Subject: Added Yang validator. X-Git-Tag: releasepom-0.1.0~553 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=commitdiff_plain;h=38379f996f946943172a1c62a49c86bff1c444eb Added Yang validator. 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 --- diff --git a/opendaylight/sal/yang-prototype/code-generator/yang-model-parser-impl/src/main/java/org/opendaylight/controller/yang/model/parser/impl/YangModelParserImpl.java b/opendaylight/sal/yang-prototype/code-generator/yang-model-parser-impl/src/main/java/org/opendaylight/controller/yang/model/parser/impl/YangModelParserImpl.java index 27a65c1168..54902232ea 100644 --- a/opendaylight/sal/yang-prototype/code-generator/yang-model-parser-impl/src/main/java/org/opendaylight/controller/yang/model/parser/impl/YangModelParserImpl.java +++ b/opendaylight/sal/yang-prototype/code-generator/yang-model-parser-impl/src/main/java/org/opendaylight/controller/yang/model/parser/impl/YangModelParserImpl.java @@ -119,6 +119,21 @@ public class YangModelParserImpl implements YangModelParser { final List trees = parseStreams(yangFiles); final ModuleBuilder[] builders = new ModuleBuilder[trees.size()]; + // validation + // if validation fails with any file, do not continue and throw + // exception + for (int i = 0; i < trees.size(); i++) { + try { + final YangModelValidationListener yangModelParser = new YangModelValidationListener(); + walker.walk(yangModelParser, trees.get(i)); + } catch (IllegalStateException e) { + // wrap exception to add information about which file failed + throw new YangValidationException( + "Yang validation failed for file" + yangFiles[i], e); + } + } + + YangModelParserListenerImpl yangModelParser = null; for (int i = 0; i < trees.size(); i++) { yangModelParser = new YangModelParserListenerImpl(); diff --git a/opendaylight/sal/yang-prototype/code-generator/yang-model-parser-impl/src/main/java/org/opendaylight/controller/yang/model/parser/impl/YangModelParserListenerImpl.java b/opendaylight/sal/yang-prototype/code-generator/yang-model-parser-impl/src/main/java/org/opendaylight/controller/yang/model/parser/impl/YangModelParserListenerImpl.java index 6d51868d98..765220bf2a 100644 --- a/opendaylight/sal/yang-prototype/code-generator/yang-model-parser-impl/src/main/java/org/opendaylight/controller/yang/model/parser/impl/YangModelParserListenerImpl.java +++ b/opendaylight/sal/yang-prototype/code-generator/yang-model-parser-impl/src/main/java/org/opendaylight/controller/yang/model/parser/impl/YangModelParserListenerImpl.java @@ -82,7 +82,7 @@ final class YangModelParserListenerImpl extends YangParserBaseListener { private String yangModelPrefix; private Date revision = new Date(0L); - private final DateFormat simpleDateFormat = new SimpleDateFormat( + final static DateFormat simpleDateFormat = new SimpleDateFormat( "yyyy-mm-dd"); private final Stack actualPath = new Stack(); 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 index 0000000000..b8a5cf3050 --- /dev/null +++ b/opendaylight/sal/yang-prototype/code-generator/yang-model-parser-impl/src/main/java/org/opendaylight/controller/yang/model/parser/impl/YangModelValidationListener.java @@ -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 uniquePrefixes; + private final Set uniqueImports; + private final Set uniqueIncludes; + + public YangModelValidationListener() { + super(); + uniquePrefixes = new HashSet(); + uniqueImports = new HashSet(); + uniqueIncludes = new HashSet(); + } + + /** + * Rules: + *
    + *
  1. Identifier contains only permitted characters
  2. + *
  3. One revision statements present
  4. + *
  5. One header statements present
  6. + *
+ */ + @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: + *
    + *
  1. Identifier contains only permitted characters
  2. + *
  3. One revision statements present
  4. + *
  5. One header statements present
  6. + *
+ */ + @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: + *
    + *
  1. One Belongs-to statement present
  2. + *
+ */ + @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: + *
    + *
  1. Identifier contains only permitted characters
  2. + *
  3. One Prefix statement child
  4. + *
+ */ + @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: + *
    + *
  1. At least one Revision statement present
  2. + *
+ */ + @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: + *
    + *
  1. One Namespace statement present
  2. + *
+ */ + @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: + *
    + *
  1. Namespace string can be parsed as URI
  2. + *
+ */ + @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: + *
    + *
  1. Identifier contains only permitted characters
  2. + *
  3. Every import(identified by identifier) within a module/submodule is + * present only once
  4. + *
  5. One prefix statement child
  6. + *
  7. One revision-date statement child
  8. + *
+ */ + @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: + *
    + *
  1. Date is in valid format
  2. + *
+ */ + @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: + *
    + *
  1. Identifier contains only permitted characters
  2. + *
  3. Every include(identified by identifier) within a module/submodule is + * present only once
  4. + *
  5. One Revision-date statement child
  6. + *
+ */ + @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: + *
    + *
  1. Yang-version is specified as 1
  2. + *
+ */ + @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: + *
    + *
  1. Date is in valid format
  2. + *
+ */ + @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: + *
    + *
  1. Identifier contains only permitted characters
  2. + *
  3. Every prefix(identified by identifier) within a module/submodule is + * presented only once
  4. + *
+ */ + @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 index 0000000000..43d39406de --- /dev/null +++ b/opendaylight/sal/yang-prototype/code-generator/yang-model-parser-impl/src/main/java/org/opendaylight/controller/yang/model/parser/impl/YangValidationException.java @@ -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 index 0000000000..76715e864d --- /dev/null +++ b/opendaylight/sal/yang-prototype/code-generator/yang-model-parser-impl/src/test/java/org/opendaylight/controller/yang/model/parser/impl/YangModelValidationListenerTest_Module.java @@ -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 index 0000000000..056e869db8 --- /dev/null +++ b/opendaylight/sal/yang-prototype/code-generator/yang-model-parser-impl/src/test/java/org/opendaylight/controller/yang/model/parser/impl/YangModelValidationListenerTest_SubModule.java @@ -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(); + } +} diff --git a/opendaylight/sal/yang-prototype/code-generator/yang-model-parser-impl/src/test/resources/types/custom-types-test@2012-4-4.yang b/opendaylight/sal/yang-prototype/code-generator/yang-model-parser-impl/src/test/resources/types/custom-types-test@2012-4-4.yang index d52068a4ad..9f839b24a4 100644 --- a/opendaylight/sal/yang-prototype/code-generator/yang-model-parser-impl/src/test/resources/types/custom-types-test@2012-4-4.yang +++ b/opendaylight/sal/yang-prototype/code-generator/yang-model-parser-impl/src/test/resources/types/custom-types-test@2012-4-4.yang @@ -5,6 +5,8 @@ module custom-types-test { prefix "iit"; organization "opendaylight"; + revision 2012-04-16 { + } contact "WILL-BE-DEFINED-LATER";