// Copyright Keysight Technologies 2012-2019
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at http://mozilla.org/MPL/2.0/.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Xml.Serialization;
using NUnit.Framework;
namespace OpenTap.Engine.UnitTests.UnitTestUtils
{
///
/// This class can test INotifyPropertyChanged types and make sure that the setters and getters behave correspondingly.
/// PropertyChanged events should always refer to an EXISTING property.
/// The event should NOT be invoked if the value is set to the current value.
///
[TestFixture]
public class ValidatingObjectConformance
{
static bool validProp(PropertyInfo prop)
{
if (!(prop.CanRead && prop.CanWrite))
{
return false;
}
if (prop.HasAttribute())
{
return false;
}
if (prop.HasAttribute())
{
if (prop.GetAttribute().Browsable == false)
{
return false;
}
}
return true;
}
static object GetUniqueValue(Type genType)
{
try
{
Type finalType = genType;
if (genType.IsPrimitive)
{
if (genType.IsEnum)
{
var vals = Enum.GetValues(genType);
if (vals.Length > 1)
{
return vals.GetValue(1);
}
return vals.GetValue(0);
}
if (genType == typeof(string))
{
return "..";
}
//126 random value. The works with most types.
return Convert.ChangeType(126, genType);
}
else if (genType.IsInterface || genType.IsAbstract)
{
var iface = PluginManager.GetPlugins(genType)
.FirstOrDefault(item => !item.IsInterface);
finalType = iface;
}
else if (genType == typeof(string))
{
return new Guid().ToString();
}
else if (genType.IsArray)
{
var elemType = genType.GetElementType();
var outarr = Array.CreateInstance(elemType, 1);
outarr.SetValue(GetUniqueValue(elemType), 0);
return outarr;
}
return Activator.CreateInstance(finalType);
}
catch (Exception)
{
return null;
}
}
static bool propertyChangedOk(Type holder, PropertyChangedEventArgs args)
{
string propStr = args.PropertyName;
if (String.IsNullOrEmpty(propStr))
{
return true;
}
foreach (string subStr in propStr.Split(','))
{
var prop = holder.GetProperty(subStr);
if (null == prop || !prop.CanRead)
{
return false;
}
}
return true;
}
public class ErrorPropException : Exception
{
public ErrorPropException(string msg)
: base(msg)
{
}
}
public class WarningPropException : ErrorPropException
{
public WarningPropException(string msg) : base(msg)
{
}
}
static public List testTypeProperties(Type tp)
{
List ex = new List();
if (!tp.IsClass || !tp.HasInterface())
{
return ex;
}
var properties = tp.GetProperties(BindingFlags.Instance
| BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.NonPublic)
.Where(validProp).ToArray();
if (properties.Length == 0)
{
return ex;
}
INotifyPropertyChanged instance;
try
{
instance = (INotifyPropertyChanged)Activator.CreateInstance(tp);
}
catch (Exception)
{
return ex;
}
instance.PropertyChanged += (s, e) =>
{
if (!propertyChangedOk(tp, e))
{
throw new ErrorPropException(String.Format("Property '{0}' does not exist on type '{1}'.", e.PropertyName, tp.FullName));
}
};
bool shouldNotChange = false;
instance.PropertyChanged += (s, e) =>
{
if (shouldNotChange)
{
throw new WarningPropException(String.Format("Event invoked even when value is the same. Property: '{0}' Type: '{1}'", e.PropertyName, tp.FullName));
}
};
foreach (var prop in properties)
{
try
{
prop.SetValue(instance, GetUniqueValue(prop.PropertyType), null);
//Test set/get same value
shouldNotChange = true;
prop.SetValue(instance, prop.GetValue(instance, null), null);
shouldNotChange = false;
}
catch (Exception _ex)
{
if (_ex.InnerException is ErrorPropException)
{
ex.Add(_ex.InnerException);
}
}
}
return ex;
}
[Test, Ignore("This will generate false positives.")]
public void TestINotifyPropertyChanged()
{
List exceptions = new List();
foreach (var type in PluginManager.GetPlugins())
{
exceptions.AddRange(testTypeProperties(type));
}
if (exceptions.Any())
{
var warnings = exceptions.Where(ex => ex is WarningPropException);
warnings.ForEach(ex =>
{
Debug.Print("Warning: " + ex.Message);
});
var errors = exceptions.Where(ex => !(ex is WarningPropException)).ToList();
if (errors.Count > 0)
{
throw new AggregateException(errors);
}
}
}
public static void TestAssembly(Assembly asm)
{
List exceptions = new List();
foreach (var type in asm.GetTypes())
{
exceptions.AddRange(testTypeProperties(type));
}
if (exceptions.Any())
{
var warnings = exceptions.Where(ex => ex is WarningPropException);
warnings.ForEach(ex =>
{
Debug.Print("Warning: " + ex.Message);
});
var errors = exceptions.Where(ex => !(ex is WarningPropException)).ToList();
if (errors.Count > 0)
{
throw new AggregateException(errors);
}
}
}
}
}