/// <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 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; }); }