avro 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env ruby
2
+ # Licensed to the Apache Software Foundation (ASF) under one
3
+ # or more contributor license agreements. See the NOTICE file
4
+ # distributed with this work for additional information
5
+ # regarding copyright ownership. The ASF licenses this file
6
+ # to you under the Apache License, Version 2.0 (the
7
+ # "License"); you may not use this file except in compliance
8
+ # with the License. You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ require 'socket'
19
+ require 'avro'
20
+
21
+ MAIL_PROTOCOL_JSON = <<-JSON
22
+ {"namespace": "example.proto",
23
+ "protocol": "Mail",
24
+
25
+ "types": [
26
+ {"name": "Message", "type": "record",
27
+ "fields": [
28
+ {"name": "to", "type": "string"},
29
+ {"name": "from", "type": "string"},
30
+ {"name": "body", "type": "string"}
31
+ ]
32
+ }
33
+ ],
34
+
35
+ "messages": {
36
+ "send": {
37
+ "request": [{"name": "message", "type": "Message"}],
38
+ "response": "string"
39
+ },
40
+ "replay": {
41
+ "request": [],
42
+ "response": "string"
43
+ }
44
+ }
45
+ }
46
+ JSON
47
+
48
+ MAIL_PROTOCOL = Avro::Protocol.parse(MAIL_PROTOCOL_JSON)
49
+
50
+ def make_requestor(server_address, port, protocol)
51
+ sock = TCPSocket.new(server_address, port)
52
+ client = Avro::IPC::SocketTransport.new(sock)
53
+ Avro::IPC::Requestor.new(protocol, client)
54
+ end
55
+
56
+ if $0 == __FILE__
57
+ if ![3, 4].include?(ARGV.length)
58
+ raise "Usage: <to> <from> <body> [<count>]"
59
+ end
60
+
61
+ # client code - attach to the server and send a message
62
+ # fill in the Message record
63
+ message = {
64
+ 'to' => ARGV[0],
65
+ 'from' => ARGV[1],
66
+ 'body' => ARGV[2]
67
+ }
68
+
69
+ num_messages = ARGV[3].to_i
70
+ num_message = 1 if num_messages == 0
71
+
72
+ # build the parameters for the request
73
+ params = {'message' => message}
74
+
75
+ # send the requests and print the result
76
+ num_messages.times do
77
+ requestor = make_requestor('localhost', 9090, MAIL_PROTOCOL)
78
+ result = requestor.request('send', params)
79
+ puts("Result: " + result)
80
+ end
81
+
82
+ # try out a replay message
83
+ requestor = make_requestor('localhost', 9090, MAIL_PROTOCOL)
84
+ result = requestor.request('replay', {})
85
+ puts("Replay Result: " + result)
86
+ end
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env ruby
2
+ # Licensed to the Apache Software Foundation (ASF) under one
3
+ # or more contributor license agreements. See the NOTICE file
4
+ # distributed with this work for additional information
5
+ # regarding copyright ownership. The ASF licenses this file
6
+ # to you under the Apache License, Version 2.0 (the
7
+ # "License"); you may not use this file except in compliance
8
+ # with the License. You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ require 'socket'
19
+ require 'avro'
20
+
21
+ MAIL_PROTOCOL_JSON = <<-EOS
22
+ {"namespace": "example.proto",
23
+ "protocol": "Mail",
24
+
25
+ "types": [
26
+ {"name": "Message", "type": "record",
27
+ "fields": [
28
+ {"name": "to", "type": "string"},
29
+ {"name": "from", "type": "string"},
30
+ {"name": "body", "type": "string"}
31
+ ]
32
+ }
33
+ ],
34
+
35
+ "messages": {
36
+ "send": {
37
+ "request": [{"name": "message", "type": "Message"}],
38
+ "response": "string"
39
+ },
40
+ "replay": {
41
+ "request": [],
42
+ "response": "string"
43
+ }
44
+ }
45
+ }
46
+ EOS
47
+
48
+ MAIL_PROTOCOL = Avro::Protocol.parse(MAIL_PROTOCOL_JSON)
49
+
50
+ class MailResponder < Avro::IPC::Responder
51
+ def initialize
52
+ super(MAIL_PROTOCOL)
53
+ end
54
+
55
+ def call(message, request)
56
+ if message.name == 'send'
57
+ request_content = request['message']
58
+ "Sent message to #{request_content['to']} from #{request_content['from']} with body #{request_content['body']}"
59
+ elsif message.name == 'replay'
60
+ 'replay'
61
+ end
62
+ end
63
+ end
64
+
65
+ class RequestHandler
66
+ def initialize(address, port)
67
+ @ip_address = address
68
+ @port = port
69
+ end
70
+
71
+ def run
72
+ server = TCPServer.new(@ip_address, @port)
73
+ while (session = server.accept)
74
+ handle(session)
75
+ session.close
76
+ end
77
+ end
78
+ end
79
+
80
+ class MailHandler < RequestHandler
81
+ def handle(request)
82
+ responder = MailResponder.new()
83
+ transport = Avro::IPC::SocketTransport.new(request)
84
+ transport.write_framed_message(responder.respond(transport))
85
+ end
86
+ end
87
+
88
+ if $0 == __FILE__
89
+ handler = MailHandler.new('localhost', 9090)
90
+ handler.run
91
+ end
@@ -0,0 +1,23 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one
2
+ # or more contributor license agreements. See the NOTICE file
3
+ # distributed with this work for additional information
4
+ # regarding copyright ownership. The ASF licenses this file
5
+ # to you under the Apache License, Version 2.0 (the
6
+ # "License"); you may not use this file except in compliance
7
+ # with the License. You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'rubygems'
18
+ require 'test/unit'
19
+ require 'stringio'
20
+ require 'fileutils'
21
+ FileUtils.mkdir_p('tmp')
22
+ require 'avro'
23
+ require 'random_data'
@@ -0,0 +1,361 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one
2
+ # or more contributor license agreements. See the NOTICE file
3
+ # distributed with this work for additional information
4
+ # regarding copyright ownership. The ASF licenses this file
5
+ # to you under the Apache License, Version 2.0 (the
6
+ # "License"); you may not use this file except in compliance
7
+ # with the License. You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'test_help'
18
+
19
+ class TestIO < Test::Unit::TestCase
20
+ DATAFILE = 'tmp/test.rb.avro'
21
+ Schema = Avro::Schema
22
+
23
+ def test_null
24
+ check_default('"null"', "null", nil)
25
+ end
26
+
27
+ def test_boolean
28
+ check_default('"boolean"', "true", true)
29
+ check_default('"boolean"', "false", false)
30
+ end
31
+
32
+ def test_string
33
+ check_default('"string"', '"foo"', "foo")
34
+ end
35
+
36
+ def test_bytes
37
+ check_default('"bytes"', '"foo"', "foo")
38
+ end
39
+
40
+ def test_int
41
+ check_default('"int"', "5", 5)
42
+ end
43
+
44
+ def test_long
45
+ check_default('"long"', "9", 9)
46
+ end
47
+
48
+ def test_float
49
+ check_default('"float"', "1.2", 1.2)
50
+ end
51
+
52
+ def test_double
53
+ check_default('"double"', "1.2", 1.2)
54
+ end
55
+
56
+ def test_array
57
+ array_schema = '{"type": "array", "items": "long"}'
58
+ check_default(array_schema, "[1]", [1])
59
+ end
60
+
61
+ def test_map
62
+ map_schema = '{"type": "map", "values": "long"}'
63
+ check_default(map_schema, '{"a": 1}', {"a" => 1})
64
+ end
65
+
66
+ def test_record
67
+ record_schema = <<EOS
68
+ {"type": "record",
69
+ "name": "Test",
70
+ "fields": [{"name": "f",
71
+ "type": "long"}]}
72
+ EOS
73
+ check_default(record_schema, '{"f": 11}', {"f" => 11})
74
+ end
75
+
76
+ def test_enum
77
+ enum_schema = '{"type": "enum", "name": "Test","symbols": ["A", "B"]}'
78
+ check_default(enum_schema, '"B"', "B")
79
+ end
80
+
81
+ def test_recursive
82
+ recursive_schema = <<EOS
83
+ {"type": "record",
84
+ "name": "Node",
85
+ "fields": [{"name": "label", "type": "string"},
86
+ {"name": "children",
87
+ "type": {"type": "array", "items": "Node"}}]}
88
+ EOS
89
+ check(recursive_schema)
90
+ end
91
+
92
+ def test_union
93
+ union_schema = <<EOS
94
+ ["string",
95
+ "null",
96
+ "long",
97
+ {"type": "record",
98
+ "name": "Cons",
99
+ "fields": [{"name": "car", "type": "string"},
100
+ {"name": "cdr", "type": "string"}]}]
101
+ EOS
102
+ check(union_schema)
103
+ check_default('["double", "long"]', "1.1", 1.1)
104
+ end
105
+
106
+ def test_lisp
107
+ lisp_schema = <<EOS
108
+ {"type": "record",
109
+ "name": "Lisp",
110
+ "fields": [{"name": "value",
111
+ "type": ["null", "string",
112
+ {"type": "record",
113
+ "name": "Cons",
114
+ "fields": [{"name": "car", "type": "Lisp"},
115
+ {"name": "cdr", "type": "Lisp"}]}]}]}
116
+ EOS
117
+ check(lisp_schema)
118
+ end
119
+
120
+ def test_fixed
121
+ fixed_schema = '{"type": "fixed", "name": "Test", "size": 1}'
122
+ check_default(fixed_schema, '"a"', "a")
123
+ end
124
+
125
+ def test_enum_with_duplicate
126
+ str = '{"type": "enum", "name": "Test","symbols" : ["AA", "AA"]}'
127
+ assert_raises(Avro::SchemaParseError) do
128
+ schema = Avro::Schema.parse str
129
+ end
130
+ end
131
+
132
+ BINARY_INT_ENCODINGS = [
133
+ [0, '00'],
134
+ [-1, '01'],
135
+ [1, '02'],
136
+ [-2, '03'],
137
+ [2, '04'],
138
+ [-64, '7f'],
139
+ [64, '80 01'],
140
+ [8192, '80 80 01'],
141
+ [-8193, '81 80 01'],
142
+ ]
143
+
144
+ def avro_hexlify(reader)
145
+ bytes = []
146
+ current_byte = reader.read(1)
147
+ bytes << hexlify(current_byte)
148
+ while (current_byte[0] & 0x80) != 0
149
+ current_byte = reader.read(1)
150
+ bytes << hexlify(current_byte)
151
+ end
152
+ bytes.join ' '
153
+ end
154
+
155
+ def hexlify(msg)
156
+ msg.split("").collect { |c| c[0].to_s(16).rjust(2, '0') }.join
157
+ end
158
+
159
+ def test_binary_int_encoding
160
+ for value, hex_encoding in BINARY_INT_ENCODINGS
161
+ # write datum in binary to string buffer
162
+ buffer = StringIO.new
163
+ encoder = Avro::IO::BinaryEncoder.new(buffer)
164
+ datum_writer = Avro::IO::DatumWriter.new(Avro::Schema.parse('"int"'))
165
+ datum_writer.write(value, encoder)
166
+
167
+ buffer.seek(0)
168
+ hex_val = avro_hexlify(buffer)
169
+
170
+ assert_equal hex_encoding, hex_val
171
+ end
172
+ end
173
+
174
+ def test_binary_long_encoding
175
+ for value, hex_encoding in BINARY_INT_ENCODINGS
176
+ buffer = StringIO.new
177
+ encoder = Avro::IO::BinaryEncoder.new(buffer)
178
+ datum_writer = Avro::IO::DatumWriter.new(Avro::Schema.parse('"long"'))
179
+ datum_writer.write(value, encoder)
180
+
181
+ # read it out of the buffer and hexlify it
182
+ buffer.seek(0)
183
+ hex_val = avro_hexlify(buffer)
184
+
185
+ assert_equal hex_encoding, hex_val
186
+ end
187
+ end
188
+
189
+ def test_skip_long
190
+ for value_to_skip, hex_encoding in BINARY_INT_ENCODINGS
191
+ value_to_read = 6253
192
+
193
+ # write some data in binary to string buffer
194
+ writer = StringIO.new
195
+ encoder = Avro::IO::BinaryEncoder.new(writer)
196
+ datum_writer = Avro::IO::DatumWriter.new(Avro::Schema.parse('"long"'))
197
+ datum_writer.write(value_to_skip, encoder)
198
+ datum_writer.write(value_to_read, encoder)
199
+
200
+ # skip the value
201
+ reader = StringIO.new(writer.string())
202
+ decoder = Avro::IO::BinaryDecoder.new(reader)
203
+ decoder.skip_long()
204
+
205
+ # read data from string buffer
206
+ datum_reader = Avro::IO::DatumReader.new(Avro::Schema.parse('"long"'))
207
+ read_value = datum_reader.read(decoder)
208
+
209
+ # check it
210
+ assert_equal value_to_read, read_value
211
+ end
212
+ end
213
+
214
+ def test_skip_int
215
+ for value_to_skip, hex_encoding in BINARY_INT_ENCODINGS
216
+ value_to_read = 6253
217
+
218
+ writer = StringIO.new
219
+ encoder = Avro::IO::BinaryEncoder.new(writer)
220
+ datum_writer = Avro::IO::DatumWriter.new(Avro::Schema.parse('"int"'))
221
+ datum_writer.write(value_to_skip, encoder)
222
+ datum_writer.write(value_to_read, encoder)
223
+
224
+ reader = StringIO.new(writer.string)
225
+ decoder = Avro::IO::BinaryDecoder.new(reader)
226
+ decoder.skip_int
227
+
228
+ datum_reader = Avro::IO::DatumReader.new(Avro::Schema.parse('"int"'))
229
+ read_value = datum_reader.read(decoder)
230
+
231
+ assert_equal value_to_read, read_value
232
+ end
233
+ end
234
+
235
+ def test_schema_promotion
236
+ promotable_schemas = ['"int"', '"long"', '"float"', '"double"']
237
+ incorrect = 0
238
+ promotable_schemas.each_with_index do |ws, i|
239
+ writers_schema = Avro::Schema.parse(ws)
240
+ datum_to_write = 219
241
+ for rs in promotable_schemas[(i + 1)..-1]
242
+ readers_schema = Avro::Schema.parse(rs)
243
+ writer, enc, dw = write_datum(datum_to_write, writers_schema)
244
+ datum_read = read_datum(writer, writers_schema, readers_schema)
245
+ if datum_read != datum_to_write
246
+ incorrect += 1
247
+ end
248
+ end
249
+ assert_equal(incorrect, 0)
250
+ end
251
+ end
252
+ private
253
+
254
+ def check_default(schema_json, default_json, default_value)
255
+ check(schema_json)
256
+ actual_schema = '{"type": "record", "name": "Foo", "fields": []}'
257
+ actual = Avro::Schema.parse(actual_schema)
258
+
259
+ expected_schema = <<EOS
260
+ {"type": "record",
261
+ "name": "Foo",
262
+ "fields": [{"name": "f", "type": #{schema_json}, "default": #{default_json}}]}
263
+ EOS
264
+ expected = Avro::Schema.parse(expected_schema)
265
+
266
+ reader = Avro::IO::DatumReader.new(actual, expected)
267
+ record = reader.read(Avro::IO::BinaryDecoder.new(StringIO.new))
268
+ assert_equal default_value, record["f"]
269
+ end
270
+
271
+ def check(str)
272
+ # parse schema, then convert back to string
273
+ schema = Avro::Schema.parse str
274
+
275
+ parsed_string = schema.to_s
276
+
277
+ # test that the round-trip didn't mess up anything
278
+ # NB: I don't think we should do this. Why enforce ordering?
279
+ assert_equal(Yajl.load(str),
280
+ Yajl.load(parsed_string))
281
+
282
+ # test __eq__
283
+ assert_equal(schema, Avro::Schema.parse(str))
284
+
285
+ # test hashcode doesn't generate infinite recursion
286
+ schema.hash
287
+
288
+ # test serialization of random data
289
+ randomdata = RandomData.new(schema)
290
+ 9.times { checkser(schema, randomdata) }
291
+
292
+ # test writing of data to file
293
+ check_datafile(schema)
294
+ end
295
+
296
+ def checkser(schm, randomdata)
297
+ datum = randomdata.next
298
+ assert validate(schm, datum)
299
+ w = Avro::IO::DatumWriter.new(schm)
300
+ writer = StringIO.new "", "w"
301
+ w.write(datum, Avro::IO::BinaryEncoder.new(writer))
302
+ r = datum_reader(schm)
303
+ reader = StringIO.new(writer.string)
304
+ ob = r.read(Avro::IO::BinaryDecoder.new(reader))
305
+ assert_equal(datum, ob) # FIXME check on assertdata conditional
306
+ end
307
+
308
+ def check_datafile(schm)
309
+ seed = 0
310
+ count = 10
311
+ random_data = RandomData.new(schm, seed)
312
+
313
+
314
+ f = File.open(DATAFILE, 'wb')
315
+ dw = Avro::DataFile::Writer.new(f, datum_writer(schm), schm)
316
+ count.times{ dw << random_data.next }
317
+ dw.close
318
+
319
+ random_data = RandomData.new(schm, seed)
320
+
321
+
322
+ f = File.open(DATAFILE, 'r+')
323
+ dr = Avro::DataFile::Reader.new(f, datum_reader(schm))
324
+
325
+ last_index = nil
326
+ dr.each_with_index do |data, c|
327
+ last_index = c
328
+ # FIXME assertdata conditional
329
+ assert_equal(random_data.next, data)
330
+ end
331
+ dr.close
332
+ assert_equal count, last_index+1
333
+ end
334
+
335
+ def validate(schm, datum)
336
+ Avro::Schema.validate(schm, datum)
337
+ end
338
+
339
+ def datum_writer(schm)
340
+ Avro::IO::DatumWriter.new(schm)
341
+ end
342
+
343
+ def datum_reader(schm)
344
+ Avro::IO::DatumReader.new(schm)
345
+ end
346
+
347
+ def write_datum(datum, writers_schema)
348
+ writer = StringIO.new
349
+ encoder = Avro::IO::BinaryEncoder.new(writer)
350
+ datum_writer = Avro::IO::DatumWriter.new(writers_schema)
351
+ datum_writer.write(datum, encoder)
352
+ [writer, encoder, datum_writer]
353
+ end
354
+
355
+ def read_datum(buffer, writers_schema, readers_schema=nil)
356
+ reader = StringIO.new(buffer.string)
357
+ decoder = Avro::IO::BinaryDecoder.new(reader)
358
+ datum_reader = Avro::IO::DatumReader.new(writers_schema, readers_schema)
359
+ datum_reader.read(decoder)
360
+ end
361
+ end