Add UTF8Encoding compat tests (dotnet/corefx#41910)
authorLevi Broderick <GrabYourPitchforks@users.noreply.github.com>
Sat, 19 Oct 2019 05:37:16 +0000 (22:37 -0700)
committerGitHub <noreply@github.com>
Sat, 19 Oct 2019 05:37:16 +0000 (22:37 -0700)
Commit migrated from https://github.com/dotnet/corefx/commit/5590d3896f012c1c9b7774774456331a36ba1d57

src/libraries/System.Text.Encoding/tests/UTF8Encoding/UTF8EncodingTests.cs

index 60cdb83..87d5df8 100644 (file)
@@ -102,10 +102,29 @@ namespace System.Text.Tests
         public void Clone(UTF8Encoding encoding)
         {
             UTF8Encoding clone = (UTF8Encoding)encoding.Clone();
+            Assert.False(clone.IsReadOnly);
             Assert.NotSame(encoding, clone);
             Assert.Equal(encoding, clone);
         }
 
+        [Fact]
+        public void Clone_CanCloneUtf8SingletonAndSetFallbacks()
+        {
+            // The Encoding.UTF8 singleton is a sealed subclass of UTF8Encoding that has
+            // its own internal fast-track logic that might make assumptions about any
+            // configured replacement behavior. This test clones the singleton instance
+            // to ensure that we can properly set custom fallbacks and that they're
+            // honored by the Encoding.
+
+            UTF8Encoding clone = (UTF8Encoding)Encoding.UTF8.Clone();
+
+            clone.DecoderFallback = new DecoderReplacementFallback("[BAD]");
+            clone.EncoderFallback = new EncoderReplacementFallback("?");
+
+            Assert.Equal("ab[BAD]xy", clone.GetString(new byte[] { (byte)'a', (byte)'b', 0xC0, (byte)'x', (byte)'y' }));
+            Assert.Equal(new byte[] { (byte)'a', (byte)'?', (byte)'c' }, clone.GetBytes("a\ud800c"));
+        }
+
         public static IEnumerable<object[]> Equals_TestData()
         {
             UTF8Encoding encoding = new UTF8Encoding();
@@ -151,5 +170,99 @@ namespace System.Text.Tests
                 Assert.Equal(expected, encoding.GetHashCode().Equals(value.GetHashCode()));
             }
         }
+
+        [Fact]
+        public void CustomSubclassMethodOverrides()
+        {
+            // Ensures that we don't inadvertently implement methods like UTF8Encoding.GetBytes(string)
+            // without accounting for the fact that a subclassed type may have overridden GetBytes(char[], int, int).
+
+            UTF8Encoding encoding = new CustomUTF8Encoding();
+
+            Assert.Equal(new byte[] { (byte)'!', (byte)'a', (byte)'!', (byte)'b', (byte)'!', (byte)'c', (byte)'!' }, encoding.GetBytes("abc"));
+            Assert.Equal("*a*b*c*".ToCharArray(), encoding.GetChars(new byte[] { (byte)'a', (byte)'b', (byte)'c' }));
+            Assert.Equal("~a~b~c~", encoding.GetString(new byte[] { (byte)'a', (byte)'b', (byte)'c' }));
+        }
+
+        /// <summary>
+        /// An Encoding which is not actually legitimate UTF-8, but which nevertheless
+        /// subclasses UTF8Encoding. A realistic scenario where one might do this would be to
+        /// support "wobbly" data (where the code points U+D800..DFFF are round-trippable).
+        /// </summary>
+        private class CustomUTF8Encoding : UTF8Encoding
+        {
+            public override int GetByteCount(string chars)
+            {
+                return chars.Length * 2 + 1;
+            }
+
+            public override int GetBytes(string chars, int charIndex, int charCount, byte[] bytes, int byteIndex)
+            {
+                // We'll narrow chars to bytes and surround each char with an exclamation point.
+
+                List<byte> builder = new List<byte>()
+                {
+                    (byte)'!'
+                };
+
+                foreach (char ch in chars.AsSpan(charIndex, charCount))
+                {
+                    builder.Add((byte)ch);
+                    builder.Add((byte)'!');
+                }
+
+                builder.CopyTo(bytes, byteIndex);
+                return builder.Count;
+            }
+
+            public override int GetCharCount(byte[] bytes, int index, int count)
+            {
+                return count * 2 + 1;
+            }
+
+            public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex)
+            {
+                // We'll widen bytes to chars and surround each char with an asterisk.
+
+                List<char> builder = new List<char>()
+                {
+                    '*'
+                };
+
+                foreach (byte b in bytes.AsSpan(byteIndex, byteCount))
+                {
+                    builder.Add((char)b);
+                    builder.Add('*');
+                }
+
+                builder.CopyTo(chars, charIndex);
+                return builder.Count;
+            }
+
+            public override string GetString(byte[] bytes, int byteIndex, int byteCount)
+            {
+                // We'll widen bytes to chars and surround each char with a tilde.
+
+                StringBuilder builder = new StringBuilder("~");
+
+                foreach (byte b in bytes.AsSpan(byteIndex, byteCount))
+                {
+                    builder.Append((char)b);
+                    builder.Append('~');
+                }
+
+                return builder.ToString();
+            }
+
+            public override int GetMaxByteCount(int charCount)
+            {
+                return charCount * 2 + 1;
+            }
+
+            public override int GetMaxCharCount(int byteCount)
+            {
+                return byteCount * 2 + 1;
+            }
+        }
     }
 }