228 lines
11 KiB
C#
228 lines
11 KiB
C#
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
|
|
|
namespace System.Data.Entity
|
|
{
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using Xunit;
|
|
|
|
/// <summary>
|
|
/// Helpers for verifying strings that are pulled from a localized resources
|
|
/// </summary>
|
|
public class StringResourceVerifier
|
|
{
|
|
private readonly AssemblyResourceLookup _lookup;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the StringResourceVerifier class.
|
|
/// </summary>
|
|
/// <param name="lookup"> Lookup to be used for locating string resources </param>
|
|
public StringResourceVerifier(AssemblyResourceLookup lookup)
|
|
{
|
|
_lookup = lookup;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines if the supplied string is an instance of the string defined in localized resources
|
|
/// </summary>
|
|
/// <param name="expectedResourceKey"> The key of the resource string to match against </param>
|
|
/// <param name="actualMessage"> String value to be verified </param>
|
|
/// <param name="stringParameters"> Expected values for string.Format placeholders in the resource string If none are supplied then any values for placeholders in the resource string will count as a match </param>
|
|
/// <returns> True if the string matches, false otherwise </returns>
|
|
public bool IsMatch(string expectedResourceKey, string actualMessage, params object[] stringParameters)
|
|
{
|
|
return IsMatch(expectedResourceKey, actualMessage, true, stringParameters);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines if the supplied string is an instance of the string defined in localized resources
|
|
/// </summary>
|
|
/// <param name="expectedResourceKey"> The key of the resource string to match against </param>
|
|
/// <param name="actualMessage"> String value to be verified </param>
|
|
/// <param name="isExactMatch"> Determines whether the exception message must be exact match of the message in the resource file, or just contain it. </param>
|
|
/// <param name="stringParameters"> Expected values for string.Format placeholders in the resource string If none are supplied then any values for placeholders in the resource string will count as a match </param>
|
|
/// <returns> True if the string matches, false otherwise </returns>
|
|
public bool IsMatch(string expectedResourceKey, string actualMessage, bool isExactMatch, params object[] stringParameters)
|
|
{
|
|
string messageFromResources;
|
|
return IsMatch(expectedResourceKey, actualMessage, isExactMatch, stringParameters, out messageFromResources);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verified the supplied string is an instance of the string defined in resources
|
|
/// </summary>
|
|
/// <param name="expectedResourceKey"> The key of the resource string to match against </param>
|
|
/// <param name="actualMessage"> String value to be verified </param>
|
|
/// <param name="stringParameters"> Expected values for string.Format placeholders in the resource string If none are supplied then any values for placeholders in the resource string will count as a match </param>
|
|
public void VerifyMatch(string expectedResourceKey, string actualMessage, params object[] stringParameters)
|
|
{
|
|
VerifyMatch(expectedResourceKey, actualMessage, true, stringParameters);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines if the supplied string is an instance of the string defined in localized resources
|
|
/// If the string in the resource file contains string.Format place holders then the actual message can contain any values for these
|
|
/// </summary>
|
|
/// <param name="expectedResourceKey"> The key of the resource string to match against </param>
|
|
/// <param name="actualMessage"> String value to be verified </param>
|
|
/// <param name="isExactMatch"> Determines whether the exception message must be exact match of the message in the resource file, or just contain it. </param>
|
|
/// <param name="stringParameters"> Expected values for string.Format placeholders in the resource string If none are supplied then any values for placeholders in the resource string will count as a match </param>
|
|
public void VerifyMatch(string expectedResourceKey, string actualMessage, bool isExactMatch, params object[] stringParameters)
|
|
{
|
|
string messageFromResources;
|
|
Assert.True(
|
|
IsMatch(
|
|
expectedResourceKey, actualMessage, isExactMatch, stringParameters,
|
|
out messageFromResources),
|
|
string.Format(
|
|
@"Actual string does not match the message defined in resources.
|
|
Expected:'{0}',
|
|
Actual:'{1}'",
|
|
messageFromResources, actualMessage));
|
|
}
|
|
|
|
private static bool IsMatchWithAnyPlaceholderValues(string expectedMessage, string actualMessage, bool isExactMatch)
|
|
{
|
|
// Find the sections of the Exception message seperated by {x} tags
|
|
var sections = FindMessageSections(expectedMessage);
|
|
|
|
// Check that each section is present in the actual message in correct order
|
|
var indexToCheckFrom = 0;
|
|
foreach (var section in sections)
|
|
{
|
|
// Check if it is present in the actual message
|
|
var index = actualMessage.IndexOf(section, indexToCheckFrom, StringComparison.Ordinal);
|
|
if (index < 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (section.Length == 0
|
|
&& section == sections.Last())
|
|
{
|
|
// If the last section is a zero-length string
|
|
// then this indicates that the placeholder is the
|
|
// last thing in the resource. Thus every section
|
|
// matched and the placeholder takes up the rest of string
|
|
// from the actual message.
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// continue checking from the end of this section
|
|
// (Ensures sections are in the correct order)
|
|
indexToCheckFrom = index + section.Length;
|
|
}
|
|
}
|
|
|
|
// If we reach the end then everything is a match
|
|
return isExactMatch ? indexToCheckFrom == actualMessage.Length : indexToCheckFrom <= actualMessage.Length;
|
|
}
|
|
|
|
private static IEnumerable<string> FindMessageSections(string messageFromResources)
|
|
{
|
|
// Find the start and end index of each section of the string
|
|
var sections = new List<StringSection>();
|
|
|
|
// Start with a section that spans the whole string
|
|
// While there are still place holders shorten the previous section to end at the next { and start a new section from the following }
|
|
sections.Add(
|
|
new StringSection
|
|
{
|
|
StartIndex = 0,
|
|
EndIndex = messageFromResources.Length
|
|
});
|
|
var indexToCheckFrom = 0;
|
|
while (messageFromResources.IndexOf("{", indexToCheckFrom, StringComparison.Ordinal) >= 0)
|
|
{
|
|
// Find the indexes to split the new section around
|
|
var previous = sections.Last();
|
|
var previousEndIndex = messageFromResources.IndexOf("{", indexToCheckFrom, StringComparison.Ordinal);
|
|
var nextStartIndex = messageFromResources.IndexOf("}", previousEndIndex, StringComparison.Ordinal) + 1;
|
|
|
|
// If there are no remaining closing tags then we are done
|
|
if (nextStartIndex == 0)
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// Contents of place holder must be integer
|
|
int temp;
|
|
var intContents =
|
|
int.TryParse(messageFromResources.Substring(previousEndIndex + 1, nextStartIndex - previousEndIndex - 2), out temp);
|
|
|
|
// Place holder must not be escaped (i.e. {{0}})
|
|
var escaped = messageFromResources[previousEndIndex] == '{'
|
|
&& nextStartIndex < messageFromResources.Length
|
|
&& messageFromResources[nextStartIndex] == '}';
|
|
|
|
if (!intContents || escaped)
|
|
{
|
|
indexToCheckFrom++;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Shorten the previous section to end at the {
|
|
previous.EndIndex = previousEndIndex;
|
|
|
|
// Add the remaining string after the } into a new section,
|
|
// even if the '}' is the last character in the string. This
|
|
// helps verification ensure that there is actually a wildcard
|
|
// at this point in the string instead of the string ending
|
|
// without a wildcard.
|
|
sections.Add(
|
|
new StringSection
|
|
{
|
|
StartIndex = nextStartIndex,
|
|
EndIndex = messageFromResources.Length
|
|
});
|
|
indexToCheckFrom = nextStartIndex;
|
|
}
|
|
|
|
// Pull out the sections
|
|
return
|
|
sections.Select(
|
|
s => messageFromResources.Substring(s.StartIndex, s.EndIndex - s.StartIndex).Replace("{{", "{").Replace("}}", "}"));
|
|
}
|
|
|
|
private bool IsMatch(
|
|
string expectedResourceKey, string actualMessage, bool isExactMatch, object[] stringParameters, out string messageFromResources)
|
|
{
|
|
ExceptionHelpers.CheckStringArgumentIsNotNullOrEmpty(expectedResourceKey, "expectedResourceKey");
|
|
ExceptionHelpers.CheckArgumentNotNull(actualMessage, "actualMessage");
|
|
|
|
messageFromResources = _lookup.LookupString(expectedResourceKey);
|
|
|
|
if (stringParameters.Length == 0)
|
|
{
|
|
return IsMatchWithAnyPlaceholderValues(messageFromResources, actualMessage, isExactMatch);
|
|
}
|
|
else
|
|
{
|
|
messageFromResources = string.Format(CultureInfo.InvariantCulture, messageFromResources, stringParameters);
|
|
|
|
return isExactMatch ? actualMessage == messageFromResources : actualMessage.Contains(messageFromResources);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Represents a section of a string
|
|
/// </summary>
|
|
private class StringSection
|
|
{
|
|
/// <summary>
|
|
/// Gets or sets the index the section starts at
|
|
/// </summary>
|
|
public int StartIndex { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the index the section ends at
|
|
/// </summary>
|
|
public int EndIndex { get; set; }
|
|
}
|
|
}
|
|
}
|