axodox / AxoCover

Nice and free .Net code coverage support for Visual Studio with OpenCover.
https://marketplace.visualstudio.com/items?itemName=axodox1.AxoCover
MIT License
250 stars 60 forks source link

Switch statement with more than 7 cases reports failed branching #82

Closed GregReddick closed 7 years ago

GregReddick commented 7 years ago

This is a simplified test case from some production code in Visual Studio 2017, using MSTest or MSTestV2.

namespace ConsoleApp1
{
    using System;

    public class Program
    {
        public static void Main(string[] args)
        {
            string result = TestSwitch(args[0]);
            Console.WriteLine(result);
        }

        public static string TestSwitch(string arg)
        {
            string result;

            switch (arg)
            {
                default:
                    result = arg;
                    break;
                case "Blank":
                    result = arg;
                    break;
                case "CalendarRound":
                    result = arg;
                    break;
                case "CGlyph":
                    result = arg;
                    break;
                case "CGlyphType":
                    result = arg;
                    break;
                case "DistanceNumber":
                    result = arg;
                    break;
                case "DistanceNumberPlus":
                    result = arg;
                    break;
                case "DistanceNumberDecimal":
                    result = arg;
                    break;
            }

            return result;
        }
    }
}

Here is the simplified test code:

namespace ConsoleApp1.Tests
{
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    [TestClass]
    public class ProgramTests
    {
        [TestMethod]
        public void MainTest()
        {
            Program.Main(new string[] { null });
            Program.TestSwitch("Blank");
            Program.TestSwitch("CalendarRound");
            Program.TestSwitch("CGlyph");
            Program.TestSwitch("CGlyphType");
            Program.TestSwitch("DistanceNumber");
            Program.TestSwitch("DistanceNumberPlus");
            Program.TestSwitch("DistanceNumberDecimal");
        }
    }
}

If you run this test, it reports 61.90% on the branching with yellow circles. Comment out one case statement, and it shows 100% with all branches hit. It may have to do with the compiler using a dictionary to handle complex switch statements.

axodox commented 7 years ago

Yes, after adding the last case it switches to use hash to identify the right choice.

Before last case:

.method public hidebysig static string  TestSwitch(string arg) cil managed
{
  // Code size       117 (0x75)
  .maxstack  2
  .locals init ([0] string result,
           [1] string V_1,
           [2] string V_2)
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  stloc.1
  IL_0003:  ldloc.1
  IL_0004:  ldstr      "Blank"
  IL_0009:  call       bool [mscorlib]System.String::op_Equality(string,
                                                                 string)
  IL_000e:  brtrue.s   IL_0057
  IL_0010:  ldloc.1
  IL_0011:  ldstr      "CalendarRound"
  IL_0016:  call       bool [mscorlib]System.String::op_Equality(string,
                                                                 string)
  IL_001b:  brtrue.s   IL_005b
  IL_001d:  ldloc.1
  IL_001e:  ldstr      "CGlyph"
  IL_0023:  call       bool [mscorlib]System.String::op_Equality(string,
                                                                 string)
  IL_0028:  brtrue.s   IL_005f
  IL_002a:  ldloc.1
  IL_002b:  ldstr      "CGlyphType"
  IL_0030:  call       bool [mscorlib]System.String::op_Equality(string,
                                                                 string)
  IL_0035:  brtrue.s   IL_0063
  IL_0037:  ldloc.1
  IL_0038:  ldstr      "DistanceNumber"
  IL_003d:  call       bool [mscorlib]System.String::op_Equality(string,
                                                                 string)
  IL_0042:  brtrue.s   IL_0067
  IL_0044:  ldloc.1
  IL_0045:  ldstr      "DistanceNumberPlus"
  IL_004a:  call       bool [mscorlib]System.String::op_Equality(string,
                                                                 string)
  IL_004f:  brtrue.s   IL_006b
  IL_0051:  br.s       IL_0053
  IL_0053:  ldarg.0
  IL_0054:  stloc.0
  IL_0055:  br.s       IL_006f
  IL_0057:  ldarg.0
  IL_0058:  stloc.0
  IL_0059:  br.s       IL_006f
  IL_005b:  ldarg.0
  IL_005c:  stloc.0
  IL_005d:  br.s       IL_006f
  IL_005f:  ldarg.0
  IL_0060:  stloc.0
  IL_0061:  br.s       IL_006f
  IL_0063:  ldarg.0
  IL_0064:  stloc.0
  IL_0065:  br.s       IL_006f
  IL_0067:  ldarg.0
  IL_0068:  stloc.0
  IL_0069:  br.s       IL_006f
  IL_006b:  ldarg.0
  IL_006c:  stloc.0
  IL_006d:  br.s       IL_006f
  IL_006f:  ldloc.0
  IL_0070:  stloc.2
  IL_0071:  br.s       IL_0073
  IL_0073:  ldloc.2
  IL_0074:  ret
} // end of method Program::TestSwitch

After last case:

.method public hidebysig static string  TestSwitch(string arg) cil managed
{
  // Code size       242 (0xf2)
  .maxstack  2
  .locals init ([0] string result,
           [1] string V_1,
           [2] uint32 V_2,
           [3] string V_3)
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  stloc.1
  IL_0003:  ldloc.1
  IL_0004:  call       uint32 '<PrivateImplementationDetails>'::ComputeStringHash(string)
  IL_0009:  stloc.2
  IL_000a:  ldloc.2
  IL_000b:  ldc.i4     0x42e40961
  IL_0010:  bgt.un.s   IL_0033
  IL_0012:  ldloc.2
  IL_0013:  ldc.i4     0xe808c9d
  IL_0018:  beq.s      IL_0072
  IL_001a:  br.s       IL_001c
  IL_001c:  ldloc.2
  IL_001d:  ldc.i4     0x23e2dea0
  IL_0022:  beq.s      IL_0090
  IL_0024:  br.s       IL_0026
  IL_0026:  ldloc.2
  IL_0027:  ldc.i4     0x42e40961
  IL_002c:  beq.s      IL_0063
  IL_002e:  br         IL_00cc
  IL_0033:  ldloc.2
  IL_0034:  ldc.i4     0x7df0b0e8
  IL_0039:  bgt.un.s   IL_004f
  IL_003b:  ldloc.2
  IL_003c:  ldc.i4     0x773cbaaf
  IL_0041:  beq.s      IL_009f
  IL_0043:  br.s       IL_0045
  IL_0045:  ldloc.2
  IL_0046:  ldc.i4     0x7df0b0e8
  IL_004b:  beq.s      IL_0081
  IL_004d:  br.s       IL_00cc
  IL_004f:  ldloc.2
  IL_0050:  ldc.i4     0xc1439dae
  IL_0055:  beq.s      IL_00bd
  IL_0057:  br.s       IL_0059
  IL_0059:  ldloc.2
  IL_005a:  ldc.i4     0xc95bb4b3
  IL_005f:  beq.s      IL_00ae
  IL_0061:  br.s       IL_00cc
  IL_0063:  ldloc.1
  IL_0064:  ldstr      "Blank"
  IL_0069:  call       bool [mscorlib]System.String::op_Equality(string,
                                                                 string)
  IL_006e:  brtrue.s   IL_00d0
  IL_0070:  br.s       IL_00cc
  IL_0072:  ldloc.1
  IL_0073:  ldstr      "CalendarRound"
  IL_0078:  call       bool [mscorlib]System.String::op_Equality(string,
                                                                 string)
  IL_007d:  brtrue.s   IL_00d4
  IL_007f:  br.s       IL_00cc
  IL_0081:  ldloc.1
  IL_0082:  ldstr      "CGlyph"
  IL_0087:  call       bool [mscorlib]System.String::op_Equality(string,
                                                                 string)
  IL_008c:  brtrue.s   IL_00d8
  IL_008e:  br.s       IL_00cc
  IL_0090:  ldloc.1
  IL_0091:  ldstr      "CGlyphType"
  IL_0096:  call       bool [mscorlib]System.String::op_Equality(string,
                                                                 string)
  IL_009b:  brtrue.s   IL_00dc
  IL_009d:  br.s       IL_00cc
  IL_009f:  ldloc.1
  IL_00a0:  ldstr      "DistanceNumber"
  IL_00a5:  call       bool [mscorlib]System.String::op_Equality(string,
                                                                 string)
  IL_00aa:  brtrue.s   IL_00e0
  IL_00ac:  br.s       IL_00cc
  IL_00ae:  ldloc.1
  IL_00af:  ldstr      "DistanceNumberPlus"
  IL_00b4:  call       bool [mscorlib]System.String::op_Equality(string,
                                                                 string)
  IL_00b9:  brtrue.s   IL_00e4
  IL_00bb:  br.s       IL_00cc
  IL_00bd:  ldloc.1
  IL_00be:  ldstr      "DistanceNumberDecimal"
  IL_00c3:  call       bool [mscorlib]System.String::op_Equality(string,
                                                                 string)
  IL_00c8:  brtrue.s   IL_00e8
  IL_00ca:  br.s       IL_00cc
  IL_00cc:  ldarg.0
  IL_00cd:  stloc.0
  IL_00ce:  br.s       IL_00ec
  IL_00d0:  ldarg.0
  IL_00d1:  stloc.0
  IL_00d2:  br.s       IL_00ec
  IL_00d4:  ldarg.0
  IL_00d5:  stloc.0
  IL_00d6:  br.s       IL_00ec
  IL_00d8:  ldarg.0
  IL_00d9:  stloc.0
  IL_00da:  br.s       IL_00ec
  IL_00dc:  ldarg.0
  IL_00dd:  stloc.0
  IL_00de:  br.s       IL_00ec
  IL_00e0:  ldarg.0
  IL_00e1:  stloc.0
  IL_00e2:  br.s       IL_00ec
  IL_00e4:  ldarg.0
  IL_00e5:  stloc.0
  IL_00e6:  br.s       IL_00ec
  IL_00e8:  ldarg.0
  IL_00e9:  stloc.0
  IL_00ea:  br.s       IL_00ec
  IL_00ec:  ldloc.0
  IL_00ed:  stloc.3
  IL_00ee:  br.s       IL_00f0
  IL_00f0:  ldloc.3
  IL_00f1:  ret
} // end of method Program::TestSwitch
axodox commented 7 years ago

This is however not an AxoCover issue - as AxoCover only visualizes the OpenCover results. So please report it at https://github.com/OpenCover/opencover/issues