File: Sdk\Exceptions\RaisesException.cs
Web Access
Project: src\src\Microsoft.DotNet.XUnitAssert\src\Microsoft.DotNet.XUnitAssert.csproj (xunit.assert)
#if XUNIT_NULLABLE
#nullable enable
#endif

using System;
using System.Linq;
using System.Reflection;

namespace Xunit.Sdk
{
	/// <summary>
	/// Exception thrown when code unexpectedly fails to raise an event.
	/// </summary>
#if XUNIT_VISIBILITY_INTERNAL
	internal
#else
	public
#endif
	class RaisesException : XunitException
	{
#if XUNIT_NULLABLE
		readonly string? stackTrace = null;
#else
		readonly string stackTrace = null;
#endif

		/// <summary>
		/// Creates a new instance of the <see cref="RaisesException" /> class. Call this constructor
		/// when no event was raised.
		/// </summary>
		/// <param name="expected">The type of the event args that was expected</param>
		public RaisesException(Type expected)
			: base("(No event was raised)")
		{
			Expected = ConvertToSimpleTypeName(expected.GetTypeInfo());
			Actual = "(No event was raised)";
		}

		/// <summary>
		/// Creates a new instance of the <see cref="RaisesException" /> class. Call this constructor
		/// when an
		/// </summary>
		/// <param name="expected"></param>
		/// <param name="actual"></param>
		public RaisesException(Type expected, Type actual)
			: base("(Raised event did not match expected event)")
		{
			Expected = ConvertToSimpleTypeName(expected.GetTypeInfo());
			Actual = ConvertToSimpleTypeName(actual.GetTypeInfo());
		}

		/// <summary>
		/// Gets the actual value.
		/// </summary>
		public string Actual { get; }

		/// <summary>
		/// Gets the expected value.
		/// </summary>
		public string Expected { get; }

		/// <summary>
		/// Gets a message that describes the current exception. Includes the expected and actual values.
		/// </summary>
		/// <returns>The error message that explains the reason for the exception, or an empty string("").</returns>
		/// <filterpriority>1</filterpriority>
		public override string Message =>
			$"{base.Message}{Environment.NewLine}{Expected ?? "(null)"}{Environment.NewLine}{Actual ?? "(null)"}";

		/// <summary>
		/// Gets a string representation of the frames on the call stack at the time the current exception was thrown.
		/// </summary>
		/// <returns>A string that describes the contents of the call stack, with the most recent method call appearing first.</returns>
#if XUNIT_NULLABLE
		public override string? StackTrace => stackTrace ?? base.StackTrace;
#else
		public override string StackTrace => stackTrace ?? base.StackTrace;
#endif

		static string ConvertToSimpleTypeName(TypeInfo typeInfo)
		{
			if (!typeInfo.IsGenericType)
				return typeInfo.Name;

			var simpleNames = typeInfo.GenericTypeArguments.Select(type => ConvertToSimpleTypeName(type.GetTypeInfo()));
			var backTickIdx = typeInfo.Name.IndexOf('`');
			if (backTickIdx < 0)
				backTickIdx = typeInfo.Name.Length;  // F# doesn't use backticks for generic type names

			return $"{typeInfo.Name.Substring(0, backTickIdx)}<{string.Join(", ", simpleNames)}>";
		}
	}
}