blob: e3249459dd3392aa3ddd75a999d7f1f69702239f [file] [log] [blame]
/**
* 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;
}
}
}