From f4600723aa5e0fa99905eacd3338b33728a6c68f Mon Sep 17 00:00:00 2001 From: Jan Srnicek Date: Wed, 26 Oct 2016 10:14:11 +0200 Subject: HONEYCOMB-266 - Test data injection with @InjectTestData Field/Method param injection of data from json files that are bindable/parsable by provided yang schema Change-Id: I726ef5d92e85d93d1e48175287b6192538965dd5 Signed-off-by: Maros Marsalek Signed-off-by: Jan Srnicek --- infra/pom.xml | 1 + infra/test-utils/asciidoc/Readme.adoc | 3 + infra/test-utils/pom.xml | 56 +++++++++ infra/test-utils/test-api/asciidoc/Readme.adoc | 3 + infra/test-utils/test-api/pom.xml | 41 +++++++ .../test-utils/test-api/src/main/yang/hc-data.yang | 71 ++++++++++++ infra/test-utils/test-tools/asciidoc/Readme.adoc | 3 + infra/test-utils/test-tools/pom.xml | 87 ++++++++++++++ .../honeycomb/test/tools/HoneycombTestRunner.java | 128 ++++++++++++++++++++ .../test/tools/InjectableTestMethodInvoker.java | 48 ++++++++ .../honeycomb/test/tools/YangContextProducer.java | 80 +++++++++++++ .../test/tools/annotations/InjectTestData.java | 33 ++++++ .../tools/annotations/InjectablesProcessor.java | 101 ++++++++++++++++ .../tools/annotations/SchemaContextProvider.java | 27 +++++ .../test/tools/factories/ChildNodeDataFactory.java | 77 ++++++++++++ .../test/tools/factories/RootNodeDataFactory.java | 42 +++++++ .../test/tools/factories/YangDataFactory.java | 109 +++++++++++++++++ .../test/tools/HoneycombTestRunnerTest.java | 129 +++++++++++++++++++++ .../src/test/resources/containerInList.json | 5 + .../src/test/resources/leafInAugment.json | 7 ++ .../src/test/resources/nestedContainer.json | 5 + .../src/test/resources/simpleContainerEmpty.json | 4 + .../io/fd/honeycomb/translate/util/JsonUtils.java | 10 +- 23 files changed, 1069 insertions(+), 1 deletion(-) create mode 100644 infra/test-utils/asciidoc/Readme.adoc create mode 100644 infra/test-utils/pom.xml create mode 100644 infra/test-utils/test-api/asciidoc/Readme.adoc create mode 100644 infra/test-utils/test-api/pom.xml create mode 100644 infra/test-utils/test-api/src/main/yang/hc-data.yang create mode 100644 infra/test-utils/test-tools/asciidoc/Readme.adoc create mode 100644 infra/test-utils/test-tools/pom.xml create mode 100644 infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/HoneycombTestRunner.java create mode 100644 infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/InjectableTestMethodInvoker.java create mode 100644 infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/YangContextProducer.java create mode 100644 infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/annotations/InjectTestData.java create mode 100644 infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/annotations/InjectablesProcessor.java create mode 100644 infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/annotations/SchemaContextProvider.java create mode 100644 infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/factories/ChildNodeDataFactory.java create mode 100644 infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/factories/RootNodeDataFactory.java create mode 100644 infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/factories/YangDataFactory.java create mode 100644 infra/test-utils/test-tools/src/test/java/io/fd/honeycomb/test/tools/HoneycombTestRunnerTest.java create mode 100644 infra/test-utils/test-tools/src/test/resources/containerInList.json create mode 100644 infra/test-utils/test-tools/src/test/resources/leafInAugment.json create mode 100644 infra/test-utils/test-tools/src/test/resources/nestedContainer.json create mode 100644 infra/test-utils/test-tools/src/test/resources/simpleContainerEmpty.json diff --git a/infra/pom.xml b/infra/pom.xml index b21fe44ef..840372daf 100644 --- a/infra/pom.xml +++ b/infra/pom.xml @@ -42,6 +42,7 @@ impl minimal-distribution it + test-utils diff --git a/infra/test-utils/asciidoc/Readme.adoc b/infra/test-utils/asciidoc/Readme.adoc new file mode 100644 index 000000000..17458f01a --- /dev/null +++ b/infra/test-utils/asciidoc/Readme.adoc @@ -0,0 +1,3 @@ += test-utils + +Overview of test-utils \ No newline at end of file diff --git a/infra/test-utils/pom.xml b/infra/test-utils/pom.xml new file mode 100644 index 000000000..a4f2bb7c8 --- /dev/null +++ b/infra/test-utils/pom.xml @@ -0,0 +1,56 @@ + + + + + + io.fd.honeycomb.common + honeycomb-parent + 1.16.12-SNAPSHOT + ../../common/honeycomb-parent + + 4.0.0 + + io.fd.honeycomb.infra + test-utils + pom + + test-api + test-tools + + + + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + org.apache.maven.plugins + maven-install-plugin + + true + + + + + \ No newline at end of file diff --git a/infra/test-utils/test-api/asciidoc/Readme.adoc b/infra/test-utils/test-api/asciidoc/Readme.adoc new file mode 100644 index 000000000..2add76702 --- /dev/null +++ b/infra/test-utils/test-api/asciidoc/Readme.adoc @@ -0,0 +1,3 @@ += test-api + +Overview of test-api \ No newline at end of file diff --git a/infra/test-utils/test-api/pom.xml b/infra/test-utils/test-api/pom.xml new file mode 100644 index 000000000..77db6a9b8 --- /dev/null +++ b/infra/test-utils/test-api/pom.xml @@ -0,0 +1,41 @@ + + + + + + api-parent + io.fd.honeycomb.common + 1.16.12-SNAPSHOT + + 4.0.0 + + io.fd.honeycomb.infra.test + test-api + + + + org.opendaylight.mdsal.model + yang-ext + + + org.opendaylight.controller + sal-binding-broker-impl + + + \ No newline at end of file diff --git a/infra/test-utils/test-api/src/main/yang/hc-data.yang b/infra/test-utils/test-api/src/main/yang/hc-data.yang new file mode 100644 index 000000000..a817a249f --- /dev/null +++ b/infra/test-utils/test-api/src/main/yang/hc-data.yang @@ -0,0 +1,71 @@ +module hc-data{ + + yang-version 1; + namespace "urn:opendaylight:params:xml:ns:yang:hc:data"; + prefix "hc-d-i"; + + revision "2015-01-05" { + description "Testing HC model for test data injection"; + } + + import yang-ext { + prefix "ext"; + } + + container simple-container{ + list simple-list{ + key name; + leaf name{ + type string; + } + container cont-under-list{ + leaf nested-name{ + type string; + } + } + } + + container nested-container{ + leaf name{ + type string; + } + } + + container nested-container-with-list{ + list nested-list{ + key name; + leaf name{ + type string; + } + } + } + + choice simple-choice{ + case first-case{ + leaf name{ + type string; + } + } + + case second-case{ + container case-container{ + leaf name{ + type string; + } + } + } + } + + container augmented-container{ + + } + } + + augment /hc-d-i:simple-container/hc-d-i:augmented-container{ + ext:augment-identifier "aug-container-augmentation"; + + leaf name-in-augment{ + type string; + } + } +} \ No newline at end of file diff --git a/infra/test-utils/test-tools/asciidoc/Readme.adoc b/infra/test-utils/test-tools/asciidoc/Readme.adoc new file mode 100644 index 000000000..eaa7e6bc4 --- /dev/null +++ b/infra/test-utils/test-tools/asciidoc/Readme.adoc @@ -0,0 +1,3 @@ += test-tools + +Overview of test-tools \ No newline at end of file diff --git a/infra/test-utils/test-tools/pom.xml b/infra/test-utils/test-tools/pom.xml new file mode 100644 index 000000000..90a155390 --- /dev/null +++ b/infra/test-utils/test-tools/pom.xml @@ -0,0 +1,87 @@ + + + + + + io.fd.honeycomb.common + impl-parent + 1.16.12-SNAPSHOT + ../../../common/impl-parent + + 4.0.0 + + io.fd.honeycomb.infra + test-tools + + + + io.fd.honeycomb.infra.test + test-api + ${project.version} + + + io.fd.honeycomb + translate-utils + ${project.version} + + + org.opendaylight.controller + sal-core-api + + + org.opendaylight.controller + sal-binding-api + + + org.opendaylight.mdsal + mdsal-binding-dom-codec + + + org.opendaylight.controller + sal-binding-broker-impl + + + org.opendaylight.yangtools + yang-data-codec-gson + + + com.google.guava + guava + + + + junit + junit + compile + + + org.skinny-framework + skinny-logback + ${skinny.logback.version} + compile + + + + org.mockito + mockito-core + test + + + + \ No newline at end of file diff --git a/infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/HoneycombTestRunner.java b/infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/HoneycombTestRunner.java new file mode 100644 index 000000000..906a5fc87 --- /dev/null +++ b/infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/HoneycombTestRunner.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.honeycomb.test.tools; + +import io.fd.honeycomb.test.tools.annotations.InjectablesProcessor; +import io.fd.honeycomb.test.tools.factories.ChildNodeDataFactory; +import io.fd.honeycomb.test.tools.factories.RootNodeDataFactory; +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Parameter; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import org.apache.commons.lang3.StringUtils; +import org.junit.runners.BlockJUnit4ClassRunner; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.InitializationError; +import org.junit.runners.model.Statement; +import org.opendaylight.controller.md.sal.binding.impl.BindingToNormalizedNodeCodec; +import org.opendaylight.yangtools.sal.binding.generator.impl.ModuleInfoBackedContext; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.data.impl.codec.DeserializationException; +import org.opendaylight.yangtools.yang.data.util.AbstractModuleStringInstanceIdentifierCodec; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class HoneycombTestRunner extends BlockJUnit4ClassRunner implements YangContextProducer, InjectablesProcessor { + + private static final Logger LOG = LoggerFactory.getLogger(HoneycombTestRunner.class); + + private SchemaContext schemaContext; + private BindingToNormalizedNodeCodec serializer; + private AbstractModuleStringInstanceIdentifierCodec iidParser; + + private ChildNodeDataFactory childNodeDataFactory; + private RootNodeDataFactory rootNodeDataFactory; + + public HoneycombTestRunner(final Class klass) throws InitializationError { + super(klass); + } + + @Override + protected Object createTest() throws Exception { + final Object test = super.createTest(); + LOG.debug("Initializing test {}", test); + + // Get schema context from annotated method + final ModuleInfoBackedContext ctx = getCheckedModuleInfoContext(test); + schemaContext = ctx.getSchemaContext(); + // Create serializer from it in order to later transform NormalizedNodes into BA + serializer = createSerializer(ctx, schemaContext); + // Create InstanceIdentifier Codec in order to later transform string represented IID into YangInstanceIdentifier + iidParser = getIIDCodec(ctx); + + childNodeDataFactory = new ChildNodeDataFactory(schemaContext, serializer, iidParser); + rootNodeDataFactory = new RootNodeDataFactory(schemaContext, serializer, iidParser); + + injectFields(test); + return test; + } + + @Override + protected void validatePublicVoidNoArgMethods(final Class annotation, final boolean isStatic, + final List errors) { + // ignores this check,in case its super needed, can be implemented, but without checking no args(because of custom invoker) + } + + /** + * Allows method parameters injection + * */ + @Override + protected Statement methodInvoker(final FrameworkMethod method, final Object test) { + return new InjectableTestMethodInvoker(method, test, Arrays.stream(method.getMethod().getParameters()) + .map(this::injectValueOrNull) + .collect(Collectors.toList()) + .toArray()); + } + + private Object injectValueOrNull(final Parameter parameter) { + return isInjectable(parameter) + ? getData(resourcePath(parameter), instanceIdentifier(parameter).orNull(), parameter.getType()) + : null; + } + + /** + * Inject fields with dat from @InjectTestData.resourcePath + */ + private void injectFields(final Object testInstance) { + + // iterate over all injectable fields + injectableFields(testInstance.getClass()).forEach(field -> { + LOG.debug("Processing field {}", field); + injectField(field, testInstance, + getData(resourcePath(field), instanceIdentifier(field).orNull(), field.getType())); + }); + } + + private DataObject getData(final String resourcePath, @Nullable final String identifier, + final Class injectedType) { + try { + if (StringUtils.isNotEmpty(identifier)) { + LOG.debug("Processing {} as child node {}", injectedType, identifier); + return childNodeDataFactory.getChildNodeData(identifier, resourcePath); + } else { + LOG.debug("Processing {} as root node", injectedType); + return rootNodeDataFactory.getRootNodeData(getRootInstanceIdentifier(injectedType), resourcePath); + } + } catch (DeserializationException | IOException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/InjectableTestMethodInvoker.java b/infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/InjectableTestMethodInvoker.java new file mode 100644 index 000000000..b63371328 --- /dev/null +++ b/infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/InjectableTestMethodInvoker.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.honeycomb.test.tools; + +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.Statement; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Invoker that allows injecting of method parameters + */ +class InjectableTestMethodInvoker extends Statement { + + private static final Logger LOG = LoggerFactory.getLogger(InjectableTestMethodInvoker.class); + + private final FrameworkMethod testMethod; + private final Object target; + private final Object[] invocationParams; + + + InjectableTestMethodInvoker(final FrameworkMethod testMethod, final Object target, + final Object[] invocationParams) { + this.testMethod = testMethod; + this.target = target; + this.invocationParams = invocationParams; + } + + @Override + public void evaluate() throws Throwable { + LOG.debug("Invoking @Test[{}] on target[{}] with params {}", testMethod.getName(), target, invocationParams); + testMethod.invokeExplosively(target, invocationParams); + } +} diff --git a/infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/YangContextProducer.java b/infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/YangContextProducer.java new file mode 100644 index 000000000..23af9733d --- /dev/null +++ b/infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/YangContextProducer.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.honeycomb.test.tools; + +import static com.google.common.base.Preconditions.checkState; + +import io.fd.honeycomb.test.tools.annotations.SchemaContextProvider; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import javassist.ClassPool; +import org.apache.commons.lang3.reflect.MethodUtils; +import org.opendaylight.controller.md.sal.binding.impl.BindingToNormalizedNodeCodec; +import org.opendaylight.yangtools.binding.data.codec.gen.impl.StreamWriterGenerator; +import org.opendaylight.yangtools.binding.data.codec.impl.BindingNormalizedNodeCodecRegistry; +import org.opendaylight.yangtools.sal.binding.generator.impl.ModuleInfoBackedContext; +import org.opendaylight.yangtools.sal.binding.generator.util.BindingRuntimeContext; +import org.opendaylight.yangtools.sal.binding.generator.util.JavassistUtils; +import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactory; +import org.opendaylight.yangtools.yang.data.util.AbstractModuleStringInstanceIdentifierCodec; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; + +/** + * Common logic to initialize serializers/deserializers/etc while working with yang based data + */ +interface YangContextProducer { + + default ModuleInfoBackedContext getCheckedModuleInfoContext(final Object test) + throws IllegalAccessException, InvocationTargetException { + final Method[] suitableMethods = + MethodUtils.getMethodsWithAnnotation(test.getClass(), SchemaContextProvider.class); + checkState(suitableMethods.length == 1, "Only single method should be @SchemaContextProvider, actual : %s", + suitableMethods); + final Object possibleContext = suitableMethods[0].invoke(test); + checkState(possibleContext instanceof ModuleInfoBackedContext, "%s is not ModuleInfoBackedContext", + possibleContext); + return ModuleInfoBackedContext.class.cast(possibleContext); + } + + /** + * Get a codec for instance identifiers. + */ + default AbstractModuleStringInstanceIdentifierCodec getIIDCodec(final ModuleInfoBackedContext ctx) + throws NoSuchMethodException, ClassNotFoundException, InstantiationException, IllegalAccessException, + java.lang.reflect.InvocationTargetException { + // Reusing codec for JSON ... not public so here goes reflection + + final JSONCodecFactory jsonCodecFactory = JSONCodecFactory.create(ctx.getSchemaContext()); + final Constructor cstr = + Class.forName("org.opendaylight.yangtools.yang.data.codec.gson.JSONStringInstanceIdentifierCodec") + .getDeclaredConstructor(SchemaContext.class, JSONCodecFactory.class); + cstr.setAccessible(true); + return (AbstractModuleStringInstanceIdentifierCodec) cstr.newInstance(ctx.getSchemaContext(), jsonCodecFactory); + } + + default BindingToNormalizedNodeCodec createSerializer(final ModuleInfoBackedContext moduleInfoBackedContext, + final SchemaContext schemaContexts) { + + final BindingNormalizedNodeCodecRegistry codecRegistry = + new BindingNormalizedNodeCodecRegistry( + StreamWriterGenerator.create(JavassistUtils.forClassPool(ClassPool.getDefault()))); + codecRegistry + .onBindingRuntimeContextUpdated(BindingRuntimeContext.create(moduleInfoBackedContext, schemaContexts)); + return new BindingToNormalizedNodeCodec(moduleInfoBackedContext, codecRegistry); + } +} diff --git a/infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/annotations/InjectTestData.java b/infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/annotations/InjectTestData.java new file mode 100644 index 000000000..e30d82ce0 --- /dev/null +++ b/infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/annotations/InjectTestData.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.honeycomb.test.tools.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.PARAMETER}) +public @interface InjectTestData { + + String NO_ID = "NO_ID"; + + String resourcePath(); + + String id() default NO_ID; +} diff --git a/infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/annotations/InjectablesProcessor.java b/infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/annotations/InjectablesProcessor.java new file mode 100644 index 000000000..c364938ed --- /dev/null +++ b/infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/annotations/InjectablesProcessor.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.honeycomb.test.tools.annotations; + +import static io.fd.honeycomb.test.tools.annotations.InjectTestData.NO_ID; + +import com.google.common.base.Optional; +import java.lang.reflect.Field; +import java.lang.reflect.Parameter; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.data.rev150105.$YangModuleInfoImpl; +import org.opendaylight.yangtools.sal.binding.generator.impl.ModuleInfoBackedContext; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.YangModuleInfo; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; + +/** + * Common logic for @InjectTestData + */ +public interface InjectablesProcessor { + + default List injectableFields(final Class testClass) { + return FieldUtils.getFieldsListWithAnnotation(testClass, InjectTestData.class); + } + + default boolean isInjectable(final Parameter parameter) { + return parameter.isAnnotationPresent(InjectTestData.class); + } + + default String resourcePath(final Field field) { + return field.getAnnotation(InjectTestData.class).resourcePath(); + } + + default String resourcePath(final Parameter parameter) { + return parameter.getAnnotation(InjectTestData.class).resourcePath(); + } + + default Optional instanceIdentifier(final Field field) { + final String identifier = field.getAnnotation(InjectTestData.class).id(); + // == used instead of equals to ensure constant was used + if (NO_ID.equals(identifier)) { + return Optional.absent(); + } else { + return Optional.of(identifier); + } + } + + default Optional instanceIdentifier(final Parameter parameter) { + final String identifier = parameter.getAnnotation(InjectTestData.class).id(); + // == used instead of equals to ensure constant was used + if (NO_ID.equals(identifier)) { + return Optional.absent(); + } else { + return Optional.of(identifier); + } + } + + default void injectField(final Field field, final Object testInstance, final DataObject data) { + field.setAccessible(true); + try { + FieldUtils.writeField(field, testInstance, data); + } catch (IllegalAccessException e) { + throw new IllegalStateException("Unable to access field " + field); + } + } + + default YangInstanceIdentifier getRootInstanceIdentifier(final Class type) { + try { + return YangInstanceIdentifier.of(QName.class.cast(type.getField("QNAME").get(null))); + } catch (IllegalAccessException e) { + throw new IllegalStateException("Constant QNAME not accessible for type" + type); + } catch (NoSuchFieldException e) { + throw new IllegalStateException("Class " + type + " does not have QName defined"); + } + } + + default ModuleInfoBackedContext provideSchemaContextFor(final Set modules) { + final ModuleInfoBackedContext moduleInfoBackedContext = ModuleInfoBackedContext.create(); + moduleInfoBackedContext.addModuleInfos(modules); + return moduleInfoBackedContext; + } +} diff --git a/infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/annotations/SchemaContextProvider.java b/infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/annotations/SchemaContextProvider.java new file mode 100644 index 000000000..5639dd837 --- /dev/null +++ b/infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/annotations/SchemaContextProvider.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.honeycomb.test.tools.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface SchemaContextProvider { +} diff --git a/infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/factories/ChildNodeDataFactory.java b/infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/factories/ChildNodeDataFactory.java new file mode 100644 index 000000000..05812123e --- /dev/null +++ b/infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/factories/ChildNodeDataFactory.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.honeycomb.test.tools.factories; + + +import com.google.common.base.Optional; +import java.io.IOException; +import javax.annotation.Nonnull; +import org.opendaylight.controller.md.sal.binding.impl.BindingToNormalizedNodeCodec; +import org.opendaylight.yangtools.sal.binding.generator.impl.BindingSchemaContextUtils; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.impl.codec.DeserializationException; +import org.opendaylight.yangtools.yang.data.util.AbstractModuleStringInstanceIdentifierCodec; +import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ChildNodeDataFactory extends YangDataFactory { + + private static final Logger LOG = LoggerFactory.getLogger(ChildNodeDataFactory.class); + + public ChildNodeDataFactory(@Nonnull final SchemaContext schemaContext, + @Nonnull final BindingToNormalizedNodeCodec serializer, + @Nonnull final AbstractModuleStringInstanceIdentifierCodec iidParser) { + super(schemaContext, serializer, iidParser); + } + + public DataObject getChildNodeData(final String instanceIdentifier, + final String resourcePath) throws DeserializationException, IOException { + // Parse string ID into YangId + final YangInstanceIdentifier nodeYid = iidParser.deserialize(instanceIdentifier); + // Look for parent YangId + final YangInstanceIdentifier parentYid = nodeYid.getParent(); + + if (parentYid.isEmpty()) { + throw new IllegalArgumentException( + "Attempt to process root node as children has been detected,to process root nodes just don't use id in @InjectTestData"); + } + // Find Schema node for parent of data that's currently being parsed (needed when parsing the data into NormalizedNodes) + return getDataForNode(nodeYid, resourcePath, getNonRootParentSchema(parentYid)); + } + + private DataNodeContainer getNonRootParentSchema(final YangInstanceIdentifier parentYangId) + throws DeserializationException { + LOG.debug("Processing parent identifier {}", parentYangId); + final Optional> parentInstanceId = serializer.toBinding(parentYangId); + if (!parentInstanceId.isPresent()) { + throw new IllegalStateException("Unable to resolve " + parentYangId + " to instance identifier"); + } + + final Optional dataNodeContainerOptional = + BindingSchemaContextUtils.findDataNodeContainer(schemaContext, parentInstanceId.get()); + + if (!dataNodeContainerOptional.isPresent()) { + throw new IllegalArgumentException("Error finding DataNodeContainer for " + parentInstanceId.get()); + } + + return dataNodeContainerOptional.get(); + } +} diff --git a/infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/factories/RootNodeDataFactory.java b/infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/factories/RootNodeDataFactory.java new file mode 100644 index 000000000..3aacea016 --- /dev/null +++ b/infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/factories/RootNodeDataFactory.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.honeycomb.test.tools.factories; + + +import java.io.IOException; +import javax.annotation.Nonnull; +import org.opendaylight.controller.md.sal.binding.impl.BindingToNormalizedNodeCodec; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.impl.codec.DeserializationException; +import org.opendaylight.yangtools.yang.data.util.AbstractModuleStringInstanceIdentifierCodec; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; + +public class RootNodeDataFactory extends YangDataFactory { + + public RootNodeDataFactory(@Nonnull final SchemaContext schemaContext, + @Nonnull final BindingToNormalizedNodeCodec serializer, + @Nonnull final AbstractModuleStringInstanceIdentifierCodec iidParser) { + super(schemaContext, serializer, iidParser); + } + + public DataObject getRootNodeData(final YangInstanceIdentifier rootInstanceIdentifier, + final String resourcePath) throws DeserializationException, IOException { + //entire schema context is parent schema in this case + return getDataForNode(rootInstanceIdentifier, resourcePath, schemaContext); + } +} diff --git a/infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/factories/YangDataFactory.java b/infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/factories/YangDataFactory.java new file mode 100644 index 000000000..ecf556cd2 --- /dev/null +++ b/infra/test-utils/test-tools/src/main/java/io/fd/honeycomb/test/tools/factories/YangDataFactory.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.honeycomb.test.tools.factories; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.Optional; +import com.google.common.collect.Iterables; +import io.fd.honeycomb.translate.util.JsonUtils; +import java.io.IOException; +import java.util.AbstractMap; +import java.util.Map; +import javax.annotation.Nonnull; +import org.opendaylight.controller.md.sal.binding.impl.BindingToNormalizedNodeCodec; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; +import org.opendaylight.yangtools.yang.data.impl.codec.DeserializationException; +import org.opendaylight.yangtools.yang.data.util.AbstractModuleStringInstanceIdentifierCodec; +import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.SchemaNode; + +/** + * Common logic for reading of yang data + */ +abstract class YangDataFactory { + + final SchemaContext schemaContext; + final BindingToNormalizedNodeCodec serializer; + final AbstractModuleStringInstanceIdentifierCodec iidParser; + + YangDataFactory(@Nonnull final SchemaContext schemaContext, + @Nonnull final BindingToNormalizedNodeCodec serializer, + @Nonnull final AbstractModuleStringInstanceIdentifierCodec iidParser) { + this.schemaContext = checkNotNull(schemaContext, "SchemaContext cannot be null"); + this.serializer = checkNotNull(serializer, "Serializer cannot be null"); + this.iidParser = checkNotNull(iidParser, "Instance identifier parser cannot be null"); + } + + DataObject getDataForNode(final YangInstanceIdentifier nodeYangIdentifier, + final String resourcePath, + final DataNodeContainer parentSchema) + throws DeserializationException, IOException { + + // Reads resources from provided resource path + final ContainerNode rootData = getCheckedRootData(resourcePath, parentSchema); + + // Now transform the single child from JSON into BA format + final DataContainerChild actualData = + extractCheckedSingleChild(rootData); + + return getCheckedBinding(nodeYangIdentifier, actualData).getValue(); + } + + private ContainerNode getCheckedRootData(final String resourcePath, final DataNodeContainer parentSchema) + throws IOException { + // TODO the cast to SchemaNode is dangerous and would not work for Augments, Choices and some other nodes maybe. At least check + // TODO not sure if this is true, while testing this code was working fine event while processing choices/cases, + // TODO only problem is to find suitable codec that can process cases,etc + // Transform JSON into NormalizedNode + + final ContainerNode rootData = JsonUtils.readJson(schemaContext, + checkNotNull(this.getClass().getResource(resourcePath), "Unable to find resource %s", resourcePath) + .openStream(), ((SchemaNode) parentSchema)); + + checkArgument(rootData.getValue().size() == 1, "Only a single data node is expected in %s, but there were: %s", + resourcePath, rootData.getValue()); + return rootData; + } + + private DataContainerChild extractCheckedSingleChild( + final ContainerNode rootData) { + // Now transform the single child from JSON into BA format + final DataContainerChild actualData = + Iterables.getFirst(rootData.getValue(), null); + + checkNotNull(actualData, "Unable to extract single child from %s", rootData); + return actualData; + } + + private Map.Entry, DataObject> getCheckedBinding( + final YangInstanceIdentifier nodeYangIdentifier, + final DataContainerChild actualData) + throws DeserializationException { + final Optional, DataObject>> ba = + serializer.toBinding(new AbstractMap.SimpleImmutableEntry<>(nodeYangIdentifier, actualData)); + + checkArgument(ba.isPresent(), "Unable to convert to binding %s", nodeYangIdentifier); + return ba.get(); + } +} diff --git a/infra/test-utils/test-tools/src/test/java/io/fd/honeycomb/test/tools/HoneycombTestRunnerTest.java b/infra/test-utils/test-tools/src/test/java/io/fd/honeycomb/test/tools/HoneycombTestRunnerTest.java new file mode 100644 index 000000000..42de7ed4d --- /dev/null +++ b/infra/test-utils/test-tools/src/test/java/io/fd/honeycomb/test/tools/HoneycombTestRunnerTest.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.honeycomb.test.tools; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import io.fd.honeycomb.test.tools.annotations.InjectTestData; +import io.fd.honeycomb.test.tools.annotations.InjectablesProcessor; +import io.fd.honeycomb.test.tools.annotations.SchemaContextProvider; +import java.util.Collections; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.data.rev150105.$YangModuleInfoImpl; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.data.rev150105.AugContainerAugmentation; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.data.rev150105.SimpleContainer; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.data.rev150105.SimpleContainerBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.data.rev150105.simple.container.NestedContainer; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.data.rev150105.simple.container.simple.list.ContUnderList; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.data.rev150105.simple.container.simple.list.ContUnderListBuilder; +import org.opendaylight.yangtools.sal.binding.generator.impl.ModuleInfoBackedContext; + +@RunWith(HoneycombTestRunner.class) +public class HoneycombTestRunnerTest implements InjectablesProcessor { + + @InjectTestData(resourcePath = "/simpleContainerEmpty.json") + private SimpleContainer simpleContainer; + + @InjectTestData(resourcePath = "/nestedContainer.json", id = "/hc-data:simple-container/hc-data:nested-container") + private NestedContainer nestedContainer; + + @InjectTestData(resourcePath = "/leafInAugment.json") + private SimpleContainer containerWithLeafInAugment; + + @InjectTestData(resourcePath = "/containerInList.json", id = "/hc-data:simple-container" + + "/hc-data:simple-list[hc-data:name='nameUnderSimpleList']" + + "/hc-data:cont-under-list") + private ContUnderList containerUnderList; + + @SchemaContextProvider + public ModuleInfoBackedContext getSchemaContext() { + return provideSchemaContextFor(Collections.singleton($YangModuleInfoImpl.getInstance())); + } + + @Test + public void testSimpleContainer() { + assertNotNull(simpleContainer); + } + + @Test + public void testNestedContainer() { + assertNotNull(nestedContainer); + assertEquals("abcd", nestedContainer.getName()); + } + + @Test + public void testLeafInAugmentedContainer() { + assertNotNull(containerWithLeafInAugment); + assertNotNull(containerWithLeafInAugment.getAugmentedContainer()); + assertEquals("nameInAugment", containerWithLeafInAugment.getAugmentedContainer().getAugmentation( + AugContainerAugmentation.class).getNameInAugment()); + } + + @Test + public void testContainerUnderList() { + assertNotNull(containerUnderList); + assertEquals("nestedName", containerUnderList.getNestedName()); + } + + @Test + public void testParameterInjectionRootNode( + @InjectTestData(resourcePath = "/simpleContainerEmpty.json") SimpleContainer container) { + assertNotNull(container); + } + + @Test + public void testParameterInjectionChildNode( + @InjectTestData(resourcePath = "/nestedContainer.json", + id = "/hc-data:simple-container/hc-data:nested-container") NestedContainer container) { + assertNotNull(container); + } + + @Test + public void testParameterInjectionMultiple( + @InjectTestData(resourcePath = "/simpleContainerEmpty.json") SimpleContainer containerFirst, + @InjectTestData(resourcePath = "/nestedContainer.json", + id = "/hc-data:simple-container/hc-data:nested-container") NestedContainer containerSecond) { + assertNotNull(containerFirst); + assertNotNull(containerSecond); + } + + @Test + public void testParameterInjectionOneNonInjectable( + @InjectTestData(resourcePath = "/simpleContainerEmpty.json") SimpleContainer containerFirst, + String thisOneShouldBeNull) { + assertNotNull(containerFirst); + assertNull(thisOneShouldBeNull); + } + + @Test + public void testConsistenceSimpleNode( + @InjectTestData(resourcePath = "/simpleContainerEmpty.json") SimpleContainer container) { + + assertEquals(new SimpleContainerBuilder().build(), simpleContainer); + } + + @Test + public void testConsistenceWithLeafNode( + @InjectTestData(resourcePath = "/containerInList.json", id = "/hc-data:simple-container" + + "/hc-data:simple-list[hc-data:name='nameUnderSimpleList']" + + "/hc-data:cont-under-list") ContUnderList containerUnderList) { + assertEquals(new ContUnderListBuilder().setNestedName("nestedName").build(), containerUnderList); + } +} \ No newline at end of file diff --git a/infra/test-utils/test-tools/src/test/resources/containerInList.json b/infra/test-utils/test-tools/src/test/resources/containerInList.json new file mode 100644 index 000000000..b55675e7c --- /dev/null +++ b/infra/test-utils/test-tools/src/test/resources/containerInList.json @@ -0,0 +1,5 @@ +{ + "cont-under-list":{ + "nested-name":"nestedName" + } +} \ No newline at end of file diff --git a/infra/test-utils/test-tools/src/test/resources/leafInAugment.json b/infra/test-utils/test-tools/src/test/resources/leafInAugment.json new file mode 100644 index 000000000..d3c52f3b8 --- /dev/null +++ b/infra/test-utils/test-tools/src/test/resources/leafInAugment.json @@ -0,0 +1,7 @@ +{ + "simple-container": { + "augmented-container":{ + "name-in-augment": "nameInAugment" + } + } +} \ No newline at end of file diff --git a/infra/test-utils/test-tools/src/test/resources/nestedContainer.json b/infra/test-utils/test-tools/src/test/resources/nestedContainer.json new file mode 100644 index 000000000..9cc986b87 --- /dev/null +++ b/infra/test-utils/test-tools/src/test/resources/nestedContainer.json @@ -0,0 +1,5 @@ +{ + "nested-container":{ + "name":"abcd" + } +} \ No newline at end of file diff --git a/infra/test-utils/test-tools/src/test/resources/simpleContainerEmpty.json b/infra/test-utils/test-tools/src/test/resources/simpleContainerEmpty.json new file mode 100644 index 000000000..4d56712c3 --- /dev/null +++ b/infra/test-utils/test-tools/src/test/resources/simpleContainerEmpty.json @@ -0,0 +1,4 @@ +{ + "simple-container": { + } +} \ No newline at end of file diff --git a/infra/translate-utils/src/main/java/io/fd/honeycomb/translate/util/JsonUtils.java b/infra/translate-utils/src/main/java/io/fd/honeycomb/translate/util/JsonUtils.java index d551802e4..3216cac20 100644 --- a/infra/translate-utils/src/main/java/io/fd/honeycomb/translate/util/JsonUtils.java +++ b/infra/translate-utils/src/main/java/io/fd/honeycomb/translate/util/JsonUtils.java @@ -39,6 +39,7 @@ import org.opendaylight.yangtools.yang.data.impl.schema.Builders; import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter; import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeAttrBuilder; import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.SchemaNode; import org.opendaylight.yangtools.yang.model.api.SchemaPath; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -79,11 +80,18 @@ public final class JsonUtils { */ public static ContainerNode readJsonRoot(@Nonnull final SchemaContext schemaContext, @Nonnull final InputStream stream) { + // if root node, parent schema == schema context + return readJson(schemaContext, stream, schemaContext); + } + + public static ContainerNode readJson(@Nonnull final SchemaContext schemaContext, + @Nonnull final InputStream stream, + @Nonnull final SchemaNode parentSchema) { final DataContainerNodeAttrBuilder builder = Builders.containerBuilder().withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(schemaContext.getQName())); final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(builder); - try (final JsonParserStream jsonParser = JsonParserStream.create(writer, schemaContext)) { + try (final JsonParserStream jsonParser = JsonParserStream.create(writer, schemaContext, parentSchema)) { final JsonReader reader = new JsonReader(new InputStreamReader(stream, Charsets.UTF_8)); jsonParser.parse(reader); } catch (IOException e) { -- cgit 1.2.3-korg