2 * Copyright (c) 2010-2020 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.io.transport.modbus.test;
15 import static org.junit.jupiter.api.Assertions.*;
17 import java.util.Arrays;
18 import java.util.Collection;
19 import java.util.Optional;
20 import java.util.function.Function;
21 import java.util.function.Supplier;
22 import java.util.stream.Collectors;
23 import java.util.stream.Stream;
24 import java.util.stream.Stream.Builder;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.junit.jupiter.params.ParameterizedTest;
28 import org.junit.jupiter.params.provider.MethodSource;
29 import org.openhab.core.library.types.DecimalType;
30 import org.openhab.io.transport.modbus.ModbusBitUtilities;
31 import org.openhab.io.transport.modbus.ModbusConstants.ValueType;
32 import org.openhab.io.transport.modbus.ModbusRegisterArray;
35 * @author Sami Salonen - Initial contribution
37 public class BitUtilitiesExtractIndividualMethodsTest {
39 public static Collection<Object[]> data() {
40 // We use test data from BitUtilitiesExtractStateFromRegistersTest
41 // In BitUtilitiesExtractStateFromRegistersTest the data is aligned to registers
43 // Here (in registerVariations) we generate offsetted variations of the byte data
44 // to test extractXX which can operate on data aligned on byte-level, not just data aligned on-register level
45 Collection<Object[]> data = BitUtilitiesExtractStateFromRegistersTest.data();
46 return data.stream().flatMap(values -> {
47 Object expectedResult = values[0];
48 ValueType type = (ValueType) values[1];
49 ModbusRegisterArray registers = (ModbusRegisterArray) values[2];
50 int index = (int) values[3];
51 return registerVariations(expectedResult, type, registers, index);
52 }).collect(Collectors.toList());
55 public static Stream<Object[]> filteredTestData(ValueType type) {
56 return data().stream().filter(values -> (ValueType) values[1] == type);
60 * Generate register variations for extractXX functions
63 * @return entries of (byte[], byteIndex)
65 private static Stream<Object[]> registerVariations(Object expectedResult, ValueType type,
66 ModbusRegisterArray registers, int index) {
67 byte[] origBytes = registers.getBytes();
68 int origRegisterIndex = index;
69 int origByteIndex = origRegisterIndex * 2;
71 Builder<Object[]> streamBuilder = Stream.builder();
72 for (int offset = 0; offset < 5; offset++) {
73 int byteIndex = origByteIndex + offset;
74 byte[] bytesOffsetted = new byte[origBytes.length + offset];
75 for (int i = 0; i < bytesOffsetted.length; i++) {
76 bytesOffsetted[i] = 99;
78 System.arraycopy(origBytes, 0, bytesOffsetted, offset, origBytes.length);
80 streamBuilder.add(new Object[] { expectedResult, type, bytesOffsetted, byteIndex });
82 // offsetted, with no extra bytes following
83 // (this is only done for successfull cases to avoid copyOfRange padding with zeros
84 if (!(expectedResult instanceof Class)) {
85 byte[] bytesOffsettedCutExtra = Arrays.copyOfRange(bytesOffsetted, 0, byteIndex + type.getBits() / 8);
86 if (bytesOffsettedCutExtra.length != bytesOffsetted.length) {
87 streamBuilder.add(new Object[] { expectedResult, type, bytesOffsettedCutExtra, byteIndex });
91 return streamBuilder.build();
94 private void testIndividual(Object expectedResult, ValueType type, byte[] bytes, int byteIndex,
95 Supplier<Number> methodUnderTest, Function<DecimalType, Number> expectedPrimitive) {
96 testIndividual(expectedResult, type, bytes, byteIndex, methodUnderTest, expectedPrimitive, null);
99 @SuppressWarnings("unchecked")
100 private void testIndividual(Object expectedResult, ValueType type, byte[] bytes, int byteIndex,
101 Supplier<Number> methodUnderTest, Function<DecimalType, Number> expectedPrimitive,
102 @Nullable Number defaultWhenEmptyOptional) {
103 String testExplanation = String.format("bytes=%s, byteIndex=%d, type=%s", Arrays.toString(bytes), byteIndex,
105 final Object expectedNumber;
106 if (expectedResult instanceof Class && Exception.class.isAssignableFrom((Class<?>) expectedResult)) {
107 assertThrows((Class<? extends Throwable>) expectedResult, () -> methodUnderTest.get());
108 } else if (expectedResult instanceof Optional<?>) {
109 assertTrue(!((Optional<?>) expectedResult).isPresent());
110 if (defaultWhenEmptyOptional == null) {
111 fail("Should provide defaultWhenEmptyOptional");
115 DecimalType expectedDecimal = (DecimalType) expectedResult;
116 expectedNumber = expectedPrimitive.apply(expectedDecimal);
117 assertEquals(expectedNumber, methodUnderTest.get(), testExplanation);
121 public static Stream<Object[]> filteredTestDataSInt16() {
122 return filteredTestData(ValueType.INT16);
126 @MethodSource("filteredTestDataSInt16")
127 public void testExtractIndividualSInt16(Object expectedResult, ValueType type, byte[] bytes, int byteIndex)
128 throws InstantiationException, IllegalAccessException {
129 testIndividual(expectedResult, type, bytes, byteIndex, () -> ModbusBitUtilities.extractSInt16(bytes, byteIndex),
130 decimal -> decimal.shortValue());
133 public static Stream<Object[]> filteredTestDataUInt16() {
134 return filteredTestData(ValueType.UINT16);
138 @MethodSource("filteredTestDataUInt16")
139 public void testExtractIndividualUInt16(Object expectedResult, ValueType type, byte[] bytes, int byteIndex)
140 throws InstantiationException, IllegalAccessException {
141 testIndividual(expectedResult, type, bytes, byteIndex, () -> ModbusBitUtilities.extractUInt16(bytes, byteIndex),
142 decimal -> decimal.intValue());
145 public static Stream<Object[]> filteredTestDataSInt32() {
146 return filteredTestData(ValueType.INT32);
150 @MethodSource("filteredTestDataSInt32")
151 public void testExtractIndividualSInt32(Object expectedResult, ValueType type, byte[] bytes, int byteIndex)
152 throws InstantiationException, IllegalAccessException {
153 testIndividual(expectedResult, type, bytes, byteIndex, () -> ModbusBitUtilities.extractSInt32(bytes, byteIndex),
154 decimal -> decimal.intValue());
157 public static Stream<Object[]> filteredTestDataUInt32() {
158 return filteredTestData(ValueType.UINT32);
162 @MethodSource("filteredTestDataUInt32")
163 public void testExtractIndividualUInt32(Object expectedResult, ValueType type, byte[] bytes, int byteIndex)
164 throws InstantiationException, IllegalAccessException {
165 testIndividual(expectedResult, type, bytes, byteIndex, () -> ModbusBitUtilities.extractUInt32(bytes, byteIndex),
166 decimal -> decimal.longValue());
169 public static Stream<Object[]> filteredTestDataSInt32Swap() {
170 return filteredTestData(ValueType.INT32_SWAP);
174 @MethodSource("filteredTestDataSInt32Swap")
175 public void testExtractIndividualSInt32Swap(Object expectedResult, ValueType type, byte[] bytes, int byteIndex)
176 throws InstantiationException, IllegalAccessException {
177 testIndividual(expectedResult, type, bytes, byteIndex,
178 () -> ModbusBitUtilities.extractSInt32Swap(bytes, byteIndex), decimal -> decimal.intValue());
181 public static Stream<Object[]> filteredTestDataUInt32Swap() {
182 return filteredTestData(ValueType.UINT32_SWAP);
186 @MethodSource("filteredTestDataUInt32Swap")
187 public void testExtractIndividualUInt32Swap(Object expectedResult, ValueType type, byte[] bytes, int byteIndex)
188 throws InstantiationException, IllegalAccessException {
189 testIndividual(expectedResult, type, bytes, byteIndex,
190 () -> ModbusBitUtilities.extractUInt32Swap(bytes, byteIndex), decimal -> decimal.longValue());
193 public static Stream<Object[]> filteredTestDataSInt64() {
194 return filteredTestData(ValueType.INT64);
198 @MethodSource("filteredTestDataSInt64")
199 public void testExtractIndividualSInt64(Object expectedResult, ValueType type, byte[] bytes, int byteIndex)
200 throws InstantiationException, IllegalAccessException {
201 testIndividual(expectedResult, type, bytes, byteIndex, () -> ModbusBitUtilities.extractSInt64(bytes, byteIndex),
202 decimal -> decimal.longValue());
205 public static Stream<Object[]> filteredTestDataUInt64() {
206 return filteredTestData(ValueType.UINT64);
210 @MethodSource("filteredTestDataUInt64")
211 public void testExtractIndividualUInt64(Object expectedResult, ValueType type, byte[] bytes, int byteIndex)
212 throws InstantiationException, IllegalAccessException {
213 testIndividual(expectedResult, type, bytes, byteIndex, () -> ModbusBitUtilities.extractUInt64(bytes, byteIndex),
214 decimal -> decimal.toBigDecimal().toBigIntegerExact());
217 public static Stream<Object[]> filteredTestDataSInt64Swap() {
218 return filteredTestData(ValueType.INT64_SWAP);
222 @MethodSource("filteredTestDataSInt64Swap")
223 public void testExtractIndividualSInt64Swap(Object expectedResult, ValueType type, byte[] bytes, int byteIndex)
224 throws InstantiationException, IllegalAccessException {
225 testIndividual(expectedResult, type, bytes, byteIndex,
226 () -> ModbusBitUtilities.extractSInt64Swap(bytes, byteIndex), decimal -> decimal.longValue());
229 public static Stream<Object[]> filteredTestDataUInt64Swap() {
230 return filteredTestData(ValueType.UINT64_SWAP);
234 @MethodSource("filteredTestDataUInt64Swap")
235 public void testExtractIndividualUInt64Swap(Object expectedResult, ValueType type, byte[] bytes, int byteIndex)
236 throws InstantiationException, IllegalAccessException {
237 testIndividual(expectedResult, type, bytes, byteIndex,
238 () -> ModbusBitUtilities.extractUInt64Swap(bytes, byteIndex),
239 decimal -> decimal.toBigDecimal().toBigIntegerExact());
242 public static Stream<Object[]> filteredTestDataFloat32() {
243 return filteredTestData(ValueType.FLOAT32);
247 @MethodSource("filteredTestDataFloat32")
248 public void testExtractIndividualFloat32(Object expectedResult, ValueType type, byte[] bytes, int byteIndex)
249 throws InstantiationException, IllegalAccessException {
250 testIndividual(expectedResult, type, bytes, byteIndex,
251 () -> ModbusBitUtilities.extractFloat32(bytes, byteIndex), decimal -> decimal.floatValue(), Float.NaN);
254 public static Stream<Object[]> filteredTestDataFloat32Swap() {
255 return filteredTestData(ValueType.FLOAT32_SWAP);
259 @MethodSource("filteredTestDataFloat32Swap")
260 public void testExtractIndividualFloat32Swap(Object expectedResult, ValueType type, byte[] bytes, int byteIndex)
261 throws InstantiationException, IllegalAccessException {
262 testIndividual(expectedResult, type, bytes, byteIndex,
263 () -> ModbusBitUtilities.extractFloat32Swap(bytes, byteIndex), decimal -> decimal.floatValue(),