diff options
Diffstat (limited to 'infra/test-utils')
21 files changed, 1059 insertions, 0 deletions
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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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. + --> + +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <groupId>io.fd.honeycomb.common</groupId> + <artifactId>honeycomb-parent</artifactId> + <version>1.16.12-SNAPSHOT</version> + <relativePath>../../common/honeycomb-parent</relativePath> + </parent> + <modelVersion>4.0.0</modelVersion> + + <groupId>io.fd.honeycomb.infra</groupId> + <artifactId>test-utils</artifactId> + <packaging>pom</packaging> + <modules> + <module>test-api</module> + <module>test-tools</module> + </modules> + + <!-- DO NOT install or deploy the repo root pom as it's only needed to initiate a build --> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-deploy-plugin</artifactId> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-install-plugin</artifactId> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + </plugins> + </build> +</project>
\ 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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. + --> + +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <artifactId>api-parent</artifactId> + <groupId>io.fd.honeycomb.common</groupId> + <version>1.16.12-SNAPSHOT</version> + </parent> + <modelVersion>4.0.0</modelVersion> + + <groupId>io.fd.honeycomb.infra.test</groupId> + <artifactId>test-api</artifactId> + + <dependencies> + <dependency> + <groupId>org.opendaylight.mdsal.model</groupId> + <artifactId>yang-ext</artifactId> + </dependency> + <dependency> + <groupId>org.opendaylight.controller</groupId> + <artifactId>sal-binding-broker-impl</artifactId> + </dependency> + </dependencies> +</project>
\ 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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. + --> + +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <groupId>io.fd.honeycomb.common</groupId> + <artifactId>impl-parent</artifactId> + <version>1.16.12-SNAPSHOT</version> + <relativePath>../../../common/impl-parent</relativePath> + </parent> + <modelVersion>4.0.0</modelVersion> + + <groupId>io.fd.honeycomb.infra</groupId> + <artifactId>test-tools</artifactId> + + <dependencies> + <dependency> + <groupId>io.fd.honeycomb.infra.test</groupId> + <artifactId>test-api</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.fd.honeycomb</groupId> + <artifactId>translate-utils</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.opendaylight.controller</groupId> + <artifactId>sal-core-api</artifactId> + </dependency> + <dependency> + <groupId>org.opendaylight.controller</groupId> + <artifactId>sal-binding-api</artifactId> + </dependency> + <dependency> + <groupId>org.opendaylight.mdsal</groupId> + <artifactId>mdsal-binding-dom-codec</artifactId> + </dependency> + <dependency> + <groupId>org.opendaylight.controller</groupId> + <artifactId>sal-binding-broker-impl</artifactId> + </dependency> + <dependency> + <groupId>org.opendaylight.yangtools</groupId> + <artifactId>yang-data-codec-gson</artifactId> + </dependency> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + </dependency> + + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>org.skinny-framework</groupId> + <artifactId>skinny-logback</artifactId> + <version>${skinny.logback.version}</version> + <scope>compile</scope> + </dependency> + + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <scope>test</scope> + </dependency> + + </dependencies> +</project>
\ 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<? extends Annotation> annotation, final boolean isStatic, + final List<Throwable> 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<Field> 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<String> 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<String> 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<? extends YangModuleInfo> 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<InstanceIdentifier<? extends DataObject>> parentInstanceId = serializer.toBinding(parentYangId); + if (!parentInstanceId.isPresent()) { + throw new IllegalStateException("Unable to resolve " + parentYangId + " to instance identifier"); + } + + final Optional<DataNodeContainer> 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<? extends YangInstanceIdentifier.PathArgument, ?> 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<? extends YangInstanceIdentifier.PathArgument, ?> extractCheckedSingleChild( + final ContainerNode rootData) { + // Now transform the single child from JSON into BA format + final DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?> actualData = + Iterables.getFirst(rootData.getValue(), null); + + checkNotNull(actualData, "Unable to extract single child from %s", rootData); + return actualData; + } + + private Map.Entry<InstanceIdentifier<? extends DataObject>, DataObject> getCheckedBinding( + final YangInstanceIdentifier nodeYangIdentifier, + final DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?> actualData) + throws DeserializationException { + final Optional<Map.Entry<InstanceIdentifier<? extends DataObject>, 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 |