avro 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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