--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Reflection;
+using System.Text;
+using System.Text.RegularExpressions;
+using NUnit.Framework;
+
+namespace MonoTests.System {
+ public class StringTester {
+ private const string ext = ".txt";
+ public static string Location = "./StringTests/";
+ public static bool CreateMode = false;
+
+ private static readonly Dictionary<string, Asserts> asserts = new Dictionary<string, Asserts> ();
+
+ public static void Assert (string id, string actual, string message)
+ {
+ string testFullName = GetTestMethodName ();
+
+ Asserts testAsserts;
+ if (!asserts.TryGetValue (testFullName, out testAsserts)) {
+ testAsserts = new Asserts ();
+
+ if (!CreateMode) {
+ string filePath = Path.GetFullPath (Path.Combine (Location, testFullName + ext));
+ if (!File.Exists (filePath)) {
+ NUnit.Framework.Assert.Ignore (filePath + " does not exist. \n" +
+ "The file should be generated by running on .NET the same test with StringTester.CreateMode = true.");
+ }
+
+ testAsserts.Load (filePath);
+ }
+
+ asserts.Add (testFullName, testAsserts);
+ }
+
+ if (CreateMode) {
+ testAsserts.AddExpected (id, actual);
+ return;
+ }
+
+ string expected = testAsserts.GetExpected (id);
+ NUnit.Framework.Assert.AreEqual (expected, actual, message);
+ }
+
+ public static void Save ()
+ {
+ if (!CreateMode)
+ return;
+
+ foreach (var test in asserts)
+ test.Value.Save (Path.Combine (Location, test.Key + ext));
+ }
+
+ public static string GetTestMethodName ()
+ {
+ var stackTrace = new StackTrace ();
+ foreach (StackFrame stackFrame in stackTrace.GetFrames ()) {
+ MethodBase methodBase = stackFrame.GetMethod ();
+ Object [] attributes = methodBase.GetCustomAttributes (typeof (TestAttribute), false);
+ if (attributes.Length >= 1)
+ return methodBase.DeclaringType.FullName + "." + methodBase.Name;
+ }
+ return "Not called from a test method";
+ }
+
+ public class Asserts {
+ private const string escapes = "\n\t\0\r\\";
+ private const string unescapes = @"\n\t\0\r\\";
+ private static readonly Regex regex = new Regex (@"\\u(?<Value>[a-zA-Z0-9]{4})", RegexOptions.Compiled);
+ private readonly Dictionary<string, string> values = new Dictionary<string, string> ();
+
+ public void AddExpected (string id, string value)
+ {
+ values.Add (id, value);
+ }
+
+ public string GetExpected (string id)
+ {
+ return values [id];
+ }
+
+ public void Save (string filePath)
+ {
+ string dir = Path.GetDirectoryName (filePath);
+ if (!Directory.Exists (dir))
+ Directory.CreateDirectory (dir);
+
+ var sw = new StreamWriter (filePath, false, Encoding.UTF8);
+
+ foreach (var kv in values) {
+ sw.WriteLine (Escape (kv.Key));
+ sw.WriteLine (Escape (kv.Value));
+ }
+
+ sw.Close ();
+ }
+
+ public void Load (string filePath)
+ {
+ if (!File.Exists (filePath))
+ return;
+
+ var sr = new StreamReader (filePath, Encoding.UTF8);
+
+ while (sr.Peek () > 0) {
+ string id = Unescape (sr.ReadLine ());
+
+ if (sr.Peek () == 0)
+ break;
+
+ string value = Unescape (sr.ReadLine ());
+ values.Add (id, value);
+ }
+
+ sr.Close ();
+ }
+
+ private static string Escape (string str)
+ {
+ var sb = new StringBuilder ();
+ foreach (char c in str) {
+ int i = escapes.IndexOf (c);
+ if (i != -1) {
+ sb.Append (unescapes.Substring (i*2, 2));
+ continue;
+ }
+
+ if (c >= 0x7f || c < 0x20) {
+ sb.Append (string.Format (@"\u{0:x4}", (int) c));
+ continue;
+ }
+
+ sb.Append (c);
+ }
+ return sb.ToString ();
+ }
+
+ private static string Unescape (string str)
+ {
+ for (int i = 0; i < escapes.Length; i++)
+ str = str.Replace (unescapes.Substring (i*2, 2), "" + escapes [i]);
+
+ return regex.Replace (str,
+ m => ((char) int.Parse (m.Groups ["Value"].Value, NumberStyles.HexNumber)).ToString ());
+ }
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+using System;
+using System.Reflection;
+using System.Text;
+using NUnit.Framework;
+
+namespace MonoTests.System {
+ [TestFixture]
+ public class UriPermutationsTest {
+
+ // Set this to true to generate the expected values
+ // The tests should run first on .NET with CreateMode = true
+ // The generated files should then be used when running the tests in Mono with CreateMode = false
+ private const bool createMode = true;
+
+ private const string location = "./Test/System/UriPermutationsTest/";
+
+ private const string nonAsciiTestedChars = "☕";
+
+ private static readonly string [] schemes = {
+ "http://", "https://", "file://", "ftp://", "gopher://", "ldap://", "mailto:",
+ "net.pipe://", "net.tcp://", "news:", "nntp://", "telnet://", "custom:", "custom://"
+ };
+
+ private static readonly string [] componentLocations = {
+ "a/a", "a/a/a", "a/a?", "a/a#"
+ };
+
+ private static readonly string [] reduceLocations = {
+ "a/b/a./a", "a/b/.a/a", "a/b/./a", "a/b/a../a", "a/b/..a/a", "a/b/../a", "a/b/.../a"
+ };
+
+ public static readonly bool IriParsing;
+
+ static UriPermutationsTest ()
+ {
+ FieldInfo iriParsingField = typeof (Uri).GetField ("s_IriParsing",
+ BindingFlags.Static | BindingFlags.GetField | BindingFlags.NonPublic);
+ if (iriParsingField != null)
+ IriParsing = (bool) iriParsingField.GetValue (null);
+ }
+
+ [SetUp]
+ public void Setup()
+ {
+ StringTester.CreateMode = createMode;
+ StringTester.Location = location;
+ StringTester.Location += (IriParsing) ? "IriParsing" : "NoIriParsing";
+ }
+
+ [TearDown]
+ public void Teardown()
+ {
+ StringTester.Save();
+ }
+
+ private string GetTestedChars ()
+ {
+ var sb = new StringBuilder ();
+ for (int c = 0; c <= 0x7f; c++)
+ sb.Append ((char) c);
+
+ foreach (char c in nonAsciiTestedChars)
+ sb.Append (c);
+
+ return sb.ToString ();
+ }
+
+ internal static string HexEscapeMultiByte (char character)
+ {
+ const string hex_upper_chars = "0123456789ABCDEF";
+ string ret = "";
+ byte [] bytes = Encoding.UTF8.GetBytes (new [] {character});
+ foreach (byte b in bytes)
+ ret += "%" + hex_upper_chars [((b & 0xf0) >> 4)] + hex_upper_chars [((b & 0x0f))];
+
+ return ret;
+ }
+
+ private void TestScheme(Action<string> action)
+ {
+ foreach (string scheme in schemes)
+ action(scheme);
+ }
+
+ private delegate string UriToStringDelegate (Uri uri);
+
+ private void TestLocation (string id, string str, UriToStringDelegate toString, bool testRelative = true)
+ {
+ TestScheme (scheme => {
+ string uri = scheme + str;
+ string actual = toString (new Uri (scheme + str, UriKind.Absolute));
+ StringTester.Assert (scheme + id, actual, "");
+ });
+
+ if (!testRelative)
+ return;
+
+ string relActual = toString (new Uri ("./" + str, UriKind.Relative));
+ StringTester.Assert ("./" + id, relActual, "");
+ }
+
+ private void TestLocations (string [] locations, string id, string str, UriToStringDelegate toString,
+ bool testRelative = true)
+ {
+ foreach (string location in locations)
+ TestLocation (location + id, location + str, toString, testRelative);
+ }
+
+ private void TestPercentageEncoding (UriToStringDelegate toString, bool testRelative = false, string id = "")
+ {
+ string unescapedStr = GetTestedChars ();
+
+ var sb = new StringBuilder ();
+ foreach (char c in unescapedStr)
+ sb.Append (HexEscapeMultiByte (c));
+ string escapedStr = sb.ToString ();
+
+ TestLocations (componentLocations, "[un]" + id, escapedStr, toString, testRelative);
+ TestLocations (componentLocations, "[es]" + id, escapedStr, toString, testRelative);
+ }
+
+ private void TestReduce (UriToStringDelegate toString, bool testRelative = true)
+ {
+ TestLocations (reduceLocations, "", "", toString, testRelative);
+ }
+
+ private void TestComponent (UriComponents component)
+ {
+ TestPercentageEncoding (uri => uri.GetComponents (component, UriFormat.SafeUnescaped), id: "[SafeUnescaped]");
+ TestPercentageEncoding (uri => uri.GetComponents (component, UriFormat.Unescaped), id: "[Unescaped]");
+ TestPercentageEncoding (uri => uri.GetComponents (component, UriFormat.UriEscaped), id: "[UriEscaped]");
+ }
+
+ [Test]
+ public void PercentageEncoding_AbsoluteUri ()
+ {
+ TestPercentageEncoding (uri => uri.AbsoluteUri);
+ }
+
+ [Test]
+ public void PercentageEncoding_Fragment ()
+ {
+ TestPercentageEncoding (uri => uri.Fragment);
+ }
+
+ [Test]
+ public void PercentageEncoding_GetComponents_AbsoluteUri ()
+ {
+ TestComponent (UriComponents.AbsoluteUri);
+ }
+
+ [Test]
+ public void PercentageEncoding_GetComponents_Fragment ()
+ {
+ TestComponent (UriComponents.Fragment);
+ }
+
+ [Test]
+ public void PercentageEncoding_GetComponents_Host ()
+ {
+ TestComponent (UriComponents.Host);
+ }
+
+ [Test]
+ public void PercentageEncoding_GetComponents_Path ()
+ {
+ TestComponent (UriComponents.Path);
+ }
+
+ [Test]
+ public void PercentageEncoding_GetComponents_PathAndQuery ()
+ {
+ TestComponent (UriComponents.PathAndQuery);
+ }
+
+ [Test]
+ public void PercentageEncoding_GetComponents_Query ()
+ {
+ TestComponent (UriComponents.Query);
+ }
+
+ [Test]
+ public void PercentageEncoding_LocalPath ()
+ {
+ TestPercentageEncoding (uri => uri.LocalPath);
+ }
+
+ [Test]
+ public void PercentageEncoding_Query ()
+ {
+ TestPercentageEncoding (uri => uri.Query);
+ }
+
+ [Test]
+ public void PercentageEncoding_ToString ()
+ {
+ TestPercentageEncoding (uri => uri.ToString (), true);
+ }
+
+ [Test]
+ public void Reduce_AbsoluteUri ()
+ {
+ TestReduce (uri => uri.AbsoluteUri, false);
+ }
+
+ [Test]
+ public void Reduce_LocalPath ()
+ {
+ TestReduce (uri => uri.LocalPath, false);
+ }
+
+ [Test]
+ public void Reduce_ToString ()
+ {
+ TestReduce (uri => uri.ToString (), true);
+ }
+ }
+}
\ No newline at end of file