From dc294f24d277efd938acb563a265779d4365cf14 Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Fri, 23 Dec 2022 20:28:37 +0100 Subject: [PATCH] Protobuf performance fix (#473) * Fix protobuf performance issue * Improve protobuf varint encoding performance * ScalaFix * Restricting zoned date time protobuf encoding test to fix JDK 1.8 issue * Another attempt to fix JDK 8 * ScalaFix * Fix test on JDK8 --- .../zio/schema/codec/ProtobufBenchmarks.scala | 36 +- .../zio/schema/codec/ProtobufCodec.scala | 69 +++- .../zio/schema/codec/ProtobufCodecSpec.scala | 368 ++++++++++-------- 3 files changed, 291 insertions(+), 182 deletions(-) diff --git a/benchmarks/src/main/scala/zio/schema/codec/ProtobufBenchmarks.scala b/benchmarks/src/main/scala/zio/schema/codec/ProtobufBenchmarks.scala index ec6dec66d..da14574f1 100644 --- a/benchmarks/src/main/scala/zio/schema/codec/ProtobufBenchmarks.scala +++ b/benchmarks/src/main/scala/zio/schema/codec/ProtobufBenchmarks.scala @@ -1,8 +1,10 @@ package zio.schema.codec import org.openjdk.jmh.annotations._ +import zio.Chunk import java.util.concurrent.TimeUnit +import scala.util.Random @State(Scope.Thread) @BenchmarkMode(Array(Mode.Throughput)) @@ -17,12 +19,24 @@ class ProtobufBenchmarks { @Param(Array("1000")) var size: Int = _ + @Param(Array("30721")) + var bigSize: Int = _ + var outputs: Array[Any] = _ + var bigByteChunk: Chunk[Byte] = _ + var encodedBigByteChunk: Chunk[Byte] = _ + var byteChunkCodec: BinaryCodec[Chunk[Byte]] = _ + @Setup - def allocateOutputs(): Unit = + def setup(): Unit = { outputs = Array.ofDim[Any](size) + byteChunkCodec = ProtobufCodec.protobufCodec[Chunk[Byte]] + bigByteChunk = Chunk.fromArray(Random.nextBytes(bigSize)) + encodedBigByteChunk = byteChunkCodec.encode(bigByteChunk) + } + @Benchmark def enumEncoding(): Array[Any] = { for (i <- 0 until size) { @@ -39,4 +53,24 @@ class ProtobufBenchmarks { outputs } + @Benchmark + def encodeLargeByteChunk(): Chunk[Byte] = + byteChunkCodec.encode(bigByteChunk) + + @Benchmark + def decodeLargeByteChunk(): Either[DecodeError, Chunk[Byte]] = + byteChunkCodec.decode(encodedBigByteChunk) } + +object ProtobufBenchmarksProfiling extends App { + + val bigSize = 30721 + val byteChunkCodec = ProtobufCodec.protobufCodec[Chunk[Byte]] + val bigByteChunk = Chunk.fromArray(Random.nextBytes(bigSize)) + val encodedBigByteChunk = byteChunkCodec.encode(bigByteChunk) + + while(true) { + val decoded = byteChunkCodec.decode(encodedBigByteChunk) + println(s"Decoded ${decoded.map(_.length)} bytes") + } +} \ No newline at end of file diff --git a/zio-schema-protobuf/shared/src/main/scala/zio/schema/codec/ProtobufCodec.scala b/zio-schema-protobuf/shared/src/main/scala/zio/schema/codec/ProtobufCodec.scala index 96d4a4c58..656c3ad85 100644 --- a/zio-schema-protobuf/shared/src/main/scala/zio/schema/codec/ProtobufCodec.scala +++ b/zio-schema-protobuf/shared/src/main/scala/zio/schema/codec/ProtobufCodec.scala @@ -3,6 +3,7 @@ package zio.schema.codec import java.nio.charset.StandardCharsets import java.nio.{ ByteBuffer, ByteOrder } import java.time._ +import java.time.format.DateTimeFormatter import java.util.UUID import scala.collection.immutable.ListMap @@ -13,7 +14,7 @@ import zio.schema._ import zio.schema.codec.DecodeError.{ ExtraFields, MalformedField, MissingField } import zio.schema.codec.ProtobufCodec.Protobuf.WireType.LengthDelimited import zio.stream.ZPipeline -import zio.{ Cause, Chunk, Unsafe, ZIO } +import zio.{ Cause, Chunk, ChunkBuilder, Unsafe, ZIO } object ProtobufCodec { @@ -391,22 +392,37 @@ object ProtobufCodec { case (StandardType.OffsetDateTimeType, v: OffsetDateTime) => encodePrimitive(fieldNumber, StandardType.StringType, v.toString) case (StandardType.ZonedDateTimeType, v: ZonedDateTime) => - encodePrimitive(fieldNumber, StandardType.StringType, v.toString) + encodePrimitive(fieldNumber, StandardType.StringType, v.format(DateTimeFormatter.ISO_ZONED_DATE_TIME)) case (_, _) => throw new NotImplementedError(s"No encoder for $standardType") } - private def encodeVarInt(value: Int): Chunk[Byte] = - encodeVarInt(value.toLong) + private def encodeVarInt(value: Int): Chunk[Byte] = { + val builder = ChunkBuilder.make[Byte](5) + encodeVarInt(value.toLong, builder) + builder.result() + } private def encodeVarInt(value: Long): Chunk[Byte] = { - val base128 = value & 0x7F - val higherBits = value >>> 7 + val builder = ChunkBuilder.make[Byte](10) + encodeVarInt(value, builder) + builder.result() + } - if (higherBits != 0x00) { - (0x80 | base128).byteValue() +: encodeVarInt(higherBits) - } else { - Chunk(base128.byteValue()) + private def encodeVarInt(value: Long, builder: ChunkBuilder[Byte]): Unit = { + var current = value + var higherBits = current >>> 7 + var done = false + + while (!done) { + if (higherBits != 0x00) { + builder += (0x80 | (current & 0x7F)).byteValue() + current = higherBits + higherBits = higherBits >>> 7 + } else { + builder += (current & 0x7F).byteValue() + done = true + } } } @@ -445,6 +461,8 @@ object ProtobufCodec { def peek(context: DecoderContext): Chunk[Byte] = chunk.slice(position, position + length(context)) + def peek: Byte = chunk.byte(position) + def move(count: Int): Unit = position += count @@ -929,28 +947,41 @@ object ProtobufCodec { /** * Decodes bytes to following types: int32, int64, uint32, uint64, sint32, sint64, bool, enumN. * Takes index of first byte which is inside 0 - 127 range. - * Returns remainder of the bytes together with computed value. * * (0 -> 127) & 0x80 => 0, (128 -> 255) & 0x80 => 128 * (0 << 7 => 0, 1 << 7 => 128, 2 << 7 => 256, 3 << 7 => 384 * 1 & 0X7F => 1, 127 & 0x7F => 127, 128 & 0x7F => 0, 129 & 0x7F => 1 */ - private def varIntDecoder(context: DecoderContext): Long = - if (state.length(context) == 0) { + private def varIntDecoder(context: DecoderContext): Long = { + val maxLength = state.length(context) + if (maxLength == 0) { throw MalformedField(Schema.primitive[Long], "Failed to decode VarInt. Unexpected end of chunk") } else { - val chunk = state.peek(context) - val length = chunk.indexWhere(octet => (octet.longValue() & 0x80) != 0x80) + 1 - if (length <= 0) { + var count = 0 + var done = false + var result = 0L + while (count < maxLength && !done) { + val byte = state.peek + result = result | (byte & 0x7f).toLong << (count * 7) + + state.move(1) + if ((byte & 0x80) == 0) { + done = true + } else { + count += 1 + } + } + + if (!done) { throw MalformedField( Schema.primitive[Long], "Failed to decode VarInt. No byte within the range 0 - 127 are present" ) - } else { - state.move(length) - chunk.take(length).foldRight(0L)((octet, v) => (v << 7) + (octet & 0x7F)) } + + result } + } private def binaryDecoder(context: DecoderContext): Chunk[Byte] = state.all(context) diff --git a/zio-schema-protobuf/shared/src/test/scala-2/zio/schema/codec/ProtobufCodecSpec.scala b/zio-schema-protobuf/shared/src/test/scala-2/zio/schema/codec/ProtobufCodecSpec.scala index 958a8f85f..1cbf86be8 100644 --- a/zio-schema-protobuf/shared/src/test/scala-2/zio/schema/codec/ProtobufCodecSpec.scala +++ b/zio-schema-protobuf/shared/src/test/scala-2/zio/schema/codec/ProtobufCodecSpec.scala @@ -15,7 +15,6 @@ import zio.stream.{ ZSink, ZStream } import zio.test.Assertion._ import zio.test._ -// TODO: use generators instead of manual encode/decode object ProtobufCodecSpec extends ZIOSpecDefault { import Schema._ @@ -119,19 +118,25 @@ object ProtobufCodecSpec extends ZIOSpecDefault { } yield assert(ed)(equalTo(HighArity())) }, test("integer") { - for { - ed2 <- encodeAndDecodeNS(schemaBasicInt, BasicInt(150)) - } yield assert(ed2)(equalTo(BasicInt(150))) + check(Gen.int) { value => + for { + ed2 <- encodeAndDecodeNS(schemaBasicInt, BasicInt(value)) + } yield assert(ed2)(equalTo(BasicInt(value))) + } }, test("integer inside wrapper class") { - for { - ed2 <- encodeAndDecodeNS(basicIntWrapperSchema, BasicIntWrapper(BasicInt(150))) - } yield assert(ed2)(equalTo(BasicIntWrapper(BasicInt(150)))) + check(Gen.int) { value => + for { + ed2 <- encodeAndDecodeNS(basicIntWrapperSchema, BasicIntWrapper(BasicInt(value))) + } yield assert(ed2)(equalTo(BasicIntWrapper(BasicInt(value)))) + } }, test("string") { - for { - ed2 <- encodeAndDecodeNS(Schema[String], "hello world") - } yield assert(ed2)(equalTo("hello world")) + check(Gen.string) { value => + for { + ed2 <- encodeAndDecodeNS(Schema[String], value) + } yield assert(ed2)(equalTo(value)) + } }, test("empty string") { for { @@ -152,19 +157,28 @@ object ProtobufCodecSpec extends ZIOSpecDefault { } yield assert(ed2)(equalTo(DynamicValue.Primitive("", StandardType.StringType))) }, test("two integers") { - for { - ed2 <- encodeAndDecodeNS(schemaBasicTwoInts, BasicTwoInts(150, 151)) - } yield assert(ed2)(equalTo(BasicTwoInts(150, 151))) + check(Gen.int, Gen.int) { + case (a, b) => + for { + ed2 <- encodeAndDecodeNS(schemaBasicTwoInts, BasicTwoInts(a, b)) + } yield assert(ed2)(equalTo(BasicTwoInts(a, b))) + } }, test("two integers inside wrapper class") { - for { - ed2 <- encodeAndDecodeNS(basicTwoIntWrapperSchema, BasicTwoIntWrapper(BasicTwoInts(150, 151))) - } yield assert(ed2)(equalTo(BasicTwoIntWrapper(BasicTwoInts(150, 151)))) + check(Gen.int, Gen.int) { + case (a, b) => + for { + ed2 <- encodeAndDecodeNS(basicTwoIntWrapperSchema, BasicTwoIntWrapper(BasicTwoInts(a, b))) + } yield assert(ed2)(equalTo(BasicTwoIntWrapper(BasicTwoInts(a, b)))) + } }, test("two wrapped integers inside wrapper class") { - for { - e2 <- encodeAndDecodeNS(separateWrapper, SeparateWrapper(BasicInt(150), BasicInt(151))) - } yield assert(e2)(equalTo(SeparateWrapper(BasicInt(150), BasicInt(151)))) + check(Gen.int, Gen.int) { + case (a, b) => + for { + e2 <- encodeAndDecodeNS(separateWrapper, SeparateWrapper(BasicInt(a), BasicInt(b))) + } yield assert(e2)(equalTo(SeparateWrapper(BasicInt(a), BasicInt(b)))) + } }, test("complex product and string and integer") { for { @@ -172,187 +186,213 @@ object ProtobufCodecSpec extends ZIOSpecDefault { } yield assert(ed2)(equalTo(message)) }, test("booleans") { - val value = true - for { - ed <- encodeAndDecode(Schema[Boolean], value) - ed2 <- encodeAndDecodeNS(Schema[Boolean], value) - } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + check(Gen.boolean) { value => + for { + ed <- encodeAndDecode(Schema[Boolean], value) + ed2 <- encodeAndDecodeNS(Schema[Boolean], value) + } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + } }, test("shorts") { - val value = 5.toShort - for { - ed <- encodeAndDecode(Schema[Short], value) - ed2 <- encodeAndDecodeNS(Schema[Short], value) - } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + check(Gen.short) { value => + for { + ed <- encodeAndDecode(Schema[Short], value) + ed2 <- encodeAndDecodeNS(Schema[Short], value) + } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + } }, test("longs") { - val value = 1000L - for { - ed <- encodeAndDecode(Schema[Long], value) - ed2 <- encodeAndDecodeNS(Schema[Long], value) - } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + check(Gen.long) { value => + for { + ed <- encodeAndDecode(Schema[Long], value) + ed2 <- encodeAndDecodeNS(Schema[Long], value) + } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + } }, test("floats") { - val value = 0.001f - for { - ed <- encodeAndDecode(Schema[Float], value) - ed2 <- encodeAndDecodeNS(Schema[Float], value) - } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + check(Gen.float) { value => + for { + ed <- encodeAndDecode(Schema[Float], value) + ed2 <- encodeAndDecodeNS(Schema[Float], value) + } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + } }, test("doubles") { - val value = 0.001 - for { - ed <- encodeAndDecode(Schema[Double], value) - ed2 <- encodeAndDecodeNS(Schema[Double], value) - } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + check(Gen.double) { value => + for { + ed <- encodeAndDecode(Schema[Double], value) + ed2 <- encodeAndDecodeNS(Schema[Double], value) + } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + } }, test("bytes") { - val value = Chunk.fromArray("some bytes".getBytes) - for { - ed <- encodeAndDecode(Schema[Chunk[Byte]], value) - ed2 <- encodeAndDecodeNS(Schema[Chunk[Byte]], value) - } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + check(Gen.chunkOf(Gen.byte)) { value => + for { + ed <- encodeAndDecode(Schema[Chunk[Byte]], value) + ed2 <- encodeAndDecodeNS(Schema[Chunk[Byte]], value) + } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + } }, test("chars") { - val value = 'c' - for { - ed <- encodeAndDecode(Schema[Char], value) - ed2 <- encodeAndDecodeNS(Schema[Char], value) - } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + check(Gen.printableChar) { value => + for { + ed <- encodeAndDecode(Schema[Char], value) + ed2 <- encodeAndDecodeNS(Schema[Char], value) + } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + } }, test("uuids") { - val value = UUID.randomUUID - for { - ed <- encodeAndDecode(Schema[UUID], value) - ed2 <- encodeAndDecodeNS(Schema[UUID], value) - } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + check(Gen.uuid) { value => + for { + ed <- encodeAndDecode(Schema[UUID], value) + ed2 <- encodeAndDecodeNS(Schema[UUID], value) + } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + } }, test("day of weeks") { - val value = DayOfWeek.of(3) - for { - ed <- encodeAndDecode(Schema[DayOfWeek], value) - ed2 <- encodeAndDecodeNS(Schema[DayOfWeek], value) - } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + check(Gen.dayOfWeek) { value => + for { + ed <- encodeAndDecode(Schema[DayOfWeek], value) + ed2 <- encodeAndDecodeNS(Schema[DayOfWeek], value) + } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + } }, test("months") { - val value = Month.of(3) - for { - ed <- encodeAndDecode(Schema[Month], value) - ed2 <- encodeAndDecodeNS(Schema[Month], value) - } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + check(Gen.month) { value => + for { + ed <- encodeAndDecode(Schema[Month], value) + ed2 <- encodeAndDecodeNS(Schema[Month], value) + } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + } }, test("month days") { - val value = MonthDay.of(1, 31) - for { - ed <- encodeAndDecode(Schema[MonthDay], value) - ed2 <- encodeAndDecodeNS(Schema[MonthDay], value) - } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + check(Gen.monthDay) { value => + for { + ed <- encodeAndDecode(Schema[MonthDay], value) + ed2 <- encodeAndDecodeNS(Schema[MonthDay], value) + } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + } }, test("periods") { - val value = Period.of(5, 3, 1) - for { - ed <- encodeAndDecode(Schema[Period], value) - ed2 <- encodeAndDecodeNS(Schema[Period], value) - } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + check(Gen.period) { value => + for { + ed <- encodeAndDecode(Schema[Period], value) + ed2 <- encodeAndDecodeNS(Schema[Period], value) + } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + } }, test("years") { - val value = Year.of(2020) - for { - ed <- encodeAndDecode(Schema[Year], value) - ed2 <- encodeAndDecodeNS(Schema[Year], value) - } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + check(Gen.year) { value => + for { + ed <- encodeAndDecode(Schema[Year], value) + ed2 <- encodeAndDecodeNS(Schema[Year], value) + } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + } }, test("year months") { - val value = YearMonth.of(2020, 5) - for { - ed <- encodeAndDecode(Schema[YearMonth], value) - ed2 <- encodeAndDecodeNS(Schema[YearMonth], value) - } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + check(Gen.yearMonth) { value => + for { + ed <- encodeAndDecode(Schema[YearMonth], value) + ed2 <- encodeAndDecodeNS(Schema[YearMonth], value) + } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + } }, test("zone ids") { - val value = ZoneId.systemDefault() - for { - ed <- encodeAndDecode(Schema[ZoneId], value) - ed2 <- encodeAndDecodeNS(Schema[ZoneId], value) - } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + check(Gen.zoneId) { value => + for { + ed <- encodeAndDecode(Schema[ZoneId], value) + ed2 <- encodeAndDecodeNS(Schema[ZoneId], value) + } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + } }, test("zone offsets") { - val value = ZoneOffset.ofHours(6) - for { - ed <- encodeAndDecode(Schema[ZoneOffset], value) - ed2 <- encodeAndDecodeNS(Schema[ZoneOffset], value) - } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + check(Gen.zoneOffset) { value => + for { + ed <- encodeAndDecode(Schema[ZoneOffset], value) + ed2 <- encodeAndDecodeNS(Schema[ZoneOffset], value) + } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + } }, test("durations") { - val value = java.time.Duration.ofDays(12) - for { - ed <- encodeAndDecode(Primitive(StandardType.DurationType), value) - ed2 <- encodeAndDecodeNS(Primitive(StandardType.DurationType), value) - } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + check(Gen.finiteDuration) { value => + for { + ed <- encodeAndDecode(Primitive(StandardType.DurationType), value) + ed2 <- encodeAndDecodeNS(Primitive(StandardType.DurationType), value) + } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + } }, test("instants") { - val value = Instant.now() - for { - ed <- encodeAndDecode(Primitive(StandardType.InstantType), value) - ed2 <- encodeAndDecodeNS(Primitive(StandardType.InstantType), value) - } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + check(Gen.instant) { value => + for { + ed <- encodeAndDecode(Primitive(StandardType.InstantType), value) + ed2 <- encodeAndDecodeNS(Primitive(StandardType.InstantType), value) + } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + } }, test("local dates") { - val value = LocalDate.now() - for { - ed <- encodeAndDecode(Primitive(StandardType.LocalDateType), value) - ed2 <- encodeAndDecodeNS(Primitive(StandardType.LocalDateType), value) - } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + check(Gen.localDate) { value => + for { + ed <- encodeAndDecode(Primitive(StandardType.LocalDateType), value) + ed2 <- encodeAndDecodeNS(Primitive(StandardType.LocalDateType), value) + } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + } }, test("local times") { - val value = LocalTime.now() - for { - ed <- encodeAndDecode(Primitive(StandardType.LocalTimeType), value) - ed2 <- encodeAndDecodeNS(Primitive(StandardType.LocalTimeType), value) - } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + check(Gen.localTime) { value => + for { + ed <- encodeAndDecode(Primitive(StandardType.LocalTimeType), value) + ed2 <- encodeAndDecodeNS(Primitive(StandardType.LocalTimeType), value) + } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + } }, test("local date times") { - val value = LocalDateTime.now() - for { - ed <- encodeAndDecode( - Primitive(StandardType.LocalDateTimeType), - value - ) - ed2 <- encodeAndDecodeNS( - Primitive(StandardType.LocalDateTimeType), - value - ) - } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + check(Gen.localDateTime) { value => + for { + ed <- encodeAndDecode( + Primitive(StandardType.LocalDateTimeType), + value + ) + ed2 <- encodeAndDecodeNS( + Primitive(StandardType.LocalDateTimeType), + value + ) + } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + } }, test("offset times") { - val value = OffsetTime.now() - for { - ed <- encodeAndDecode(Primitive(StandardType.OffsetTimeType), value) - ed2 <- encodeAndDecodeNS(Primitive(StandardType.OffsetTimeType), value) - } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + check(Gen.offsetTime) { value => + for { + ed <- encodeAndDecode(Primitive(StandardType.OffsetTimeType), value) + ed2 <- encodeAndDecodeNS(Primitive(StandardType.OffsetTimeType), value) + } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + } }, test("offset date times") { - val value = OffsetDateTime.now() - val offsetDateSchema = Primitive(StandardType.OffsetDateTimeType) - for { - ed <- encodeAndDecode(offsetDateSchema, value) - ed2 <- encodeAndDecodeNS(offsetDateSchema, value) - } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + check(Gen.offsetDateTime) { value => + val offsetDateSchema = Primitive(StandardType.OffsetDateTimeType) + for { + ed <- encodeAndDecode(offsetDateSchema, value) + ed2 <- encodeAndDecodeNS(offsetDateSchema, value) + } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + } }, test("zoned date times") { - val zoneSchema = Primitive(StandardType.ZonedDateTimeType) - val now = ZonedDateTime.now() - for { - ed <- encodeAndDecode(zoneSchema, now) - ed2 <- encodeAndDecodeNS(zoneSchema, now) - } yield assert(ed)(equalTo(Chunk(now))) && assert(ed2)(equalTo(now)) + check(Gen.zonedDateTime.filter(_.getZone != ZoneId.of("GMT0"))) { value => // https://bugs.openjdk.org/browse/JDK-8138664 + val zoneSchema = Primitive(StandardType.ZonedDateTimeType) + for { + ed <- encodeAndDecode(zoneSchema, value) + ed2 <- encodeAndDecodeNS(zoneSchema, value) + } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + } }, test("packed sequences") { - val list = PackedList(List(3, 270, 86942)) - for { - ed <- encodeAndDecode(schemaPackedList, list) - ed2 <- encodeAndDecodeNS(schemaPackedList, list) - } yield assert(ed)(equalTo(Chunk(list))) && assert(ed2)(equalTo(list)) + check(Gen.listOf(Gen.int)) { ints => + val list = PackedList(ints) + for { + ed <- encodeAndDecode(schemaPackedList, list) + ed2 <- encodeAndDecodeNS(schemaPackedList, list) + } yield assert(ed)(equalTo(Chunk(list))) && assert(ed2)(equalTo(list)) + } }, test("empty packed sequence") { val list = PackedList(List.empty) @@ -362,11 +402,13 @@ object ProtobufCodecSpec extends ZIOSpecDefault { } yield assert(ed)(equalTo(Chunk(list))) && assert(ed2)(equalTo(list)) }, test("non-packed sequences") { - val list = UnpackedList(List("foo", "bar", "baz")) - for { - ed <- encodeAndDecode(schemaUnpackedList, list) - ed2 <- encodeAndDecodeNS(schemaUnpackedList, list) - } yield assert(ed)(equalTo(Chunk(list))) && assert(ed2)(equalTo(list)) + check(Gen.listOf(Gen.string)) { strings => + val list = UnpackedList(strings) + for { + ed <- encodeAndDecode(schemaUnpackedList, list) + ed2 <- encodeAndDecodeNS(schemaUnpackedList, list) + } yield assert(ed)(equalTo(Chunk(list))) && assert(ed2)(equalTo(list)) + } }, test("empty non-packed sequence") { val list = UnpackedList(List.empty) @@ -408,11 +450,13 @@ object ProtobufCodecSpec extends ZIOSpecDefault { } yield assert(ed)(equalTo(Chunk(wrapper))) && assert(ed2)(equalTo(oneOf)) }, test("tuples") { - val value = (123, "foo") - for { - ed <- encodeAndDecode(schemaTuple, value) - ed2 <- encodeAndDecodeNS(schemaTuple, value) - } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + check(Gen.int, Gen.string) { (a, b) => + val value = (a, b) + for { + ed <- encodeAndDecode(schemaTuple, value) + ed2 <- encodeAndDecodeNS(schemaTuple, value) + } yield assert(ed)(equalTo(Chunk(value))) && assert(ed2)(equalTo(value)) + } }, test("either left") { val either = Left(9)