From 1b2ec92ed43caa0af347abc390e75d021343aee2 Mon Sep 17 00:00:00 2001 From: "Taro L. Saito" Date: Mon, 17 May 2021 13:32:32 -0700 Subject: [PATCH] Timestamp support (#565) * Add add support for Timestamp type * Add timestamp tests * Fix timestamp pack overflow * Add timestamp value support * Support timestamp in unpackValue * Use AirSpec for integration with ScalaCheck * Fix ValueFactoryTest * Remove scalatest * Remove xerial-core dependency * Remove unnecessary dependencies * Variable.writeTo roundtrip tests * Use consistent code style Co-authored-by: Sadayuki Furuhashi --- README.md | 2 +- build.sbt | 18 +- .../java/org/msgpack/core/MessagePack.java | 2 + .../java/org/msgpack/core/MessagePacker.java | 107 ++ .../org/msgpack/core/MessageUnpacker.java | 62 +- .../value/ImmutableTimestampValue.java | 26 + .../org/msgpack/value/ImmutableValue.java | 3 + .../org/msgpack/value/TimestampValue.java | 33 + .../main/java/org/msgpack/value/Value.java | 18 + .../java/org/msgpack/value/ValueFactory.java | 17 + .../java/org/msgpack/value/ValueType.java | 6 + .../main/java/org/msgpack/value/Variable.java | 112 ++- .../value/impl/AbstractImmutableValue.java | 13 + .../impl/ImmutableTimestampValueImpl.java | 198 ++++ .../msgpack/core/InvalidDataReadTest.scala | 34 +- .../core/MessageBufferPackerTest.scala | 22 +- .../org/msgpack/core/MessageFormatTest.scala | 13 +- .../org/msgpack/core/MessagePackSpec.scala | 26 +- .../org/msgpack/core/MessagePackTest.scala | 941 +++++++++--------- .../org/msgpack/core/MessagePackerTest.scala | 59 +- .../msgpack/core/MessageUnpackerTest.scala | 137 ++- .../msgpack/core/buffer/ByteStringTest.scala | 20 +- .../core/buffer/MessageBufferInputTest.scala | 65 +- .../core/buffer/MessageBufferOutputTest.scala | 30 +- .../core/buffer/MessageBufferTest.scala | 377 ++++--- .../core/example/MessagePackExampleTest.scala | 14 +- .../value/RawStringValueImplTest.scala | 22 +- .../org/msgpack/value/ValueFactoryTest.scala | 77 +- .../scala/org/msgpack/value/ValueTest.scala | 31 +- .../org/msgpack/value/ValueTypeTest.scala | 95 +- .../org/msgpack/value/VariableTest.scala | 310 ++++++ 31 files changed, 1934 insertions(+), 956 deletions(-) create mode 100644 msgpack-core/src/main/java/org/msgpack/value/ImmutableTimestampValue.java create mode 100644 msgpack-core/src/main/java/org/msgpack/value/TimestampValue.java create mode 100644 msgpack-core/src/main/java/org/msgpack/value/impl/ImmutableTimestampValueImpl.java create mode 100644 msgpack-core/src/test/scala/org/msgpack/value/VariableTest.scala diff --git a/README.md b/README.md index 2a6571ad3..1a7c1f439 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ Here is a list of sbt commands for daily development: > ~test:compile # Compile both source and test codes > ~test # Run tests upon source code change > ~testOnly *MessagePackTest # Run tests in the specified class -> ~testOnly *MessagePackTest -- -n prim # Run the test tagged as "prim" +> ~testOnly *MessagePackTest -- (pattern) # Run tests matching the pattern > project msgpack-core # Focus on a specific project > package # Create a jar file in the target folder of each project > findbugs # Produce findbugs report in target/findbugs diff --git a/build.sbt b/build.sbt index a97c20f7b..eae3cd51a 100644 --- a/build.sbt +++ b/build.sbt @@ -5,6 +5,8 @@ Global / concurrentRestrictions := Seq( Tags.limit(Tags.Test, 1) ) +val AIRFRAME_VERSION = "20.4.1" + // Use dynamic snapshot version strings for non tagged versions ThisBuild / dynverSonatypeSnapshots := true // Use coursier friendly version separator @@ -71,15 +73,19 @@ lazy val msgpackCore = Project(id = "msgpack-core", base = file("msgpack-core")) "org.msgpack.value", "org.msgpack.value.impl" ), + testFrameworks += new TestFramework("wvlet.airspec.Framework"), libraryDependencies ++= Seq( // msgpack-core should have no external dependencies junitInterface, - "org.scalatest" %% "scalatest" % "3.2.8" % "test", - "org.scalacheck" %% "scalacheck" % "1.15.4" % "test", - "org.xerial" %% "xerial-core" % "3.6.0" % "test", - "org.msgpack" % "msgpack" % "0.6.12" % "test", - "commons-codec" % "commons-codec" % "1.12" % "test", - "com.typesafe.akka" %% "akka-actor" % "2.5.23" % "test" + "org.wvlet.airframe" %% "airframe-json" % AIRFRAME_VERSION % "test", + "org.wvlet.airframe" %% "airspec" % AIRFRAME_VERSION % "test", + // Add property testing support with forAll methods + "org.scalacheck" %% "scalacheck" % "1.15.4" % "test", + // For performance comparison with msgpack v6 + "org.msgpack" % "msgpack" % "0.6.12" % "test", + // For integration test with Akka + "com.typesafe.akka" %% "akka-actor" % "2.5.23" % "test", + "org.scala-lang.modules" %% "scala-collection-compat" % "2.4.3" % "test" ) ) diff --git a/msgpack-core/src/main/java/org/msgpack/core/MessagePack.java b/msgpack-core/src/main/java/org/msgpack/core/MessagePack.java index ed8b1e405..edd449b34 100644 --- a/msgpack-core/src/main/java/org/msgpack/core/MessagePack.java +++ b/msgpack-core/src/main/java/org/msgpack/core/MessagePack.java @@ -165,6 +165,8 @@ public static final boolean isFixedRaw(byte b) public static final byte MAP32 = (byte) 0xdf; public static final byte NEGFIXINT_PREFIX = (byte) 0xe0; + + public static final byte EXT_TIMESTAMP = (byte) -1; } private MessagePack() diff --git a/msgpack-core/src/main/java/org/msgpack/core/MessagePacker.java b/msgpack-core/src/main/java/org/msgpack/core/MessagePacker.java index 6837f9f72..4cf789d9f 100644 --- a/msgpack-core/src/main/java/org/msgpack/core/MessagePacker.java +++ b/msgpack-core/src/main/java/org/msgpack/core/MessagePacker.java @@ -32,6 +32,7 @@ import java.nio.charset.CharsetEncoder; import java.nio.charset.CoderResult; import java.nio.charset.CodingErrorAction; +import java.time.Instant; import static org.msgpack.core.MessagePack.Code.ARRAY16; import static org.msgpack.core.MessagePack.Code.ARRAY32; @@ -41,6 +42,7 @@ import static org.msgpack.core.MessagePack.Code.EXT16; import static org.msgpack.core.MessagePack.Code.EXT32; import static org.msgpack.core.MessagePack.Code.EXT8; +import static org.msgpack.core.MessagePack.Code.EXT_TIMESTAMP; import static org.msgpack.core.MessagePack.Code.FALSE; import static org.msgpack.core.MessagePack.Code.FIXARRAY_PREFIX; import static org.msgpack.core.MessagePack.Code.FIXEXT1; @@ -798,6 +800,111 @@ else if (s.length() < (1 << 16)) { return this; } + /** + * Writes a Timestamp value. + * + *

+ * This method writes a timestamp value using timestamp format family. + * + * @param instant the timestamp to be written + * @return this packer + * @throws IOException when underlying output throws IOException + */ + public MessagePacker packTimestamp(Instant instant) + throws IOException + { + return packTimestamp(instant.getEpochSecond(), instant.getNano()); + } + + /** + * Writes a Timesamp value using a millisecond value (e.g., System.currentTimeMillis()) + * @param millis the millisecond value + * @return this packer + * @throws IOException when underlying output throws IOException + */ + public MessagePacker packTimestamp(long millis) + throws IOException + { + return packTimestamp(Instant.ofEpochMilli(millis)); + } + + private static final long NANOS_PER_SECOND = 1000000000L; + + /** + * Writes a Timestamp value. + * + *

+ * This method writes a timestamp value using timestamp format family. + * + * @param epochSecond the number of seconds from 1970-01-01T00:00:00Z + * @param nanoAdjustment the nanosecond adjustment to the number of seconds, positive or negative + * @return this + * @throws IOException when underlying output throws IOException + * @throws ArithmeticException when epochSecond plus nanoAdjustment in seconds exceeds the range of long + */ + public MessagePacker packTimestamp(long epochSecond, int nanoAdjustment) + throws IOException, ArithmeticException + { + long sec = Math.addExact(epochSecond, Math.floorDiv(nanoAdjustment, NANOS_PER_SECOND)); + long nsec = Math.floorMod((long) nanoAdjustment, NANOS_PER_SECOND); + + if (sec >>> 34 == 0) { + // sec can be serialized in 34 bits. + long data64 = (nsec << 34) | sec; + if ((data64 & 0xffffffff00000000L) == 0L) { + // sec can be serialized in 32 bits and nsec is 0. + // use timestamp 32 + writeTimestamp32((int) sec); + } + else { + // sec exceeded 32 bits or nsec is not 0. + // use timestamp 64 + writeTimestamp64(data64); + } + } + else { + // use timestamp 96 format + writeTimestamp96(sec, (int) nsec); + } + return this; + } + + private void writeTimestamp32(int sec) + throws IOException + { + // timestamp 32 in fixext 4 + ensureCapacity(6); + buffer.putByte(position++, FIXEXT4); + buffer.putByte(position++, EXT_TIMESTAMP); + buffer.putInt(position, sec); + position += 4; + } + + private void writeTimestamp64(long data64) + throws IOException + { + // timestamp 64 in fixext 8 + ensureCapacity(10); + buffer.putByte(position++, FIXEXT8); + buffer.putByte(position++, EXT_TIMESTAMP); + buffer.putLong(position, data64); + position += 8; + } + + private void writeTimestamp96(long sec, int nsec) + throws IOException + { + // timestamp 96 in ext 8 + ensureCapacity(15); + buffer.putByte(position++, EXT8); + buffer.putByte(position++, (byte) 12); // length of nsec and sec + buffer.putByte(position++, EXT_TIMESTAMP); + buffer.putInt(position, nsec); + position += 4; + buffer.putLong(position, sec); + position += 8; + } + /** * Writes header of an Array value. *

diff --git a/msgpack-core/src/main/java/org/msgpack/core/MessageUnpacker.java b/msgpack-core/src/main/java/org/msgpack/core/MessageUnpacker.java index 6d0d57ab3..b43204beb 100644 --- a/msgpack-core/src/main/java/org/msgpack/core/MessageUnpacker.java +++ b/msgpack-core/src/main/java/org/msgpack/core/MessageUnpacker.java @@ -32,7 +32,9 @@ import java.nio.charset.CharsetDecoder; import java.nio.charset.CoderResult; import java.nio.charset.CodingErrorAction; +import java.time.Instant; +import static org.msgpack.core.MessagePack.Code.EXT_TIMESTAMP; import static org.msgpack.core.Preconditions.checkNotNull; /** @@ -595,6 +597,12 @@ private static MessagePackException unexpected(String expected, byte b) } } + private static MessagePackException unexpectedExtension(String expected, int expectedType, int actualType) + { + return new MessageTypeException(String.format("Expected extension type %s (%d), but got extension type %d", + expected, expectedType, actualType)); + } + public ImmutableValue unpackValue() throws IOException { @@ -643,7 +651,12 @@ public ImmutableValue unpackValue() } case EXTENSION: { ExtensionTypeHeader extHeader = unpackExtensionTypeHeader(); - return ValueFactory.newExtension(extHeader.getType(), readPayload(extHeader.getLength())); + switch (extHeader.getType()) { + case EXT_TIMESTAMP: + return ValueFactory.newTimestamp(unpackTimestamp(extHeader)); + default: + return ValueFactory.newExtension(extHeader.getType(), readPayload(extHeader.getLength())); + } } default: throw new MessageNeverUsedFormatException("Unknown value type"); @@ -707,7 +720,13 @@ public Variable unpackValue(Variable var) } case EXTENSION: { ExtensionTypeHeader extHeader = unpackExtensionTypeHeader(); - var.setExtensionValue(extHeader.getType(), readPayload(extHeader.getLength())); + switch (extHeader.getType()) { + case EXT_TIMESTAMP: + var.setTimestampValue(unpackTimestamp(extHeader)); + break; + default: + var.setExtensionValue(extHeader.getType(), readPayload(extHeader.getLength())); + } return var; } default: @@ -1257,6 +1276,45 @@ private String decodeStringFastPath(int length) } } + public Instant unpackTimestamp() + throws IOException + { + ExtensionTypeHeader ext = unpackExtensionTypeHeader(); + return unpackTimestamp(ext); + } + + /** + * Internal method that can be used only when the extension type header is already read. + */ + private Instant unpackTimestamp(ExtensionTypeHeader ext) throws IOException + { + if (ext.getType() != EXT_TIMESTAMP) { + throw unexpectedExtension("Timestamp", EXT_TIMESTAMP, ext.getType()); + } + switch (ext.getLength()) { + case 4: { + // Need to convert Java's int (int32) to uint32 + long u32 = readInt() & 0xffffffffL; + return Instant.ofEpochSecond(u32); + } + case 8: { + long data64 = readLong(); + int nsec = (int) (data64 >>> 34); + long sec = data64 & 0x00000003ffffffffL; + return Instant.ofEpochSecond(sec, nsec); + } + case 12: { + // Need to convert Java's int (int32) to uint32 + long nsecU32 = readInt() & 0xffffffffL; + long sec = readLong(); + return Instant.ofEpochSecond(sec, nsecU32); + } + default: + throw new MessageFormatException(String.format("Timestamp extension type (%d) expects 4, 8, or 12 bytes of payload but got %d bytes", + EXT_TIMESTAMP, ext.getLength())); + } + } + /** * Reads header of an array. * diff --git a/msgpack-core/src/main/java/org/msgpack/value/ImmutableTimestampValue.java b/msgpack-core/src/main/java/org/msgpack/value/ImmutableTimestampValue.java new file mode 100644 index 000000000..bd4a901bb --- /dev/null +++ b/msgpack-core/src/main/java/org/msgpack/value/ImmutableTimestampValue.java @@ -0,0 +1,26 @@ +// +// MessagePack for Java +// +// 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 org.msgpack.value; + +/** + * Immutable representation of MessagePack's Timestamp type. + * + * @see org.msgpack.value.TimestampValue + */ +public interface ImmutableTimestampValue + extends TimestampValue, ImmutableValue +{ +} diff --git a/msgpack-core/src/main/java/org/msgpack/value/ImmutableValue.java b/msgpack-core/src/main/java/org/msgpack/value/ImmutableValue.java index 88798a13d..f85c69bac 100644 --- a/msgpack-core/src/main/java/org/msgpack/value/ImmutableValue.java +++ b/msgpack-core/src/main/java/org/msgpack/value/ImmutableValue.java @@ -47,4 +47,7 @@ public interface ImmutableValue @Override public ImmutableStringValue asStringValue(); + + @Override + public ImmutableTimestampValue asTimestampValue(); } diff --git a/msgpack-core/src/main/java/org/msgpack/value/TimestampValue.java b/msgpack-core/src/main/java/org/msgpack/value/TimestampValue.java new file mode 100644 index 000000000..465579b01 --- /dev/null +++ b/msgpack-core/src/main/java/org/msgpack/value/TimestampValue.java @@ -0,0 +1,33 @@ +// +// MessagePack for Java +// +// 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 org.msgpack.value; + +import java.time.Instant; + +/** + * Value representation of MessagePack's Timestamp type. + */ +public interface TimestampValue + extends ExtensionValue +{ + long getEpochSecond(); + + int getNano(); + + long toEpochMillis(); + + Instant toInstant(); +} diff --git a/msgpack-core/src/main/java/org/msgpack/value/Value.java b/msgpack-core/src/main/java/org/msgpack/value/Value.java index 546dfbbf4..a3d1ac365 100644 --- a/msgpack-core/src/main/java/org/msgpack/value/Value.java +++ b/msgpack-core/src/main/java/org/msgpack/value/Value.java @@ -16,6 +16,7 @@ package org.msgpack.value; import org.msgpack.core.MessagePacker; +import org.msgpack.core.MessageTypeCastException; import java.io.IOException; @@ -180,6 +181,14 @@ public interface Value */ boolean isExtensionValue(); + /** + * Returns true if the type of this value is Timestamp. + * + * If this method returns true, {@code asTimestamp} never throws exceptions. + * Note that you can't use instanceof or cast ((MapValue) thisValue) to check type of a value because type of a mutable value is variable. + */ + boolean isTimestampValue(); + /** * Returns the value as {@code NilValue}. Otherwise throws {@code MessageTypeCastException}. * @@ -280,6 +289,15 @@ public interface Value */ ExtensionValue asExtensionValue(); + /** + * Returns the value as {@code TimestampValue}. Otherwise throws {@code MessageTypeCastException}. + * + * Note that you can't use instanceof or cast ((TimestampValue) thisValue) to check type of a value because type of a mutable value is variable. + * + * @throws MessageTypeCastException If type of this value is not Map. + */ + TimestampValue asTimestampValue(); + /** * Serializes the value using the specified {@code MessagePacker} * diff --git a/msgpack-core/src/main/java/org/msgpack/value/ValueFactory.java b/msgpack-core/src/main/java/org/msgpack/value/ValueFactory.java index 21a4f85dd..dc1e28da8 100644 --- a/msgpack-core/src/main/java/org/msgpack/value/ValueFactory.java +++ b/msgpack-core/src/main/java/org/msgpack/value/ValueFactory.java @@ -25,8 +25,10 @@ import org.msgpack.value.impl.ImmutableMapValueImpl; import org.msgpack.value.impl.ImmutableNilValueImpl; import org.msgpack.value.impl.ImmutableStringValueImpl; +import org.msgpack.value.impl.ImmutableTimestampValueImpl; import java.math.BigInteger; +import java.time.Instant; import java.util.AbstractMap; import java.util.Arrays; import java.util.LinkedHashMap; @@ -295,4 +297,19 @@ public static ImmutableExtensionValue newExtension(byte type, byte[] data) { return new ImmutableExtensionValueImpl(type, data); } + + public static ImmutableTimestampValue newTimestamp(Instant timestamp) + { + return new ImmutableTimestampValueImpl(timestamp); + } + + public static ImmutableTimestampValue newTimestamp(long millis) + { + return newTimestamp(Instant.ofEpochMilli(millis)); + } + + public static ImmutableTimestampValue newTimestamp(long epochSecond, int nanoAdjustment) + { + return newTimestamp(Instant.ofEpochSecond(epochSecond, nanoAdjustment)); + } } diff --git a/msgpack-core/src/main/java/org/msgpack/value/ValueType.java b/msgpack-core/src/main/java/org/msgpack/value/ValueType.java index b06478402..8eeb957b3 100644 --- a/msgpack-core/src/main/java/org/msgpack/value/ValueType.java +++ b/msgpack-core/src/main/java/org/msgpack/value/ValueType.java @@ -36,6 +36,12 @@ public enum ValueType MAP(false, false), EXTENSION(false, false); + /** + * Design note: We do not add Timestamp as a ValueType here because + * detecting Timestamp values requires reading 1-3 bytes ahead while the other + * value types can be determined just by reading the first one byte. + */ + private final boolean numberType; private final boolean rawType; diff --git a/msgpack-core/src/main/java/org/msgpack/value/Variable.java b/msgpack-core/src/main/java/org/msgpack/value/Variable.java index 28295fe1c..ae88170c7 100644 --- a/msgpack-core/src/main/java/org/msgpack/value/Variable.java +++ b/msgpack-core/src/main/java/org/msgpack/value/Variable.java @@ -30,6 +30,7 @@ import java.nio.charset.CharacterCodingException; import java.nio.charset.CharsetDecoder; import java.nio.charset.CodingErrorAction; +import java.time.Instant; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; @@ -109,6 +110,12 @@ public boolean isExtensionValue() return getValueType().isExtensionType(); } + @Override + public boolean isTimestampValue() + { + return false; + } + @Override public NilValue asNilValue() { @@ -175,6 +182,12 @@ public ExtensionValue asExtensionValue() throw new MessageTypeCastException(); } + @Override + public TimestampValue asTimestampValue() + { + throw new MessageTypeCastException(); + } + @Override public boolean equals(Object obj) { @@ -211,7 +224,8 @@ public static enum Type RAW_STRING(ValueType.STRING), LIST(ValueType.ARRAY), MAP(ValueType.MAP), - EXTENSION(ValueType.EXTENSION); + EXTENSION(ValueType.EXTENSION), + TIMESTAMP(ValueType.EXTENSION); private final ValueType valueType; @@ -235,6 +249,7 @@ public ValueType getValueType() private final ArrayValueAccessor arrayAccessor = new ArrayValueAccessor(); private final MapValueAccessor mapAccessor = new MapValueAccessor(); private final ExtensionValueAccessor extensionAccessor = new ExtensionValueAccessor(); + private final TimestampValueAccessor timestampAccessor = new TimestampValueAccessor(); private Type type; @@ -1031,6 +1046,86 @@ public void writeTo(MessagePacker pk) } } + public Variable setTimestampValue(Instant timestamp) + { + this.type = Type.TIMESTAMP; + this.accessor = timestampAccessor; + this.objectValue = ValueFactory.newTimestamp(timestamp); + return this; + } + + private class TimestampValueAccessor + extends AbstractValueAccessor + implements TimestampValue + { + @Override + public boolean isTimestampValue() + { + return true; + } + + @Override + public ValueType getValueType() + { + return ValueType.EXTENSION; + } + + @Override + public TimestampValue asTimestampValue() + { + return this; + } + + @Override + public ImmutableTimestampValue immutableValue() + { + return (ImmutableTimestampValue) objectValue; + } + + @Override + public byte getType() + { + return ((ImmutableTimestampValue) objectValue).getType(); + } + + @Override + public byte[] getData() + { + return ((ImmutableTimestampValue) objectValue).getData(); + } + + @Override + public void writeTo(MessagePacker pk) + throws IOException + { + ((ImmutableTimestampValue) objectValue).writeTo(pk); + } + + @Override + public long getEpochSecond() + { + return ((ImmutableTimestampValue) objectValue).getEpochSecond(); + } + + @Override + public int getNano() + { + return ((ImmutableTimestampValue) objectValue).getNano(); + } + + @Override + public long toEpochMillis() + { + return ((ImmutableTimestampValue) objectValue).toEpochMillis(); + } + + @Override + public Instant toInstant() + { + return ((ImmutableTimestampValue) objectValue).toInstant(); + } + } + //// // Value // @@ -1144,6 +1239,12 @@ public boolean isExtensionValue() return getValueType().isExtensionType(); } + @Override + public boolean isTimestampValue() + { + return this.type == Type.TIMESTAMP; + } + @Override public NilValue asNilValue() { @@ -1242,4 +1343,13 @@ public ExtensionValue asExtensionValue() } return (ExtensionValue) accessor; } + + @Override + public TimestampValue asTimestampValue() + { + if (!isTimestampValue()) { + throw new MessageTypeCastException(); + } + return (TimestampValue) accessor; + } } diff --git a/msgpack-core/src/main/java/org/msgpack/value/impl/AbstractImmutableValue.java b/msgpack-core/src/main/java/org/msgpack/value/impl/AbstractImmutableValue.java index 1dae99cf2..18fcd2753 100644 --- a/msgpack-core/src/main/java/org/msgpack/value/impl/AbstractImmutableValue.java +++ b/msgpack-core/src/main/java/org/msgpack/value/impl/AbstractImmutableValue.java @@ -27,6 +27,7 @@ import org.msgpack.value.ImmutableNumberValue; import org.msgpack.value.ImmutableRawValue; import org.msgpack.value.ImmutableStringValue; +import org.msgpack.value.ImmutableTimestampValue; import org.msgpack.value.ImmutableValue; abstract class AbstractImmutableValue @@ -98,6 +99,12 @@ public boolean isExtensionValue() return getValueType().isExtensionType(); } + @Override + public boolean isTimestampValue() + { + return false; + } + @Override public ImmutableNilValue asNilValue() { @@ -163,4 +170,10 @@ public ImmutableExtensionValue asExtensionValue() { throw new MessageTypeCastException(); } + + @Override + public ImmutableTimestampValue asTimestampValue() + { + throw new MessageTypeCastException(); + } } diff --git a/msgpack-core/src/main/java/org/msgpack/value/impl/ImmutableTimestampValueImpl.java b/msgpack-core/src/main/java/org/msgpack/value/impl/ImmutableTimestampValueImpl.java new file mode 100644 index 000000000..227c85d0b --- /dev/null +++ b/msgpack-core/src/main/java/org/msgpack/value/impl/ImmutableTimestampValueImpl.java @@ -0,0 +1,198 @@ +// +// MessagePack for Java +// +// 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 org.msgpack.value.impl; + +import org.msgpack.core.MessagePacker; +import org.msgpack.core.buffer.MessageBuffer; +import org.msgpack.value.ExtensionValue; +import org.msgpack.value.ImmutableExtensionValue; +import org.msgpack.value.ImmutableTimestampValue; +import org.msgpack.value.TimestampValue; +import org.msgpack.value.Value; +import org.msgpack.value.ValueType; + +import java.io.IOException; +import java.time.Instant; +import java.util.Arrays; + +import static org.msgpack.core.MessagePack.Code.EXT_TIMESTAMP; + +/** + * {@code ImmutableTimestampValueImpl} Implements {@code ImmutableTimestampValue} using a {@code byte} and a {@code byte[]} fields. + * + * @see TimestampValue + */ +public class ImmutableTimestampValueImpl + extends AbstractImmutableValue + implements ImmutableExtensionValue, ImmutableTimestampValue +{ + private final Instant instant; + private byte[] data; + + public ImmutableTimestampValueImpl(Instant timestamp) + { + this.instant = timestamp; + } + + @Override + public boolean isTimestampValue() + { + return true; + } + + @Override + public byte getType() + { + return EXT_TIMESTAMP; + } + + @Override + public ValueType getValueType() + { + // Note: Future version should return ValueType.TIMESTAMP instead. + return ValueType.EXTENSION; + } + + @Override + public ImmutableTimestampValue immutableValue() + { + return this; + } + + @Override + public ImmutableExtensionValue asExtensionValue() + { + return this; + } + + @Override + public ImmutableTimestampValue asTimestampValue() + { + return this; + } + + @Override + public byte[] getData() + { + if (data == null) { + // See MessagePacker.packTimestampImpl + byte[] bytes; + long sec = getEpochSecond(); + int nsec = getNano(); + if (sec >>> 34 == 0) { + long data64 = (nsec << 34) | sec; + if ((data64 & 0xffffffff00000000L) == 0L) { + bytes = new byte[4]; + MessageBuffer.wrap(bytes).putInt(0, (int) sec); + } + else { + bytes = new byte[8]; + MessageBuffer.wrap(bytes).putLong(0, data64); + } + } + else { + bytes = new byte[12]; + MessageBuffer buffer = MessageBuffer.wrap(bytes); + buffer.putInt(0, nsec); + buffer.putLong(4, sec); + } + data = bytes; + } + return data; + } + + @Override + public long getEpochSecond() + { + return instant.getEpochSecond(); + } + + @Override + public int getNano() + { + return instant.getNano(); + } + + @Override + public long toEpochMillis() + { + return instant.toEpochMilli(); + } + + @Override + public Instant toInstant() + { + return instant; + } + + @Override + public void writeTo(MessagePacker packer) + throws IOException + { + packer.packTimestamp(instant); + } + + @Override + public boolean equals(Object o) + { + // Implements same behavior with ImmutableExtensionValueImpl. + if (o == this) { + return true; + } + if (!(o instanceof Value)) { + return false; + } + Value v = (Value) o; + + if (!v.isExtensionValue()) { + return false; + } + ExtensionValue ev = v.asExtensionValue(); + + // Here should use isTimestampValue and asTimestampValue instead. However, because + // adding these methods to Value interface can't keep backward compatibility without + // using "default" keyword since Java 7, here uses instanceof of and cast instead. + if (ev instanceof TimestampValue) { + TimestampValue tv = (TimestampValue) ev; + return instant.equals(tv.toInstant()); + } + else { + return EXT_TIMESTAMP == ev.getType() && Arrays.equals(getData(), ev.getData()); + } + } + + @Override + public int hashCode() + { + // Implements same behavior with ImmutableExtensionValueImpl. + int hash = EXT_TIMESTAMP; + hash *= 31; + hash = instant.hashCode(); + return hash; + } + + @Override + public String toJson() + { + return "\"" + toInstant().toString() + "\""; + } + + @Override + public String toString() + { + return toInstant().toString(); + } +} diff --git a/msgpack-core/src/test/scala/org/msgpack/core/InvalidDataReadTest.scala b/msgpack-core/src/test/scala/org/msgpack/core/InvalidDataReadTest.scala index 4950da82a..4e39ff85c 100644 --- a/msgpack-core/src/test/scala/org/msgpack/core/InvalidDataReadTest.scala +++ b/msgpack-core/src/test/scala/org/msgpack/core/InvalidDataReadTest.scala @@ -1,23 +1,25 @@ package org.msgpack.core +import org.msgpack.core.MessagePackSpec.createMessagePackData +import wvlet.airspec.AirSpec + /** - * - */ -class InvalidDataReadTest extends MessagePackSpec { + * + */ +class InvalidDataReadTest extends AirSpec { - "Reading long EXT32" in { - // Prepare an EXT32 data with 2GB (Int.MaxValue size) payload for testing the behavior of MessageUnpacker.skipValue() - // Actually preparing 2GB of data, however, is too much for CI, so we create only the header part. - val msgpack = createMessagePackData(p => p.packExtensionTypeHeader(MessagePack.Code.EXT32, Int.MaxValue)) - val u = MessagePack.newDefaultUnpacker(msgpack) - try { - // This error will be thrown after reading the header as the input has no EXT32 body - intercept[MessageInsufficientBufferException] { - u.skipValue() - } - } - finally { - u.close() + test("Reading long EXT32") { + // Prepare an EXT32 data with 2GB (Int.MaxValue size) payload for testing the behavior of MessageUnpacker.skipValue() + // Actually preparing 2GB of data, however, is too much for CI, so we create only the header part. + val msgpack = createMessagePackData(p => p.packExtensionTypeHeader(MessagePack.Code.EXT32, Int.MaxValue)) + val u = MessagePack.newDefaultUnpacker(msgpack) + try { + // This error will be thrown after reading the header as the input has no EXT32 body + intercept[MessageInsufficientBufferException] { + u.skipValue() } + } finally { + u.close() + } } } diff --git a/msgpack-core/src/test/scala/org/msgpack/core/MessageBufferPackerTest.scala b/msgpack-core/src/test/scala/org/msgpack/core/MessageBufferPackerTest.scala index 2194e42ea..58b29f435 100644 --- a/msgpack-core/src/test/scala/org/msgpack/core/MessageBufferPackerTest.scala +++ b/msgpack-core/src/test/scala/org/msgpack/core/MessageBufferPackerTest.scala @@ -17,12 +17,12 @@ package org.msgpack.core import java.io.ByteArrayOutputStream import java.util.Arrays - import org.msgpack.value.ValueFactory._ +import wvlet.airspec.AirSpec -class MessageBufferPackerTest extends MessagePackSpec { - "MessageBufferPacker" should { - "be equivalent to ByteArrayOutputStream" in { +class MessageBufferPackerTest extends AirSpec { + test("MessageBufferPacker") { + test("be equivalent to ByteArrayOutputStream") { val packer1 = MessagePack.newDefaultBufferPacker packer1.packValue(newMap(newString("a"), newInteger(1), newString("b"), newString("s"))) @@ -34,17 +34,17 @@ class MessageBufferPackerTest extends MessagePackSpec { packer1.toByteArray shouldBe stream.toByteArray } - "clear unflushed" in { + test("clear unflushed") { val packer = MessagePack.newDefaultBufferPacker - packer.packInt(1); - packer.clear(); - packer.packInt(2); + packer.packInt(1) + packer.clear() + packer.packInt(2) - packer.toByteArray shouldBe Array(2) + packer.toByteArray shouldBe Array[Byte](2) val buffer = packer.toBufferList().get(0) - buffer.toByteArray() shouldBe Array(2) + buffer.toByteArray() shouldBe Array[Byte](2) val array = Arrays.copyOf(buffer.sliceAsByteBuffer().array(), buffer.size()) - array shouldBe Array(2) + array shouldBe Array[Byte](2) } } diff --git a/msgpack-core/src/test/scala/org/msgpack/core/MessageFormatTest.scala b/msgpack-core/src/test/scala/org/msgpack/core/MessageFormatTest.scala index e7e9a4c36..5f3447617 100644 --- a/msgpack-core/src/test/scala/org/msgpack/core/MessageFormatTest.scala +++ b/msgpack-core/src/test/scala/org/msgpack/core/MessageFormatTest.scala @@ -17,20 +17,21 @@ package org.msgpack.core import org.msgpack.core.MessagePack.Code import org.msgpack.value.ValueType -import org.scalatest.exceptions.TestFailedException +import wvlet.airspec.AirSpec +import wvlet.airspec.spi.AirSpecException import scala.util.Random /** * Created on 2014/05/07. */ -class MessageFormatTest extends MessagePackSpec { - "MessageFormat" should { - "cover all byte codes" in { +class MessageFormatTest extends AirSpec with Benchmark { + test("MessageFormat") { + test("cover all byte codes") { def checkV(b: Byte, tpe: ValueType) { try MessageFormat.valueOf(b).getValueType shouldBe tpe catch { - case e: TestFailedException => + case e: AirSpecException => error(f"Failure when looking at byte ${b}%02x") throw e } @@ -102,7 +103,7 @@ class MessageFormatTest extends MessagePackSpec { } } - "improve the valueOf performance" in { + test("improve the valueOf performance") { val N = 1000000 val idx = (0 until N).map(x => Random.nextInt(256).toByte).toArray[Byte] diff --git a/msgpack-core/src/test/scala/org/msgpack/core/MessagePackSpec.scala b/msgpack-core/src/test/scala/org/msgpack/core/MessagePackSpec.scala index ae1f5ae45..dee315cd9 100644 --- a/msgpack-core/src/test/scala/org/msgpack/core/MessagePackSpec.scala +++ b/msgpack-core/src/test/scala/org/msgpack/core/MessagePackSpec.scala @@ -15,22 +15,13 @@ // package org.msgpack.core -import java.io.ByteArrayOutputStream -import org.scalatest._ -import org.scalatest.matchers.should.Matchers -import org.scalatest.prop.TableDrivenPropertyChecks -import org.scalatest.wordspec.AnyWordSpec -import xerial.core.log.{LogLevel, Logger} -import xerial.core.util.{TimeReport, Timer} - -import scala.language.implicitConversions - -trait MessagePackSpec extends AnyWordSpec with Matchers with GivenWhenThen with OptionValues with BeforeAndAfter with Benchmark with Logger { +import wvlet.log.LogLevel +import wvlet.log.io.{TimeReport, Timer} - implicit def toTag(s: String): Tag = Tag(s) +import java.io.ByteArrayOutputStream +object MessagePackSpec { def toHex(arr: Array[Byte]) = arr.map(x => f"$x%02x").mkString(" ") - def createMessagePackData(f: MessagePacker => Unit): Array[Byte] = { val b = new ByteArrayOutputStream() val packer = MessagePack.newDefaultPacker(b) @@ -41,20 +32,19 @@ trait MessagePackSpec extends AnyWordSpec with Matchers with GivenWhenThen with } trait Benchmark extends Timer { + private val numWarmUpRuns = 10 - val numWarmUpRuns = 10 - - override protected def time[A](blockName: String, logLevel: LogLevel, repeat: Int)(f: => A): TimeReport = { + override protected def time[A](blockName: String, logLevel: LogLevel = LogLevel.INFO, repeat: Int = 1, blockRepeat: Int = 1)(f: => A): TimeReport = { super.time(blockName, logLevel = LogLevel.INFO, repeat)(f) } - override protected def block[A](name: String, repeat: Int)(f: => A): TimeReport = { + override protected def block[A](name: String)(f: => A): TimeReport = { var i = 0 while (i < numWarmUpRuns) { f i += 1 } - super.block(name, repeat)(f) + super.block(name)(f) } } diff --git a/msgpack-core/src/test/scala/org/msgpack/core/MessagePackTest.scala b/msgpack-core/src/test/scala/org/msgpack/core/MessagePackTest.scala index d2a209bb0..f60702ed8 100644 --- a/msgpack-core/src/test/scala/org/msgpack/core/MessagePackTest.scala +++ b/msgpack-core/src/test/scala/org/msgpack/core/MessagePackTest.scala @@ -15,28 +15,31 @@ // package org.msgpack.core +import org.msgpack.core.MessagePack.{Code, PackerConfig, UnpackerConfig} +import org.msgpack.core.MessagePackSpec.toHex +import org.msgpack.value.{Value, Variable} +import org.scalacheck.Prop.propBoolean +import org.scalacheck.{Arbitrary, Gen} +import wvlet.airspec.AirSpec +import wvlet.airspec.spi.PropertyCheck + import java.io.ByteArrayOutputStream import java.math.BigInteger import java.nio.CharBuffer import java.nio.charset.{CodingErrorAction, UnmappableCharacterException} -import org.msgpack.core.MessagePack.Code -import org.msgpack.core.MessagePack.{PackerConfig, UnpackerConfig} -import org.msgpack.value.{Value, Variable} -import org.scalacheck.Arbitrary -import org.scalacheck.Prop.{forAll, propBoolean} - +import java.time.Instant import scala.util.Random /** * Created on 2014/05/07. */ -class MessagePackTest extends MessagePackSpec { +class MessagePackTest extends AirSpec with PropertyCheck with Benchmark { - def isValidUTF8(s: String) = { + private def isValidUTF8(s: String) = { MessagePack.UTF8.newEncoder().canEncode(s) } - def containsUnmappableCharacter(s: String): Boolean = { + private def containsUnmappableCharacter(s: String): Boolean = { try { MessagePack.UTF8 .newEncoder() @@ -50,549 +53,585 @@ class MessagePackTest extends MessagePackSpec { } } - "MessagePack" should { + test("clone packer config") { + val config = new PackerConfig() + .withBufferSize(10) + .withBufferFlushThreshold(32 * 1024) + .withSmallStringOptimizationThreshold(142) + val copy = config.clone() - "clone packer config" in { - val config = new PackerConfig() - .withBufferSize(10) - .withBufferFlushThreshold(32 * 1024) - .withSmallStringOptimizationThreshold(142) - val copy = config.clone() + copy shouldBe config + } - copy shouldBe config - } + test("clone unpacker config") { + val config = new UnpackerConfig() + .withBufferSize(1) + .withActionOnMalformedString(CodingErrorAction.IGNORE) + .withActionOnUnmappableString(CodingErrorAction.REPORT) + .withAllowReadingBinaryAsString(false) + .withStringDecoderBufferSize(34) + .withStringSizeLimit(4324) + + val copy = config.clone() + copy shouldBe config + } - "clone unpacker config" in { - val config = new UnpackerConfig() - .withBufferSize(1) - .withActionOnMalformedString(CodingErrorAction.IGNORE) - .withActionOnUnmappableString(CodingErrorAction.REPORT) - .withAllowReadingBinaryAsString(false) - .withStringDecoderBufferSize(34) - .withStringSizeLimit(4324) + test("detect fixint values") { - val copy = config.clone() - copy shouldBe config + for (i <- 0 until 0x79) { + Code.isPosFixInt(i.toByte) shouldBe true } - "detect fixint values" in { - - for (i <- 0 until 0x79) { - Code.isPosFixInt(i.toByte) shouldBe true - } - - for (i <- 0x80 until 0xFF) { - Code.isPosFixInt(i.toByte) shouldBe false - } + for (i <- 0x80 until 0xFF) { + Code.isPosFixInt(i.toByte) shouldBe false } + } - "detect fixarray values" in { - val packer = MessagePack.newDefaultBufferPacker() - packer.packArrayHeader(0) - packer.close - val bytes = packer.toByteArray - MessagePack.newDefaultUnpacker(bytes).unpackArrayHeader() shouldBe 0 - try { - MessagePack.newDefaultUnpacker(bytes).unpackMapHeader() - fail("Shouldn't reach here") - } catch { - case e: MessageTypeException => // OK - } + test("detect fixarray values") { + val packer = MessagePack.newDefaultBufferPacker() + packer.packArrayHeader(0) + packer.close + val bytes = packer.toByteArray + MessagePack.newDefaultUnpacker(bytes).unpackArrayHeader() shouldBe 0 + try { + MessagePack.newDefaultUnpacker(bytes).unpackMapHeader() + fail("Shouldn't reach here") + } catch { + case e: MessageTypeException => // OK } + } - "detect fixmap values" in { - val packer = MessagePack.newDefaultBufferPacker() - packer.packMapHeader(0) - packer.close - val bytes = packer.toByteArray - MessagePack.newDefaultUnpacker(bytes).unpackMapHeader() shouldBe 0 - try { - MessagePack.newDefaultUnpacker(bytes).unpackArrayHeader() - fail("Shouldn't reach here") - } catch { - case e: MessageTypeException => // OK - } + test("detect fixmap values") { + val packer = MessagePack.newDefaultBufferPacker() + packer.packMapHeader(0) + packer.close + val bytes = packer.toByteArray + MessagePack.newDefaultUnpacker(bytes).unpackMapHeader() shouldBe 0 + try { + MessagePack.newDefaultUnpacker(bytes).unpackArrayHeader() + fail("Shouldn't reach here") + } catch { + case e: MessageTypeException => // OK } + } - "detect fixint quickly" in { + test("detect fixint quickly") { - val N = 100000 - val idx = (0 until N).map(x => Random.nextInt(256).toByte).toArray[Byte] + val N = 100000 + val idx = (0 until N).map(x => Random.nextInt(256).toByte).toArray[Byte] - time("check fixint", repeat = 100) { + time("check fixint", repeat = 100) { - block("mask") { - var i = 0 - var count = 0 - while (i < N) { - if ((idx(i) & Code.POSFIXINT_MASK) == 0) { - count += 1 - } - i += 1 + block("mask") { + var i = 0 + var count = 0 + while (i < N) { + if ((idx(i) & Code.POSFIXINT_MASK) == 0) { + count += 1 } + i += 1 } + } - block("mask in func") { - var i = 0 - var count = 0 - while (i < N) { - if (Code.isPosFixInt(idx(i))) { - count += 1 - } - i += 1 + block("mask in func") { + var i = 0 + var count = 0 + while (i < N) { + if (Code.isPosFixInt(idx(i))) { + count += 1 } + i += 1 } + } - block("shift cmp") { - var i = 0 - var count = 0 - while (i < N) { - if ((idx(i) >>> 7) == 0) { - count += 1 - } - i += 1 + block("shift cmp") { + var i = 0 + var count = 0 + while (i < N) { + if ((idx(i) >>> 7) == 0) { + count += 1 } - + i += 1 } } } - "detect neg fix int values" in { - - for (i <- 0 until 0xe0) { - Code.isNegFixInt(i.toByte) shouldBe false - } + } - for (i <- 0xe0 until 0xFF) { - Code.isNegFixInt(i.toByte) shouldBe true - } + test("detect neg fix int values") { + for (i <- 0 until 0xe0) { + Code.isNegFixInt(i.toByte) shouldBe false } - def check[A]( - v: A, - pack: MessagePacker => Unit, - unpack: MessageUnpacker => A, - packerConfig: PackerConfig = new PackerConfig(), - unpackerConfig: UnpackerConfig = new UnpackerConfig() - ): Boolean = { - var b: Array[Byte] = null - try { - val bs = new ByteArrayOutputStream() - val packer = packerConfig.newPacker(bs) - pack(packer) - packer.close() - - b = bs.toByteArray - - val unpacker = unpackerConfig.newUnpacker(b) - val ret = unpack(unpacker) - ret shouldBe v - true - } catch { - case e: Exception => - warn(e.getMessage) - if (b != null) { - warn(s"packed data (size:${b.length}): ${toHex(b)}") - } - throw e - } + for (i <- 0xe0 until 0xFF) { + Code.isNegFixInt(i.toByte) shouldBe true } - def checkException[A]( - v: A, - pack: MessagePacker => Unit, - unpack: MessageUnpacker => A, - packerConfig: PackerConfig = new PackerConfig(), - unpaackerConfig: UnpackerConfig = new UnpackerConfig() - ): Unit = { - var b: Array[Byte] = null - val bs = new ByteArrayOutputStream() - val packer = packerConfig.newPacker(bs) + } + + private def check[A]( + v: A, + pack: MessagePacker => Unit, + unpack: MessageUnpacker => A, + packerConfig: PackerConfig = new PackerConfig(), + unpackerConfig: UnpackerConfig = new UnpackerConfig() + ): Boolean = { + var b: Array[Byte] = null + try { + val bs = new ByteArrayOutputStream() + val packer = packerConfig.newPacker(bs) pack(packer) packer.close() b = bs.toByteArray - val unpacker = unpaackerConfig.newUnpacker(b) + val unpacker = unpackerConfig.newUnpacker(b) val ret = unpack(unpacker) - - fail("cannot not reach here") + ret shouldBe v + true + } catch { + case e: Exception => + warn(e.getMessage) + if (b != null) { + warn(s"packed data (size:${b.length}): ${toHex(b)}") + } + throw e } + } - def checkOverflow[A](v: A, pack: MessagePacker => Unit, unpack: MessageUnpacker => A) { - try { - checkException[A](v, pack, unpack) - } catch { - case e: MessageIntegerOverflowException => // OK - } + private def checkException[A]( + v: A, + pack: MessagePacker => Unit, + unpack: MessageUnpacker => A, + packerConfig: PackerConfig = new PackerConfig(), + unpaackerConfig: UnpackerConfig = new UnpackerConfig() + ): Unit = { + var b: Array[Byte] = null + val bs = new ByteArrayOutputStream() + val packer = packerConfig.newPacker(bs) + pack(packer) + packer.close() + + b = bs.toByteArray + + val unpacker = unpaackerConfig.newUnpacker(b) + val ret = unpack(unpacker) + + fail("cannot not reach here") + } + + private def checkOverflow[A](v: A, pack: MessagePacker => Unit, unpack: MessageUnpacker => A) { + try { + checkException[A](v, pack, unpack) + } catch { + case e: MessageIntegerOverflowException => // OK } + } - "pack/unpack primitive values" taggedAs ("prim") in { - forAll { (v: Boolean) => - check(v, _.packBoolean(v), _.unpackBoolean) - } - forAll { (v: Byte) => - check(v, _.packByte(v), _.unpackByte) - } - forAll { (v: Short) => - check(v, _.packShort(v), _.unpackShort) - } - forAll { (v: Int) => - check(v, _.packInt(v), _.unpackInt) - } - forAll { (v: Float) => - check(v, _.packFloat(v), _.unpackFloat) - } - forAll { (v: Long) => - check(v, _.packLong(v), _.unpackLong) - } - forAll { (v: Double) => - check(v, _.packDouble(v), _.unpackDouble) - } - check(null, _.packNil, { unpacker => - unpacker.unpackNil(); null - }) + test("pack/unpack primitive values") { + forAll { (v: Boolean) => + check(v, _.packBoolean(v), _.unpackBoolean) + } + forAll { (v: Byte) => + check(v, _.packByte(v), _.unpackByte) + } + forAll { (v: Short) => + check(v, _.packShort(v), _.unpackShort) + } + forAll { (v: Int) => + check(v, _.packInt(v), _.unpackInt) } + forAll { (v: Float) => + check(v, _.packFloat(v), _.unpackFloat) + } + forAll { (v: Long) => + check(v, _.packLong(v), _.unpackLong) + } + forAll { (v: Double) => + check(v, _.packDouble(v), _.unpackDouble) + } + check(null, _.packNil, { unpacker => + unpacker.unpackNil(); null + }) + } - "skipping a nil value" taggedAs ("try") in { - check(true, _.packNil, _.tryUnpackNil) - check(false, { packer => - packer.packString("val") - }, { unpacker => - unpacker.tryUnpackNil() - }) - check("val", { packer => - packer.packString("val") - }, { unpacker => - unpacker.tryUnpackNil(); unpacker.unpackString() - }) - check("val", { packer => - packer.packNil(); packer.packString("val") - }, { unpacker => - unpacker.tryUnpackNil(); unpacker.unpackString() - }) - try { - checkException(null, { _ => - }, _.tryUnpackNil) - } catch { - case e: MessageInsufficientBufferException => // OK - } + test("skipping a nil value") { + check(true, _.packNil, _.tryUnpackNil) + check(false, { packer => + packer.packString("val") + }, { unpacker => + unpacker.tryUnpackNil() + }) + check("val", { packer => + packer.packString("val") + }, { unpacker => + unpacker.tryUnpackNil(); unpacker.unpackString() + }) + check("val", { packer => + packer.packNil(); packer.packString("val") + }, { unpacker => + unpacker.tryUnpackNil(); unpacker.unpackString() + }) + try { + checkException(null, { _ => + }, _.tryUnpackNil) + } catch { + case e: MessageInsufficientBufferException => // OK } + } - "pack/unpack integer values" taggedAs ("int") in { - val sampleData = Seq[Long](Int.MinValue.toLong - - 10, - -65535, - -8191, - -1024, - -255, - -127, - -63, - -31, - -15, - -7, - -3, - -1, - 0, - 2, - 4, - 8, - 16, - 32, - 64, - 128, - 256, - 1024, - 8192, - 65536, - Int.MaxValue.toLong + 10) - for (v <- sampleData) { - check(v, _.packLong(v), _.unpackLong) - - if (v.isValidInt) { - val vi = v.toInt - check(vi, _.packInt(vi), _.unpackInt) - } else { - checkOverflow(v, _.packLong(v), _.unpackInt) - } + test("pack/unpack integer values") { + val sampleData = Seq[Long](Int.MinValue.toLong - + 10, + -65535, + -8191, + -1024, + -255, + -127, + -63, + -31, + -15, + -7, + -3, + -1, + 0, + 2, + 4, + 8, + 16, + 32, + 64, + 128, + 256, + 1024, + 8192, + 65536, + Int.MaxValue.toLong + 10) + for (v <- sampleData) { + check(v, _.packLong(v), _.unpackLong) + + if (v.isValidInt) { + val vi = v.toInt + check(vi, _.packInt(vi), _.unpackInt) + } else { + checkOverflow(v, _.packLong(v), _.unpackInt) + } + + if (v.isValidShort) { + val vi = v.toShort + check(vi, _.packShort(vi), _.unpackShort) + } else { + checkOverflow(v, _.packLong(v), _.unpackShort) + } + + if (v.isValidByte) { + val vi = v.toByte + check(vi, _.packByte(vi), _.unpackByte) + } else { + checkOverflow(v, _.packLong(v), _.unpackByte) + } - if (v.isValidShort) { - val vi = v.toShort - check(vi, _.packShort(vi), _.unpackShort) - } else { - checkOverflow(v, _.packLong(v), _.unpackShort) - } + } - if (v.isValidByte) { - val vi = v.toByte - check(vi, _.packByte(vi), _.unpackByte) - } else { - checkOverflow(v, _.packLong(v), _.unpackByte) - } + } - } + test("pack/unpack BigInteger") { + forAll { (a: Long) => + val v = BigInteger.valueOf(a) + check(v, _.packBigInteger(v), _.unpackBigInteger) + } + for (bi <- Seq(BigInteger.valueOf(Long.MaxValue).add(BigInteger.valueOf(1)))) { + check(bi, _.packBigInteger(bi), _.unpackBigInteger()) } - "pack/unpack BigInteger" taggedAs ("bi") in { - forAll { (a: Long) => - val v = BigInteger.valueOf(a) - check(v, _.packBigInteger(v), _.unpackBigInteger) + for (bi <- Seq(BigInteger.valueOf(Long.MaxValue).shiftLeft(10))) { + try { + checkException(bi, _.packBigInteger(bi), _.unpackBigInteger()) + fail("cannot reach here") + } catch { + case e: IllegalArgumentException => // OK } + } - for (bi <- Seq(BigInteger.valueOf(Long.MaxValue).add(BigInteger.valueOf(1)))) { - check(bi, _.packBigInteger(bi), _.unpackBigInteger()) - } + } - for (bi <- Seq(BigInteger.valueOf(Long.MaxValue).shiftLeft(10))) { - try { - checkException(bi, _.packBigInteger(bi), _.unpackBigInteger()) - fail("cannot reach here") - } catch { - case e: IllegalArgumentException => // OK - } - } + test("pack/unpack strings") { + val utf8Strings = Arbitrary.arbitrary[String].suchThat(isValidUTF8 _) + utf8Strings.map { v => + check(v, _.packString(v), _.unpackString) + } + } + test("pack/unpack large strings") { + // Large string + val strLen = Seq(1000, 2000, 10000, 50000, 100000, 500000) + for (l <- strLen) { + val v: String = + Iterator.continually(Random.nextString(l * 10)).find(isValidUTF8).get + check(v, _.packString(v), _.unpackString) } + } - "pack/unpack strings" taggedAs ("string") in { - val utf8Strings = Arbitrary.arbitrary[String].suchThat(isValidUTF8 _) - utf8Strings.map { v => - check(v, _.packString(v), _.unpackString) + test("report errors when packing/unpacking malformed strings") { + // TODO produce malformed utf-8 strings in Java8" + pending + // Create 100 malformed UTF8 Strings + val r = new Random(0) + val malformedStrings = Iterator + .continually { + val b = new Array[Byte](10) + r.nextBytes(b) + b + } + .filter(b => !isValidUTF8(new String(b))) + .take(100) + + for (malformedBytes <- malformedStrings) { + // Pack tests + val malformed = new String(malformedBytes) + try { + checkException(malformed, _.packString(malformed), _.unpackString()) + } catch { + case e: MessageStringCodingException => // OK } - } - "pack/unpack large strings" taggedAs ("large-string") in { - // Large string - val strLen = Seq(1000, 2000, 10000, 50000, 100000, 500000) - for (l <- strLen) { - val v: String = - Iterator.continually(Random.nextString(l * 10)).find(isValidUTF8).get - check(v, _.packString(v), _.unpackString) + try { + checkException(malformed, { packer => + packer.packRawStringHeader(malformedBytes.length) + packer.writePayload(malformedBytes) + }, _.unpackString()) + } catch { + case e: MessageStringCodingException => // OK } } + } - "report errors when packing/unpacking malformed strings" taggedAs ("malformed") in { - // TODO produce malformed utf-8 strings in Java8" - pending - // Create 100 malformed UTF8 Strings - val r = new Random(0) - val malformedStrings = Iterator - .continually { - val b = new Array[Byte](10) - r.nextBytes(b) - b - } - .filter(b => !isValidUTF8(new String(b))) - .take(100) - - for (malformedBytes <- malformedStrings) { - // Pack tests - val malformed = new String(malformedBytes) - try { - checkException(malformed, _.packString(malformed), _.unpackString()) - } catch { - case e: MessageStringCodingException => // OK - } + test("report errors when packing/unpacking strings that contain unmappable characters") { - try { - checkException(malformed, { packer => - packer.packRawStringHeader(malformedBytes.length) - packer.writePayload(malformedBytes) - }, _.unpackString()) - } catch { - case e: MessageStringCodingException => // OK - } - } - } + val unmappable = Array[Byte](0xfc.toByte, 0x0a.toByte) + //val unmappableChar = Array[Char](new Character(0xfc0a).toChar) - "report errors when packing/unpacking strings that contain unmappable characters" taggedAs ("unmap") in { + // Report error on unmappable character + val unpackerConfig = new UnpackerConfig() + .withActionOnMalformedString(CodingErrorAction.REPORT) + .withActionOnUnmappableString(CodingErrorAction.REPORT) - val unmappable = Array[Byte](0xfc.toByte, 0x0a.toByte) - //val unmappableChar = Array[Char](new Character(0xfc0a).toChar) + for (bytes <- Seq(unmappable)) { + try { + checkException(bytes, { packer => + packer.packRawStringHeader(bytes.length) + packer.writePayload(bytes) + }, _.unpackString(), new PackerConfig(), unpackerConfig) + } catch { + case e: MessageStringCodingException => // OK + } + } + } - // Report error on unmappable character - val unpackerConfig = new UnpackerConfig() - .withActionOnMalformedString(CodingErrorAction.REPORT) - .withActionOnUnmappableString(CodingErrorAction.REPORT) + test("pack/unpack binary") { + forAll { (v: Array[Byte]) => + check( + v, { packer => + packer.packBinaryHeader(v.length); packer.writePayload(v) + }, { unpacker => + val len = unpacker.unpackBinaryHeader() + val out = new Array[Byte](len) + unpacker.readPayload(out, 0, len) + out + } + ) + } - for (bytes <- Seq(unmappable)) { - When("unpacking") - try { - checkException(bytes, { packer => - packer.packRawStringHeader(bytes.length) - packer.writePayload(bytes) - }, _.unpackString(), new PackerConfig(), unpackerConfig) - } catch { - case e: MessageStringCodingException => // OK + val len = Seq(1000, 2000, 10000, 50000, 100000, 500000) + for (l <- len) { + val v = new Array[Byte](l) + Random.nextBytes(v) + check( + v, { packer => + packer.packBinaryHeader(v.length); packer.writePayload(v) + }, { unpacker => + val len = unpacker.unpackBinaryHeader() + val out = new Array[Byte](len) + unpacker.readPayload(out, 0, len) + out } - } + ) } + } - "pack/unpack binary" taggedAs ("binary") in { - forAll { (v: Array[Byte]) => - check( - v, { packer => - packer.packBinaryHeader(v.length); packer.writePayload(v) - }, { unpacker => - val len = unpacker.unpackBinaryHeader() - val out = new Array[Byte](len) - unpacker.readPayload(out, 0, len) - out - } - ) - } + val testHeaderLength = Seq(1, 2, 4, 8, 16, 17, 32, 64, 255, 256, 1000, 2000, 10000, 50000, 100000, 500000) - val len = Seq(1000, 2000, 10000, 50000, 100000, 500000) - for (l <- len) { - val v = new Array[Byte](l) - Random.nextBytes(v) - check( - v, { packer => - packer.packBinaryHeader(v.length); packer.writePayload(v) - }, { unpacker => - val len = unpacker.unpackBinaryHeader() - val out = new Array[Byte](len) - unpacker.readPayload(out, 0, len) - out + test("pack/unpack arrays") { + forAll { (v: Array[Int]) => + check( + v, { packer => + packer.packArrayHeader(v.length) + v.map(packer.packInt(_)) + }, { unpacker => + val len = unpacker.unpackArrayHeader() + val out = new Array[Int](len) + for (i <- 0 until v.length) { + out(i) = unpacker.unpackInt } - ) - } + out + } + ) } - val testHeaderLength = Seq(1, 2, 4, 8, 16, 17, 32, 64, 255, 256, 1000, 2000, 10000, 50000, 100000, 500000) - - "pack/unpack arrays" taggedAs ("array") in { - forAll { (v: Array[Int]) => - check( - v, { packer => - packer.packArrayHeader(v.length) - v.map(packer.packInt(_)) - }, { unpacker => - val len = unpacker.unpackArrayHeader() - val out = new Array[Int](len) - for (i <- 0 until v.length) { - out(i) = unpacker.unpackInt - } - out - } - ) - } + for (l <- testHeaderLength) { + check(l, _.packArrayHeader(l), _.unpackArrayHeader()) + } - for (l <- testHeaderLength) { - check(l, _.packArrayHeader(l), _.unpackArrayHeader()) - } + try { + checkException(0, _.packArrayHeader(-1), _.unpackArrayHeader) + } catch { + case e: IllegalArgumentException => // OK + } - try { - checkException(0, _.packArrayHeader(-1), _.unpackArrayHeader) - } catch { - case e: IllegalArgumentException => // OK - } + } - } + test("pack/unpack maps") { + forAll { (v: Array[Int]) => + val m = v.map(i => (i, i.toString)).toSeq - "pack/unpack maps" taggedAs ("map") in { - forAll { (v: Array[Int]) => - val m = v.map(i => (i, i.toString)) - - check( - m, { packer => - packer.packMapHeader(v.length) - m.map { - case (k: Int, v: String) => - packer.packInt(k) - packer.packString(v) - } - }, { unpacker => - val len = unpacker.unpackMapHeader() - val b = Seq.newBuilder[(Int, String)] - for (i <- 0 until len) { - b += ((unpacker.unpackInt, unpacker.unpackString)) - } - b.result + check( + m, { packer => + packer.packMapHeader(v.length) + m.map { + case (k: Int, v: String) => + packer.packInt(k) + packer.packString(v) } - ) - } - - for (l <- testHeaderLength) { - check(l, _.packMapHeader(l), _.unpackMapHeader()) - } + }, { unpacker => + val len = unpacker.unpackMapHeader() + val b = Seq.newBuilder[(Int, String)] + for (i <- 0 until len) { + b += ((unpacker.unpackInt, unpacker.unpackString)) + } + b.result + } + ) + } - try { - checkException(0, _.packMapHeader(-1), _.unpackMapHeader) - } catch { - case e: IllegalArgumentException => // OK - } + for (l <- testHeaderLength) { + check(l, _.packMapHeader(l), _.unpackMapHeader()) + } + try { + checkException(0, _.packMapHeader(-1), _.unpackMapHeader) + } catch { + case e: IllegalArgumentException => // OK } - "pack/unpack extension types" taggedAs ("ext") in { - forAll { (dataLen: Int, tpe: Byte) => - val l = Math.abs(dataLen) - l >= 0 ==> { - val ext = - new ExtensionTypeHeader(ExtensionTypeHeader.checkedCastToByte(tpe), l) - check(ext, _.packExtensionTypeHeader(ext.getType, ext.getLength), _.unpackExtensionTypeHeader()) - } - } + } - for (l <- testHeaderLength) { - val ext = new ExtensionTypeHeader(ExtensionTypeHeader.checkedCastToByte(Random.nextInt(128)), l) + test("pack/unpack extension types") { + forAll { (dataLen: Int, tpe: Byte) => + val l = Math.abs(dataLen) + l >= 0 ==> { + val ext = + new ExtensionTypeHeader(ExtensionTypeHeader.checkedCastToByte(tpe), l) check(ext, _.packExtensionTypeHeader(ext.getType, ext.getLength), _.unpackExtensionTypeHeader()) } + } + for (l <- testHeaderLength) { + val ext = new ExtensionTypeHeader(ExtensionTypeHeader.checkedCastToByte(Random.nextInt(128)), l) + check(ext, _.packExtensionTypeHeader(ext.getType, ext.getLength), _.unpackExtensionTypeHeader()) } - "pack/unpack maps in lists" in { - val aMap = List(Map("f" -> "x")) + } - check( - aMap, { packer => - packer.packArrayHeader(aMap.size) - for (m <- aMap) { - packer.packMapHeader(m.size) - for ((k, v) <- m) { - packer.packString(k) - packer.packString(v) - } + test("pack/unpack maps in lists") { + val aMap = List(Map("f" -> "x")) + + check( + aMap, { packer => + packer.packArrayHeader(aMap.size) + for (m <- aMap) { + packer.packMapHeader(m.size) + for ((k, v) <- m) { + packer.packString(k) + packer.packString(v) } - }, { unpacker => - val v = new Variable() - unpacker.unpackValue(v) - import scala.collection.JavaConverters._ - v.asArrayValue().asScala - .map { m => - val mv = m.asMapValue() - val kvs = mv.getKeyValueArray - - kvs - .grouped(2) - .map({ kvp: Array[Value] => - val k = kvp(0) - val v = kvp(1) - - (k.asStringValue().asString, v.asStringValue().asString) - }) - .toMap - } - .toList } - ) + }, { unpacker => + val v = new Variable() + unpacker.unpackValue(v) + import scala.collection.JavaConverters._ + v.asArrayValue().asScala + .map { m => + val mv = m.asMapValue() + val kvs = mv.getKeyValueArray + + kvs + .grouped(2) + .map({ kvp: Array[Value] => + val k = kvp(0) + val v = kvp(1) + + (k.asStringValue().asString, v.asStringValue().asString) + }) + .toMap + } + .toList + } + ) + } + + test("pack/unpack timestamp values") { + val posLong = Gen.chooseNum[Long](-31557014167219200L, 31556889864403199L) + val posInt = Gen.chooseNum(0, 1000000000 - 1) // NANOS_PER_SECOND + forAll(posLong, posInt) { (second: Long, nano: Int) => + val v = Instant.ofEpochSecond(second, nano) + check(v, { _.packTimestamp(v) }, { _.unpackTimestamp() }) + } + // Using different insterfaces + forAll(posLong, posInt) { (second: Long, nano: Int) => + val v = Instant.ofEpochSecond(second, nano) + check(v, { _.packTimestamp(second, nano) }, { _.unpackTimestamp() }) + } + val secLessThan34bits = Gen.chooseNum[Long](0, 1L << 34) + forAll(secLessThan34bits, posInt) { (second: Long, nano: Int) => + val v = Instant.ofEpochSecond(second, nano) + check(v, _.packTimestamp(v), _.unpackTimestamp()) + } + forAll(secLessThan34bits, posInt) { (second: Long, nano: Int) => + val v = Instant.ofEpochSecond(second, nano) + check(v, _.packTimestamp(second, nano), _.unpackTimestamp()) } + // Corner-cases around uint32 boundaries + for (v <- Seq( + Instant.ofEpochSecond(Instant.now().getEpochSecond, 123456789L), // uint32 nanoseq (out of int32 range) + Instant.ofEpochSecond(-1302749144L, 0), // 1928-09-19T21:14:16Z + Instant.ofEpochSecond(-747359729L, 0), // 1946-04-27T00:04:31Z + Instant.ofEpochSecond(4257387427L, 0) // 2104-11-29T07:37:07Z + )) { + check(v, _.packTimestamp(v), _.unpackTimestamp()) + } + } + + test("pack/unpack timestamp in millis") { + val posLong = Gen.chooseNum[Long](-31557014167219200L, 31556889864403199L) + forAll(posLong) { (millis: Long) => + val v = Instant.ofEpochMilli(millis) + check(v, { _.packTimestamp(millis) }, { _.unpackTimestamp() }) + } } - "MessagePack.PackerConfig" should { - "be immutable" in { + test("MessagePack.PackerConfig") { + test("should be immutable") { val a = new MessagePack.PackerConfig() val b = a.withBufferSize(64 * 1024) a.equals(b) shouldBe false } - "implement equals" in { + test("should implement equals") { val a = new MessagePack.PackerConfig() val b = new MessagePack.PackerConfig() a.equals(b) shouldBe true @@ -602,14 +641,14 @@ class MessagePackTest extends MessagePackSpec { } } - "MessagePack.UnpackerConfig" should { - "be immutable" in { + test("MessagePack.UnpackerConfig") { + test("should be immutable") { val a = new MessagePack.UnpackerConfig() val b = a.withBufferSize(64 * 1024) a.equals(b) shouldBe false } - "implement equals" in { + test("implement equals") { val a = new MessagePack.UnpackerConfig() val b = new MessagePack.UnpackerConfig() a.equals(b) shouldBe true diff --git a/msgpack-core/src/test/scala/org/msgpack/core/MessagePackerTest.scala b/msgpack-core/src/test/scala/org/msgpack/core/MessagePackerTest.scala index 5024963fd..096a811b4 100644 --- a/msgpack-core/src/test/scala/org/msgpack/core/MessagePackerTest.scala +++ b/msgpack-core/src/test/scala/org/msgpack/core/MessagePackerTest.scala @@ -15,21 +15,21 @@ // package org.msgpack.core -import java.io.{ByteArrayOutputStream, File, FileInputStream, FileOutputStream} - -import org.msgpack.core.MessagePack.{UnpackerConfig, PackerConfig} +import org.msgpack.core.MessagePack.PackerConfig import org.msgpack.core.buffer.{ChannelBufferOutput, OutputStreamBufferOutput} import org.msgpack.value.ValueFactory -import xerial.core.io.IOUtil +import wvlet.airspec.AirSpec +import wvlet.log.io.IOUtil.withResource +import java.io.{ByteArrayOutputStream, File, FileInputStream, FileOutputStream} import scala.util.Random /** * */ -class MessagePackerTest extends MessagePackSpec { +class MessagePackerTest extends AirSpec with Benchmark { - def verifyIntSeq(answer: Array[Int], packed: Array[Byte]) { + private def verifyIntSeq(answer: Array[Int], packed: Array[Byte]) { val unpacker = MessagePack.newDefaultUnpacker(packed) val b = Array.newBuilder[Int] while (unpacker.hasNext) { @@ -40,27 +40,27 @@ class MessagePackerTest extends MessagePackSpec { result shouldBe answer } - def createTempFile = { + private def createTempFile = { val f = File.createTempFile("msgpackTest", "msgpack") f.deleteOnExit f } - def createTempFileWithOutputStream = { + private def createTempFileWithOutputStream = { val f = createTempFile val out = new FileOutputStream(f) (f, out) } - def createTempFileWithChannel = { + private def createTempFileWithChannel = { val (f, out) = createTempFileWithOutputStream val ch = out.getChannel (f, ch) } - "MessagePacker" should { + test("MessagePacker") { - "reset the internal states" in { + test("reset the internal states") { val intSeq = (0 until 100).map(i => Random.nextInt).toArray val b = new ByteArrayOutputStream @@ -86,13 +86,12 @@ class MessagePackerTest extends MessagePackSpec { verifyIntSeq(intSeq3, b3.toByteArray) } - "improve the performance via reset method" taggedAs ("reset") in { - + test("improve the performance via reset method") { val N = 1000 val t = time("packer", repeat = 10) { block("no-buffer-reset") { val out = new ByteArrayOutputStream - IOUtil.withResource(MessagePack.newDefaultPacker(out)) { packer => + withResource(MessagePack.newDefaultPacker(out)) { packer => for (i <- 0 until N) { val outputStream = new ByteArrayOutputStream() packer @@ -105,7 +104,7 @@ class MessagePackerTest extends MessagePackSpec { block("buffer-reset") { val out = new ByteArrayOutputStream - IOUtil.withResource(MessagePack.newDefaultPacker(out)) { packer => + withResource(MessagePack.newDefaultPacker(out)) { packer => val bufferOut = new OutputStreamBufferOutput(new ByteArrayOutputStream()) for (i <- 0 until N) { @@ -119,10 +118,10 @@ class MessagePackerTest extends MessagePackSpec { } } - t("buffer-reset").averageWithoutMinMax should be <= t("no-buffer-reset").averageWithoutMinMax + t("buffer-reset").averageWithoutMinMax <= t("no-buffer-reset").averageWithoutMinMax shouldBe true } - "pack larger string array than byte buf" taggedAs ("larger-string-array-than-byte-buf") in { + test("pack larger string array than byte buf") { // Based on https://github.com/msgpack/msgpack-java/issues/154 def test(bufferSize: Int, stringSize: Int): Boolean = { @@ -148,7 +147,7 @@ class MessagePackerTest extends MessagePackSpec { } } - "reset OutputStreamBufferOutput" in { + test("reset OutputStreamBufferOutput") { val (f0, out0) = createTempFileWithOutputStream val packer = MessagePack.newDefaultPacker(out0) packer.packInt(99) @@ -178,7 +177,7 @@ class MessagePackerTest extends MessagePackSpec { up1.close } - "reset ChannelBufferOutput" in { + test("reset ChannelBufferOutput") { val (f0, out0) = createTempFileWithChannel val packer = MessagePack.newDefaultPacker(out0) packer.packInt(99) @@ -208,7 +207,7 @@ class MessagePackerTest extends MessagePackSpec { up1.close } - "pack a lot of String within expected time" in { + test("pack a lot of String within expected time") { val count = 20000 def measureDuration(outputStream: java.io.OutputStream) = { @@ -231,14 +230,14 @@ class MessagePackerTest extends MessagePackSpec { measureDuration(fileOutput) } } - t("file-output-stream").averageWithoutMinMax shouldBe <(t("byte-array-output-stream").averageWithoutMinMax * 5) + t("file-output-stream").averageWithoutMinMax < (t("byte-array-output-stream").averageWithoutMinMax * 5) shouldBe true } } - "compute totalWrittenBytes" in { + test("compute totalWrittenBytes") { val out = new ByteArrayOutputStream val packerTotalWrittenBytes = - IOUtil.withResource(MessagePack.newDefaultPacker(out)) { packer => + withResource(MessagePack.newDefaultPacker(out)) { packer => packer .packByte(0) // 1 .packBoolean(true) // 1 @@ -254,7 +253,7 @@ class MessagePackerTest extends MessagePackSpec { out.toByteArray.length shouldBe packerTotalWrittenBytes } - "support read-only buffer" taggedAs ("read-only") in { + test("support read-only buffer") { val payload = Array[Byte](1) val out = new ByteArrayOutputStream() val packer = MessagePack @@ -264,7 +263,7 @@ class MessagePackerTest extends MessagePackSpec { .close() } - "pack small string with STR8" in { + test("pack small string with STR8") { val packer = new PackerConfig().newBufferPacker() packer.packString("Hello. This is a string longer than 32 characters!") val b = packer.toByteArray @@ -274,7 +273,7 @@ class MessagePackerTest extends MessagePackSpec { f shouldBe MessageFormat.STR8 } - "be able to disable STR8 for backward compatibility" in { + test("be able to disable STR8 for backward compatibility") { val config = new PackerConfig() .withStr8FormatSupport(false) @@ -285,7 +284,7 @@ class MessagePackerTest extends MessagePackSpec { f shouldBe MessageFormat.STR16 } - "be able to disable STR8 when using CharsetEncoder" in { + test("be able to disable STR8 when using CharsetEncoder") { val config = new PackerConfig() .withStr8FormatSupport(false) .withSmallStringOptimizationThreshold(0) // Disable small string optimization @@ -294,19 +293,19 @@ class MessagePackerTest extends MessagePackSpec { packer.packString("small string") val unpacker = MessagePack.newDefaultUnpacker(packer.toByteArray) val f = unpacker.getNextFormat - f shouldNot be(MessageFormat.STR8) + f shouldNotBe MessageFormat.STR8 val s = unpacker.unpackString() s shouldBe "small string" } - "write raw binary" taggedAs ("raw-binary") in { + test("write raw binary") { val packer = new MessagePack.PackerConfig().newBufferPacker() val msg = Array[Byte](-127, -92, 116, 121, 112, 101, -92, 112, 105, 110, 103) packer.writePayload(msg) } - "append raw binary" taggedAs ("append-raw-binary") in { + test("append raw binary") { val packer = new MessagePack.PackerConfig().newBufferPacker() val msg = Array[Byte](-127, -92, 116, 121, 112, 101, -92, 112, 105, 110, 103) diff --git a/msgpack-core/src/test/scala/org/msgpack/core/MessageUnpackerTest.scala b/msgpack-core/src/test/scala/org/msgpack/core/MessageUnpackerTest.scala index c2c738515..5ae597f27 100644 --- a/msgpack-core/src/test/scala/org/msgpack/core/MessageUnpackerTest.scala +++ b/msgpack-core/src/test/scala/org/msgpack/core/MessageUnpackerTest.scala @@ -15,14 +15,16 @@ // package org.msgpack.core -import java.io._ -import java.nio.ByteBuffer -import java.util.Collections - +import org.msgpack.core.MessagePackSpec.{createMessagePackData, toHex} import org.msgpack.core.buffer._ import org.msgpack.value.ValueType -import xerial.core.io.IOUtil._ +import wvlet.airspec.AirSpec +import wvlet.log.LogSupport +import wvlet.log.io.IOUtil.withResource +import java.io._ +import java.nio.ByteBuffer +import java.util.Collections import scala.collection.JavaConverters._ import scala.util.Random @@ -43,12 +45,12 @@ object MessageUnpackerTest { } } -import MessageUnpackerTest._ +import org.msgpack.core.MessageUnpackerTest._ -class MessageUnpackerTest extends MessagePackSpec { +class MessageUnpackerTest extends AirSpec with Benchmark { - val universal = MessageBuffer.allocate(0).isInstanceOf[MessageBufferU] - def testData: Array[Byte] = { + private val universal = MessageBuffer.allocate(0).isInstanceOf[MessageBufferU] + private def testData: Array[Byte] = { val out = new ByteArrayOutputStream() val packer = MessagePack.newDefaultPacker(out) @@ -68,9 +70,9 @@ class MessageUnpackerTest extends MessagePackSpec { arr } - val intSeq = (for (i <- 0 until 100) yield Random.nextInt()).toArray[Int] + private val intSeq = (for (i <- 0 until 100) yield Random.nextInt()).toArray[Int] - def testData2: Array[Byte] = { + private def testData2: Array[Byte] = { val out = new ByteArrayOutputStream() val packer = MessagePack.newDefaultPacker(out); @@ -86,7 +88,7 @@ class MessageUnpackerTest extends MessagePackSpec { arr } - def write(packer: MessagePacker, r: Random) { + private def write(packer: MessagePacker, r: Random) { val tpeIndex = Iterator .continually(r.nextInt(MessageFormat.values().length)) .find(_ != MessageFormat.NEVER_USED.ordinal()) @@ -142,7 +144,7 @@ class MessageUnpackerTest extends MessagePackSpec { } } - def testData3(N: Int): Array[Byte] = { + private def testData3(N: Int): Array[Byte] = { val out = new ByteArrayOutputStream() val packer = MessagePack.newDefaultPacker(out) @@ -160,7 +162,7 @@ class MessageUnpackerTest extends MessagePackSpec { arr } - def readValue(unpacker: MessageUnpacker) { + private def readValue(unpacker: MessageUnpacker) { val f = unpacker.getNextFormat() f.getValueType match { case ValueType.ARRAY => @@ -181,7 +183,7 @@ class MessageUnpackerTest extends MessagePackSpec { } } - def createTempFile = { + private def createTempFile = { val f = File.createTempFile("msgpackTest", "msgpack") f.deleteOnExit val p = MessagePack.newDefaultPacker(new FileOutputStream(f)) @@ -190,12 +192,12 @@ class MessageUnpackerTest extends MessagePackSpec { f } - def checkFile(u: MessageUnpacker) = { + private def checkFile(u: MessageUnpacker) = { u.unpackInt shouldBe 99 u.hasNext shouldBe false } - def unpackers(data: Array[Byte]): Seq[MessageUnpacker] = { + private def unpackers(data: Array[Byte]): Seq[MessageUnpacker] = { val bb = ByteBuffer.allocate(data.length) val db = ByteBuffer.allocateDirect(data.length) bb.put(data).flip() @@ -210,7 +212,7 @@ class MessageUnpackerTest extends MessagePackSpec { builder.result() } - def unpackerCollectionWithVariousBuffers(data: Array[Byte], chunkSize: Int): Seq[MessageUnpacker] = { + private def unpackerCollectionWithVariousBuffers(data: Array[Byte], chunkSize: Int): Seq[MessageUnpacker] = { val seqBytes = Seq.newBuilder[MessageBufferInput] val seqByteBuffers = Seq.newBuilder[MessageBufferInput] val seqDirectBuffers = Seq.newBuilder[MessageBufferInput] @@ -238,9 +240,9 @@ class MessageUnpackerTest extends MessagePackSpec { builder.result() } - "MessageUnpacker" should { + test("MessageUnpacker") { - "parse message packed data" taggedAs ("unpack") in { + test("parse message packed data") { val arr = testData for (unpacker <- unpackers(arr)) { @@ -255,7 +257,7 @@ class MessageUnpackerTest extends MessagePackSpec { } } - "skip reading values" in { + test("skip reading values") { for (unpacker <- unpackers(testData)) { var skipCount = 0 @@ -269,7 +271,7 @@ class MessageUnpackerTest extends MessagePackSpec { } } - "compare skip performance" taggedAs ("skip") in { + test("compare skip performance") { val N = 10000 val data = testData3(N) @@ -297,8 +299,7 @@ class MessageUnpackerTest extends MessagePackSpec { } - "parse int data" in { - + test("parse int data") { debug(intSeq.mkString(", ")) for (unpacker <- unpackers(testData2)) { @@ -319,15 +320,13 @@ class MessageUnpackerTest extends MessagePackSpec { } } - ib.result shouldBe intSeq + ib.result shouldBe intSeq.toSeq unpacker.getTotalReadBytes shouldBe testData2.length } - } - "read data at the buffer boundary" taggedAs ("boundary") in { - - trait SplitTest { + test("read data at the buffer boundary") { + trait SplitTest extends LogSupport { val data: Array[Byte] def run { for (unpacker <- unpackers(data)) { @@ -362,7 +361,7 @@ class MessageUnpackerTest extends MessagePackSpec { new SplitTest { val data = testData3(30) }.run } - "read integer at MessageBuffer boundaries" taggedAs ("integer-buffer-boundary") in { + test("read integer at MessageBuffer boundaries") { val packer = MessagePack.newDefaultBufferPacker() (0 until 1170).foreach { i => packer.packLong(0x0011223344556677L) @@ -385,7 +384,7 @@ class MessageUnpackerTest extends MessagePackSpec { } } - "read string at MessageBuffer boundaries" taggedAs ("string-buffer-boundary") in { + test("read string at MessageBuffer boundaries") { val packer = MessagePack.newDefaultBufferPacker() (0 until 1170).foreach { i => packer.packString("hello world") @@ -408,7 +407,7 @@ class MessageUnpackerTest extends MessagePackSpec { } } - "be faster than msgpack-v6 skip" taggedAs ("cmp-skip") in { + test("be faster than msgpack-v6 skip") { trait Fixture { val unpacker: MessageUnpacker @@ -434,7 +433,6 @@ class MessageUnpackerTest extends MessagePackSpec { val t = time("skip performance", repeat = N) { block("v6") { - import org.msgpack.`type`.{ValueType => ValueTypeV6} val v6 = new org.msgpack.MessagePack() val unpacker = new org.msgpack.unpacker.MessagePackUnpacker(v6, new ByteArrayInputStream(data)) var count = 0 @@ -466,15 +464,16 @@ class MessageUnpackerTest extends MessagePackSpec { } } - t("v7-array").averageWithoutMinMax should be <= t("v6").averageWithoutMinMax - t("v7-array-buffer").averageWithoutMinMax should be <= t("v6").averageWithoutMinMax - if (!universal) - t("v7-direct-buffer").averageWithoutMinMax should be <= t("v6").averageWithoutMinMax + t("v7-array").averageWithoutMinMax <= t("v6").averageWithoutMinMax shouldBe true + t("v7-array-buffer").averageWithoutMinMax <= t("v6").averageWithoutMinMax shouldBe true + if (!universal) { + t("v7-direct-buffer").averageWithoutMinMax <= t("v6").averageWithoutMinMax shouldBe true + } } import org.msgpack.`type`.{ValueType => ValueTypeV6} - "be faster than msgpack-v6 read value" taggedAs ("cmp-unpack") in { + test("be faster than msgpack-v6 read value") { def readValueV6(unpacker: org.msgpack.unpacker.MessagePackUnpacker) { val vt = unpacker.getNextType() @@ -598,12 +597,12 @@ class MessageUnpackerTest extends MessagePackSpec { if (t("v7-array-buffer").averageWithoutMinMax > t("v6").averageWithoutMinMax) { warn(s"v7-array-buffer ${t("v7-array-buffer").averageWithoutMinMax} is slower than v6 ${t("v6").averageWithoutMinMax}") } - if (!universal) - t("v7-direct-buffer").averageWithoutMinMax should be <= t("v6").averageWithoutMinMax - + if (!universal) { + t("v7-direct-buffer").averageWithoutMinMax <= t("v6").averageWithoutMinMax shouldBe true + } } - "be faster for reading binary than v6" taggedAs ("cmp-binary") in { + test("be faster for reading binary than v6") { val bos = new ByteArrayOutputStream() val packer = MessagePack.newDefaultPacker(bos) @@ -702,37 +701,37 @@ class MessageUnpackerTest extends MessagePackSpec { } } - "read payload as a reference" taggedAs ("ref") in { + test("read payload as a reference") { val dataSizes = Seq(0, 1, 5, 8, 16, 32, 128, 256, 1024, 2000, 10000, 100000) for (s <- dataSizes) { - When(f"data size is $s%,d") - val data = new Array[Byte](s) - Random.nextBytes(data) - val b = new ByteArrayOutputStream() - val packer = MessagePack.newDefaultPacker(b) - packer.packBinaryHeader(s) - packer.writePayload(data) - packer.close() - - for (unpacker <- unpackers(b.toByteArray)) { - val len = unpacker.unpackBinaryHeader() - len shouldBe s - val ref = unpacker.readPayloadAsReference(len) - unpacker.close() - ref.size() shouldBe s - val stored = new Array[Byte](len) - ref.getBytes(0, stored, 0, len) + test(f"data size is $s%,d") { + val data = new Array[Byte](s) + Random.nextBytes(data) + val b = new ByteArrayOutputStream() + val packer = MessagePack.newDefaultPacker(b) + packer.packBinaryHeader(s) + packer.writePayload(data) + packer.close() + + for (unpacker <- unpackers(b.toByteArray)) { + val len = unpacker.unpackBinaryHeader() + len shouldBe s + val ref = unpacker.readPayloadAsReference(len) + unpacker.close() + ref.size() shouldBe s + val stored = new Array[Byte](len) + ref.getBytes(0, stored, 0, len) - stored shouldBe data + stored shouldBe data + } } } - } - "reset the internal states" taggedAs ("reset") in { + test("reset the internal states") { val data = intSeq val b = createMessagePackData(packer => data foreach packer.packInt) @@ -769,7 +768,7 @@ class MessageUnpackerTest extends MessagePackSpec { } - "improve the performance via reset method" taggedAs ("reset-arr") in { + test("improve the performance via reset method") { val out = new ByteArrayOutputStream val packer = MessagePack.newDefaultPacker(out) @@ -821,7 +820,7 @@ class MessageUnpackerTest extends MessagePackSpec { // t("reuse-array-input").averageWithoutMinMax should be <= t("no-buffer-reset").averageWithoutMinMax } - "reset ChannelBufferInput" in { + test("reset ChannelBufferInput") { val f0 = createTempFile val u = MessagePack.newDefaultUnpacker(new FileInputStream(f0).getChannel) checkFile(u) @@ -833,7 +832,7 @@ class MessageUnpackerTest extends MessagePackSpec { u.close } - "reset InputStreamBufferInput" in { + test("reset InputStreamBufferInput") { val f0 = createTempFile val u = MessagePack.newDefaultUnpacker(new FileInputStream(f0)) checkFile(u) @@ -845,7 +844,7 @@ class MessageUnpackerTest extends MessagePackSpec { u.close } - "unpack large string data" taggedAs ("large-string") in { + test("unpack large string data") { def createLargeData(stringLength: Int): Array[Byte] = { val out = new ByteArrayOutputStream() val packer = MessagePack.newDefaultPacker(out) @@ -874,7 +873,7 @@ class MessageUnpackerTest extends MessagePackSpec { } } - "unpack string crossing end of buffer" in { + test("unpack string crossing end of buffer") { def check(expected: String, strLen: Int) = { val bytes = new Array[Byte](strLen) val out = new ByteArrayOutputStream @@ -910,7 +909,7 @@ class MessageUnpackerTest extends MessagePackSpec { } } - "read value length at buffer boundary" taggedAs ("number-boundary") in { + test("read value length at buffer boundary") { val input = new SplitMessageBufferInput( Array(Array[Byte](MessagePack.Code.STR16), Array[Byte](0x00), diff --git a/msgpack-core/src/test/scala/org/msgpack/core/buffer/ByteStringTest.scala b/msgpack-core/src/test/scala/org/msgpack/core/buffer/ByteStringTest.scala index ed79ef6ab..42872fc44 100644 --- a/msgpack-core/src/test/scala/org/msgpack/core/buffer/ByteStringTest.scala +++ b/msgpack-core/src/test/scala/org/msgpack/core/buffer/ByteStringTest.scala @@ -16,14 +16,16 @@ package org.msgpack.core.buffer import akka.util.ByteString -import org.msgpack.core.{MessagePack, MessagePackSpec, MessageUnpacker} +import org.msgpack.core.MessagePack +import org.msgpack.core.MessagePackSpec.createMessagePackData +import wvlet.airspec.AirSpec -class ByteStringTest extends MessagePackSpec { +class ByteStringTest extends AirSpec { - val unpackedString = "foo" - val byteString = ByteString(createMessagePackData(_.packString(unpackedString))) + private val unpackedString = "foo" + private val byteString = ByteString(createMessagePackData(_.packString(unpackedString))) - def unpackString(messageBuffer: MessageBuffer) = { + private def unpackString(messageBuffer: MessageBuffer) = { val input = new MessageBufferInput { private var isRead = false @@ -41,12 +43,14 @@ class ByteStringTest extends MessagePackSpec { MessagePack.newDefaultUnpacker(input).unpackString() } - "Unpacking a ByteString's ByteBuffer" should { - "fail with a regular MessageBuffer" in { + test("Unpacking a ByteString's ByteBuffer") { + test("fail with a regular MessageBuffer") { // can't demonstrate with new ByteBufferInput(byteString.asByteBuffer) // as Travis tests run with JDK6 that picks up MessageBufferU - a[RuntimeException] shouldBe thrownBy(unpackString(new MessageBuffer(byteString.asByteBuffer))) + intercept[RuntimeException] { + unpackString(new MessageBuffer(byteString.asByteBuffer)) + } } } } diff --git a/msgpack-core/src/test/scala/org/msgpack/core/buffer/MessageBufferInputTest.scala b/msgpack-core/src/test/scala/org/msgpack/core/buffer/MessageBufferInputTest.scala index 060e436a1..4cc4a98a7 100644 --- a/msgpack-core/src/test/scala/org/msgpack/core/buffer/MessageBufferInputTest.scala +++ b/msgpack-core/src/test/scala/org/msgpack/core/buffer/MessageBufferInputTest.scala @@ -15,35 +15,35 @@ // package org.msgpack.core.buffer +import org.msgpack.core.MessagePack +import wvlet.airspec.AirSpec +import wvlet.log.io.IOUtil.withResource + import java.io._ -import java.net.{InetSocketAddress} +import java.net.InetSocketAddress import java.nio.ByteBuffer import java.nio.channels.{ServerSocketChannel, SocketChannel} import java.util.concurrent.{Callable, Executors, TimeUnit} import java.util.zip.{GZIPInputStream, GZIPOutputStream} - -import org.msgpack.core.{MessagePack, MessagePackSpec} -import xerial.core.io.IOUtil._ - import scala.util.Random -class MessageBufferInputTest extends MessagePackSpec { +class MessageBufferInputTest extends AirSpec { - val targetInputSize = + private val targetInputSize = Seq(0, 10, 500, 1000, 2000, 4000, 8000, 10000, 30000, 50000, 100000) - def testData(size: Int) = { + private def testData(size: Int) = { //debug(s"test data size: ${size}") val b = new Array[Byte](size) Random.nextBytes(b) b } - def testDataSet = { + private def testDataSet = { targetInputSize.map(testData) } - def runTest(factory: Array[Byte] => MessageBufferInput) { + private def runTest(factory: Array[Byte] => MessageBufferInput) { for (b <- testDataSet) { checkInputData(b, factory(b)) } @@ -74,30 +74,31 @@ class MessageBufferInputTest extends MessagePackSpec { } } - def checkInputData(inputData: Array[Byte], in: MessageBufferInput) { - When(s"input data size = ${inputData.length}") - var cursor = 0 - for (m <- Iterator.continually(in.next).takeWhile(_ != null)) { - m.toByteArray() shouldBe inputData.slice(cursor, cursor + m.size()) - cursor += m.size() + private def checkInputData(inputData: Array[Byte], in: MessageBufferInput) { + test(s"When input data size = ${inputData.length}") { + var cursor = 0 + for (m <- Iterator.continually(in.next).takeWhile(_ != null)) { + m.toByteArray() shouldBe inputData.slice(cursor, cursor + m.size()) + cursor += m.size() + } + cursor shouldBe inputData.length } - cursor shouldBe inputData.length } - "MessageBufferInput" should { - "support byte arrays" in { + test("MessageBufferInput") { + test("support byte arrays") { runTest(new ArrayBufferInput(_)) } - "support ByteBuffers" in { + test("support ByteBuffers") { runTest(b => new ByteBufferInput(b.toByteBuffer)) } - "support InputStreams" taggedAs ("is") in { + test("support InputStreams") { runTest(b => new InputStreamBufferInput(new GZIPInputStream(new ByteArrayInputStream(b.compress)))) } - "support file input channel" taggedAs ("fc") in { + test("support file input channel") { runTest { b => val tmp = b.saveToTmpFile try { @@ -110,13 +111,13 @@ class MessageBufferInputTest extends MessagePackSpec { } } - def createTempFile = { + private def createTempFile = { val f = File.createTempFile("msgpackTest", "msgpack") f.deleteOnExit f } - def createTempFileWithInputStream = { + private def createTempFileWithInputStream = { val f = createTempFile val out = new FileOutputStream(f) MessagePack.newDefaultPacker(out).packInt(42).close @@ -124,19 +125,19 @@ class MessageBufferInputTest extends MessagePackSpec { (f, in) } - def createTempFileWithChannel = { + private def createTempFileWithChannel = { val (f, in) = createTempFileWithInputStream val ch = in.getChannel (f, ch) } - def readInt(buf: MessageBufferInput): Int = { + private def readInt(buf: MessageBufferInput): Int = { val unpacker = MessagePack.newDefaultUnpacker(buf) unpacker.unpackInt } - "InputStreamBufferInput" should { - "reset buffer" in { + test("InputStreamBufferInput") { + test("reset buffer") { val (f0, in0) = createTempFileWithInputStream val buf = new InputStreamBufferInput(in0) readInt(buf) shouldBe 42 @@ -146,7 +147,7 @@ class MessageBufferInputTest extends MessagePackSpec { readInt(buf) shouldBe 42 } - "be non-blocking" taggedAs ("non-blocking") in { + test("be non-blocking") { withResource(new PipedOutputStream()) { pipedOutputStream => withResource(new PipedInputStream()) { pipedInputStream => @@ -173,8 +174,8 @@ class MessageBufferInputTest extends MessagePackSpec { } } - "ChannelBufferInput" should { - "reset buffer" in { + test("ChannelBufferInput") { + test("reset buffer") { val (f0, in0) = createTempFileWithChannel val buf = new ChannelBufferInput(in0) readInt(buf) shouldBe 42 @@ -184,7 +185,7 @@ class MessageBufferInputTest extends MessagePackSpec { readInt(buf) shouldBe 42 } - "unpack without blocking" in { + test("unpack without blocking") { val server = ServerSocketChannel.open.bind(new InetSocketAddress("localhost", 0)) val executorService = Executors.newCachedThreadPool diff --git a/msgpack-core/src/test/scala/org/msgpack/core/buffer/MessageBufferOutputTest.scala b/msgpack-core/src/test/scala/org/msgpack/core/buffer/MessageBufferOutputTest.scala index e048e1ba1..ea9cde57e 100644 --- a/msgpack-core/src/test/scala/org/msgpack/core/buffer/MessageBufferOutputTest.scala +++ b/msgpack-core/src/test/scala/org/msgpack/core/buffer/MessageBufferOutputTest.scala @@ -15,62 +15,62 @@ // package org.msgpack.core.buffer -import java.io._ +import wvlet.airspec.AirSpec -import org.msgpack.core.MessagePackSpec +import java.io._ -class MessageBufferOutputTest extends MessagePackSpec { +class MessageBufferOutputTest extends AirSpec { - def createTempFile = { + private def createTempFile = { val f = File.createTempFile("msgpackTest", "msgpack") f.deleteOnExit f } - def createTempFileWithOutputStream = { + private def createTempFileWithOutputStream = { val f = createTempFile val out = new FileOutputStream(f) (f, out) } - def createTempFileWithChannel = { + private def createTempFileWithChannel = { val (f, out) = createTempFileWithOutputStream val ch = out.getChannel (f, ch) } - def writeIntToBuf(buf: MessageBufferOutput) = { + private def writeIntToBuf(buf: MessageBufferOutput) = { val mb0 = buf.next(8) mb0.putInt(0, 42) buf.writeBuffer(4) buf.close } - "OutputStreamBufferOutput" should { - "reset buffer" in { + test("OutputStreamBufferOutput") { + test("reset buffer") { val (f0, out0) = createTempFileWithOutputStream val buf = new OutputStreamBufferOutput(out0) writeIntToBuf(buf) - f0.length.toInt should be > 0 + f0.length.toInt > 0 shouldBe true val (f1, out1) = createTempFileWithOutputStream buf.reset(out1) writeIntToBuf(buf) - f1.length.toInt should be > 0 + f1.length.toInt > 0 shouldBe true } } - "ChannelBufferOutput" should { - "reset buffer" in { + test("ChannelBufferOutput") { + test("reset buffer") { val (f0, ch0) = createTempFileWithChannel val buf = new ChannelBufferOutput(ch0) writeIntToBuf(buf) - f0.length.toInt should be > 0 + f0.length.toInt >= 0 shouldBe true val (f1, ch1) = createTempFileWithChannel buf.reset(ch1) writeIntToBuf(buf) - f1.length.toInt should be > 0 + f1.length.toInt > 0 shouldBe true } } } diff --git a/msgpack-core/src/test/scala/org/msgpack/core/buffer/MessageBufferTest.scala b/msgpack-core/src/test/scala/org/msgpack/core/buffer/MessageBufferTest.scala index f0f66b4af..4bb9c69da 100644 --- a/msgpack-core/src/test/scala/org/msgpack/core/buffer/MessageBufferTest.scala +++ b/msgpack-core/src/test/scala/org/msgpack/core/buffer/MessageBufferTest.scala @@ -15,246 +15,245 @@ // package org.msgpack.core.buffer -import java.nio.ByteBuffer - -import org.msgpack.core.MessagePackSpec +import org.msgpack.core.Benchmark +import wvlet.airspec.AirSpec +import java.nio.ByteBuffer import scala.util.Random /** * Created on 2014/05/01. */ -class MessageBufferTest extends MessagePackSpec { +class MessageBufferTest extends AirSpec with Benchmark { - "MessageBuffer" should { + private val universal = MessageBuffer.allocate(0).isInstanceOf[MessageBufferU] - val universal = MessageBuffer.allocate(0).isInstanceOf[MessageBufferU] - "check buffer type" in { - val b = MessageBuffer.allocate(0) - info(s"MessageBuffer type: ${b.getClass.getName}") - } + test("check buffer type") { + val b = MessageBuffer.allocate(0) + info(s"MessageBuffer type: ${b.getClass.getName}") + } - "wrap byte array considering position and remaining values" taggedAs ("wrap-ba") in { - val d = Array[Byte](10, 11, 12, 13, 14, 15, 16, 17, 18, 19) - val mb = MessageBuffer.wrap(d, 2, 2) - mb.getByte(0) shouldBe 12 - mb.size() shouldBe 2 - } + test("wrap byte array considering position and remaining values") { + val d = Array[Byte](10, 11, 12, 13, 14, 15, 16, 17, 18, 19) + val mb = MessageBuffer.wrap(d, 2, 2) + mb.getByte(0) shouldBe 12 + mb.size() shouldBe 2 + } - "wrap ByteBuffer considering position and remaining values" taggedAs ("wrap-bb") in { - val d = Array[Byte](10, 11, 12, 13, 14, 15, 16, 17, 18, 19) - val subset = ByteBuffer.wrap(d, 2, 2) - val mb = MessageBuffer.wrap(subset) - mb.getByte(0) shouldBe 12 - mb.size() shouldBe 2 - } + test("wrap ByteBuffer considering position and remaining values") { + val d = Array[Byte](10, 11, 12, 13, 14, 15, 16, 17, 18, 19) + val subset = ByteBuffer.wrap(d, 2, 2) + val mb = MessageBuffer.wrap(subset) + mb.getByte(0) shouldBe 12 + mb.size() shouldBe 2 + } + + test("have better performance than ByteBuffer") { - "have better performance than ByteBuffer" in { + val N = 1000000 + val M = 64 * 1024 * 1024 - val N = 1000000 - val M = 64 * 1024 * 1024 + val ub = MessageBuffer.allocate(M) + val ud = + if (universal) MessageBuffer.wrap(ByteBuffer.allocate(M)) + else MessageBuffer.wrap(ByteBuffer.allocateDirect(M)) + val hb = ByteBuffer.allocate(M) + val db = ByteBuffer.allocateDirect(M) - val ub = MessageBuffer.allocate(M) - val ud = - if (universal) MessageBuffer.wrap(ByteBuffer.allocate(M)) - else MessageBuffer.wrap(ByteBuffer.allocateDirect(M)) - val hb = ByteBuffer.allocate(M) - val db = ByteBuffer.allocateDirect(M) + def bench(f: Int => Unit) { + var i = 0 + while (i < N) { + f((i * 4) % M) + i += 1 + } + } + + val r = new Random(0) + val rs = new Array[Int](N) + (0 until N).map(i => rs(i) = r.nextInt(N)) + def randomBench(f: Int => Unit) { + var i = 0 + while (i < N) { + f((rs(i) * 4) % M) + i += 1 + } + } - def bench(f: Int => Unit) { + val rep = 3 + info(f"Reading buffers (of size:${M}%,d) ${N}%,d x $rep times") + time("sequential getInt", repeat = rep) { + block("unsafe array") { var i = 0 while (i < N) { - f((i * 4) % M) + ub.getInt((i * 4) % M) i += 1 } } - val r = new Random(0) - val rs = new Array[Int](N) - (0 until N).map(i => rs(i) = r.nextInt(N)) - def randomBench(f: Int => Unit) { + block("unsafe direct") { var i = 0 while (i < N) { - f((rs(i) * 4) % M) + ud.getInt((i * 4) % M) i += 1 } } - val rep = 3 - info(f"Reading buffers (of size:${M}%,d) ${N}%,d x $rep times") - time("sequential getInt", repeat = rep) { - block("unsafe array") { - var i = 0 - while (i < N) { - ub.getInt((i * 4) % M) - i += 1 - } - } - - block("unsafe direct") { - var i = 0 - while (i < N) { - ud.getInt((i * 4) % M) - i += 1 - } - } - - block("allocate") { - var i = 0 - while (i < N) { - hb.getInt((i * 4) % M) - i += 1 - } + block("allocate") { + var i = 0 + while (i < N) { + hb.getInt((i * 4) % M) + i += 1 } + } - block("allocateDirect") { - var i = 0 - while (i < N) { - db.getInt((i * 4) % M) - i += 1 - } + block("allocateDirect") { + var i = 0 + while (i < N) { + db.getInt((i * 4) % M) + i += 1 } } + } - time("random getInt", repeat = rep) { - block("unsafe array") { - var i = 0 - while (i < N) { - ub.getInt((rs(i) * 4) % M) - i += 1 - } + time("random getInt", repeat = rep) { + block("unsafe array") { + var i = 0 + while (i < N) { + ub.getInt((rs(i) * 4) % M) + i += 1 } + } - block("unsafe direct") { - var i = 0 - while (i < N) { - ud.getInt((rs(i) * 4) % M) - i += 1 - } + block("unsafe direct") { + var i = 0 + while (i < N) { + ud.getInt((rs(i) * 4) % M) + i += 1 } + } - block("allocate") { - var i = 0 - while (i < N) { - hb.getInt((rs(i) * 4) % M) - i += 1 - } + block("allocate") { + var i = 0 + while (i < N) { + hb.getInt((rs(i) * 4) % M) + i += 1 } + } - block("allocateDirect") { - var i = 0 - while (i < N) { - db.getInt((rs(i) * 4) % M) - i += 1 - } + block("allocateDirect") { + var i = 0 + while (i < N) { + db.getInt((rs(i) * 4) % M) + i += 1 } } } - val builder = Seq.newBuilder[MessageBuffer] - builder += MessageBuffer.allocate(10) - builder += MessageBuffer.wrap(ByteBuffer.allocate(10)) - if (!universal) builder += MessageBuffer.wrap(ByteBuffer.allocateDirect(10)) - val buffers = builder.result() - - "convert to ByteBuffer" in { - for (t <- buffers) { - val bb = t.sliceAsByteBuffer - bb.position() shouldBe 0 - bb.limit() shouldBe 10 - bb.capacity shouldBe 10 - } + } + + private val builder = Seq.newBuilder[MessageBuffer] + builder += MessageBuffer.allocate(10) + builder += MessageBuffer.wrap(ByteBuffer.allocate(10)) + if (!universal) builder += MessageBuffer.wrap(ByteBuffer.allocateDirect(10)) + private val buffers = builder.result() + + test("convert to ByteBuffer") { + for (t <- buffers) { + val bb = t.sliceAsByteBuffer + bb.position() shouldBe 0 + bb.limit() shouldBe 10 + bb.capacity shouldBe 10 } + } - "put ByteBuffer on itself" in { - for (t <- buffers) { - val b = Array[Byte](0x02, 0x03) - val srcArray = ByteBuffer.wrap(b) - val srcHeap = ByteBuffer.allocate(b.length) - srcHeap.put(b).flip - val srcOffHeap = ByteBuffer.allocateDirect(b.length) - srcOffHeap.put(b).flip - - for (src <- Seq(srcArray, srcHeap, srcOffHeap)) { - // Write header bytes - val header = Array[Byte](0x00, 0x01) - t.putBytes(0, header, 0, header.length) - // Write src after the header - t.putByteBuffer(header.length, src, header.length) - - t.getByte(0) shouldBe 0x00 - t.getByte(1) shouldBe 0x01 - t.getByte(2) shouldBe 0x02 - t.getByte(3) shouldBe 0x03 - } + test("put ByteBuffer on itself") { + for (t <- buffers) { + val b = Array[Byte](0x02, 0x03) + val srcArray = ByteBuffer.wrap(b) + val srcHeap = ByteBuffer.allocate(b.length) + srcHeap.put(b).flip + val srcOffHeap = ByteBuffer.allocateDirect(b.length) + srcOffHeap.put(b).flip + + for (src <- Seq(srcArray, srcHeap, srcOffHeap)) { + // Write header bytes + val header = Array[Byte](0x00, 0x01) + t.putBytes(0, header, 0, header.length) + // Write src after the header + t.putByteBuffer(header.length, src, header.length) + + t.getByte(0) shouldBe 0x00 + t.getByte(1) shouldBe 0x01 + t.getByte(2) shouldBe 0x02 + t.getByte(3) shouldBe 0x03 } } + } - "put MessageBuffer on itself" in { - for (t <- buffers) { - val b = Array[Byte](0x02, 0x03) - val srcArray = ByteBuffer.wrap(b) - val srcHeap = ByteBuffer.allocate(b.length) - srcHeap.put(b).flip - val srcOffHeap = ByteBuffer.allocateDirect(b.length) - srcOffHeap.put(b).flip - val builder = Seq.newBuilder[ByteBuffer] - builder ++= Seq(srcArray, srcHeap) - if (!universal) builder += srcOffHeap - - for (src <- builder.result().map(d => MessageBuffer.wrap(d))) { - // Write header bytes - val header = Array[Byte](0x00, 0x01) - t.putBytes(0, header, 0, header.length) - // Write src after the header - t.putMessageBuffer(header.length, src, 0, header.length) - - t.getByte(0) shouldBe 0x00 - t.getByte(1) shouldBe 0x01 - t.getByte(2) shouldBe 0x02 - t.getByte(3) shouldBe 0x03 - } + test("put MessageBuffer on itself") { + for (t <- buffers) { + val b = Array[Byte](0x02, 0x03) + val srcArray = ByteBuffer.wrap(b) + val srcHeap = ByteBuffer.allocate(b.length) + srcHeap.put(b).flip + val srcOffHeap = ByteBuffer.allocateDirect(b.length) + srcOffHeap.put(b).flip + val builder = Seq.newBuilder[ByteBuffer] + builder ++= Seq(srcArray, srcHeap) + if (!universal) builder += srcOffHeap + + for (src <- builder.result().map(d => MessageBuffer.wrap(d))) { + // Write header bytes + val header = Array[Byte](0x00, 0x01) + t.putBytes(0, header, 0, header.length) + // Write src after the header + t.putMessageBuffer(header.length, src, 0, header.length) + + t.getByte(0) shouldBe 0x00 + t.getByte(1) shouldBe 0x01 + t.getByte(2) shouldBe 0x02 + t.getByte(3) shouldBe 0x03 } } + } - "copy sliced buffer" in { - def prepareBytes: Array[Byte] = { - Array[Byte](0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07) - } + test("copy sliced buffer") { + def prepareBytes: Array[Byte] = { + Array[Byte](0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07) + } - def prepareDirectBuffer: ByteBuffer = { - val directBuffer = ByteBuffer.allocateDirect(prepareBytes.length) - directBuffer.put(prepareBytes) - directBuffer.flip - directBuffer - } + def prepareDirectBuffer: ByteBuffer = { + val directBuffer = ByteBuffer.allocateDirect(prepareBytes.length) + directBuffer.put(prepareBytes) + directBuffer.flip + directBuffer + } - def checkSliceAndCopyTo(srcBuffer: MessageBuffer, dstBuffer: MessageBuffer) = { - val sliced = srcBuffer.slice(2, 5) - - sliced.size() shouldBe 5 - sliced.getByte(0) shouldBe 0x02 - sliced.getByte(1) shouldBe 0x03 - sliced.getByte(2) shouldBe 0x04 - sliced.getByte(3) shouldBe 0x05 - sliced.getByte(4) shouldBe 0x06 - - sliced.copyTo(3, dstBuffer, 1, 2) // copy 0x05 and 0x06 to dstBuffer[1] and [2] - - dstBuffer.getByte(0) shouldBe 0x00 - dstBuffer.getByte(1) shouldBe 0x05 // copied by sliced.getByte(3) - dstBuffer.getByte(2) shouldBe 0x06 // copied by sliced.getByte(4) - dstBuffer.getByte(3) shouldBe 0x03 - dstBuffer.getByte(4) shouldBe 0x04 - dstBuffer.getByte(5) shouldBe 0x05 - dstBuffer.getByte(6) shouldBe 0x06 - dstBuffer.getByte(7) shouldBe 0x07 - } + def checkSliceAndCopyTo(srcBuffer: MessageBuffer, dstBuffer: MessageBuffer) = { + val sliced = srcBuffer.slice(2, 5) + + sliced.size() shouldBe 5 + sliced.getByte(0) shouldBe 0x02 + sliced.getByte(1) shouldBe 0x03 + sliced.getByte(2) shouldBe 0x04 + sliced.getByte(3) shouldBe 0x05 + sliced.getByte(4) shouldBe 0x06 + + sliced.copyTo(3, dstBuffer, 1, 2) // copy 0x05 and 0x06 to dstBuffer[1] and [2] + + dstBuffer.getByte(0) shouldBe 0x00 + dstBuffer.getByte(1) shouldBe 0x05 // copied by sliced.getByte(3) + dstBuffer.getByte(2) shouldBe 0x06 // copied by sliced.getByte(4) + dstBuffer.getByte(3) shouldBe 0x03 + dstBuffer.getByte(4) shouldBe 0x04 + dstBuffer.getByte(5) shouldBe 0x05 + dstBuffer.getByte(6) shouldBe 0x06 + dstBuffer.getByte(7) shouldBe 0x07 + } - checkSliceAndCopyTo(MessageBuffer.wrap(prepareBytes), MessageBuffer.wrap(prepareBytes)) - checkSliceAndCopyTo(MessageBuffer.wrap(ByteBuffer.wrap(prepareBytes)), MessageBuffer.wrap(ByteBuffer.wrap(prepareBytes))) - if (!universal) { - checkSliceAndCopyTo(MessageBuffer.wrap(prepareDirectBuffer), MessageBuffer.wrap(prepareDirectBuffer)) - } + checkSliceAndCopyTo(MessageBuffer.wrap(prepareBytes), MessageBuffer.wrap(prepareBytes)) + checkSliceAndCopyTo(MessageBuffer.wrap(ByteBuffer.wrap(prepareBytes)), MessageBuffer.wrap(ByteBuffer.wrap(prepareBytes))) + if (!universal) { + checkSliceAndCopyTo(MessageBuffer.wrap(prepareDirectBuffer), MessageBuffer.wrap(prepareDirectBuffer)) } } } diff --git a/msgpack-core/src/test/scala/org/msgpack/core/example/MessagePackExampleTest.scala b/msgpack-core/src/test/scala/org/msgpack/core/example/MessagePackExampleTest.scala index cbbfd8751..05dfa6e65 100644 --- a/msgpack-core/src/test/scala/org/msgpack/core/example/MessagePackExampleTest.scala +++ b/msgpack-core/src/test/scala/org/msgpack/core/example/MessagePackExampleTest.scala @@ -15,28 +15,28 @@ // package org.msgpack.core.example -import org.msgpack.core.MessagePackSpec +import wvlet.airspec.AirSpec /** * */ -class MessagePackExampleTest extends MessagePackSpec { +class MessagePackExampleTest extends AirSpec { - "example" should { + test("example") { - "have basic usage" in { + test("have basic usage") { MessagePackExample.basicUsage() } - "have packer usage" in { + test("have packer usage") { MessagePackExample.packer() } - "have file read/write example" in { + test("have file read/write example") { MessagePackExample.readAndWriteFile(); } - "have configuration example" in { + test("have configuration example") { MessagePackExample.configuration(); } } diff --git a/msgpack-core/src/test/scala/org/msgpack/value/RawStringValueImplTest.scala b/msgpack-core/src/test/scala/org/msgpack/value/RawStringValueImplTest.scala index 7de9d6c6f..fb340553c 100644 --- a/msgpack-core/src/test/scala/org/msgpack/value/RawStringValueImplTest.scala +++ b/msgpack-core/src/test/scala/org/msgpack/value/RawStringValueImplTest.scala @@ -15,20 +15,18 @@ // package org.msgpack.value -import org.msgpack.core.MessagePackSpec +import wvlet.airspec.AirSpec -class RawStringValueImplTest extends MessagePackSpec { +class RawStringValueImplTest extends AirSpec { - "StringValue" should { - "return the same hash code if they are equal" in { - val str = "a" - val a1 = ValueFactory.newString(str.getBytes("UTF-8")) - val a2 = ValueFactory.newString(str) + test("return the same hash code if they are equal") { + val str = "a" + val a1 = ValueFactory.newString(str.getBytes("UTF-8")) + val a2 = ValueFactory.newString(str) - a1.shouldEqual(a2) - a1.hashCode.shouldEqual(a2.hashCode) - a2.shouldEqual(a1) - a2.hashCode.shouldEqual(a1.hashCode) - } + a1 shouldBe a2 + a1.hashCode shouldBe a2.hashCode + a2 shouldBe a1 + a2.hashCode shouldBe a1.hashCode } } diff --git a/msgpack-core/src/test/scala/org/msgpack/value/ValueFactoryTest.scala b/msgpack-core/src/test/scala/org/msgpack/value/ValueFactoryTest.scala index c9bc8f8f0..b667ddfcf 100644 --- a/msgpack-core/src/test/scala/org/msgpack/value/ValueFactoryTest.scala +++ b/msgpack-core/src/test/scala/org/msgpack/value/ValueFactoryTest.scala @@ -15,27 +15,29 @@ // package org.msgpack.value -import org.msgpack.core.MessagePackSpec -import org.scalacheck.Prop.forAll +import org.scalacheck.Gen +import wvlet.airspec.AirSpec +import wvlet.airspec.spi.PropertyCheck /** * */ -class ValueFactoryTest extends MessagePackSpec { +class ValueFactoryTest extends AirSpec with PropertyCheck { - def isValid(v: Value, - expected: ValueType, - isNil: Boolean = false, - isBoolean: Boolean = false, - isInteger: Boolean = false, - isString: Boolean = false, - isFloat: Boolean = false, - isBinary: Boolean = false, - isArray: Boolean = false, - isMap: Boolean = false, - isExtension: Boolean = false, - isRaw: Boolean = false, - isNumber: Boolean = false): Boolean = { + private def isValid(v: Value, + expected: ValueType, + isNil: Boolean = false, + isBoolean: Boolean = false, + isInteger: Boolean = false, + isString: Boolean = false, + isFloat: Boolean = false, + isBinary: Boolean = false, + isArray: Boolean = false, + isMap: Boolean = false, + isExtension: Boolean = false, + isRaw: Boolean = false, + isNumber: Boolean = false, + isTimestamp: Boolean = false): Boolean = { v.isNilValue shouldBe isNil v.isBooleanValue shouldBe isBoolean v.isIntegerValue shouldBe isInteger @@ -47,33 +49,70 @@ class ValueFactoryTest extends MessagePackSpec { v.isExtensionValue shouldBe isExtension v.isRawValue shouldBe isRaw v.isNumberValue shouldBe isNumber + v.isTimestampValue shouldBe isTimestamp true } - "ValueFactory" should { - - "create valid type values" in { + test("ValueFactory") { + test("nil") { isValid(ValueFactory.newNil(), expected = ValueType.NIL, isNil = true) + } + + test("boolean") { forAll { (v: Boolean) => isValid(ValueFactory.newBoolean(v), expected = ValueType.BOOLEAN, isBoolean = true) } + } + + test("int") { forAll { (v: Int) => isValid(ValueFactory.newInteger(v), expected = ValueType.INTEGER, isInteger = true, isNumber = true) } + } + + test("float") { forAll { (v: Float) => isValid(ValueFactory.newFloat(v), expected = ValueType.FLOAT, isFloat = true, isNumber = true) } + } + test("string") { forAll { (v: String) => isValid(ValueFactory.newString(v), expected = ValueType.STRING, isString = true, isRaw = true) } + } + + test("array") { forAll { (v: Array[Byte]) => isValid(ValueFactory.newBinary(v), expected = ValueType.BINARY, isBinary = true, isRaw = true) } + } + + test("empty array") { isValid(ValueFactory.emptyArray(), expected = ValueType.ARRAY, isArray = true) + } + + test("empty map") { isValid(ValueFactory.emptyMap(), expected = ValueType.MAP, isMap = true) + } + + test("ext") { forAll { (v: Array[Byte]) => isValid(ValueFactory.newExtension(0, v), expected = ValueType.EXTENSION, isExtension = true, isRaw = false) } } + + test("timestamp") { + forAll { (millis: Long) => + isValid(ValueFactory.newTimestamp(millis), expected = ValueType.EXTENSION, isExtension = true, isTimestamp = true) + } + } + + test("timestamp sec/nano") { + val posLong = Gen.chooseNum[Long](-31557014167219200L, 31556889864403199L) + val posInt = Gen.chooseNum(0, 1000000000 - 1) // NANOS_PER_SECOND + forAll(posLong, posInt) { (sec: Long, nano: Int) => + isValid(ValueFactory.newTimestamp(sec, nano), expected = ValueType.EXTENSION, isExtension = true, isTimestamp = true) + } + } } } diff --git a/msgpack-core/src/test/scala/org/msgpack/value/ValueTest.scala b/msgpack-core/src/test/scala/org/msgpack/value/ValueTest.scala index eeb663bc6..83cbde6bf 100644 --- a/msgpack-core/src/test/scala/org/msgpack/value/ValueTest.scala +++ b/msgpack-core/src/test/scala/org/msgpack/value/ValueTest.scala @@ -15,31 +15,34 @@ // package org.msgpack.value +import org.msgpack.core.MessagePackSpec.createMessagePackData + import java.math.BigInteger import org.msgpack.core._ -import org.scalacheck.Prop.{forAll, propBoolean} - -import scala.util.parsing.json.JSON +import org.scalacheck.Prop.propBoolean +import wvlet.airframe.json.JSON +import wvlet.airspec.AirSpec +import wvlet.airspec.spi.PropertyCheck -class ValueTest extends MessagePackSpec { - def checkSuccinctType(pack: MessagePacker => Unit, expectedAtMost: MessageFormat): Boolean = { +class ValueTest extends AirSpec with PropertyCheck { + private def checkSuccinctType(pack: MessagePacker => Unit, expectedAtMost: MessageFormat): Boolean = { val b = createMessagePackData(pack) val v1 = MessagePack.newDefaultUnpacker(b).unpackValue() val mf = v1.asIntegerValue().mostSuccinctMessageFormat() mf.getValueType shouldBe ValueType.INTEGER - mf.ordinal() shouldBe <=(expectedAtMost.ordinal()) + mf.ordinal() <= expectedAtMost.ordinal() shouldBe true val v2 = new Variable MessagePack.newDefaultUnpacker(b).unpackValue(v2) val mf2 = v2.asIntegerValue().mostSuccinctMessageFormat() mf2.getValueType shouldBe ValueType.INTEGER - mf2.ordinal() shouldBe <=(expectedAtMost.ordinal()) + mf2.ordinal() <= expectedAtMost.ordinal() shouldBe true true } - "Value" should { - "tell most succinct integer type" in { + test("Value") { + test("tell most succinct integer type") { forAll { (v: Byte) => checkSuccinctType(_.packByte(v), MessageFormat.INT8) } @@ -63,7 +66,7 @@ class ValueTest extends MessagePackSpec { } } - "produce json strings" in { + test("produce json strings") { import ValueFactory._ @@ -94,9 +97,9 @@ class ValueTest extends MessagePackSpec { .put(newString("address"), newArray(newString("xxx-xxxx"), newString("yyy-yyyy"))) .put(newString("name"), newString("mitsu")) .build() - val i1 = JSON.parseFull(m.toJson) - val i2 = JSON.parseFull(m.toString) // expect json value - val a1 = JSON.parseFull("""{"id":1001,"name":"mitsu","address":["xxx-xxxx","yyy-yyyy"]}""") + val i1 = JSON.parse(m.toJson) + val i2 = JSON.parse(m.toString) // expect json value + val a1 = JSON.parse("""{"id":1001,"name":"mitsu","address":["xxx-xxxx","yyy-yyyy"]}""") // Equals as JSON map i1 shouldBe a1 i2 shouldBe a1 @@ -108,7 +111,7 @@ class ValueTest extends MessagePackSpec { } - "check appropriate range for integers" in { + test("check appropriate range for integers") { import ValueFactory._ import java.lang.Byte import java.lang.Short diff --git a/msgpack-core/src/test/scala/org/msgpack/value/ValueTypeTest.scala b/msgpack-core/src/test/scala/org/msgpack/value/ValueTypeTest.scala index 445eda32c..4627faba4 100644 --- a/msgpack-core/src/test/scala/org/msgpack/value/ValueTypeTest.scala +++ b/msgpack-core/src/test/scala/org/msgpack/value/ValueTypeTest.scala @@ -16,73 +16,70 @@ package org.msgpack.value import org.msgpack.core.MessagePack.Code._ -import org.msgpack.core.{MessageFormat, MessageFormatException, MessagePackSpec} +import org.msgpack.core.{MessageFormat, MessageFormatException} +import wvlet.airspec.AirSpec /** * Created on 2014/05/06. */ -class ValueTypeTest extends MessagePackSpec { +class ValueTypeTest extends AirSpec { - "ValueType" should { - - "lookup ValueType from a byte value" taggedAs ("code") in { - - def check(b: Byte, tpe: ValueType) { - MessageFormat.valueOf(b).getValueType shouldBe tpe - } + test("lookup ValueType from a byte value") { + def check(b: Byte, tpe: ValueType) { + MessageFormat.valueOf(b).getValueType shouldBe tpe + } - for (i <- 0 until 0x7f) { - check(i.toByte, ValueType.INTEGER) - } + for (i <- 0 until 0x7f) { + check(i.toByte, ValueType.INTEGER) + } - for (i <- 0x80 until 0x8f) { - check(i.toByte, ValueType.MAP) - } + for (i <- 0x80 until 0x8f) { + check(i.toByte, ValueType.MAP) + } - for (i <- 0x90 until 0x9f) { - check(i.toByte, ValueType.ARRAY) - } + for (i <- 0x90 until 0x9f) { + check(i.toByte, ValueType.ARRAY) + } - check(NIL, ValueType.NIL) + check(NIL, ValueType.NIL) - try { - MessageFormat.valueOf(NEVER_USED).getValueType - fail("NEVER_USED type should not have ValueType") - } catch { - case e: MessageFormatException => - // OK - } + try { + MessageFormat.valueOf(NEVER_USED).getValueType + fail("NEVER_USED type should not have ValueType") + } catch { + case e: MessageFormatException => + // OK + } - check(TRUE, ValueType.BOOLEAN) - check(FALSE, ValueType.BOOLEAN) + check(TRUE, ValueType.BOOLEAN) + check(FALSE, ValueType.BOOLEAN) - for (t <- Seq(BIN8, BIN16, BIN32)) { - check(t, ValueType.BINARY) - } + for (t <- Seq(BIN8, BIN16, BIN32)) { + check(t, ValueType.BINARY) + } - for (t <- Seq(FIXEXT1, FIXEXT2, FIXEXT4, FIXEXT8, FIXEXT16, EXT8, EXT16, EXT32)) { - check(t, ValueType.EXTENSION) - } + for (t <- Seq(FIXEXT1, FIXEXT2, FIXEXT4, FIXEXT8, FIXEXT16, EXT8, EXT16, EXT32)) { + check(t, ValueType.EXTENSION) + } - for (t <- Seq(INT8, INT16, INT32, INT64, UINT8, UINT16, UINT32, UINT64)) { - check(t, ValueType.INTEGER) - } + for (t <- Seq(INT8, INT16, INT32, INT64, UINT8, UINT16, UINT32, UINT64)) { + check(t, ValueType.INTEGER) + } - for (t <- Seq(STR8, STR16, STR32)) { - check(t, ValueType.STRING) - } + for (t <- Seq(STR8, STR16, STR32)) { + check(t, ValueType.STRING) + } - for (t <- Seq(FLOAT32, FLOAT64)) { - check(t, ValueType.FLOAT) - } + for (t <- Seq(FLOAT32, FLOAT64)) { + check(t, ValueType.FLOAT) + } - for (t <- Seq(ARRAY16, ARRAY32)) { - check(t, ValueType.ARRAY) - } + for (t <- Seq(ARRAY16, ARRAY32)) { + check(t, ValueType.ARRAY) + } - for (i <- 0xe0 until 0xff) { - check(i.toByte, ValueType.INTEGER) - } + for (i <- 0xe0 until 0xff) { + check(i.toByte, ValueType.INTEGER) } } } diff --git a/msgpack-core/src/test/scala/org/msgpack/value/VariableTest.scala b/msgpack-core/src/test/scala/org/msgpack/value/VariableTest.scala new file mode 100644 index 000000000..67ea2efef --- /dev/null +++ b/msgpack-core/src/test/scala/org/msgpack/value/VariableTest.scala @@ -0,0 +1,310 @@ +// +// MessagePack for Java +// +// 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 org.msgpack.value + +import org.msgpack.core.{MessagePack, MessagePacker, MessageTypeCastException} +import wvlet.airspec.AirSpec +import wvlet.airspec.spi.PropertyCheck + +import java.time.Instant +import java.util +import scala.jdk.CollectionConverters._ + +/** + * + */ +class VariableTest extends AirSpec with PropertyCheck { + private def check(pack: MessagePacker => Unit, checker: Variable => Unit): Unit = { + val packer = MessagePack.newDefaultBufferPacker() + pack(packer) + val msgpack = packer.toByteArray + packer.close() + val v = new Variable() + val unpacker = MessagePack.newDefaultUnpacker(msgpack) + unpacker.unpackValue(v) + checker(v) + unpacker.close() + } + + /** + * Test Value -> MsgPack -> Value + */ + private def roundTrip(v: Value): Unit = { + val packer = MessagePack.newDefaultBufferPacker() + v.writeTo(packer) + val msgpack = packer.toByteArray + val unpacker = MessagePack.newDefaultUnpacker(msgpack) + val v1 = unpacker.unpackValue() + unpacker.close() + v shouldBe v1 + v.immutableValue() shouldBe v1 + } + + private def validateValue[V <: Value]( + v: V, + asNil: Boolean = false, + asBoolean: Boolean = false, + asInteger: Boolean = false, + asFloat: Boolean = false, + asBinary: Boolean = false, + asString: Boolean = false, + asArray: Boolean = false, + asMap: Boolean = false, + asExtension: Boolean = false, + asTimestamp: Boolean = false + ): V = { + v.isNilValue shouldBe asNil + v.isBooleanValue shouldBe asBoolean + v.isIntegerValue shouldBe asInteger + v.isNumberValue shouldBe asInteger | asFloat + v.isFloatValue shouldBe asFloat + v.isRawValue shouldBe asBinary | asString + v.isBinaryValue shouldBe asBinary + v.isStringValue shouldBe asString + v.isArrayValue shouldBe asArray + v.isMapValue shouldBe asMap + v.isExtensionValue shouldBe asExtension | asTimestamp + v.isTimestampValue shouldBe asTimestamp + + if (asNil) { + v.getValueType shouldBe ValueType.NIL + roundTrip(v) + } else { + intercept[MessageTypeCastException] { + v.asNilValue() + } + } + + if (asBoolean) { + v.getValueType shouldBe ValueType.BOOLEAN + roundTrip(v) + } else { + intercept[MessageTypeCastException] { + v.asBooleanValue() + } + } + + if (asInteger) { + v.getValueType shouldBe ValueType.INTEGER + roundTrip(v) + } else { + intercept[MessageTypeCastException] { + v.asIntegerValue() + } + } + + if (asFloat) { + v.getValueType shouldBe ValueType.FLOAT + roundTrip(v) + } else { + intercept[MessageTypeCastException] { + v.asFloatValue() + } + } + + if (asBinary | asString) { + v.asRawValue() + roundTrip(v) + } else { + intercept[MessageTypeCastException] { + v.asRawValue() + } + } + + if (asBinary) { + v.getValueType shouldBe ValueType.BINARY + roundTrip(v) + } else { + intercept[MessageTypeCastException] { + v.asBinaryValue() + } + } + + if (asString) { + v.getValueType shouldBe ValueType.STRING + roundTrip(v) + } else { + intercept[MessageTypeCastException] { + v.asStringValue() + } + } + + if (asArray) { + v.getValueType shouldBe ValueType.ARRAY + roundTrip(v) + } else { + intercept[MessageTypeCastException] { + v.asArrayValue() + } + } + + if (asMap) { + v.getValueType shouldBe ValueType.MAP + roundTrip(v) + } else { + intercept[MessageTypeCastException] { + v.asMapValue() + } + } + + if (asExtension) { + v.getValueType shouldBe ValueType.EXTENSION + roundTrip(v) + } else { + intercept[MessageTypeCastException] { + v.asExtensionValue() + } + } + + if (asTimestamp) { + v.getValueType shouldBe ValueType.EXTENSION + roundTrip(v) + } else { + intercept[MessageTypeCastException] { + v.asTimestampValue() + } + } + + v + } + + test("Variable") { + test("read nil") { + check( + _.packNil, + checker = { v => + val iv = validateValue(v.asNilValue(), asNil = true) + iv.toJson shouldBe "null" + } + ) + } + + test("read integers") { + forAll { i: Int => + check( + _.packInt(i), + checker = { v => + val iv = validateValue(v.asIntegerValue(), asInteger = true) + iv.asInt() shouldBe i + iv.asLong() shouldBe i.toLong + } + ) + } + } + + test("read double") { + forAll { x: Double => + check( + _.packDouble(x), + checker = { v => + val iv = validateValue(v.asFloatValue(), asFloat = true) + //iv.toDouble shouldBe v + //iv.toFloat shouldBe x.toFloat + } + ) + } + } + + test("read boolean") { + forAll { x: Boolean => + check( + _.packBoolean(x), + checker = { v => + val iv = validateValue(v.asBooleanValue(), asBoolean = true) + iv.getBoolean shouldBe x + } + ) + } + } + + test("read binary") { + forAll { x: Array[Byte] => + check( + { packer => + packer.packBinaryHeader(x.length); packer.addPayload(x) + }, + checker = { v => + val iv = validateValue(v.asBinaryValue(), asBinary = true) + util.Arrays.equals(iv.asByteArray(), x) + } + ) + } + } + + test("read string") { + forAll { x: String => + check( + _.packString(x), + checker = { v => + val iv = validateValue(v.asStringValue(), asString = true) + iv.asString() shouldBe x + } + ) + } + } + + test("read array") { + forAll { x: Seq[Int] => + check( + { packer => + packer.packArrayHeader(x.size) + x.foreach { packer.packInt(_) } + }, + checker = { v => + val iv = validateValue(v.asArrayValue(), asArray = true) + val lst = iv.list().asScala.map(_.asIntegerValue().toInt) + lst shouldBe x + } + ) + } + } + + test("read map") { + forAll { x: Seq[Int] => + // Generate map with unique keys + val map = x.zipWithIndex.map { case (x, i) => (s"key-${i}", x) } + check( + { packer => + packer.packMapHeader(map.size) + map.foreach { x => + packer.packString(x._1) + packer.packInt(x._2) + } + }, + checker = { v => + val iv = validateValue(v.asMapValue(), asMap = true) + val lst = iv.map().asScala.map(p => (p._1.asStringValue().asString(), p._2.asIntegerValue().asInt())).toSeq + lst.sortBy(_._1) shouldBe map.sortBy(_._1) + } + ) + } + } + + test("read timestamps") { + forAll { millis: Long => + val i = Instant.ofEpochMilli(millis) + check( + _.packTimestamp(i), + checker = { v => + val ts = validateValue(v.asTimestampValue(), asTimestamp = true) + ts.isTimestampValue shouldBe true + ts.toInstant shouldBe i + } + ) + } + } + } +}