private void Equals_DifferenceOnPropertyChange( LogFileMessage message, Expression <Func <LogFileMessage, object> > property, object valueToSet) { // create a copy of the message using the copy constructor // => the copy should equal the message var otherMessage = new LogFileMessage(message); bool isEqual = message.Equals(otherMessage); Assert.True(isEqual); // set property on the message copy SetProperty(otherMessage, property, valueToSet); // the messages should be different now // check using Equals(LogMessage other) isEqual = message.Equals(otherMessage); Assert.False(isEqual); // check using Equals(ILogMessage other) isEqual = message.Equals((ILogMessage)otherMessage); Assert.False(isEqual); }
/// <summary> /// Tests getting a property of the <see cref="LogMessage"/> class. /// </summary> /// <param name="property">Property to test.</param> /// <param name="expectedDefaultValue">Expected default value of the property.</param> /// <param name="protect">true to protect the log message before setting the property; otherwise false.</param> private static void TestPropertyGetter( Expression <Func <LogFileMessage, object> > property, object expectedDefaultValue, bool protect) { var message = new LogFileMessage(); // extract information about the property to work with PropertyInfo propertyInfo; switch (property.Body.NodeType) { case ExpressionType.MemberAccess: propertyInfo = (PropertyInfo)((MemberExpression)property.Body).Member; break; case ExpressionType.Convert: propertyInfo = (PropertyInfo)((MemberExpression)((UnaryExpression)property.Body).Operand).Member; break; default: throw new NotImplementedException(); } // invoke the getter of the property and compare to the expected default value object value = propertyInfo.GetValue(message); Assert.Equal(expectedDefaultValue, value); }
/// <summary> /// Creates a new log message and prepares it for asynchronous initialization. /// (<see cref="LogMessage.IsInitialized"/> is <c>false</c> at first and set to <c>true</c> as soon as the message is initialized). /// </summary> /// <param name="readOnly"> /// true to create a read-only message that can only be set by the returned initializer; /// otherwise false. /// </param> /// <param name="initializer">Receives the initializer that allows to update the log message.</param> /// <returns>The created log message.</returns> public static LogFileMessage CreateWithAsyncInit(bool readOnly, out IFileLogMessageInitializer initializer) { var message = new LogFileMessage { IsInitializedInternal = false, IsAsyncInitPending = true, IsReadOnlyInternal = readOnly }; initializer = message; return(message); }
/// <summary> /// Reads a log message from the specified sqlite reader. /// </summary> /// <param name="reader">Sqlite reader to read from.</param> /// <returns>The read log message (is protected).</returns> private LogFileMessage ReadLogMessage(SQLiteDataReader reader) { // columns in result: // 0 = message id // 1 = timestamp // 2 = timezone offset // 3 = high precision timestamp // 4 = lost message count // 5 = process id // 6 = process name // 7 = application name // 8 = log writer name // 9 = log level name // 10 = has tags // 11 = text name long messageId = reader.GetInt64(0); var timezoneOffset = TimeSpan.FromTicks(reader.GetInt64(2)); var timestamp = new DateTimeOffset(reader.GetInt64(1) + timezoneOffset.Ticks, timezoneOffset); long highPrecisionTimestamp = reader.GetInt64(3); int lostMessageCount = reader.GetInt32(4); int processId = reader.GetInt32(5); string processName = reader.GetString(6); string applicationName = reader.GetString(7); string logWriterName = reader.GetString(8); string logLevelName = reader.GetString(9); bool hasTags = reader.GetBoolean(10); string text = reader.GetString(11); var message = new LogFileMessage().InitWith( messageId, timestamp, highPrecisionTimestamp, lostMessageCount, StringPool.Intern(logWriterName), StringPool.Intern(logLevelName), TagSet.Empty, StringPool.Intern(applicationName), StringPool.Intern(processName), processId, text); // initialize tags, if there are tags associated with the message if (hasTags) { message.Tags = GetTagsOfMessage(messageId); } // protect message from changes message.Protect(); return(message); }
/// <summary> /// Tests setting a property of the <see cref="LogFileMessage"/> class. /// </summary> /// <param name="property">Property to test.</param> /// <param name="expectedDefaultValue">Expected default value of the property.</param> /// <param name="valueToSet">Value of the property after setting it.</param> /// <param name="protect">true to protect the log message before setting the property; otherwise false.</param> private static void TestPropertySetter_WithoutPropertyChanged( Expression <Func <LogFileMessage, object> > property, object expectedDefaultValue, object valueToSet, bool protect) { var message = new LogFileMessage(); // extract information about the property to work with PropertyInfo propertyInfo; switch (property.Body.NodeType) { case ExpressionType.MemberAccess: propertyInfo = (PropertyInfo)((MemberExpression)property.Body).Member; break; case ExpressionType.Convert: propertyInfo = (PropertyInfo)((MemberExpression)((UnaryExpression)property.Body).Operand).Member; break; default: throw new NotImplementedException(); } object value = propertyInfo.GetValue(message); Assert.Equal(expectedDefaultValue, value); if (protect) { message.Protect(); var ex = Assert.Throws <TargetInvocationException>(() => propertyInfo.SetValue(message, valueToSet, null)); Assert.IsType <NotSupportedException>(ex.InnerException); value = propertyInfo.GetValue(message); Assert.Equal(expectedDefaultValue, value); } else { propertyInfo.SetValue(message, valueToSet, null); value = propertyInfo.GetValue(message); if (propertyInfo.PropertyType.IsValueType) { Assert.Equal(valueToSet, value); } else { Assert.Same(valueToSet, value); } } }
private void CreateWithAsyncInit_CreateOnly(bool readOnly) { // create a new message with asynchronous initializer var message = LogFileMessage.CreateWithAsyncInit(readOnly, out _); CheckDefaultState(message, false, readOnly); // check that the message is marked for asynchronous initialization Assert.True(message.IsAsyncInitPending); // check whether the message reflects the desired read-only state Assert.Equal(readOnly, message.IsReadOnly); }
/// <summary> /// Tests whether the specified log message has the expected default state. /// </summary> /// <param name="message">Log message to check.</param> /// <param name="inited">true, if the message is initialized; otherwise false.</param> /// <param name="readOnly">true, if the message is readOnly, otherwise false.</param> private static void CheckDefaultState( LogFileMessage message, bool inited = true, bool readOnly = false) { // check administrative properties Assert.Equal(inited, message.IsInitialized); Assert.Equal(readOnly, message.IsReadOnly); // check message specific properties Assert.Equal(-1, message.Id); Assert.Equal(0, message.LostMessageCount); Assert.Equal(default, message.Timestamp);
private void GetHashCode_DifferenceOnChangedProperty( LogFileMessage message, Expression <Func <LogFileMessage, object> > property, object valueToSet) { // create a copy of the message using the copy constructor // => the copy should have the same hash code int messageHashCode = message.GetHashCode(); var otherMessage = new LogFileMessage(message); int otherMessageHashCode = otherMessage.GetHashCode(); Assert.Equal(messageHashCode, otherMessageHashCode); // set property on the message copy SetProperty(otherMessage, property, valueToSet); // the hash code should be different now otherMessageHashCode = otherMessage.GetHashCode(); Assert.NotEqual(messageHashCode, otherMessageHashCode); }
/// <summary> /// Initializes a new instance of the <see cref="LogFileMessage"/> class copying the specified one. /// </summary> /// <param name="other">Message to copy.</param> public LogFileMessage(LogFileMessage other) : base(other) { mId = other.Id; }
private async Task Protect(bool protectInSameThread, bool withPropertyChanged) { // create a new message var message = new LogFileMessage(); Assert.False(message.IsReadOnly); CheckDefaultState(message, true, false); // ensure that the message is not marked for asynchronous initialization Assert.False(message.IsAsyncInitPending); // prepare data pulling some information out of the event handler SynchronizationContext handlerThreadSynchronizationContext = null; var changedPropertyNames = new List <string>(); var handlerCalledEvent = new ManualResetEventSlim(false); // the handler that is expected to be called on changes void PropertyChangedHandler(object sender, PropertyChangedEventArgs e) { handlerThreadSynchronizationContext = SynchronizationContext.Current; changedPropertyNames.Add(e.PropertyName); handlerCalledEvent.Set(); } // run test in a separate thread that provides a synchronization context that allows to // marshal calls into that thread await mThread.Factory.Run(() => { Assert.NotNull(SynchronizationContext.Current); }); // register the PropertyChanged event if (withPropertyChanged) { await mThread.Factory.Run(() => { message.PropertyChanged += PropertyChangedHandler; }); } // callback that initializes the message void ProtectTest() { // protect the message message.Protect(); // check administrative properties Assert.True(message.IsInitialized); // unchanged Assert.False(message.IsAsyncInitPending); // unchanged Assert.True(message.IsReadOnly); if (protectInSameThread) { if (withPropertyChanged) { // the event handler should have been called only once in the same thread // (the event handler is called directly as the registering thread is the same as the thread raising the event) Assert.True(handlerCalledEvent.IsSet); Assert.Equal(new[] { "IsReadOnly" }, changedPropertyNames.ToArray()); } else { // the event handler should not have been called Assert.False(handlerCalledEvent.IsSet); } } } // protect the message either in the context of the thread that registered the handler // or in a different - the current - thread if (protectInSameThread) { await mThread.Factory.Run(ProtectTest); } else { ProtectTest(); } if (!protectInSameThread) { // the thread registering the event and the thread protecting the message are different if (withPropertyChanged) { // event handler should run in the context of the thread that registered it Assert.True(handlerCalledEvent.Wait(1000)); Assert.Same(mThread.Context.SynchronizationContext, handlerThreadSynchronizationContext); Assert.Equal(new[] { "IsReadOnly" }, changedPropertyNames.ToArray()); } else { // the event handler should not have been called Assert.False(handlerCalledEvent.Wait(1000)); } } }
/// <summary> /// Tests setting a property of the <see cref="LogFileMessage"/> class. /// </summary> /// <param name="property">Property to test.</param> /// <param name="expectedDefaultValue">Expected default value of the property.</param> /// <param name="valueToSet">Value of the property after setting it.</param> /// <param name="protect">true to protect the log message before setting the property; otherwise false.</param> /// <param name="changeInSameThread"> /// true to change the property in the thread that registers the event; /// false to change the property and raise the event in some other thread. /// </param> private async Task TestPropertySetter_WithPropertyChanged( Expression <Func <LogFileMessage, object> > property, object expectedDefaultValue, object valueToSet, bool protect, bool changeInSameThread) { var message = new LogFileMessage(); // extract information about the property to work with PropertyInfo propertyInfo; switch (property.Body.NodeType) { case ExpressionType.MemberAccess: propertyInfo = (PropertyInfo)((MemberExpression)property.Body).Member; break; case ExpressionType.Convert: propertyInfo = (PropertyInfo)((MemberExpression)((UnaryExpression)property.Body).Operand).Member; break; default: throw new NotImplementedException(); } // check default value of the property object value = propertyInfo.GetValue(message); Assert.Equal(expectedDefaultValue, value); // prepare data pulling some information out of the event handler SynchronizationContext handlerThreadSynchronizationContext = null; var changedPropertyNames = new List <string>(); var handlerCalledEvent = new ManualResetEventSlim(false); // the handler that is expected to be called on changes void PropertyChangedHandler(object sender, PropertyChangedEventArgs e) { handlerThreadSynchronizationContext = SynchronizationContext.Current; changedPropertyNames.Add(e.PropertyName); handlerCalledEvent.Set(); } // run test in a separate thread that provides a synchronization context that allows to // marshal calls into that thread await mThread.Factory.Run(() => { Assert.NotNull(SynchronizationContext.Current); }); // register the PropertyChanged event await mThread.Factory.Run(() => { message.PropertyChanged += PropertyChangedHandler; }); // set property if (changeInSameThread) { // registering the event and changing the property is done in the same thread // => the event handler should be called directly await mThread.Factory.Run( () => { if (protect) { // protect the message // => changes the IsReadOnly property to true // => event handler is called directly message.Protect(); Assert.True(handlerCalledEvent.IsSet, "The event handler should have been called directly."); Assert.Same(SynchronizationContext.Current, handlerThreadSynchronizationContext); Assert.Equal(new[] { "IsReadOnly" }, changedPropertyNames.ToArray()); handlerCalledEvent.Reset(); // the message is protected and setting a property should throw an exception var ex = Assert.Throws <TargetInvocationException>(() => propertyInfo.SetValue(message, valueToSet, null)); Assert.IsType <NotSupportedException>(ex.InnerException); // the property value should not have changed value = propertyInfo.GetValue(message); Assert.Equal(expectedDefaultValue, value); // the event handler should not have been called directly Assert.False(handlerCalledEvent.IsSet); } else { // set property propertyInfo.SetValue(message, valueToSet, null); // the property value should not be the same as the set value value = propertyInfo.GetValue(message); if (propertyInfo.PropertyType.IsValueType) { Assert.Equal(valueToSet, value); } else { Assert.Same(valueToSet, value); } // the event handler should have been called directly Assert.True(handlerCalledEvent.IsSet); Assert.Same(SynchronizationContext.Current, handlerThreadSynchronizationContext); Assert.Equal(new[] { propertyInfo.Name }, changedPropertyNames.ToArray()); } }); } else { // the thread registering the event is different from the thread changing the property and raising the event // => the event handler should be scheduled to run on the thread that has registered the event if (protect) { // protect the message // => changes the IsReadOnly property to true // => event handler should have been scheduled to run message.Protect(); Assert.True(handlerCalledEvent.Wait(1000)); Assert.Same(mThread.Context.SynchronizationContext, handlerThreadSynchronizationContext); Assert.Equal(new[] { "IsReadOnly" }, changedPropertyNames.ToArray()); handlerCalledEvent.Reset(); // the message is protected and setting a property should throw an exception var ex = Assert.Throws <TargetInvocationException>(() => propertyInfo.SetValue(message, valueToSet, null)); Assert.IsType <NotSupportedException>(ex.InnerException); // the property value should not have changed value = propertyInfo.GetValue(message); Assert.Equal(expectedDefaultValue, value); // the event handler should neither have been called directly nor should it be scheduled to run Assert.False(handlerCalledEvent.Wait(1000)); } else { // set property propertyInfo.SetValue(message, valueToSet, null); // the property value should not be the same as the set value value = propertyInfo.GetValue(message); if (propertyInfo.PropertyType.IsValueType) { Assert.Equal(valueToSet, value); } else { Assert.Same(valueToSet, value); } // the event handler should run in the context of the thread that registered the event Assert.True(handlerCalledEvent.Wait(1000)); Assert.Same(mThread.Context.SynchronizationContext, handlerThreadSynchronizationContext); Assert.Equal(new[] { propertyInfo.Name }, changedPropertyNames.ToArray()); } } // unregister the PropertyChanged event await mThread.Factory.Run(() => { message.PropertyChanged -= PropertyChangedHandler; }); }
private void Create_Default() { var message = new LogFileMessage(); CheckDefaultState(message); }
private async Task InitWith(bool initInSameThread, bool withPropertyChanged) { // create a new message var message = new LogFileMessage(); Assert.False(message.IsReadOnly); CheckDefaultState(message, true, false); // ensure that the message is not marked for asynchronous initialization Assert.False(message.IsAsyncInitPending); // prepare data pulling some information out of the event handler SynchronizationContext handlerThreadSynchronizationContext = null; var changedPropertyNames = new List <string>(); var handlerCalledEvent = new ManualResetEventSlim(false); // the handler that is expected to be called on changes void PropertyChangedHandler(object sender, PropertyChangedEventArgs e) { handlerThreadSynchronizationContext = SynchronizationContext.Current; changedPropertyNames.Add(e.PropertyName); handlerCalledEvent.Set(); } // run test in a separate thread that provides a synchronization context that allows to // marshal calls into that thread await mThread.Factory.Run(() => { Assert.NotNull(SynchronizationContext.Current); }); // register the PropertyChanged event if (withPropertyChanged) { await mThread.Factory.Run(() => { message.PropertyChanged += PropertyChangedHandler; }); } // callback that initializes the message void InitializeTest() { // init the message message.InitWith( 1, DateTimeOffset.Parse("2020-01-01T12:00:00+01:00"), 2, 3, "Log Writer", "Log Level", new TagSet("Tag"), "Application", "Process", 42, "Some Text"); // check administrative properties Assert.True(message.IsInitialized); Assert.False(message.IsAsyncInitPending); // check message properties Assert.Equal(1, message.Id); Assert.Equal(DateTimeOffset.Parse("2020-01-01T12:00:00+01:00"), message.Timestamp); Assert.Equal(2, message.HighPrecisionTimestamp); Assert.Equal(3, message.LostMessageCount); Assert.Equal("Log Writer", message.LogWriterName); Assert.Equal("Log Level", message.LogLevelName); Assert.Equal(new TagSet("Tag"), message.Tags); Assert.Equal("Application", message.ApplicationName); Assert.Equal("Process", message.ProcessName); Assert.Equal(42, message.ProcessId); Assert.Equal("Some Text", message.Text); if (initInSameThread) { if (withPropertyChanged) { // the event handler should have been called only once in the same thread // (the event handler is called directly as the registering thread is the same as the thread raising the event) Assert.True(handlerCalledEvent.IsSet); Assert.Equal(new string[] { null }, changedPropertyNames.ToArray()); // null => all properties } else { // the event handler should not have been called Assert.False(handlerCalledEvent.IsSet); } } } // initialize the message either in the context of the thread that registered the handler // or in a different - the current - thread if (initInSameThread) { await mThread.Factory.Run(InitializeTest); } else { InitializeTest(); } if (!initInSameThread) { // the thread registering the event and the thread initializing the message are different if (withPropertyChanged) { // event handler should run in the context of the thread that registered it Assert.True(handlerCalledEvent.Wait(1000)); Assert.Same(mThread.Context.SynchronizationContext, handlerThreadSynchronizationContext); Assert.Equal(new string[] { null }, changedPropertyNames.ToArray()); } else { // the event handler should not have been called Assert.False(handlerCalledEvent.Wait(1000)); } } }