| # 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 |
| # |
| # 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. |
| """ |
| Test the protocol parsing logic. |
| """ |
| import unittest |
| from avro import protocol |
| |
| class ExampleProtocol(object): |
| def __init__(self, protocol_string, valid, name='', comment=''): |
| self._protocol_string = protocol_string |
| self._valid = valid |
| self._name = name or protocol_string # default to schema_string for name |
| self._comment = comment |
| |
| # read-only properties |
| protocol_string = property(lambda self: self._protocol_string) |
| valid = property(lambda self: self._valid) |
| name = property(lambda self: self._name) |
| |
| # read/write properties |
| def set_comment(self, new_comment): self._comment = new_comment |
| comment = property(lambda self: self._comment, set_comment) |
| |
| # |
| # Example Protocols |
| # |
| HELLO_WORLD = ExampleProtocol("""\ |
| { |
| "namespace": "com.acme", |
| "protocol": "HelloWorld", |
| |
| "types": [ |
| {"name": "Greeting", "type": "record", "fields": [ |
| {"name": "message", "type": "string"}]}, |
| {"name": "Curse", "type": "error", "fields": [ |
| {"name": "message", "type": "string"}]} |
| ], |
| |
| "messages": { |
| "hello": { |
| "request": [{"name": "greeting", "type": "Greeting" }], |
| "response": "Greeting", |
| "errors": ["Curse"] |
| } |
| } |
| } |
| """, True) |
| EXAMPLES = [ |
| HELLO_WORLD, |
| ExampleProtocol("""\ |
| {"namespace": "org.apache.avro.test", |
| "protocol": "Simple", |
| |
| "types": [ |
| {"name": "Kind", "type": "enum", "symbols": ["FOO","BAR","BAZ"]}, |
| |
| {"name": "MD5", "type": "fixed", "size": 16}, |
| |
| {"name": "TestRecord", "type": "record", |
| "fields": [ |
| {"name": "name", "type": "string", "order": "ignore"}, |
| {"name": "kind", "type": "Kind", "order": "descending"}, |
| {"name": "hash", "type": "MD5"} |
| ] |
| }, |
| |
| {"name": "TestError", "type": "error", "fields": [ |
| {"name": "message", "type": "string"} |
| ] |
| } |
| |
| ], |
| |
| "messages": { |
| |
| "hello": { |
| "request": [{"name": "greeting", "type": "string"}], |
| "response": "string" |
| }, |
| |
| "echo": { |
| "request": [{"name": "record", "type": "TestRecord"}], |
| "response": "TestRecord" |
| }, |
| |
| "add": { |
| "request": [{"name": "arg1", "type": "int"}, {"name": "arg2", "type": "int"}], |
| "response": "int" |
| }, |
| |
| "echoBytes": { |
| "request": [{"name": "data", "type": "bytes"}], |
| "response": "bytes" |
| }, |
| |
| "error": { |
| "request": [], |
| "response": "null", |
| "errors": ["TestError"] |
| } |
| } |
| |
| } |
| """, True), |
| ExampleProtocol("""\ |
| {"namespace": "org.apache.avro.test.namespace", |
| "protocol": "TestNamespace", |
| |
| "types": [ |
| {"name": "org.apache.avro.test.util.MD5", "type": "fixed", "size": 16}, |
| {"name": "TestRecord", "type": "record", |
| "fields": [ {"name": "hash", "type": "org.apache.avro.test.util.MD5"} ] |
| }, |
| {"name": "TestError", "namespace": "org.apache.avro.test.errors", |
| "type": "error", "fields": [ {"name": "message", "type": "string"} ] |
| } |
| ], |
| |
| "messages": { |
| "echo": { |
| "request": [{"name": "record", "type": "TestRecord"}], |
| "response": "TestRecord" |
| }, |
| |
| "error": { |
| "request": [], |
| "response": "null", |
| "errors": ["org.apache.avro.test.errors.TestError"] |
| } |
| |
| } |
| |
| } |
| """, True), |
| ExampleProtocol("""\ |
| {"namespace": "org.apache.avro.test.namespace", |
| "protocol": "TestImplicitNamespace", |
| |
| "types": [ |
| {"name": "org.apache.avro.test.util.MD5", "type": "fixed", "size": 16}, |
| {"name": "ReferencedRecord", "type": "record", |
| "fields": [ {"name": "foo", "type": "string"} ] }, |
| {"name": "TestRecord", "type": "record", |
| "fields": [ {"name": "hash", "type": "org.apache.avro.test.util.MD5"}, |
| {"name": "unqalified", "type": "ReferencedRecord"} ] |
| }, |
| {"name": "TestError", |
| "type": "error", "fields": [ {"name": "message", "type": "string"} ] |
| } |
| ], |
| |
| "messages": { |
| "echo": { |
| "request": [{"name": "qualified", |
| "type": "org.apache.avro.test.namespace.TestRecord"}], |
| "response": "TestRecord" |
| }, |
| |
| "error": { |
| "request": [], |
| "response": "null", |
| "errors": ["org.apache.avro.test.namespace.TestError"] |
| } |
| |
| } |
| |
| } |
| """, True), |
| ExampleProtocol("""\ |
| {"namespace": "org.apache.avro.test.namespace", |
| "protocol": "TestNamespaceTwo", |
| |
| "types": [ |
| {"name": "org.apache.avro.test.util.MD5", "type": "fixed", "size": 16}, |
| {"name": "ReferencedRecord", "type": "record", |
| "namespace": "org.apache.avro.other.namespace", |
| "fields": [ {"name": "foo", "type": "string"} ] }, |
| {"name": "TestRecord", "type": "record", |
| "fields": [ {"name": "hash", "type": "org.apache.avro.test.util.MD5"}, |
| {"name": "qualified", |
| "type": "org.apache.avro.other.namespace.ReferencedRecord"} |
| ] |
| }, |
| {"name": "TestError", |
| "type": "error", "fields": [ {"name": "message", "type": "string"} ] |
| } |
| ], |
| |
| "messages": { |
| "echo": { |
| "request": [{"name": "qualified", |
| "type": "org.apache.avro.test.namespace.TestRecord"}], |
| "response": "TestRecord" |
| }, |
| |
| "error": { |
| "request": [], |
| "response": "null", |
| "errors": ["org.apache.avro.test.namespace.TestError"] |
| } |
| |
| } |
| |
| } |
| """, True), |
| ExampleProtocol("""\ |
| {"namespace": "org.apache.avro.test.namespace", |
| "protocol": "TestValidRepeatedName", |
| |
| "types": [ |
| {"name": "org.apache.avro.test.util.MD5", "type": "fixed", "size": 16}, |
| {"name": "ReferencedRecord", "type": "record", |
| "namespace": "org.apache.avro.other.namespace", |
| "fields": [ {"name": "foo", "type": "string"} ] }, |
| {"name": "ReferencedRecord", "type": "record", |
| "fields": [ {"name": "bar", "type": "double"} ] }, |
| {"name": "TestError", |
| "type": "error", "fields": [ {"name": "message", "type": "string"} ] |
| } |
| ], |
| |
| "messages": { |
| "echo": { |
| "request": [{"name": "qualified", |
| "type": "ReferencedRecord"}], |
| "response": "org.apache.avro.other.namespace.ReferencedRecord" |
| }, |
| |
| "error": { |
| "request": [], |
| "response": "null", |
| "errors": ["org.apache.avro.test.namespace.TestError"] |
| } |
| |
| } |
| |
| } |
| """, True), |
| ExampleProtocol("""\ |
| {"namespace": "org.apache.avro.test.namespace", |
| "protocol": "TestInvalidRepeatedName", |
| |
| "types": [ |
| {"name": "org.apache.avro.test.util.MD5", "type": "fixed", "size": 16}, |
| {"name": "ReferencedRecord", "type": "record", |
| "fields": [ {"name": "foo", "type": "string"} ] }, |
| {"name": "ReferencedRecord", "type": "record", |
| "fields": [ {"name": "bar", "type": "double"} ] }, |
| {"name": "TestError", |
| "type": "error", "fields": [ {"name": "message", "type": "string"} ] |
| } |
| ], |
| |
| "messages": { |
| "echo": { |
| "request": [{"name": "qualified", |
| "type": "ReferencedRecord"}], |
| "response": "org.apache.avro.other.namespace.ReferencedRecord" |
| }, |
| |
| "error": { |
| "request": [], |
| "response": "null", |
| "errors": ["org.apache.avro.test.namespace.TestError"] |
| } |
| |
| } |
| |
| } |
| """, False), |
| ExampleProtocol("""\ |
| {"namespace": "org.apache.avro.test", |
| "protocol": "BulkData", |
| |
| "types": [], |
| |
| "messages": { |
| |
| "read": { |
| "request": [], |
| "response": "bytes" |
| }, |
| |
| "write": { |
| "request": [ {"name": "data", "type": "bytes"} ], |
| "response": "null" |
| } |
| |
| } |
| |
| } |
| """, True), |
| ExampleProtocol("""\ |
| { |
| "protocol" : "API", |
| "namespace" : "xyz.api", |
| "types" : [ { |
| "type" : "enum", |
| "name" : "Symbology", |
| "namespace" : "xyz.api.product", |
| "symbols" : [ "OPRA", "CUSIP", "ISIN", "SEDOL" ] |
| }, { |
| "type" : "record", |
| "name" : "Symbol", |
| "namespace" : "xyz.api.product", |
| "fields" : [ { |
| "name" : "symbology", |
| "type" : "xyz.api.product.Symbology" |
| }, { |
| "name" : "symbol", |
| "type" : "string" |
| } ] |
| }, { |
| "type" : "record", |
| "name" : "MultiSymbol", |
| "namespace" : "xyz.api.product", |
| "fields" : [ { |
| "name" : "symbols", |
| "type" : { |
| "type" : "map", |
| "values" : "xyz.api.product.Symbol" |
| } |
| } ] |
| } ], |
| "messages" : { |
| } |
| } |
| """, True), |
| ] |
| |
| VALID_EXAMPLES = [e for e in EXAMPLES if e.valid] |
| |
| class TestProtocol(unittest.TestCase): |
| def test_parse(self): |
| num_correct = 0 |
| for example in EXAMPLES: |
| try: |
| protocol.parse(example.protocol_string) |
| if example.valid: |
| num_correct += 1 |
| else: |
| self.fail("Parsed invalid protocol: %s" % (example.name,)) |
| except Exception, e: |
| if not example.valid: |
| num_correct += 1 |
| else: |
| self.fail("Coudl not parse valid protocol: %s" % (example.name,)) |
| |
| fail_msg = "Parse behavior correct on %d out of %d protocols." % \ |
| (num_correct, len(EXAMPLES)) |
| self.assertEqual(num_correct, len(EXAMPLES), fail_msg) |
| |
| def test_inner_namespace_set(self): |
| print '' |
| print 'TEST INNER NAMESPACE' |
| print '===================' |
| print '' |
| proto = protocol.parse(HELLO_WORLD.protocol_string) |
| self.assertEqual(proto.namespace, "com.acme") |
| greeting_type = proto.types_dict['Greeting'] |
| self.assertEqual(greeting_type.namespace, 'com.acme') |
| |
| def test_inner_namespace_not_rendered(self): |
| proto = protocol.parse(HELLO_WORLD.protocol_string) |
| self.assertEqual('com.acme.Greeting', proto.types[0].fullname) |
| self.assertEqual('Greeting', proto.types[0].name) |
| # but there shouldn't be 'namespace' rendered to json on the inner type |
| self.assertFalse('namespace' in proto.to_json()['types'][0]) |
| |
| def test_valid_cast_to_string_after_parse(self): |
| """ |
| Test that the string generated by an Avro Protocol object |
| is, in fact, a valid Avro protocol. |
| """ |
| print '' |
| print 'TEST CAST TO STRING' |
| print '===================' |
| print '' |
| |
| num_correct = 0 |
| for example in VALID_EXAMPLES: |
| protocol_data = protocol.parse(example.protocol_string) |
| try: |
| try: |
| protocol.parse(str(protocol_data)) |
| debug_msg = "%s: STRING CAST SUCCESS" % example.name |
| num_correct += 1 |
| except: |
| debug_msg = "%s: STRING CAST FAILURE" % example.name |
| finally: |
| print debug_msg |
| |
| fail_msg = "Cast to string success on %d out of %d protocols" % \ |
| (num_correct, len(VALID_EXAMPLES)) |
| self.assertEqual(num_correct, len(VALID_EXAMPLES), fail_msg) |
| |
| def test_equivalence_after_round_trip(self): |
| """ |
| 1. Given a string, parse it to get Avro protocol "original". |
| 2. Serialize "original" to a string and parse that string |
| to generate Avro protocol "round trip". |
| 3. Ensure "original" and "round trip" protocols are equivalent. |
| """ |
| print '' |
| print 'TEST ROUND TRIP' |
| print '===============' |
| print '' |
| |
| num_correct = 0 |
| for example in VALID_EXAMPLES: |
| original_protocol = protocol.parse(example.protocol_string) |
| round_trip_protocol = protocol.parse(str(original_protocol)) |
| |
| if original_protocol == round_trip_protocol: |
| num_correct += 1 |
| debug_msg = "%s: ROUND TRIP SUCCESS" % example.name |
| else: |
| self.fail("Round trip failure: %s %s %s", (example.name, example.protocol_string, str(original_protocol))) |
| |
| fail_msg = "Round trip success on %d out of %d protocols" % \ |
| (num_correct, len(VALID_EXAMPLES)) |
| self.assertEqual(num_correct, len(VALID_EXAMPLES), fail_msg) |
| |
| if __name__ == '__main__': |
| unittest.main() |