| /** |
| * 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. |
| */ |
| using System; |
| using System.Collections; |
| using System.IO; |
| using System.Linq; |
| using NUnit.Framework; |
| using Avro.IO; |
| using System.CodeDom; |
| using System.CodeDom.Compiler; |
| using Avro.Specific; |
| using System.Reflection; |
| |
| namespace Avro.Test |
| { |
| [TestFixture] |
| class SpecificTests |
| {
|
| // The dynamically created assembly used in the test below can only be created
|
| // once otherwise repeated tests will fail as the same type name will exist in
|
| // multiple assemblies and so the type in the test and the type found by ObjectCreator
|
| // will differ. This single CompilerResults only works so long as there is only one test.
|
| // If additional tests are added then each test will need its own CompilerResults.
|
| private static CompilerResults compres;
|
|
|
| [TestCase(@"{ |
| ""protocol"" : ""MyProtocol"", |
| ""namespace"" : ""com.foo"", |
| ""types"" : [ |
| { |
| ""type"" : ""record"", |
| ""name"" : ""A"", |
| ""fields"" : [ { ""name"" : ""f1"", ""type"" : ""long"" } ] |
| }, |
| { |
| ""type"" : ""enum"", |
| ""name"" : ""MyEnum"", |
| ""symbols"" : [ ""A"", ""B"", ""C"" ] |
| }, |
| { |
| ""type"": ""fixed"", |
| ""size"": 16, |
| ""name"": ""MyFixed"" |
| }, |
| { |
| ""type"" : ""record"", |
| ""name"" : ""Z"", |
| ""fields"" : |
| [ |
| { ""name"" : ""myUInt"", ""type"" : [ ""int"", ""null"" ] }, |
| { ""name"" : ""myULong"", ""type"" : [ ""long"", ""null"" ] }, |
| { ""name"" : ""myUBool"", ""type"" : [ ""boolean"", ""null"" ] }, |
| { ""name"" : ""myUDouble"", ""type"" : [ ""double"", ""null"" ] }, |
| { ""name"" : ""myUFloat"", ""type"" : [ ""float"", ""null"" ] }, |
| { ""name"" : ""myUBytes"", ""type"" : [ ""bytes"", ""null"" ] }, |
| { ""name"" : ""myUString"", ""type"" : [ ""string"", ""null"" ] }, |
| |
| { ""name"" : ""myInt"", ""type"" : ""int"" }, |
| { ""name"" : ""myLong"", ""type"" : ""long"" }, |
| { ""name"" : ""myBool"", ""type"" : ""boolean"" }, |
| { ""name"" : ""myDouble"", ""type"" : ""double"" }, |
| { ""name"" : ""myFloat"", ""type"" : ""float"" }, |
| { ""name"" : ""myBytes"", ""type"" : ""bytes"" }, |
| { ""name"" : ""myString"", ""type"" : ""string"" }, |
| { ""name"" : ""myNull"", ""type"" : ""null"" }, |
| |
| { ""name"" : ""myFixed"", ""type"" : ""MyFixed"" }, |
| { ""name"" : ""myA"", ""type"" : ""A"" }, |
| { ""name"" : ""myE"", ""type"" : ""MyEnum"" }, |
| { ""name"" : ""myArray"", ""type"" : { ""type"" : ""array"", ""items"" : ""bytes"" } }, |
| { ""name"" : ""myArray2"", ""type"" : { ""type"" : ""array"", ""items"" : { ""type"" : ""record"", ""name"" : ""newRec"", ""fields"" : [ { ""name"" : ""f1"", ""type"" : ""long""} ] } } }, |
| { ""name"" : ""myMap"", ""type"" : { ""type"" : ""map"", ""values"" : ""string"" } }, |
| { ""name"" : ""myMap2"", ""type"" : { ""type"" : ""map"", ""values"" : ""newRec"" } }, |
| { ""name"" : ""myObject"", ""type"" : [ ""MyEnum"", ""A"", ""null"" ] }, |
| { ""name"" : ""myArray3"", ""type"" : { ""type"" : ""array"", ""items"" : { ""type"" : ""array"", ""items"" : [ ""double"", ""string"", ""null"" ] } } } |
| ] |
| } |
| ] |
| }" |
| , new object[] {3, // index of the schema to serialize |
| "com.foo.Z", // name of the schema to serialize |
| @"Console.WriteLine(""Constructing com.foo.Z..."");", // Empty Constructor. |
| @" |
| Console.WriteLine(""Populating com.foo.Z...""); |
| string bytes = ""bytes sample text""; |
| System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding(); |
| |
| myUInt=1; |
| myULong=2; |
| myUBool=true; |
| myUDouble=(double)3; |
| myUFloat=(float)4.5; |
| myUBytes = encoding.GetBytes(bytes); |
| myUString=""Hello""; |
| |
| myInt=1; |
| myLong=2; |
| myBool=true; |
| myDouble=(double)3; |
| myFloat=(float)4.5; |
| myBytes=encoding.GetBytes(bytes); |
| myString=""Hello""; |
| myNull=null; |
| |
| string fixedstr = ""My fixed record0""; |
| myFixed=new MyFixed(); myFixed.Value = encoding.GetBytes(fixedstr); |
| myA=new A(); myA.f1 = 10; |
| myE=com.foo.MyEnum.C; |
| |
| myArray=new List<byte[]>(); |
| myArray.Add(encoding.GetBytes(""a"")); |
| |
| myArray2 = new List<com.foo.newRec>(); |
| com.foo.newRec rec = new com.foo.newRec(); |
| rec.f1 = 50; |
| myArray2.Add(rec); |
| |
| myMap = new Dictionary<string, string>(); |
| myMap.Add(""key"", ""value""); |
| myMap2 = new Dictionary<string, com.foo.newRec>(); |
| com.foo.newRec newrec = new com.foo.newRec(); |
| newrec.f1 = 1200; |
| myMap2.Add(""A"", newrec); |
| myObject = myA; |
| |
| IList<System.Object> o1 = new List<System.Object>(); |
| |
| o1.Add((double)1123123121); |
| o1.Add((double)2); |
| o1.Add(null); |
| o1.Add(""fred""); |
| |
| IList<System.Object> o2 = new List<System.Object>(); |
| |
| o2.Add((double)1); |
| o2.Add((double)32531); |
| o2.Add((double)4); |
| o2.Add((double)555); |
| o2.Add((double)0); |
| |
| myArray3 = new List<IList<System.Object>>(); |
| myArray3.Add(o1); |
| myArray3.Add(o2); |
| |
| "} |
| )] |
| public void TestSpecific(string str, object[] result) |
| { |
| if(compres == null) |
| { |
| Protocol protocol = Protocol.Parse(str); |
| var codegen = new CodeGen(); |
| codegen.AddProtocol(protocol); |
| var compileUnit = codegen.GenerateCode(); |
| |
| // add a constructor to the main class using the passed assignment statements |
| CodeTypeDeclaration ctd = compileUnit.Namespaces[0].Types[(int)result[0]]; |
| CodeConstructor constructor = new CodeConstructor(); |
| constructor.Attributes = MemberAttributes.Public; |
| CodeSnippetExpression snippet = new CodeSnippetExpression((string)result[2]); |
| constructor.Statements.Add(snippet); |
| ctd.Members.Add(constructor); |
| |
| // add a function to the main class to populate the data |
| // This has been moved from constructor, as it was causing some tests to pass that shouldn't when referencing a blank object on READ. |
| |
| CodeMemberMethod method = new CodeMemberMethod(); |
| method.Attributes = MemberAttributes.Public; |
| method.Name = "Populate"; |
| CodeSnippetExpression snippet2 = new CodeSnippetExpression((string)result[3]); |
| method.Statements.Add(snippet2); |
| ctd.Members.Add(method); |
| |
| |
| |
| // compile |
| var comparam = new CompilerParameters(new string[] { "mscorlib.dll" }); |
| comparam.ReferencedAssemblies.Add("System.dll"); |
| comparam.ReferencedAssemblies.Add("Avro.dll"); |
| comparam.GenerateInMemory = true; |
| var ccp = new Microsoft.CSharp.CSharpCodeProvider(); |
| var units = new CodeCompileUnit[] { compileUnit }; |
| compres = ccp.CompileAssemblyFromDom(comparam, units);
|
| Assert.IsNotNull(compres); |
| if (compres.Errors.Count > 0) |
| { |
| for (int i = 0; i < compres.Errors.Count; i++) |
| Console.WriteLine(compres.Errors[i]); |
| } |
| Assert.IsTrue(compres.Errors.Count == 0); |
| } |
| |
| // create record |
| ISpecificRecord rec = compres.CompiledAssembly.CreateInstance((string)result[1]) as ISpecificRecord; |
| |
| // Call populate to put some data in it. |
| Type recType = rec.GetType(); ; |
| MethodInfo methodInfo = recType.GetMethod("Populate"); |
| methodInfo.Invoke(rec, null); |
| |
| var x1 = compres.CompiledAssembly.FullName; |
| |
| Assert.IsFalse(rec == null); |
| |
| // serialize |
| var stream = serialize(rec.Schema, rec); |
| |
| // deserialize |
| var rec2 = deserialize<ISpecificRecord>(stream, rec.Schema, rec.Schema); |
| Assert.IsFalse(rec2 == null); |
| AssertSpecificRecordEqual(rec, rec2); |
| } |
| |
| [TestCase] |
| public void TestEnumResolution() |
| { |
| Schema writerSchema = Schema.Parse("{\"type\":\"record\",\"name\":\"EnumRecord\",\"namespace\":\"Avro.Test\"," + |
| "\"fields\":[{\"name\":\"enumType\",\"type\": { \"type\": \"enum\", \"name\": \"EnumType\", \"symbols\": [\"FIRST\", \"SECOND\"]} }]}"); |
| |
| var testRecord = new EnumRecord(); |
| |
| Schema readerSchema = testRecord.Schema; |
| testRecord.enumType = EnumType.SECOND; |
| |
| // serialize |
| var stream = serialize(writerSchema, testRecord); |
| |
| // deserialize |
| var rec2 = deserialize<EnumRecord>(stream, writerSchema, readerSchema); |
| Assert.AreEqual( EnumType.SECOND, rec2.enumType ); |
| } |
| |
| private static S deserialize<S>(Stream ms, Schema ws, Schema rs) where S : class, ISpecificRecord |
| { |
| long initialPos = ms.Position; |
| var r = new SpecificReader<S>(ws, rs); |
| Decoder d = new BinaryDecoder(ms); |
| S output = r.Read(null, d); |
| Assert.AreEqual(ms.Length, ms.Position); // Ensure we have read everything. |
| checkAlternateDeserializers(output, ms, initialPos, ws, rs); |
| return output; |
| } |
| |
| private static void checkAlternateDeserializers<S>(S expected, Stream input, long startPos, Schema ws, Schema rs) where S : class, ISpecificRecord |
| { |
| input.Position = startPos; |
| var reader = new SpecificDatumReader<S>(ws, rs); |
| Decoder d = new BinaryDecoder(input); |
| S output = reader.Read(null, d); |
| Assert.AreEqual(input.Length, input.Position); // Ensure we have read everything. |
| AssertSpecificRecordEqual(expected, output); |
| } |
| |
| private static Stream serialize<T>(Schema ws, T actual) |
| { |
| var ms = new MemoryStream(); |
| Encoder e = new BinaryEncoder(ms); |
| var w = new SpecificWriter<T>(ws); |
| w.Write(actual, e); |
| ms.Flush(); |
| ms.Position = 0; |
| checkAlternateSerializers(ms.ToArray(), actual, ws); |
| return ms; |
| } |
| |
| private static void checkAlternateSerializers<T>(byte[] expected, T value, Schema ws) |
| { |
| var ms = new MemoryStream(); |
| var writer = new SpecificDatumWriter<T>(ws); |
| var e = new BinaryEncoder(ms); |
| writer.Write(value, e); |
| var output = ms.ToArray(); |
| |
| Assert.AreEqual(expected.Length, output.Length); |
| Assert.True(expected.SequenceEqual(output)); |
| } |
| |
| private static void AssertSpecificRecordEqual(ISpecificRecord rec1, ISpecificRecord rec2) |
| { |
| var recordSchema = (RecordSchema) rec1.Schema; |
| for (int i = 0; i < recordSchema.Count; i++) |
| { |
| var rec1Val = rec1.Get(i); |
| var rec2Val = rec2.Get(i); |
| if (rec1Val is ISpecificRecord) |
| { |
| AssertSpecificRecordEqual((ISpecificRecord)rec1Val, (ISpecificRecord)rec2Val); |
| } |
| else if (rec1Val is IList) |
| { |
| var rec1List = (IList) rec1Val; |
| if( rec1List.Count > 0 && rec1List[0] is ISpecificRecord) |
| { |
| var rec2List = (IList) rec2Val; |
| Assert.AreEqual(rec1List.Count, rec2List.Count); |
| for (int j = 0; j < rec1List.Count; j++) |
| { |
| AssertSpecificRecordEqual((ISpecificRecord)rec1List[j], (ISpecificRecord)rec2List[j]); |
| } |
| } |
| else |
| { |
| Assert.AreEqual(rec1Val, rec2Val); |
| } |
| } |
| else if (rec1Val is IDictionary) |
| { |
| var rec1Dict = (IDictionary) rec1Val; |
| var rec2Dict = (IDictionary) rec2Val; |
| Assert.AreEqual(rec2Dict.Count, rec2Dict.Count); |
| foreach (var key in rec1Dict.Keys) |
| { |
| var val1 = rec1Dict[key]; |
| var val2 = rec2Dict[key]; |
| if (val1 is ISpecificRecord) |
| { |
| AssertSpecificRecordEqual((ISpecificRecord)val1, (ISpecificRecord)val2); |
| } |
| else |
| { |
| Assert.AreEqual(val1, val2); |
| } |
| } |
| } |
| else |
| { |
| Assert.AreEqual(rec1Val, rec2Val); |
| } |
| } |
| } |
| } |
| |
| enum EnumType |
| { |
| THIRD, |
| FIRST, |
| SECOND |
| } |
| |
| class EnumRecord : ISpecificRecord |
| { |
| public EnumType enumType { get; set; } |
| public Schema Schema |
| { |
| get |
| { |
| return Schema.Parse("{\"type\":\"record\",\"name\":\"EnumRecord\",\"namespace\":\"Avro.Test\"," + |
| "\"fields\":[{\"name\":\"enumType\",\"type\": { \"type\": \"enum\", \"name\":" + |
| " \"EnumType\", \"symbols\": [\"THIRD\", \"FIRST\", \"SECOND\"]} }]}"); |
| } |
| } |
| |
| public object Get(int fieldPos) |
| { |
| return enumType; |
| } |
| |
| public void Put(int fieldPos, object fieldValue) |
| { |
| enumType = (EnumType)fieldValue; |
| } |
| } |
| } |