blob: e761a57a11488398a9ef098f5714ceeabdb06c94 [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.
*/
namespace Apache.Ignite.Core.Tests.Binary
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using Apache.Ignite.Core.Binary;
using Apache.Ignite.Core.Impl;
using Apache.Ignite.Core.Impl.Binary;
using Apache.Ignite.Core.Impl.Binary.IO;
using NUnit.Framework;
/// <summary>
/// Contains tests for binary type structure.
/// </summary>
[TestFixture]
public class BinaryStructureTest
{
/** Repeat count. */
public static readonly int RepeatCnt = 10;
/** Objects per mode. */
public static readonly int ObjectsPerMode = 5;
/// <summary>
/// Test object write with different structures.
/// </summary>
[Test]
public void TestStructure()
{
for (int i = 1; i <= RepeatCnt; i++)
{
Console.WriteLine(">>> Iteration started: " + i);
// 1. Generate and shuffle objects.
IList<BranchedType> objs = new List<BranchedType>();
for (int j = 0; j < 6 * ObjectsPerMode; j++)
objs.Add(new BranchedType((j%6) + 1));
objs = IgniteUtils.Shuffle(objs);
// 2. Create new marshaller.
BinaryTypeConfiguration typeCfg = new BinaryTypeConfiguration(typeof(BranchedType));
BinaryConfiguration cfg = new BinaryConfiguration
{
TypeConfigurations = new List<BinaryTypeConfiguration> { typeCfg }
};
Marshaller marsh = new Marshaller(cfg);
// 3. Marshal all data and ensure deserialized object is fine.
// Use single stream to test object offsets
using (var stream = new BinaryHeapStream(128))
{
var writer = marsh.StartMarshal(stream);
foreach (var obj in objs)
{
Console.WriteLine(">>> Write object [mode=" + obj.mode + ']');
writer.WriteObject(obj);
}
stream.Seek(0, SeekOrigin.Begin);
var reader = marsh.StartUnmarshal(stream);
foreach (var obj in objs)
{
var other = reader.ReadObject<BranchedType>();
Assert.IsTrue(obj.Equals(other));
}
}
Console.WriteLine();
// 4. Ensure that all fields are recorded.
var desc = marsh.GetDescriptor(typeof (BranchedType));
CollectionAssert.AreEquivalent(new[] {"mode", "f2", "f3", "f4", "f5", "f6", "f7", "f8"},
desc.WriterTypeStructure.FieldTypes.Keys);
}
}
/// <summary>
/// Tests that nested raw object does not inherit outer schema.
/// </summary>
[Test]
public void TestNestedRaw()
{
var marsh = new Marshaller(new BinaryConfiguration(typeof(RawContainer), typeof(RawNested)));
var obj = new RawContainer {Int = 3, Raw = new RawNested {Int = 5}};
var res = marsh.Unmarshal<RawContainer>(marsh.Marshal(obj));
Assert.AreEqual(obj.Int, res.Int);
Assert.AreEqual(0, res.Raw.Int); // Int is not written and can't be read.
}
/// <summary>
/// Tests that nested object schemas do not interfere.
/// </summary>
[Test]
public void TestNested()
{
var marsh = new Marshaller(new BinaryConfiguration(typeof(Container), typeof(Nested)));
var obj = new Container
{
Foo = 2,
Bar = 4,
Nested = new Nested
{
Baz = 3,
Qux = 5
}
};
var res = marsh.Unmarshal<Container>(marsh.Marshal(obj));
Assert.AreEqual(2, res.Foo);
Assert.AreEqual(4, res.Bar);
Assert.AreEqual(3, res.Nested.Baz);
Assert.AreEqual(5, res.Nested.Qux);
}
/// <summary>
/// Tests a combination of changing field order and some fields being added or removed.
/// </summary>
[Test]
public void TestChangingFieldOrderAndPresence()
{
// Create two different binary structure paths, than make one of them longer to defeat the optimization.
TestChangingFieldOrderAndPresence(new[] {1, 0}, new[] {0}, new[] {0, 1});
// Growing path.
TestChangingFieldOrderAndPresence(new[] {1}, new[] {1, 2, 3, 4}, new[] {1, 2, 3, 4, 5, 6});
// Shrinking path.
TestChangingFieldOrderAndPresence(new[] {1}, new[] {1, 2, 3, 4}, new[] {1, 2, 3}, new[] {1, 2});
}
/// <summary>
/// Tests specified field combination.
/// </summary>
private static void TestChangingFieldOrderAndPresence(params int[][] fieldSequences)
{
var marsh = new Marshaller(new BinaryConfiguration(typeof(CustomFieldOrder)));
foreach (var fields in fieldSequences)
{
CustomFieldOrder.Fields = fields;
marsh.Marshal(new CustomFieldOrder());
}
}
/// <summary>
/// Tests object with random order and presence of fields.
/// </summary>
[Test]
public void TestRandomFieldOrderAndPresence()
{
var marsh = new Marshaller(new BinaryConfiguration(typeof(RandomFieldOrder)));
var obj = new RandomFieldOrder();
for (var i = 0; i < 1000; i++)
{
var bytes = marsh.Marshal(obj);
marsh.Unmarshal<RandomFieldOrder>(bytes);
}
}
/// <summary>
/// Runs write/read test in multiple threads, using random field order to create lots of schemas.
/// </summary>
[Test]
public void TestMultithreadedRandomFields()
{
var marsh = new Marshaller(new BinaryConfiguration(typeof(RandomFieldOrder)));
var obj = new RandomFieldOrder();
TestUtils.RunMultiThreaded(() =>
{
var bytes = marsh.Marshal(obj);
marsh.Unmarshal<RandomFieldOrder>(bytes);
}, Environment.ProcessorCount, 5);
}
/// <summary>
/// Test serializing nested binarizable objects with different schemas.
/// </summary>
[Test]
public void TestSerializeNestedStructureWithDifferentSchemas()
{
var marsh = new Marshaller(new BinaryConfiguration(typeof(Item)));
var simple = new Item(new List<string> { "f1", "f2" });
var nested = new Item(new List<string> { "f3" },
new List<Item> {new Item(new List<string> { "f1" })});
var simpleMarsh = marsh.Marshal(simple);
var nestedMarsh = marsh.Marshal(nested);
Assert.AreEqual(new List<string> {"f1", "f2"}, marsh.Unmarshal<Item>(simpleMarsh).FieldNames);
Assert.IsEmpty(marsh.Unmarshal<Item>(simpleMarsh).Children);
Assert.AreEqual(new List<string> { "f3" }, marsh.Unmarshal<Item>(nestedMarsh).FieldNames);
Assert.AreEqual(1, marsh.Unmarshal<Item>(nestedMarsh).Children.Count);
Assert.AreEqual(new List<string> { "f1" }, marsh.Unmarshal<Item>(nestedMarsh).Children[0].FieldNames);
Assert.IsEmpty(marsh.Unmarshal<Item>(nestedMarsh).Children[0].Children);
}
}
/// <summary>
/// A composite structure consisting of the specified fields and optional children of the same
/// <see cref="Item"/> type.
/// </summary>
class Item : IBinarizable
{
private List<string> _fieldNames;
private List<Item> _children;
public Item(List<string> fieldNames, List<Item> children)
{
_fieldNames = fieldNames;
_children = children;
}
public Item(List<string> fieldNames)
: this(fieldNames, new List<Item>())
{
}
public List<string> FieldNames
{
get { return _fieldNames; }
}
public List<Item> Children
{
get { return _children; }
}
public void WriteBinary(IBinaryWriter writer)
{
writer.WriteCollection(nameof(_children), _children);
writer.WriteCollection(nameof(_fieldNames), _fieldNames);
foreach (var name in _fieldNames)
{
writer.WriteString(name, name);
}
}
public void ReadBinary(IBinaryReader reader)
{
_children = (List<Item>)reader.ReadCollection(
nameof(_children),
size => new List<Item>(size),
(collection, obj) => ((List<Item>) collection).Add((Item) obj));
_fieldNames = (List<string>)reader.ReadCollection(
nameof(_fieldNames),
size => new List<string>(size),
(collection, obj) => ((List<string>) collection).Add((string) obj));
foreach (var name in _fieldNames)
{
Assert.AreEqual(name, reader.ReadString(name));
}
}
}
[SuppressMessage("ReSharper", "InconsistentNaming")]
public class BranchedType : IBinarizable
{
public int mode;
public int f2;
public int f3;
public int f4;
public int f5;
public int f6;
public int f7;
public int f8;
public BranchedType(int mode)
{
this.mode = mode;
switch (mode)
{
case 1:
f2 = 2;
break;
case 2:
f2 = 2;
f3 = 3;
f4 = 4;
break;
case 3:
f2 = 2;
f3 = 3;
f5 = 5;
break;
case 4:
f2 = 2;
f3 = 3;
f5 = 5;
f6 = 6;
break;
case 5:
f2 = 2;
f3 = 3;
f7 = 7;
break;
case 6:
f8 = 8;
break;
}
}
public void WriteBinary(IBinaryWriter writer)
{
writer.WriteInt("mode", mode);
switch (mode)
{
case 1:
writer.WriteInt("f2", f2);
break;
case 2:
writer.WriteInt("f2", f2);
writer.WriteInt("f3", f3);
writer.WriteInt("f4", f4);
break;
case 3:
writer.WriteInt("f2", f2);
writer.WriteInt("f3", f3);
writer.WriteInt("f5", f5);
break;
case 4:
writer.WriteInt("f2", f2);
writer.WriteInt("f3", f3);
writer.WriteInt("f5", f5);
writer.WriteInt("f6", f6);
break;
case 5:
writer.WriteInt("f2", f2);
writer.WriteInt("f3", f3);
writer.WriteInt("f7", f7);
break;
case 6:
writer.WriteInt("f8", f8);
break;
}
}
public void ReadBinary(IBinaryReader reader)
{
mode = reader.ReadInt("mode");
switch (mode)
{
case 1:
f2 = reader.ReadInt("f2");
break;
case 2:
f4 = reader.ReadInt("f4");
f3 = reader.ReadInt("f3");
f2 = reader.ReadInt("f2");
break;
case 3:
f5 = reader.ReadInt("f5");
f2 = reader.ReadInt("f2");
f3 = reader.ReadInt("f3");
break;
case 4:
f5 = reader.ReadInt("f5");
f6 = reader.ReadInt("f6");
f2 = reader.ReadInt("f2");
f3 = reader.ReadInt("f3");
break;
case 5:
f3 = reader.ReadInt("f3");
f2 = reader.ReadInt("f2");
f7 = reader.ReadInt("f7");
break;
case 6:
f8 = reader.ReadInt("f8");
break;
}
}
public bool Equals(BranchedType other)
{
return mode == other.mode && f2 == other.f2 && f3 == other.f3 && f4 == other.f4 && f5 == other.f5 &&
f6 == other.f6 && f7 == other.f7 && f8 == other.f8;
}
}
public class RawContainer : IBinarizable
{
public int Int { get; set; }
public RawNested Raw { get; set; }
public void WriteBinary(IBinaryWriter writer)
{
writer.WriteInt("int", Int);
writer.WriteObject("raw", Raw);
}
public void ReadBinary(IBinaryReader reader)
{
Int = reader.ReadInt("int");
Raw = reader.ReadObject<RawNested>("raw");
}
}
public class RawNested : IBinarizable
{
public int Int { get; set; }
public void WriteBinary(IBinaryWriter writer)
{
// Write only raw data.
writer.GetRawWriter().WriteIntArray(Enumerable.Range(1, 100).ToArray());
}
public void ReadBinary(IBinaryReader reader)
{
// Attempt to read even though we did not write fields.
// If schema is carried over, there will be a broken result.
Int = reader.ReadInt("int");
}
}
public class Container : IBinarizable
{
public int Foo { get; set; }
public int Bar { get; set; }
public Nested Nested { get; set; }
public void WriteBinary(IBinaryWriter writer)
{
writer.WriteInt("foo", Foo);
writer.WriteInt("bar", Bar);
writer.WriteObject("nested", Nested);
}
public void ReadBinary(IBinaryReader reader)
{
// Read in reverse order to defeat structure optimization.
Bar = reader.ReadInt("bar");
Foo = reader.ReadInt("foo");
Nested = reader.ReadObject<Nested>("nested");
}
}
public class Nested : IBinarizable
{
public int Baz { get; set; }
public int Qux { get; set; }
public void WriteBinary(IBinaryWriter writer)
{
writer.WriteInt("baz", Baz);
writer.WriteInt("qux", Qux);
}
public void ReadBinary(IBinaryReader reader)
{
// Read in reverse order to defeat structure optimization.
Qux = reader.ReadInt("qux");
Baz = reader.ReadInt("baz");
}
}
public class RandomFieldOrder : IBinarizable
{
public const int FieldCount = 5;
public void WriteBinary(IBinaryWriter writer)
{
var fieldNames = GetRandomOrderFieldNames().ToArray();
foreach (var fieldName in fieldNames)
{
writer.WriteString(fieldName, fieldName);
}
}
public void ReadBinary(IBinaryReader reader)
{
var fieldNames = GetRandomOrderFieldNames().ToArray();
foreach (var fieldName in fieldNames)
{
var fieldValue = reader.ReadString(fieldName);
if (fieldValue != null)
{
Assert.AreEqual(fieldName, fieldValue);
}
}
}
private static IEnumerable<string> GetRandomOrderFieldNames()
{
return Enumerable.Range(0, FieldCount).Select(x => "Field_" + x).OrderBy(_ => Guid.NewGuid())
.Skip(IgniteUtils.ThreadLocalRandom.Next(FieldCount));
}
}
public class CustomFieldOrder : IBinarizable
{
public static int[] Fields;
public void WriteBinary(IBinaryWriter writer)
{
foreach (var field in Fields)
{
var fieldName = "Field_" + field;
writer.WriteString(fieldName, fieldName);
}
}
public void ReadBinary(IBinaryReader reader)
{
foreach (var field in Fields)
{
var fieldName = "Field_" + field;
var fieldValue = reader.ReadString(fieldName);
if (fieldValue != null)
{
Assert.AreEqual(fieldName, fieldValue);
}
}
}
}
}