Skip to content

Commit

Permalink
Merge pull request unoplatform#16386 from Youssef1313/ci-assert
Browse files Browse the repository at this point in the history
ci: Add CI.Assert helpers
  • Loading branch information
Youssef1313 authored Apr 23, 2024
2 parents 6949140 + 811237a commit 3ecfcd9
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 6 deletions.
4 changes: 4 additions & 0 deletions src/Uno.CrossTargetting.targets
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@
<DefineConstants>$(DefineConstants);IS_CI</DefineConstants>
</PropertyGroup>

<PropertyGroup Condition="'$(CI_Build)'!='' OR '$(TF_BUILD)' == 'true' OR '$(Configuration)'=='Debug'">
<DefineConstants>$(DefineConstants);IS_CI_OR_DEBUG</DefineConstants>
</PropertyGroup>

<Target Name="_UnoOverrideNuget"
AfterTargets="AfterBuild"
DependsOnTargets="BuiltProjectOutputGroup"
Expand Down
172 changes: 172 additions & 0 deletions src/Uno.Foundation/CI.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
#nullable enable

// Mostly based on https://github.com/dotnet/runtime/blob/907eff84ef204a2d71c10e7cd726b76951b051bd/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Debug.cs

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;

namespace System.Diagnostics;

/// <summary>
/// Provides a set of properties and methods for debugging code.
/// </summary>
/// <remarks>
/// This was modified for Uno to be a "CI assert" rather than a "Debug assert".
/// In CI, we build in Release for performance reasons.
/// Still, we want to catch assertion failures.
/// Also, we keep this available on Debug.
/// </remarks>
internal static partial class CI
{
private sealed class CIAssertException : Exception
{
internal CIAssertException(string? message, string? detailMessage) :
base(message + Environment.NewLine + detailMessage)
{
}
}

[Conditional("IS_CI_OR_DEBUG")]
public static void Assert([DoesNotReturnIf(false)] bool condition) =>
Assert(condition, string.Empty, string.Empty);

[Conditional("IS_CI_OR_DEBUG")]
public static void Assert([DoesNotReturnIf(false)] bool condition, string? message) =>
Assert(condition, message, string.Empty);

[Conditional("IS_CI_OR_DEBUG")]
public static void Assert([DoesNotReturnIf(false)] bool condition, [InterpolatedStringHandlerArgument(nameof(condition))] ref AssertInterpolatedStringHandler message) =>
Assert(condition, message.ToStringAndClear());

[Conditional("IS_CI_OR_DEBUG")]
public static void Assert([DoesNotReturnIf(false)] bool condition, string? message, string? detailMessage)
{
if (!condition)
{
Fail(message, detailMessage);
}
}

[Conditional("IS_CI_OR_DEBUG")]
public static void Assert([DoesNotReturnIf(false)] bool condition, [InterpolatedStringHandlerArgument(nameof(condition))] ref AssertInterpolatedStringHandler message, [InterpolatedStringHandlerArgument(nameof(condition))] ref AssertInterpolatedStringHandler detailMessage) =>
Assert(condition, message.ToStringAndClear(), detailMessage.ToStringAndClear());

[Conditional("IS_CI_OR_DEBUG")]
#pragma warning disable CA1305 // Specify IFormatProvider
public static void Assert([DoesNotReturnIf(false)] bool condition, string? message, [StringSyntax(StringSyntaxAttribute.CompositeFormat)] string detailMessageFormat, params object?[] args) =>
Assert(condition, message, string.Format(detailMessageFormat, args));
#pragma warning restore CA1305 // Specify IFormatProvider

[Conditional("IS_CI_OR_DEBUG")]
[DoesNotReturn]
public static void Fail(string? message) =>
Fail(message, string.Empty);

[Conditional("IS_CI_OR_DEBUG")]
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)] // Preserve the frame for debugger
public static void Fail(string? message, string? detailMessage) =>
throw new CIAssertException(message, detailMessage);

/// <summary>Provides an interpolated string handler for CI.Assert that only performs formatting if the assert fails.</summary>
[EditorBrowsable(EditorBrowsableState.Never)]
[InterpolatedStringHandler]
public struct AssertInterpolatedStringHandler
{
/// <summary>The handler we use to perform the formatting.</summary>
private StringBuilder.AppendInterpolatedStringHandler _stringBuilderHandler;
private StringBuilder? _stringBuilder;

/// <summary>Creates an instance of the handler..</summary>
/// <param name="literalLength">The number of constant characters outside of interpolation expressions in the interpolated string.</param>
/// <param name="formattedCount">The number of interpolation expressions in the interpolated string.</param>
/// <param name="condition">The condition Boolean passed to the <see cref="CI"/> method.</param>
/// <param name="shouldAppend">A value indicating whether formatting should proceed.</param>
/// <remarks>This is intended to be called only by compiler-generated code. Arguments are not validated as they'd otherwise be for members intended to be used directly.</remarks>
public AssertInterpolatedStringHandler(int literalLength, int formattedCount, bool condition, out bool shouldAppend)
{
if (condition)
{
_stringBuilderHandler = default;
shouldAppend = false;
}
else
{
// Only used when failing an assert. Additional allocation here doesn't matter; just create a new StringBuilder.
_stringBuilder = new StringBuilder();
_stringBuilderHandler = new StringBuilder.AppendInterpolatedStringHandler(literalLength, formattedCount, _stringBuilder);
shouldAppend = true;
}
}

/// <summary>Extracts the built string from the handler.</summary>
internal string ToStringAndClear()
{
string s = _stringBuilder is StringBuilder sb ?
sb.ToString() :
string.Empty;
_stringBuilderHandler = default;
return s;
}

/// <summary>Writes the specified string to the handler.</summary>
/// <param name="value">The string to write.</param>
public void AppendLiteral(string value) => _stringBuilderHandler.AppendLiteral(value);

/// <summary>Writes the specified value to the handler.</summary>
/// <param name="value">The value to write.</param>
/// <typeparam name="T">The type of the value to write.</typeparam>
public void AppendFormatted<T>(T value) => _stringBuilderHandler.AppendFormatted(value);

/// <summary>Writes the specified value to the handler.</summary>
/// <param name="value">The value to write.</param>
/// <param name="format">The format string.</param>
/// <typeparam name="T">The type of the value to write.</typeparam>
public void AppendFormatted<T>(T value, string? format) => _stringBuilderHandler.AppendFormatted(value, format);

/// <summary>Writes the specified value to the handler.</summary>
/// <param name="value">The value to write.</param>
/// <param name="alignment">Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value.</param>
/// <typeparam name="T">The type of the value to write.</typeparam>
public void AppendFormatted<T>(T value, int alignment) => _stringBuilderHandler.AppendFormatted(value, alignment);

/// <summary>Writes the specified value to the handler.</summary>
/// <param name="value">The value to write.</param>
/// <param name="format">The format string.</param>
/// <param name="alignment">Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value.</param>
/// <typeparam name="T">The type of the value to write.</typeparam>
public void AppendFormatted<T>(T value, int alignment, string? format) => _stringBuilderHandler.AppendFormatted(value, alignment, format);

/// <summary>Writes the specified character span to the handler.</summary>
/// <param name="value">The span to write.</param>
public void AppendFormatted(ReadOnlySpan<char> value) => _stringBuilderHandler.AppendFormatted(value);

/// <summary>Writes the specified string of chars to the handler.</summary>
/// <param name="value">The span to write.</param>
/// <param name="alignment">Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value.</param>
/// <param name="format">The format string.</param>
public void AppendFormatted(ReadOnlySpan<char> value, int alignment = 0, string? format = null) => _stringBuilderHandler.AppendFormatted(value, alignment, format);

/// <summary>Writes the specified value to the handler.</summary>
/// <param name="value">The value to write.</param>
public void AppendFormatted(string? value) => _stringBuilderHandler.AppendFormatted(value);

/// <summary>Writes the specified value to the handler.</summary>
/// <param name="value">The value to write.</param>
/// <param name="alignment">Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value.</param>
/// <param name="format">The format string.</param>
public void AppendFormatted(string? value, int alignment = 0, string? format = null) => _stringBuilderHandler.AppendFormatted(value, alignment, format);

/// <summary>Writes the specified value to the handler.</summary>
/// <param name="value">The value to write.</param>
/// <param name="alignment">Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value.</param>
/// <param name="format">The format string.</param>
public void AppendFormatted(object? value, int alignment = 0, string? format = null) => _stringBuilderHandler.AppendFormatted(value, alignment, format);
}
}
8 changes: 4 additions & 4 deletions src/Uno.UI/UI/Xaml/Controls/TextBox/TextBox.skia.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ private void TrySetCurrentlyTyping(bool newValue)
}
else
{
global::System.Diagnostics.Debug.Assert(!_isSkiaTextBox || _selection.length == 0);
global::System.Diagnostics.CI.Assert(!_isSkiaTextBox || _selection.length == 0);
_historyIndex++;
_history.RemoveAllAt(_historyIndex);
_history.Add(new HistoryRecord(
Expand Down Expand Up @@ -1017,7 +1017,7 @@ protected override void OnDoubleTapped(DoubleTappedRoutedEventArgs args)
}

var lines = DisplayBlockInlines.GetLineIntervals();
global::System.Diagnostics.Debug.Assert(lines.Count > 0);
global::System.Diagnostics.CI.Assert(lines.Count > 0);

var end = selectionStart + selectionLength;

Expand Down Expand Up @@ -1355,7 +1355,7 @@ public void Undo()
case SentinelAction:
break;
default:
global::System.Diagnostics.Debug.Assert(false, "TextBoxActions are not exhaustively switch-matched.");
global::System.Diagnostics.CI.Assert(false, "TextBoxActions are not exhaustively switch-matched.");
break;
}
_clearHistoryOnTextChanged = true;
Expand Down Expand Up @@ -1393,7 +1393,7 @@ public void Redo()
case SentinelAction:
break;
default:
global::System.Diagnostics.Debug.Assert(false, "TextBoxActions are not exhaustively switch-matched.");
global::System.Diagnostics.CI.Assert(false, "TextBoxActions are not exhaustively switch-matched.");
break;
}
_clearHistoryOnTextChanged = true;
Expand Down
4 changes: 2 additions & 2 deletions src/Uno.UI/UI/Xaml/Documents/InlineCollection.skia.cs
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ internal Size Measure(Size availableSize, float defaultLineHeight)
else if (start + length < end)
{
// equivalently condition would be `trailingSpaces < segment.TrailingSpaces`
global::System.Diagnostics.Debug.Assert(end - (start + length) == segment.TrailingSpaces - trailingSpaces);
global::System.Diagnostics.CI.Assert(end - (start + length) == segment.TrailingSpaces - trailingSpaces);

// We could fit the segment, but not all of the trailing spaces
// These remaining trailing spaces will never be rendered, that's
Expand Down Expand Up @@ -294,7 +294,7 @@ internal Size Measure(Size availableSize, float defaultLineHeight)
}

// By this point, we must have at least dealt with the leading spaces.
global::System.Diagnostics.Debug.Assert(start >= segment.LeadingSpaces);
global::System.Diagnostics.CI.Assert(start >= segment.LeadingSpaces);

if (x > 0)
{
Expand Down

0 comments on commit 3ecfcd9

Please sign in to comment.