add tests to increase enum serialization code coverage (#40601)
authorJackson Miller <jacksonmiller7855@gmail.com>
Wed, 23 Sep 2020 04:57:04 +0000 (00:57 -0400)
committerGitHub <noreply@github.com>
Wed, 23 Sep 2020 04:57:04 +0000 (21:57 -0700)
* add tests to increase enum serialization code coverage

* implement feedback and add test to cover naming policy scenario

* remove unnecessary bound check now that all primitive types are supported

* Revert "remove unnecessary bound check now that all primitive types are supported"

This reverts commit 0a6a345ed3eaf65be8efbb51c6258a8448aaf9c1.

* implement review feedback

remove unnecessary serializer options in test
rename JsonNamingPolicy ToLower -> ToLowerNamingPolicy

* Remove commented-out code

Co-authored-by: David CantĂș <dacantu@microsoft.com>
src/libraries/System.Text.Json/tests/Serialization/EnumConverterTests.cs

index 69a3c8c..63768b0 100644 (file)
@@ -1,6 +1,7 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using System.Collections.Generic;
 using System.IO;
 using System.Threading.Tasks;
 using Xunit;
@@ -33,7 +34,7 @@ namespace System.Text.Json.Serialization.Tests
 
             // Try a unique naming policy
             options = new JsonSerializerOptions();
-            options.Converters.Add(new JsonStringEnumConverter(new ToLower()));
+            options.Converters.Add(new JsonStringEnumConverter(new ToLowerNamingPolicy()));
 
             json = JsonSerializer.Serialize(DayOfWeek.Friday, options);
             Assert.Equal(@"""friday""", json);
@@ -48,7 +49,7 @@ namespace System.Text.Json.Serialization.Tests
             Assert.Throws<JsonException>(() => JsonSerializer.Serialize((DayOfWeek)(-1), options));
         }
 
-        public class ToLower : JsonNamingPolicy
+        public class ToLowerNamingPolicy : JsonNamingPolicy
         {
             public override string ConvertName(string name) => name.ToLowerInvariant();
         }
@@ -92,7 +93,7 @@ namespace System.Text.Json.Serialization.Tests
 
             // Try a unique casing
             options = new JsonSerializerOptions();
-            options.Converters.Add(new JsonStringEnumConverter(new ToLower()));
+            options.Converters.Add(new JsonStringEnumConverter(new ToLowerNamingPolicy()));
 
             json = JsonSerializer.Serialize(FileAttributes.NoScrubData, options);
             Assert.Equal(@"""noscrubdata""", json);
@@ -144,7 +145,7 @@ namespace System.Text.Json.Serialization.Tests
             public LowerCaseEnumAttribute() { }
 
             public override JsonConverter CreateConverter(Type typeToConvert)
-                => new JsonStringEnumConverter(new ToLower());
+                => new JsonStringEnumConverter(new ToLowerNamingPolicy());
         }
 
         [Fact]
@@ -185,7 +186,7 @@ namespace System.Text.Json.Serialization.Tests
         private enum MyCustomEnum
         {
             First = 1,
-            Second =2
+            Second = 2
         }
 
         [Fact]
@@ -232,6 +233,23 @@ namespace System.Text.Json.Serialization.Tests
             }
         }
 
+        [Fact]
+        public static void MoreThan64EnumValuesToSerializeWithNamingPolicy()
+        {
+            var options = new JsonSerializerOptions
+            {
+                Converters = { new JsonStringEnumConverter(new ToLowerNamingPolicy()) }
+            };
+
+            for (int i = 0; i < 128; i++)
+            {
+                MyEnum value = (MyEnum)i;
+                string asStr = value.ToString().ToLowerInvariant();
+                string expected = char.IsLetter(asStr[0]) ? $@"""{asStr}""" : asStr;
+                Assert.Equal(expected, JsonSerializer.Serialize(value, options));
+            }
+        }
+
         [Fact, OuterLoop]
         public static void VeryLargeAmountOfEnumsToSerialize()
         {
@@ -294,5 +312,212 @@ namespace System.Text.Json.Serialization.Tests
             U = 1 << 20,
             V = 1 << 21,
         }
+
+        [Fact, OuterLoop]
+        public static void VeryLargeAmountOfEnumDictionaryKeysToSerialize()
+        {
+            // Ensure we don't throw OutOfMemoryException.
+
+            const int MaxValue = (int)MyEnum.V;
+
+            // Every value between 0 and MaxValue maps to a valid enum
+            // identifier, and is a candidate to go into the name cache.
+
+            // Write the first 45 values.
+            Dictionary<MyEnum, int> dictionary;
+            for (int i = 1; i < 46; i++)
+            {
+                dictionary = new Dictionary<MyEnum, int> { { (MyEnum)i, i } };
+                JsonSerializer.Serialize(dictionary);
+            }
+
+            // At this point, there are 60 values in the name cache;
+            // 22 cached at warm-up, the rest in the above loop.
+
+            // Ensure the approximate size limit for the name cache (a concurrent dictionary) is honored.
+            // Use multiple threads to perhaps go over the soft limit of 64, but not by more than a couple.
+            Parallel.For(
+                0,
+                8,
+                i =>
+                {
+                    dictionary = new Dictionary<MyEnum, int> { { (MyEnum)(46 + i), i } };
+                    JsonSerializer.Serialize(dictionary);
+                }
+            );
+
+            // Write the remaining enum values. The cache is capped to avoid
+            // OutOfMemoryException due to having too many cached items.
+            for (int i = 54; i <= MaxValue; i++)
+            {
+                dictionary = new Dictionary<MyEnum, int> { { (MyEnum)i, i } };
+                JsonSerializer.Serialize(dictionary);
+            }
+        }
+
+        public abstract class NumericEnumKeyDictionaryBase<T>
+        {
+            public abstract Dictionary<T, int> BuildDictionary(int i);
+
+            [Fact]
+            public void SerilizeDictionaryWhenCacheIsFull()
+            {
+                Dictionary<T, int> dictionary;
+                for (int i = 1; i <= 64; i++)
+                {
+                    dictionary = BuildDictionary(i);
+                    JsonSerializer.Serialize(dictionary);
+                }
+
+                dictionary = BuildDictionary(0);
+                string json = JsonSerializer.Serialize(dictionary);
+                Assert.Equal($"{{\"0\":0}}", json);
+            }
+        }
+
+        public class Int32EnumDictionary : NumericEnumKeyDictionaryBase<SampleEnumInt32>
+        {
+            public override Dictionary<SampleEnumInt32, int> BuildDictionary(int i) =>
+                new Dictionary<SampleEnumInt32, int> { { (SampleEnumInt32)i, i } };
+        }
+
+        public class UInt32EnumDictionary : NumericEnumKeyDictionaryBase<SampleEnumUInt32>
+        {
+            public override Dictionary<SampleEnumUInt32, int> BuildDictionary(int i) =>
+                new Dictionary<SampleEnumUInt32, int> { { (SampleEnumUInt32)i, i } };
+        }
+
+        public class UInt64EnumDictionary : NumericEnumKeyDictionaryBase<SampleEnumUInt64>
+        {
+            public override Dictionary<SampleEnumUInt64, int> BuildDictionary(int i) =>
+                new Dictionary<SampleEnumUInt64, int> { { (SampleEnumUInt64)i, i } };
+        }
+
+        public class Int64EnumDictionary : NumericEnumKeyDictionaryBase<SampleEnumInt64>
+        {
+            public override Dictionary<SampleEnumInt64, int> BuildDictionary(int i) =>
+                new Dictionary<SampleEnumInt64, int> { { (SampleEnumInt64)i, i } };
+        }
+
+        public class Int16EnumDictionary : NumericEnumKeyDictionaryBase<SampleEnumInt16>
+        {
+            public override Dictionary<SampleEnumInt16, int> BuildDictionary(int i) =>
+                new Dictionary<SampleEnumInt16, int> { { (SampleEnumInt16)i, i } };
+        }
+
+        public class UInt16EnumDictionary : NumericEnumKeyDictionaryBase<SampleEnumUInt16>
+        {
+            public override Dictionary<SampleEnumUInt16, int> BuildDictionary(int i) =>
+                new Dictionary<SampleEnumUInt16, int> { { (SampleEnumUInt16)i, i } };
+        }
+
+        public class ByteEnumDictionary : NumericEnumKeyDictionaryBase<SampleEnumByte>
+        {
+            public override Dictionary<SampleEnumByte, int> BuildDictionary(int i) =>
+                new Dictionary<SampleEnumByte, int> { { (SampleEnumByte)i, i } };
+        }
+
+        public class SByteEnumDictionary : NumericEnumKeyDictionaryBase<SampleEnumSByte>
+        {
+            public override Dictionary<SampleEnumSByte, int> BuildDictionary(int i) =>
+                new Dictionary<SampleEnumSByte, int> { { (SampleEnumSByte)i, i } };
+        }
+
+
+        [Flags]
+        public enum SampleEnumInt32
+        {
+            A = 1 << 0,
+            B = 1 << 1,
+            C = 1 << 2,
+            D = 1 << 3,
+            E = 1 << 4,
+            F = 1 << 5,
+            G = 1 << 6,
+        }
+
+        [Flags]
+        public enum SampleEnumUInt32 : uint
+        {
+            A = 1 << 0,
+            B = 1 << 1,
+            C = 1 << 2,
+            D = 1 << 3,
+            E = 1 << 4,
+            F = 1 << 5,
+            G = 1 << 6,
+        }
+
+        [Flags]
+        public enum SampleEnumUInt64 : ulong
+        {
+            A = 1 << 0,
+            B = 1 << 1,
+            C = 1 << 2,
+            D = 1 << 3,
+            E = 1 << 4,
+            F = 1 << 5,
+            G = 1 << 6,
+        }
+
+        [Flags]
+        public enum SampleEnumInt64 : long
+        {
+            A = 1 << 0,
+            B = 1 << 1,
+            C = 1 << 2,
+            D = 1 << 3,
+            E = 1 << 4,
+            F = 1 << 5,
+            G = 1 << 6,
+        }
+
+        [Flags]
+        public enum SampleEnumInt16 : short
+        {
+            A = 1 << 0,
+            B = 1 << 1,
+            C = 1 << 2,
+            D = 1 << 3,
+            E = 1 << 4,
+            F = 1 << 5,
+            G = 1 << 6,
+        }
+
+        [Flags]
+        public enum SampleEnumUInt16 : ushort
+        {
+            A = 1 << 0,
+            B = 1 << 1,
+            C = 1 << 2,
+            D = 1 << 3,
+            E = 1 << 4,
+            F = 1 << 5,
+            G = 1 << 6,
+        }
+
+        [Flags]
+        public enum SampleEnumByte : byte
+        {
+            A = 1 << 0,
+            B = 1 << 1,
+            C = 1 << 2,
+            D = 1 << 3,
+            E = 1 << 4,
+            F = 1 << 5,
+            G = 1 << 6,
+        }
+
+        [Flags]
+        public enum SampleEnumSByte : sbyte
+        {
+            A = 1 << 0,
+            B = 1 << 1,
+            C = 1 << 2,
+            D = 1 << 3,
+            E = 1 << 4,
+            F = 1 << 5,
+            G = 1 << 6,
+        }
     }
 }