blob: 9eae89307d24e19aa009f062797b204e7fbb5f74 [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.Linq;
using Apache.Ignite.Core.Binary;
using Apache.Ignite.Core.Cache.Configuration;
using Apache.Ignite.Core.Impl.Binary;
using NUnit.Framework;
/// <summary>
/// DateTime binary serialization tests.
/// </summary>
public class BinaryDateTimeTest
{
/** */
internal const String FromErrMsg = "FromJavaTicks Error!";
/** */
internal const String ToErrMsg = "ToJavaTicks Error!";
/** */
private const String NotUtcDate =
"DateTime is not UTC. Only UTC DateTime can be used for interop with other platforms.";
/// <summary>
/// Sets up the test fixture.
/// </summary>
[TestFixtureSetUp]
public void FixtureSetUp()
{
Ignition.Start(new IgniteConfiguration(TestUtils.GetTestConfiguration())
{
BinaryConfiguration = new BinaryConfiguration
{
TypeConfigurations = new[]
{
new BinaryTypeConfiguration(typeof(DateTimeObj2))
{
Serializer = new BinaryReflectiveSerializer {ForceTimestamp = true}
}
}
}
});
}
/// <summary>
/// Tears down the test fixture.
/// </summary>
[TestFixtureTearDown]
public void FixtureTearDown()
{
Ignition.StopAll(true);
}
/// <summary>
/// Tests the default behavior: DateTime is written as ISerializable object.
/// </summary>
[Test]
public void TestDefaultBehavior()
{
AssertDateTimeField<DateTimeObj>((o, d) => o.Value = d, o => o.Value, "Value");
}
/// <summary>
/// Tests the ForceTimestamp option in serializer.
/// </summary>
[Test]
public void TestSerializerForceTimestamp()
{
// Check config.
var ser = Ignition.GetIgnite()
.GetConfiguration()
.BinaryConfiguration.TypeConfigurations
.Select(x => x.Serializer)
.OfType<BinaryReflectiveSerializer>()
.Single();
Assert.IsTrue(ser.ForceTimestamp);
AssertTimestampField<DateTimeObj2>((o, d) => o.Value = d, o => o.Value, "Value");
}
/// <summary>
/// Tests TimestampAttribute applied to class members.
/// </summary>
[Test]
public void TestMemberAttributes()
{
AssertTimestampField<DateTimePropertyAttribute>((o, d) => o.Value = d, o => o.Value, "Value");
AssertTimestampField<DateTimeFieldAttribute>((o, d) => o.Value = d, o => o.Value, "Value");
AssertTimestampField<DateTimeQueryFieldAttribute>((o, d) => o.Value = d, o => o.Value, "Value");
}
/// <summary>
/// Tests TimestampAttribute applied to entire class.
/// </summary>
[Test]
public void TestClassAttributes()
{
AssertTimestampField<DateTimeClassAttribute>((o, d) => o.Value = d, o => o.Value, "Value");
AssertTimestampField<DateTimeClassAttribute2>((o, d) => o.Value = d, o => o.Value, "Value");
}
/// <summary>
/// Tests custom timestamp converter that modifies the values by adding one year on write and read.
/// This test verifies that actual converted values are used by Ignite.
/// </summary>
[Test]
public void TestAddYearTimestampConverter()
{
var cfg = new IgniteConfiguration(TestUtils.GetTestConfiguration())
{
AutoGenerateIgniteInstanceName = true,
BinaryConfiguration = new BinaryConfiguration
{
ForceTimestamp = true,
TimestampConverter = new AddYearTimestampConverter()
}
};
var ignite = Ignition.Start(cfg);
var dt = DateTime.UtcNow;
var expected = dt.AddYears(2);
// Key & value.
var cache = ignite.GetOrCreateCache<DateTime, DateTime>(TestUtils.TestName);
cache[dt] = dt;
var resEntry = cache.Single();
Assert.AreEqual(expected, resEntry.Key);
Assert.AreEqual(expected, resEntry.Value);
// Key & value array.
// Object field.
var cache2 = ignite.GetOrCreateCache<DateTimePropertyAttribute, DateTimePropertyAttribute>(
TestUtils.TestName);
cache2.RemoveAll();
var obj = new DateTimePropertyAttribute {Value = dt};
cache2[obj] = obj;
var resEntry2 = cache2.Single();
Assert.AreEqual(expected, resEntry2.Key.Value);
Assert.AreEqual(expected, resEntry2.Value.Value);
// Object array field.
var cache3 = ignite.GetOrCreateCache<DateTimeArr, DateTimeArr>(TestUtils.TestName);
cache3.RemoveAll();
var obj2 = new DateTimeArr {Value = new[]{dt}};
cache3[obj2] = obj2;
cache3[obj2] = obj2;
var resEntry3 = cache3.Single();
Assert.AreEqual(expected, resEntry3.Key.Value.Single());
Assert.AreEqual(expected, resEntry3.Value.Value.Single());
}
/// <summary>
/// Tests custom timestamp converter.
/// </summary>
[Test]
public void TestCustomTimestampConverter()
{
var cfg = new IgniteConfiguration(TestUtils.GetTestConfiguration(name: "ignite-1"))
{
BinaryConfiguration = new BinaryConfiguration
{
ForceTimestamp = true,
TimestampConverter = new TimestampConverter()
}
};
var ignite = Ignition.Start(cfg);
var binary = ignite.GetBinary();
// Check config.
Assert.NotNull(ignite.GetConfiguration().BinaryConfiguration.TimestampConverter);
AssertTimestampField<DateTimeObj2>((o, d) => o.Value = d, o => o.Value, "Value", ignite);
var dt1 = new DateTime(1997, 8, 29, 0, 0, 0, DateTimeKind.Utc);
var ex = Assert.Throws<BinaryObjectException>(() => binary.ToBinary<DateTime>(dt1));
Assert.AreEqual(ToErrMsg, ex.Message);
ex = Assert.Throws<BinaryObjectException>(() => binary.ToBinary<DateTime?[]>(new DateTime?[] {dt1}));
Assert.AreEqual(ToErrMsg, ex.Message);
var dt2 = new DateTime(1997, 8, 4, 0, 0, 0, DateTimeKind.Utc);
ex = Assert.Throws<BinaryObjectException>(() => binary.ToBinary<DateTime>(dt2));
Assert.AreEqual(FromErrMsg, ex.Message);
ex = Assert.Throws<BinaryObjectException>(() => binary.ToBinary<DateTime?[]>(new DateTime?[] {dt2}));
Assert.AreEqual(FromErrMsg, ex.Message);
var datesCache = ignite.CreateCache<DateTime, DateTime>("dates");
var check = new Action<DateTime, DateTime, String>((date1, date2, errMsg) =>
{
ex = Assert.Throws<BinaryObjectException>(() => datesCache.Put(date1, date2), "Timestamp fields should throw an error on non-UTC values");
Assert.AreEqual(errMsg, ex.Message);
});
check.Invoke(DateTime.Now, DateTime.UtcNow, NotUtcDate);
check.Invoke(DateTime.UtcNow, DateTime.Now, NotUtcDate);
check.Invoke(dt1, DateTime.UtcNow, ToErrMsg);
check.Invoke(DateTime.UtcNow, dt1, ToErrMsg);
var now = DateTime.UtcNow;
datesCache.Put(now, dt2);
ex = Assert.Throws<BinaryObjectException>(() => datesCache.Get(now), "Timestamp fields should throw an error on non-UTC values");
Assert.AreEqual(FromErrMsg, ex.Message);
datesCache.Put(now, now);
Assert.AreEqual(now, datesCache.Get(now));
var datesObjCache = ignite.CreateCache<DateTimeObj, DateTimeObj>("datesObj");
check = (date1, date2, errMsg) =>
{
ex = Assert.Throws<BinaryObjectException>(() => datesObjCache.Put(new DateTimeObj {Value = date1}, new DateTimeObj {Value = date2}),
"Timestamp fields should throw an error on non-UTC values");
Assert.AreEqual(errMsg, ex.Message);
};
check.Invoke(DateTime.Now, DateTime.UtcNow, NotUtcDate);
check.Invoke(DateTime.UtcNow, DateTime.Now, NotUtcDate);
check.Invoke(dt1, DateTime.UtcNow, ToErrMsg);
check.Invoke(DateTime.UtcNow, dt1, ToErrMsg);
var nowObj = new DateTimeObj {Value = now};
datesObjCache.Put(nowObj, new DateTimeObj {Value = dt2});
ex = Assert.Throws<BinaryObjectException>(() => datesObjCache.Get(nowObj), "Timestamp fields should throw an error on non-UTC values");
Assert.AreEqual(FromErrMsg, ex.Message);
datesObjCache.Put(nowObj, nowObj);
Assert.AreEqual(nowObj.Value, datesObjCache.Get(nowObj).Value);
}
/// <summary>
/// Asserts that specified field is serialized as DateTime object.
/// </summary>
private static void AssertDateTimeField<T>(Action<T, DateTime> setValue,
Func<T, DateTime> getValue, string fieldName) where T : new()
{
var binary = Ignition.GetIgnite().GetBinary();
foreach (var dateTime in new[] { DateTime.Now, DateTime.UtcNow, DateTime.MinValue, DateTime.MaxValue })
{
var obj = new T();
setValue(obj, dateTime);
var bin = binary.ToBinary<IBinaryObject>(obj);
var res = bin.Deserialize<T>();
Assert.AreEqual(getValue(obj), getValue(res));
Assert.AreEqual(getValue(obj), bin.GetField<IBinaryObject>(fieldName).Deserialize<DateTime>());
Assert.AreEqual("Object", bin.GetBinaryType().GetFieldTypeName(fieldName));
}
}
/// <summary>
/// Asserts that specified field is serialized as Timestamp.
/// </summary>
private static void AssertTimestampField<T>(Action<T, DateTime> setValue,
Func<T, DateTime> getValue, string fieldName, IIgnite ignite = null) where T : new()
{
// Non-UTC DateTime throws.
var binary = ignite != null ? ignite.GetBinary() : Ignition.GetIgnite().GetBinary();
var obj = new T();
setValue(obj, DateTime.Now);
var ex = Assert.Throws<BinaryObjectException>(() => binary.ToBinary<IBinaryObject>(obj),
"Timestamp fields should throw an error on non-UTC values");
Assert.AreEqual(NotUtcDate, ex.Message);
// UTC DateTime works.
setValue(obj, DateTime.UtcNow);
var bin = binary.ToBinary<IBinaryObject>(obj);
var res = bin.Deserialize<T>();
Assert.AreEqual(getValue(obj), getValue(res));
Assert.AreEqual(getValue(obj), bin.GetField<DateTime>(fieldName));
Assert.AreEqual("Timestamp", bin.GetBinaryType().GetFieldTypeName(fieldName));
}
private class DateTimeObj
{
public DateTime Value { get; set; }
}
private class DateTimeObj2
{
public DateTime Value { get; set; }
}
private class DateTimeArr
{
public DateTime[] Value { get; set; }
}
private class DateTimePropertyAttribute
{
[Timestamp]
public DateTime Value { get; set; }
}
private class DateTimeFieldAttribute
{
[Timestamp]
public DateTime Value;
}
private class DateTimeQueryFieldAttribute
{
[QuerySqlField]
public DateTime Value { get; set; }
}
[Timestamp]
private class DateTimeClassAttribute
{
public DateTime Value { get; set; }
}
[Timestamp]
private class DateTimeClassAttribute2
{
public DateTime Value;
}
private class TimestampConverter : ITimestampConverter
{
/** <inheritdoc /> */
public void ToJavaTicks(DateTime date, out long high, out int low)
{
if (date.Year == 1997 && date.Month == 8 && date.Day == 29)
throw new BinaryObjectException(BinaryDateTimeTest.ToErrMsg);
BinaryUtils.ToJavaDate(date, out high, out low);
}
/** <inheritdoc /> */
public DateTime FromJavaTicks(long high, int low)
{
var date = new DateTime(BinaryUtils.JavaDateTicks + high * TimeSpan.TicksPerMillisecond + low / 100,
DateTimeKind.Utc);
if (date.Year == 1997 && date.Month == 8 && date.Day == 4)
throw new BinaryObjectException(BinaryDateTimeTest.FromErrMsg);
return date;
}
}
private class AddYearTimestampConverter : ITimestampConverter
{
/** <inheritdoc /> */
public void ToJavaTicks(DateTime date, out long high, out int low)
{
BinaryUtils.ToJavaDate(date.AddYears(1), out high, out low);
}
/** <inheritdoc /> */
public DateTime FromJavaTicks(long high, int low)
{
var date = new DateTime(BinaryUtils.JavaDateTicks + high * TimeSpan.TicksPerMillisecond + low / 100,
DateTimeKind.Utc);
return date.AddYears(1);
}
}
}
}