| # -*- coding: utf-8 -*- |
| # frozen_string_literal: true |
| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you 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 |
| # |
| # https://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. |
| |
| require 'test_help' |
| require 'memory_profiler' |
| |
| class TestLogicalTypes < Test::Unit::TestCase |
| def test_int_date |
| schema = Avro::Schema.parse <<-SCHEMA |
| { "type": "int", "logicalType": "date" } |
| SCHEMA |
| |
| assert_equal 'date', schema.logical_type |
| today = Date.today |
| assert_encode_and_decode today, schema |
| assert_preencoded Avro::LogicalTypes::IntDate.encode(today), schema, today |
| end |
| |
| def test_int_date_conversion |
| type = Avro::LogicalTypes::IntDate |
| |
| assert_equal 5, type.encode(Date.new(1970, 1, 6)) |
| assert_equal 0, type.encode(Date.new(1970, 1, 1)) |
| assert_equal(-5, type.encode(Date.new(1969, 12, 27))) |
| |
| assert_equal Date.new(1970, 1, 6), type.decode(5) |
| assert_equal Date.new(1970, 1, 1), type.decode(0) |
| assert_equal Date.new(1969, 12, 27), type.decode(-5) |
| end |
| |
| def test_timestamp_millis_long |
| schema = Avro::Schema.parse <<-SCHEMA |
| { "type": "long", "logicalType": "timestamp-millis" } |
| SCHEMA |
| |
| # The Time.at format is (seconds, microseconds) since Epoch. |
| time = Time.at(628232400, 12000) |
| |
| assert_equal 'timestamp-millis', schema.logical_type |
| assert_encode_and_decode time, schema |
| assert_preencoded Avro::LogicalTypes::TimestampMillis.encode(time), schema, time.utc |
| end |
| |
| def test_timestamp_millis_long_conversion |
| type = Avro::LogicalTypes::TimestampMillis |
| |
| now = Time.now.utc |
| now_millis = Time.utc(now.year, now.month, now.day, now.hour, now.min, now.sec, now.usec / 1000 * 1000) |
| |
| assert_equal now_millis, type.decode(type.encode(now_millis)) |
| assert_equal 1432849613221, type.encode(Time.utc(2015, 5, 28, 21, 46, 53, 221000)) |
| assert_equal 1432849613221, type.encode(DateTime.new(2015, 5, 28, 21, 46, 53.221)) |
| assert_equal Time.utc(2015, 5, 28, 21, 46, 53, 221000), type.decode(1432849613221) |
| end |
| |
| def test_timestamp_micros_long |
| schema = Avro::Schema.parse <<-SCHEMA |
| { "type": "long", "logicalType": "timestamp-micros" } |
| SCHEMA |
| |
| # The Time.at format is (seconds, microseconds) since Epoch. |
| time = Time.at(628232400, 12345) |
| |
| assert_equal 'timestamp-micros', schema.logical_type |
| assert_encode_and_decode time, schema |
| assert_preencoded Avro::LogicalTypes::TimestampMicros.encode(time), schema, time.utc |
| end |
| |
| def test_timestamp_micros_long_conversion |
| type = Avro::LogicalTypes::TimestampMicros |
| |
| now = Time.now.utc |
| |
| assert_equal Time.utc(now.year, now.month, now.day, now.hour, now.min, now.sec, now.usec), type.decode(type.encode(now)) |
| assert_equal 1432849613221843, type.encode(Time.utc(2015, 5, 28, 21, 46, 53, 221843)) |
| assert_equal 1432849613221843, type.encode(DateTime.new(2015, 5, 28, 21, 46, 53.221843)) |
| assert_equal Time.utc(2015, 5, 28, 21, 46, 53, 221843), type.decode(1432849613221843) |
| end |
| |
| def test_parse_fixed_duration |
| schema = Avro::Schema.parse <<-SCHEMA |
| { "type": "fixed", "size": 12, "name": "fixed_dur", "logicalType": "duration" } |
| SCHEMA |
| |
| assert_equal 'duration', schema.logical_type |
| end |
| |
| def test_bytes_decimal |
| schema = Avro::Schema.parse <<-SCHEMA |
| { "type": "bytes", "logicalType": "decimal", "precision": 9, "scale": 6 } |
| SCHEMA |
| |
| assert_equal 'decimal', schema.logical_type |
| assert_equal 9, schema.precision |
| assert_equal 6, schema.scale |
| |
| assert_encode_and_decode BigDecimal('-3.4562'), schema |
| assert_encode_and_decode BigDecimal('3.4562'), schema |
| assert_encode_and_decode 15.123, schema |
| assert_encode_and_decode 15, schema |
| assert_encode_and_decode BigDecimal('0.123456'), schema |
| assert_encode_and_decode BigDecimal('0'), schema |
| assert_encode_and_decode BigDecimal('1'), schema |
| assert_encode_and_decode BigDecimal('-1'), schema |
| |
| assert_raise ArgumentError do |
| type = Avro::LogicalTypes::BytesDecimal.new(schema) |
| type.encode('1.23') |
| end |
| end |
| |
| def test_bytes_decimal_range_errors |
| schema = Avro::Schema.parse <<-SCHEMA |
| { "type": "bytes", "logicalType": "decimal", "precision": 4, "scale": 2 } |
| SCHEMA |
| |
| type = Avro::LogicalTypes::BytesDecimal.new(schema) |
| |
| assert_raises RangeError do |
| type.encode(BigDecimal('345')) |
| end |
| |
| assert_raises RangeError do |
| type.encode(BigDecimal('1.5342')) |
| end |
| |
| assert_raises RangeError do |
| type.encode(BigDecimal('-1.5342')) |
| end |
| |
| assert_raises RangeError do |
| type.encode(BigDecimal('-100.2')) |
| end |
| |
| assert_raises RangeError do |
| type.encode(BigDecimal('-99.991')) |
| end |
| end |
| |
| def test_bytes_decimal_conversion |
| schema = Avro::Schema.parse <<-SCHEMA |
| { "type": "bytes", "logicalType": "decimal", "precision": 12, "scale": 6 } |
| SCHEMA |
| |
| type = Avro::LogicalTypes::BytesDecimal.new(schema) |
| |
| enc = "\xcb\x43\x38".dup.force_encoding('BINARY') |
| assert_equal enc, type.encode(BigDecimal('-3.4562')) |
| assert_equal BigDecimal('-3.4562'), type.decode(enc) |
| |
| assert_equal "\x34\xbc\xc8".dup.force_encoding('BINARY'), type.encode(BigDecimal('3.4562')) |
| assert_equal BigDecimal('3.4562'), type.decode("\x34\xbc\xc8".dup.force_encoding('BINARY')) |
| |
| assert_equal "\x6a\x33\x0e\x87\x00".dup.force_encoding('BINARY'), type.encode(BigDecimal('456123.123456')) |
| assert_equal BigDecimal('456123.123456'), type.decode("\x6a\x33\x0e\x87\x00".dup.force_encoding('BINARY')) |
| end |
| |
| def test_logical_type_with_schema |
| exception = assert_raises(ArgumentError) do |
| Avro::LogicalTypes::LogicalTypeWithSchema.new(nil) |
| end |
| assert_equal exception.to_s, 'schema is required' |
| |
| schema = Avro::Schema.parse <<-SCHEMA |
| { "type": "bytes", "logicalType": "decimal", "precision": 12, "scale": 6 } |
| SCHEMA |
| |
| assert_nothing_raised do |
| Avro::LogicalTypes::LogicalTypeWithSchema.new(schema) |
| end |
| |
| assert_raises NotImplementedError do |
| Avro::LogicalTypes::LogicalTypeWithSchema.new(schema).encode(BigDecimal('2')) |
| end |
| |
| assert_raises NotImplementedError do |
| Avro::LogicalTypes::LogicalTypeWithSchema.new(schema).decode('foo') |
| end |
| end |
| |
| def test_bytes_decimal_object_allocations_encode |
| schema = Avro::Schema.parse <<-SCHEMA |
| { "type": "bytes", "logicalType": "decimal", "precision": 4, "scale": 2 } |
| SCHEMA |
| |
| type = Avro::LogicalTypes::BytesDecimal.new(schema) |
| |
| positive_value = BigDecimal('5.2') |
| negative_value = BigDecimal('-5.2') |
| |
| [positive_value, negative_value].each do |value| |
| report = MemoryProfiler.report do |
| type.encode(value) |
| end |
| |
| assert_equal 5, report.total_allocated |
| # Ruby 2.7 does not retain anything. Ruby 2.6 retains 1 |
| assert_operator 1, :>=, report.total_retained |
| end |
| end |
| |
| def test_bytes_decimal_object_allocations_decode |
| schema = Avro::Schema.parse <<-SCHEMA |
| { "type": "bytes", "logicalType": "decimal", "precision": 4, "scale": 2 } |
| SCHEMA |
| |
| type = Avro::LogicalTypes::BytesDecimal.new(schema) |
| |
| positive_enc = "\x02\b".dup.force_encoding('BINARY') |
| negative_enc = "\xFD\xF8".dup.force_encoding('BINARY') |
| |
| [positive_enc, negative_enc].each do |encoded| |
| report = MemoryProfiler.report do |
| type.decode(encoded) |
| end |
| |
| assert_equal 5, report.total_allocated |
| # Ruby 2.7 does not retain anything. Ruby 2.6 retains 1 or 2 |
| assert_operator 2, :>=, report.total_retained |
| end |
| end |
| |
| def encode(datum, schema) |
| buffer = StringIO.new |
| encoder = Avro::IO::BinaryEncoder.new(buffer) |
| |
| datum_writer = Avro::IO::DatumWriter.new(schema) |
| datum_writer.write(datum, encoder) |
| |
| buffer.string |
| end |
| |
| def decode(encoded, schema) |
| buffer = StringIO.new(encoded) |
| decoder = Avro::IO::BinaryDecoder.new(buffer) |
| |
| datum_reader = Avro::IO::DatumReader.new(schema, schema) |
| datum_reader.read(decoder) |
| end |
| |
| def assert_encode_and_decode(datum, schema) |
| encoded = encode(datum, schema) |
| assert_equal datum, decode(encoded, schema) |
| end |
| |
| def assert_preencoded(datum, schema, decoded) |
| encoded = encode(datum, schema) |
| assert_equal decoded, decode(encoded, schema) |
| end |
| end |