1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
|
// 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; }
}
}
}
|