diff --git a/src/Directory.Build.props b/src/Directory.Build.props index b82b02162..37ff7c4a1 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -75,6 +75,7 @@ ..\..\..\src\build\35MSSharedLib1024.snk + ..\..\..\..\src\build\35MSSharedLib1024.snk true true diff --git a/src/Microsoft.PowerFx.sln b/src/Microsoft.PowerFx.sln index 1a5641d3f..6f400eeb7 100644 --- a/src/Microsoft.PowerFx.sln +++ b/src/Microsoft.PowerFx.sln @@ -19,10 +19,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerFx.Interpret EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerFx.LanguageServerProtocol", "libraries\Microsoft.PowerFx.LanguageServerProtocol\Microsoft.PowerFx.LanguageServerProtocol.csproj", "{AC286BA6-D439-4EDA-A468-2B18DB53E163}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerFx.Core.Tests", "tests\Microsoft.PowerFx.Core.Tests\Microsoft.PowerFx.Core.Tests.csproj", "{085B4B1A-1F94-4A9C-AEBF-47E0E0B0A983}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerFx.Interpreter.Tests", "tests\Microsoft.PowerFx.Interpreter.Tests\Microsoft.PowerFx.Interpreter.Tests.csproj", "{70C7007C-3288-48F1-A1F0-02F5CC837D4E}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1BF2F760-4DBD-450B-ADED-81548000432D}" ProjectSection(SolutionItems) = preProject ..\.editorconfig = ..\.editorconfig @@ -38,10 +34,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerFx.Transport EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerFx.Connectors", "libraries\Microsoft.PowerFx.Connectors\Microsoft.PowerFx.Connectors.csproj", "{CEE27893-EE7D-4832-858F-70AF6BA325E3}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerFx.Connectors.Tests", "tests\Microsoft.PowerFx.Connectors.Tests\Microsoft.PowerFx.Connectors.Tests.csproj", "{81586372-FE63-4F6D-923D-B47E94487676}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerFx.Performance.Tests", "tests\Microsoft.PowerFx.Performance.Tests\Microsoft.PowerFx.Performance.Tests.csproj", "{5A2EEF08-7A8A-43E6-B498-EFB32B98CC5A}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{5EEC2873-35B5-4364-A2A0-97AAD7BF960D}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Repl", "tools\Repl\Repl.csproj", "{142854D7-409C-4C87-A5B8-39B51477F5B2}" @@ -55,8 +47,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Benchmark", "Benchmark", "{ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerFx.Json", "libraries\Microsoft.PowerFx.Json\Microsoft.PowerFx.Json.csproj", "{D4CC6660-8C30-41E5-A28A-C018DDDF8D96}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerFx.Json.Tests", "tests\Microsoft.PowerFx.Json.Tests\Microsoft.PowerFx.Json.Tests.csproj", "{8986E836-2F55-4D71-B23E-50F4A8427A5B}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Benchmark", "Benchmark", "{89514D49-4D5D-4423-A6EC-F0FF1D23EF43}" ProjectSection(SolutionItems) = preProject ReadBenchmarkData.ps1 = ReadBenchmarkData.ps1 @@ -65,9 +55,35 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Benchmark", "Benchmark", "{ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerFx.Repl", "libraries\Microsoft.PowerFx.Repl\Microsoft.PowerFx.Repl.csproj", "{889A85AD-6450-4F5E-A404-01CC296C90D9}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerFx.Repl.Tests", "tests\Microsoft.PowerFx.Repl.Tests\Microsoft.PowerFx.Repl.Tests.csproj", "{8A54017E-52C3-45BB-9E87-3F0876688D53}" +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.PowerFx.Connectors.Tests.Shared", "tests\Microsoft.PowerFx.Connectors.Tests.Shared\Microsoft.PowerFx.Connectors.Tests.Shared.shproj", "{B91CCC91-2A8D-4411-9A50-709A11B61ADC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.PowerFx.TexlFunctionExporter", "tests\Microsoft.PowerFx.TexlFunctionExporter\Microsoft.PowerFx.TexlFunctionExporter.csproj", "{06373068-0BDC-4BF8-9DAA-7B248C2B577E}" +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.PowerFx.Core.Tests.Shared", "tests\Microsoft.PowerFx.Core.Tests.Shared\Microsoft.PowerFx.Core.Tests.Shared.shproj", "{D274E7B2-0FED-4F9E-B39A-9FA4A8ACCF39}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.PowerFx.Interpreter.Tests.Shared", "tests\Microsoft.PowerFx.Interpreter.Tests.Shared\Microsoft.PowerFx.Interpreter.Tests.Shared.shproj", "{0DAA552E-5906-42C3-846F-2646EE2C33D3}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.PowerFx.Json.Tests.Shared", "tests\Microsoft.PowerFx.Json.Tests.Shared\Microsoft.PowerFx.Json.Tests.Shared.shproj", "{B23198D8-6652-4CE5-AEE3-8646202E6BBD}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.PowerFx.Performance.Tests.Shared", "tests\Microsoft.PowerFx.Performance.Tests.Shared\Microsoft.PowerFx.Performance.Tests.Shared.shproj", "{21E2744A-4C69-4862-894A-6922C2B8E2F8}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.PowerFx.Repl.Tests.Shared", "tests\Microsoft.PowerFx.Repl.Tests.Shared\Microsoft.PowerFx.Repl.Tests.Shared.shproj", "{F065BA6A-DB76-409B-9516-1F027753C2F4}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.PowerFx.TexlFunctionExporter.Shared", "tests\Microsoft.PowerFx.TexlFunctionExporter.Shared\Microsoft.PowerFx.TexlFunctionExporter.Shared.shproj", "{6BB3FA08-791F-4418-AD1C-FAB5EE67D88C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".Net 3.1", ".Net 3.1", "{6DEBA875-6D2F-4F19-8B7B-A2E385CB2182}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerFx.Connectors.Tests", "tests\.Net 3.1\Microsoft.PowerFx.Connectors.Tests\Microsoft.PowerFx.Connectors.Tests.csproj", "{0C397C3A-2331-4DCA-A5AF-8838308466E1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerFx.Core.Tests", "tests\.Net 3.1\Microsoft.PowerFx.Core.Tests\Microsoft.PowerFx.Core.Tests.csproj", "{99BF2BF9-3A27-412A-A489-C31C7C9E236B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerFx.Interpreter.Tests", "tests\.Net 3.1\Microsoft.PowerFx.Interpreter.Tests\Microsoft.PowerFx.Interpreter.Tests.csproj", "{7CE0C1C3-E6B7-4AF0-B6A3-FF4D0002829F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerFx.Json.Tests", "tests\.Net 3.1\Microsoft.PowerFx.Json.Tests\Microsoft.PowerFx.Json.Tests.csproj", "{68392322-F03E-4A71-8AA2-E4462031A491}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerFx.Performance.Tests", "tests\.Net 3.1\Microsoft.PowerFx.Performance.Tests\Microsoft.PowerFx.Performance.Tests.csproj", "{0D2926E8-1577-4FDC-8591-5EB9B8420480}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerFx.Repl.Tests", "tests\.Net 3.1\Microsoft.PowerFx.Repl.Tests\Microsoft.PowerFx.Repl.Tests.csproj", "{33F53D1E-90E7-45E7-9A54-A140E4AF26AE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerFx.TexlFunctionExporter", "tests\.Net 3.1\Microsoft.PowerFx.TexlFunctionExporter\Microsoft.PowerFx.TexlFunctionExporter.csproj", "{ACE89DC7-06E7-4919-B1DB-53E9B39E94F7}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -87,14 +103,6 @@ Global {AC286BA6-D439-4EDA-A468-2B18DB53E163}.Debug|Any CPU.Build.0 = Debug|Any CPU {AC286BA6-D439-4EDA-A468-2B18DB53E163}.Release|Any CPU.ActiveCfg = Release|Any CPU {AC286BA6-D439-4EDA-A468-2B18DB53E163}.Release|Any CPU.Build.0 = Release|Any CPU - {085B4B1A-1F94-4A9C-AEBF-47E0E0B0A983}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {085B4B1A-1F94-4A9C-AEBF-47E0E0B0A983}.Debug|Any CPU.Build.0 = Debug|Any CPU - {085B4B1A-1F94-4A9C-AEBF-47E0E0B0A983}.Release|Any CPU.ActiveCfg = Release|Any CPU - {085B4B1A-1F94-4A9C-AEBF-47E0E0B0A983}.Release|Any CPU.Build.0 = Release|Any CPU - {70C7007C-3288-48F1-A1F0-02F5CC837D4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {70C7007C-3288-48F1-A1F0-02F5CC837D4E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {70C7007C-3288-48F1-A1F0-02F5CC837D4E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {70C7007C-3288-48F1-A1F0-02F5CC837D4E}.Release|Any CPU.Build.0 = Release|Any CPU {ED98A690-863A-443C-9DBB-8F0F5BCACE2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {ED98A690-863A-443C-9DBB-8F0F5BCACE2B}.Debug|Any CPU.Build.0 = Debug|Any CPU {ED98A690-863A-443C-9DBB-8F0F5BCACE2B}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -103,14 +111,6 @@ Global {CEE27893-EE7D-4832-858F-70AF6BA325E3}.Debug|Any CPU.Build.0 = Debug|Any CPU {CEE27893-EE7D-4832-858F-70AF6BA325E3}.Release|Any CPU.ActiveCfg = Release|Any CPU {CEE27893-EE7D-4832-858F-70AF6BA325E3}.Release|Any CPU.Build.0 = Release|Any CPU - {81586372-FE63-4F6D-923D-B47E94487676}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {81586372-FE63-4F6D-923D-B47E94487676}.Debug|Any CPU.Build.0 = Debug|Any CPU - {81586372-FE63-4F6D-923D-B47E94487676}.Release|Any CPU.ActiveCfg = Release|Any CPU - {81586372-FE63-4F6D-923D-B47E94487676}.Release|Any CPU.Build.0 = Release|Any CPU - {5A2EEF08-7A8A-43E6-B498-EFB32B98CC5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5A2EEF08-7A8A-43E6-B498-EFB32B98CC5A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5A2EEF08-7A8A-43E6-B498-EFB32B98CC5A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5A2EEF08-7A8A-43E6-B498-EFB32B98CC5A}.Release|Any CPU.Build.0 = Release|Any CPU {142854D7-409C-4C87-A5B8-39B51477F5B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {142854D7-409C-4C87-A5B8-39B51477F5B2}.Debug|Any CPU.Build.0 = Debug|Any CPU {142854D7-409C-4C87-A5B8-39B51477F5B2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -119,22 +119,38 @@ Global {D4CC6660-8C30-41E5-A28A-C018DDDF8D96}.Debug|Any CPU.Build.0 = Debug|Any CPU {D4CC6660-8C30-41E5-A28A-C018DDDF8D96}.Release|Any CPU.ActiveCfg = Release|Any CPU {D4CC6660-8C30-41E5-A28A-C018DDDF8D96}.Release|Any CPU.Build.0 = Release|Any CPU - {8986E836-2F55-4D71-B23E-50F4A8427A5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8986E836-2F55-4D71-B23E-50F4A8427A5B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8986E836-2F55-4D71-B23E-50F4A8427A5B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8986E836-2F55-4D71-B23E-50F4A8427A5B}.Release|Any CPU.Build.0 = Release|Any CPU {889A85AD-6450-4F5E-A404-01CC296C90D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {889A85AD-6450-4F5E-A404-01CC296C90D9}.Debug|Any CPU.Build.0 = Debug|Any CPU {889A85AD-6450-4F5E-A404-01CC296C90D9}.Release|Any CPU.ActiveCfg = Release|Any CPU {889A85AD-6450-4F5E-A404-01CC296C90D9}.Release|Any CPU.Build.0 = Release|Any CPU - {8A54017E-52C3-45BB-9E87-3F0876688D53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8A54017E-52C3-45BB-9E87-3F0876688D53}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8A54017E-52C3-45BB-9E87-3F0876688D53}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8A54017E-52C3-45BB-9E87-3F0876688D53}.Release|Any CPU.Build.0 = Release|Any CPU - {06373068-0BDC-4BF8-9DAA-7B248C2B577E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {06373068-0BDC-4BF8-9DAA-7B248C2B577E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {06373068-0BDC-4BF8-9DAA-7B248C2B577E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {06373068-0BDC-4BF8-9DAA-7B248C2B577E}.Release|Any CPU.Build.0 = Release|Any CPU + {0C397C3A-2331-4DCA-A5AF-8838308466E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C397C3A-2331-4DCA-A5AF-8838308466E1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C397C3A-2331-4DCA-A5AF-8838308466E1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C397C3A-2331-4DCA-A5AF-8838308466E1}.Release|Any CPU.Build.0 = Release|Any CPU + {99BF2BF9-3A27-412A-A489-C31C7C9E236B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99BF2BF9-3A27-412A-A489-C31C7C9E236B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99BF2BF9-3A27-412A-A489-C31C7C9E236B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99BF2BF9-3A27-412A-A489-C31C7C9E236B}.Release|Any CPU.Build.0 = Release|Any CPU + {7CE0C1C3-E6B7-4AF0-B6A3-FF4D0002829F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7CE0C1C3-E6B7-4AF0-B6A3-FF4D0002829F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7CE0C1C3-E6B7-4AF0-B6A3-FF4D0002829F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7CE0C1C3-E6B7-4AF0-B6A3-FF4D0002829F}.Release|Any CPU.Build.0 = Release|Any CPU + {68392322-F03E-4A71-8AA2-E4462031A491}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {68392322-F03E-4A71-8AA2-E4462031A491}.Debug|Any CPU.Build.0 = Debug|Any CPU + {68392322-F03E-4A71-8AA2-E4462031A491}.Release|Any CPU.ActiveCfg = Release|Any CPU + {68392322-F03E-4A71-8AA2-E4462031A491}.Release|Any CPU.Build.0 = Release|Any CPU + {0D2926E8-1577-4FDC-8591-5EB9B8420480}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0D2926E8-1577-4FDC-8591-5EB9B8420480}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0D2926E8-1577-4FDC-8591-5EB9B8420480}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0D2926E8-1577-4FDC-8591-5EB9B8420480}.Release|Any CPU.Build.0 = Release|Any CPU + {33F53D1E-90E7-45E7-9A54-A140E4AF26AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {33F53D1E-90E7-45E7-9A54-A140E4AF26AE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {33F53D1E-90E7-45E7-9A54-A140E4AF26AE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {33F53D1E-90E7-45E7-9A54-A140E4AF26AE}.Release|Any CPU.Build.0 = Release|Any CPU + {ACE89DC7-06E7-4919-B1DB-53E9B39E94F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ACE89DC7-06E7-4919-B1DB-53E9B39E94F7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ACE89DC7-06E7-4919-B1DB-53E9B39E94F7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ACE89DC7-06E7-4919-B1DB-53E9B39E94F7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -143,21 +159,45 @@ Global {C4AE1B00-36BB-4D5F-8CC9-E51979BE2830} = {4269F3C3-6B42-419B-B64A-3E6DC0F1574A} {E59979B0-F267-46DC-B628-941D31C3CB7C} = {4269F3C3-6B42-419B-B64A-3E6DC0F1574A} {AC286BA6-D439-4EDA-A468-2B18DB53E163} = {4269F3C3-6B42-419B-B64A-3E6DC0F1574A} - {085B4B1A-1F94-4A9C-AEBF-47E0E0B0A983} = {AD743B78-D61F-4FBF-B620-FA83CE599A50} - {70C7007C-3288-48F1-A1F0-02F5CC837D4E} = {AD743B78-D61F-4FBF-B620-FA83CE599A50} {ED98A690-863A-443C-9DBB-8F0F5BCACE2B} = {4269F3C3-6B42-419B-B64A-3E6DC0F1574A} {CEE27893-EE7D-4832-858F-70AF6BA325E3} = {4269F3C3-6B42-419B-B64A-3E6DC0F1574A} - {81586372-FE63-4F6D-923D-B47E94487676} = {AD743B78-D61F-4FBF-B620-FA83CE599A50} - {5A2EEF08-7A8A-43E6-B498-EFB32B98CC5A} = {AD743B78-D61F-4FBF-B620-FA83CE599A50} {142854D7-409C-4C87-A5B8-39B51477F5B2} = {5EEC2873-35B5-4364-A2A0-97AAD7BF960D} {D4CC6660-8C30-41E5-A28A-C018DDDF8D96} = {4269F3C3-6B42-419B-B64A-3E6DC0F1574A} - {8986E836-2F55-4D71-B23E-50F4A8427A5B} = {AD743B78-D61F-4FBF-B620-FA83CE599A50} {89514D49-4D5D-4423-A6EC-F0FF1D23EF43} = {4269F3C3-6B42-419B-B64A-3E6DC0F1574A} {889A85AD-6450-4F5E-A404-01CC296C90D9} = {4269F3C3-6B42-419B-B64A-3E6DC0F1574A} - {8A54017E-52C3-45BB-9E87-3F0876688D53} = {AD743B78-D61F-4FBF-B620-FA83CE599A50} - {06373068-0BDC-4BF8-9DAA-7B248C2B577E} = {AD743B78-D61F-4FBF-B620-FA83CE599A50} + {B91CCC91-2A8D-4411-9A50-709A11B61ADC} = {AD743B78-D61F-4FBF-B620-FA83CE599A50} + {D274E7B2-0FED-4F9E-B39A-9FA4A8ACCF39} = {AD743B78-D61F-4FBF-B620-FA83CE599A50} + {0DAA552E-5906-42C3-846F-2646EE2C33D3} = {AD743B78-D61F-4FBF-B620-FA83CE599A50} + {B23198D8-6652-4CE5-AEE3-8646202E6BBD} = {AD743B78-D61F-4FBF-B620-FA83CE599A50} + {21E2744A-4C69-4862-894A-6922C2B8E2F8} = {AD743B78-D61F-4FBF-B620-FA83CE599A50} + {F065BA6A-DB76-409B-9516-1F027753C2F4} = {AD743B78-D61F-4FBF-B620-FA83CE599A50} + {6BB3FA08-791F-4418-AD1C-FAB5EE67D88C} = {AD743B78-D61F-4FBF-B620-FA83CE599A50} + {6DEBA875-6D2F-4F19-8B7B-A2E385CB2182} = {AD743B78-D61F-4FBF-B620-FA83CE599A50} + {0C397C3A-2331-4DCA-A5AF-8838308466E1} = {6DEBA875-6D2F-4F19-8B7B-A2E385CB2182} + {99BF2BF9-3A27-412A-A489-C31C7C9E236B} = {6DEBA875-6D2F-4F19-8B7B-A2E385CB2182} + {7CE0C1C3-E6B7-4AF0-B6A3-FF4D0002829F} = {6DEBA875-6D2F-4F19-8B7B-A2E385CB2182} + {68392322-F03E-4A71-8AA2-E4462031A491} = {6DEBA875-6D2F-4F19-8B7B-A2E385CB2182} + {0D2926E8-1577-4FDC-8591-5EB9B8420480} = {6DEBA875-6D2F-4F19-8B7B-A2E385CB2182} + {33F53D1E-90E7-45E7-9A54-A140E4AF26AE} = {6DEBA875-6D2F-4F19-8B7B-A2E385CB2182} + {ACE89DC7-06E7-4919-B1DB-53E9B39E94F7} = {6DEBA875-6D2F-4F19-8B7B-A2E385CB2182} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {30372F91-B206-4351-A621-F0E2773C337B} EndGlobalSection + GlobalSection(SharedMSBuildProjectFiles) = preSolution + tests\Microsoft.PowerFx.Connectors.Tests.Shared\Microsoft.PowerFx.Connectors.Tests.Shared.projitems*{0c397c3a-2331-4dca-a5af-8838308466e1}*SharedItemsImports = 5 + tests\Microsoft.PowerFx.Performance.Tests.Shared\Microsoft.PowerFx.Performance.Tests.Shared.projitems*{0d2926e8-1577-4fdc-8591-5eb9b8420480}*SharedItemsImports = 5 + tests\Microsoft.PowerFx.Interpreter.Tests.Shared\Microsoft.PowerFx.Interpreter.Tests.Shared.projitems*{0daa552e-5906-42c3-846f-2646ee2c33d3}*SharedItemsImports = 13 + tests\Microsoft.PowerFx.Performance.Tests.Shared\Microsoft.PowerFx.Performance.Tests.Shared.projitems*{21e2744a-4c69-4862-894a-6922c2b8e2f8}*SharedItemsImports = 13 + tests\Microsoft.PowerFx.Repl.Tests.Shared\Microsoft.PowerFx.Repl.Tests.Shared.projitems*{33f53d1e-90e7-45e7-9a54-a140e4af26ae}*SharedItemsImports = 5 + tests\Microsoft.PowerFx.Json.Tests.Shared\Microsoft.PowerFx.Json.Tests.Shared.projitems*{68392322-f03e-4a71-8aa2-e4462031a491}*SharedItemsImports = 5 + tests\Microsoft.PowerFx.TexlFunctionExporter.Shared\Microsoft.PowerFx.TexlFunctionExporter.Shared.projitems*{6bb3fa08-791f-4418-ad1c-fab5ee67d88c}*SharedItemsImports = 13 + tests\Microsoft.PowerFx.Interpreter.Tests.Shared\Microsoft.PowerFx.Interpreter.Tests.Shared.projitems*{7ce0c1c3-e6b7-4af0-b6a3-ff4d0002829f}*SharedItemsImports = 5 + tests\Microsoft.PowerFx.Core.Tests.Shared\Microsoft.PowerFx.Core.Tests.Shared.projitems*{99bf2bf9-3a27-412a-a489-c31c7c9e236b}*SharedItemsImports = 5 + tests\Microsoft.PowerFx.TexlFunctionExporter.Shared\Microsoft.PowerFx.TexlFunctionExporter.Shared.projitems*{ace89dc7-06e7-4919-b1db-53e9b39e94f7}*SharedItemsImports = 5 + tests\Microsoft.PowerFx.Json.Tests.Shared\Microsoft.PowerFx.Json.Tests.Shared.projitems*{b23198d8-6652-4ce5-aee3-8646202e6bbd}*SharedItemsImports = 13 + tests\Microsoft.PowerFx.Connectors.Tests.Shared\Microsoft.PowerFx.Connectors.Tests.Shared.projitems*{b91ccc91-2a8d-4411-9a50-709a11b61adc}*SharedItemsImports = 13 + tests\Microsoft.PowerFx.Core.Tests.Shared\Microsoft.PowerFx.Core.Tests.Shared.projitems*{d274e7b2-0fed-4f9e-b39a-9fa4a8accf39}*SharedItemsImports = 13 + tests\Microsoft.PowerFx.Repl.Tests.Shared\Microsoft.PowerFx.Repl.Tests.Shared.projitems*{f065ba6a-db76-409b-9516-1f027753c2f4}*SharedItemsImports = 13 + EndGlobalSection EndGlobal diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Microsoft.PowerFx.Connectors.Tests.csproj b/src/tests/.Net 3.1/Microsoft.PowerFx.Connectors.Tests/Microsoft.PowerFx.Connectors.Tests.csproj similarity index 69% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Microsoft.PowerFx.Connectors.Tests.csproj rename to src/tests/.Net 3.1/Microsoft.PowerFx.Connectors.Tests/Microsoft.PowerFx.Connectors.Tests.csproj index b87b53c80..ed8d5f325 100644 --- a/src/tests/Microsoft.PowerFx.Connectors.Tests/Microsoft.PowerFx.Connectors.Tests.csproj +++ b/src/tests/.Net 3.1/Microsoft.PowerFx.Connectors.Tests/Microsoft.PowerFx.Connectors.Tests.csproj @@ -26,26 +26,19 @@ - - - - - - - - - - - + - - - + + + + + + \ No newline at end of file diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/Microsoft.PowerFx.Core.Tests.csproj b/src/tests/.Net 3.1/Microsoft.PowerFx.Core.Tests/Microsoft.PowerFx.Core.Tests.csproj similarity index 61% rename from src/tests/Microsoft.PowerFx.Core.Tests/Microsoft.PowerFx.Core.Tests.csproj rename to src/tests/.Net 3.1/Microsoft.PowerFx.Core.Tests/Microsoft.PowerFx.Core.Tests.csproj index 52ab2e815..e195e1151 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests/Microsoft.PowerFx.Core.Tests.csproj +++ b/src/tests/.Net 3.1/Microsoft.PowerFx.Core.Tests/Microsoft.PowerFx.Core.Tests.csproj @@ -1,79 +1,46 @@ - - - - true - false - $(GeneratePackages) - netcoreapp3.1 - netcoreapp3.1 - $(LocalPackageVersion) - $(ReleasePackageVersion) - $(LocalPackageVersion) - $(ReleasePackageVersion) - Debug;Release - false - - - - - Microsoft.PowerFx.Core.Tests - Microsoft Power Fx Compiler Tests Core - The core test suite for all Microsoft Power Fx Compiler targets - The core test suite for all Microsoft Power Fx Compiler targets - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - PreserveNewest - - - IntellisenseTests\TestSignatures\%(FileName)%(Extension) - PreserveNewest - - - - - - - - - - - - - Always - - - - - - - - True - True - Resources.resx - - - - - ResXFileCodeGenerator - Resources.Designer.cs - - - + + + + true + false + $(GeneratePackages) + netcoreapp3.1 + netcoreapp3.1 + $(LocalPackageVersion) + $(ReleasePackageVersion) + $(LocalPackageVersion) + $(ReleasePackageVersion) + Debug;Release + false + + + + + Microsoft.PowerFx.Core.Tests + Microsoft Power Fx Compiler Tests Core + The core test suite for all Microsoft Power Fx Compiler targets + The core test suite for all Microsoft Power Fx Compiler targets + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/Microsoft.PowerFx.Interpreter.Tests.csproj b/src/tests/.Net 3.1/Microsoft.PowerFx.Interpreter.Tests/Microsoft.PowerFx.Interpreter.Tests.csproj similarity index 50% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/Microsoft.PowerFx.Interpreter.Tests.csproj rename to src/tests/.Net 3.1/Microsoft.PowerFx.Interpreter.Tests/Microsoft.PowerFx.Interpreter.Tests.csproj index 6c54ea706..fa591959f 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/Microsoft.PowerFx.Interpreter.Tests.csproj +++ b/src/tests/.Net 3.1/Microsoft.PowerFx.Interpreter.Tests/Microsoft.PowerFx.Interpreter.Tests.csproj @@ -21,33 +21,17 @@ - + - - + + + + + - - - - - PreserveNewest - - - IntellisenseTests\TestSignatures\%(FileName)%(Extension) - PreserveNewest - - - Always - - - - - - Always - - + \ No newline at end of file diff --git a/src/tests/Microsoft.PowerFx.Repl.Tests/Microsoft.PowerFx.Repl.Tests.csproj b/src/tests/.Net 3.1/Microsoft.PowerFx.Json.Tests/Microsoft.PowerFx.Json.Tests.csproj similarity index 68% rename from src/tests/Microsoft.PowerFx.Repl.Tests/Microsoft.PowerFx.Repl.Tests.csproj rename to src/tests/.Net 3.1/Microsoft.PowerFx.Json.Tests/Microsoft.PowerFx.Json.Tests.csproj index 0d13fa2fd..cd5742a26 100644 --- a/src/tests/Microsoft.PowerFx.Repl.Tests/Microsoft.PowerFx.Repl.Tests.csproj +++ b/src/tests/.Net 3.1/Microsoft.PowerFx.Json.Tests/Microsoft.PowerFx.Json.Tests.csproj @@ -10,7 +10,6 @@ - @@ -21,14 +20,15 @@ - + - - + + + + - - + \ No newline at end of file diff --git a/src/tests/Microsoft.PowerFx.Performance.Tests/Microsoft.PowerFx.Performance.Tests.csproj b/src/tests/.Net 3.1/Microsoft.PowerFx.Performance.Tests/Microsoft.PowerFx.Performance.Tests.csproj similarity index 78% rename from src/tests/Microsoft.PowerFx.Performance.Tests/Microsoft.PowerFx.Performance.Tests.csproj rename to src/tests/.Net 3.1/Microsoft.PowerFx.Performance.Tests/Microsoft.PowerFx.Performance.Tests.csproj index 6aa848bf4..711be875f 100644 --- a/src/tests/Microsoft.PowerFx.Performance.Tests/Microsoft.PowerFx.Performance.Tests.csproj +++ b/src/tests/.Net 3.1/Microsoft.PowerFx.Performance.Tests/Microsoft.PowerFx.Performance.Tests.csproj @@ -29,12 +29,13 @@ - + - - + + + diff --git a/src/tests/Microsoft.PowerFx.Json.Tests/Microsoft.PowerFx.Json.Tests.csproj b/src/tests/.Net 3.1/Microsoft.PowerFx.Repl.Tests/Microsoft.PowerFx.Repl.Tests.csproj similarity index 63% rename from src/tests/Microsoft.PowerFx.Json.Tests/Microsoft.PowerFx.Json.Tests.csproj rename to src/tests/.Net 3.1/Microsoft.PowerFx.Repl.Tests/Microsoft.PowerFx.Repl.Tests.csproj index 3b7e9035c..deeba7b15 100644 --- a/src/tests/Microsoft.PowerFx.Json.Tests/Microsoft.PowerFx.Json.Tests.csproj +++ b/src/tests/.Net 3.1/Microsoft.PowerFx.Repl.Tests/Microsoft.PowerFx.Repl.Tests.csproj @@ -10,6 +10,7 @@ + @@ -20,20 +21,16 @@ - + + + + + - - - - - TypeSystemTests\JsonTypeSnapshots\%(FileName)%(Extension) - PreserveNewest - - - + \ No newline at end of file diff --git a/src/tests/Microsoft.PowerFx.TexlFunctionExporter/Microsoft.PowerFx.TexlFunctionExporter.csproj b/src/tests/.Net 3.1/Microsoft.PowerFx.TexlFunctionExporter/Microsoft.PowerFx.TexlFunctionExporter.csproj similarity index 80% rename from src/tests/Microsoft.PowerFx.TexlFunctionExporter/Microsoft.PowerFx.TexlFunctionExporter.csproj rename to src/tests/.Net 3.1/Microsoft.PowerFx.TexlFunctionExporter/Microsoft.PowerFx.TexlFunctionExporter.csproj index 4a4c30211..a2c49ee6c 100644 --- a/src/tests/Microsoft.PowerFx.TexlFunctionExporter/Microsoft.PowerFx.TexlFunctionExporter.csproj +++ b/src/tests/.Net 3.1/Microsoft.PowerFx.TexlFunctionExporter/Microsoft.PowerFx.TexlFunctionExporter.csproj @@ -22,11 +22,12 @@ - + - + + \ No newline at end of file diff --git a/src/tests/Directory.Build.props b/src/tests/Directory.Build.props index 529741838..dec06e445 100644 --- a/src/tests/Directory.Build.props +++ b/src/tests/Directory.Build.props @@ -10,7 +10,7 @@ - + PreserveNewest diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/BaseConnectorTest.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/BaseConnectorTest.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/BaseConnectorTest.cs rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/BaseConnectorTest.cs diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/BasicRestTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/BasicRestTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/BasicRestTests.cs rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/BasicRestTests.cs diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/ConnectorWizardTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/ConnectorWizardTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/ConnectorWizardTests.cs rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/ConnectorWizardTests.cs diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/DynamicTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/DynamicTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/DynamicTests.cs rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/DynamicTests.cs diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/FileTabularConnector.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/FileTabularConnector.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/FileTabularConnector.cs rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/FileTabularConnector.cs diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Helpers/Helpers.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Helpers/Helpers.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Helpers/Helpers.cs rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Helpers/Helpers.cs diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Helpers/LoggingTestServer.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Helpers/LoggingTestServer.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Helpers/LoggingTestServer.cs rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Helpers/LoggingTestServer.cs diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/IntellisenseTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/IntellisenseTests.cs similarity index 99% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/IntellisenseTests.cs rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/IntellisenseTests.cs index cea93ba02..32d52f1cd 100644 --- a/src/tests/Microsoft.PowerFx.Connectors.Tests/IntellisenseTests.cs +++ b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/IntellisenseTests.cs @@ -2,119 +2,119 @@ // Licensed under the MIT license. using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Net.Http; -using System.Threading.Tasks; +using System.Threading.Tasks; using Microsoft.OpenApi.Models; using Microsoft.PowerFx.Intellisense; using Microsoft.PowerFx.Interpreter.Tests; using Microsoft.PowerFx.Tests; using Xunit; -using Xunit.Abstractions; - +using Xunit.Abstractions; + #pragma warning disable SA1515 // Single-line comment should be preceded by blank line #pragma warning disable SA1025 // Code should not contain multiple whitespace in a row - + namespace Microsoft.PowerFx.Connectors.Tests { public class IntellisenseTests - { - private readonly ITestOutputHelper _output; - - public IntellisenseTests(ITestOutputHelper output) - { - _output = output; - } + { + private readonly ITestOutputHelper _output; - [Theory] - // Get list of servers - [InlineData(1, 1, @"SQL.ExecuteProcedureV2(", @"""default""")] - [InlineData(1, 1, @"SQL.ExecuteProcedureV2(""", @"""default""")] // inside the string, no character - [InlineData(1, 1, @"SQL.ExecuteProcedureV2(""de", @"""default""")] // inside the string, some characters (matching) - [InlineData(1, 1, @"SQL.ExecuteProcedureV2(""dz", "")] // inside the string, not matching characters - // Get list of databases - [InlineData(2, 2, @"SQL.ExecuteProcedureV2(""pfxdev-sql.database.windows.net"",", @"""default""")] - [InlineData(2, 3, @"SQL.ExecuteProcedureV2(""default"",", @"""default""")] // testing with "default" server - [InlineData(0, 0, @"SQL.ExecuteProcedureV2(""pfxdev-sql.database.windows.net""", "")] // no comma, still on 1st param - [InlineData(0, 0, @"SQL.ExecuteProcedureV2(""pfxdev-sql"" + "".database.windows.net"",", "")] // concatenation of strings - // Get list of stored procedures - [InlineData(3, 4, @"SQL.ExecuteProcedureV2(""pfxdev-sql.database.windows.net"", ""connectortest"",", @"""[dbo].[sp_1]""|""[dbo].[sp_2]""")] - [InlineData(3, 5, @"SQL.ExecuteProcedureV2(""default"", ""connectortest"",", @"""[dbo].[sp_1]""|""[dbo].[sp_2]""")] // testing with "default" server - [InlineData(3, 6, @"SQL.ExecuteProcedureV2(""default"", ""default"",", @"""[dbo].[sp_1]""|""[dbo].[sp_2]""")] // testing with "default" server & database - // Using fake function - [InlineData(3, 4, @"SQL.ExecuteProcedureV2z(true, ""connectortest"",", @"""[dbo].[sp_1]""|""[dbo].[sp_2]""")] - public void ConnectorIntellisenseTest(int responseIndex, int queryIndex, string expression, string expectedSuggestions) - { - // These tests are exercising 'x-ms-dynamic-values' extension property - using LoggingTestServer testConnector = new LoggingTestServer(@"Swagger\SQL Server.json", _output); - OpenApiDocument apiDoc = testConnector._apiDocument; - PowerFxConfig config = new PowerFxConfig(Features.PowerFxV1); - - using HttpClient httpClient = new HttpClient(testConnector); - using PowerPlatformConnectorClient client = new PowerPlatformConnectorClient( + public IntellisenseTests(ITestOutputHelper output) + { + _output = output; + } + + [Theory] + // Get list of servers + [InlineData(1, 1, @"SQL.ExecuteProcedureV2(", @"""default""")] + [InlineData(1, 1, @"SQL.ExecuteProcedureV2(""", @"""default""")] // inside the string, no character + [InlineData(1, 1, @"SQL.ExecuteProcedureV2(""de", @"""default""")] // inside the string, some characters (matching) + [InlineData(1, 1, @"SQL.ExecuteProcedureV2(""dz", "")] // inside the string, not matching characters + // Get list of databases + [InlineData(2, 2, @"SQL.ExecuteProcedureV2(""pfxdev-sql.database.windows.net"",", @"""default""")] + [InlineData(2, 3, @"SQL.ExecuteProcedureV2(""default"",", @"""default""")] // testing with "default" server + [InlineData(0, 0, @"SQL.ExecuteProcedureV2(""pfxdev-sql.database.windows.net""", "")] // no comma, still on 1st param + [InlineData(0, 0, @"SQL.ExecuteProcedureV2(""pfxdev-sql"" + "".database.windows.net"",", "")] // concatenation of strings + // Get list of stored procedures + [InlineData(3, 4, @"SQL.ExecuteProcedureV2(""pfxdev-sql.database.windows.net"", ""connectortest"",", @"""[dbo].[sp_1]""|""[dbo].[sp_2]""")] + [InlineData(3, 5, @"SQL.ExecuteProcedureV2(""default"", ""connectortest"",", @"""[dbo].[sp_1]""|""[dbo].[sp_2]""")] // testing with "default" server + [InlineData(3, 6, @"SQL.ExecuteProcedureV2(""default"", ""default"",", @"""[dbo].[sp_1]""|""[dbo].[sp_2]""")] // testing with "default" server & database + // Using fake function + [InlineData(3, 4, @"SQL.ExecuteProcedureV2z(true, ""connectortest"",", @"""[dbo].[sp_1]""|""[dbo].[sp_2]""")] + public void ConnectorIntellisenseTest(int responseIndex, int queryIndex, string expression, string expectedSuggestions) + { + // These tests are exercising 'x-ms-dynamic-values' extension property + using LoggingTestServer testConnector = new LoggingTestServer(@"Swagger\SQL Server.json", _output); + OpenApiDocument apiDoc = testConnector._apiDocument; + PowerFxConfig config = new PowerFxConfig(Features.PowerFxV1); + + using HttpClient httpClient = new HttpClient(testConnector); + using PowerPlatformConnectorClient client = new PowerPlatformConnectorClient( "tip1-shared-002.azure-apim.net", // endpoint "a2df3fb8-e4a4-e5e6-905c-e3dff9f93b46", // environment "5f57ec83acef477b8ccc769e52fa22cc", // connectionId () => "eyJ0eXA...", - httpClient) - { - SessionId = "8e67ebdc-d402-455a-b33a-304820832383" - }; - - config.AddActionConnector("SQL", apiDoc, new ConsoleLogger(_output)); - testConnector.SetResponseFromFile(responseIndex switch - { - 0 => null, - 1 => @"Responses\SQL Server Intellisense Response 1.json", - 2 => @"Responses\SQL Server Intellisense Response 2.json", - 3 => @"Responses\SQL Server Intellisense Response 3.json", - _ => null - }); - RecalcEngine engine = new RecalcEngine(config); - BasicServiceProvider serviceProvider = new BasicServiceProvider().AddRuntimeContext(new TestConnectorRuntimeContext("SQL", client, console: _output)); - - CheckResult checkResult = engine.Check(expression, symbolTable: null); - IIntellisenseResult suggestions = engine.Suggest(checkResult, expression.Length, serviceProvider); - - string list = string.Join("|", suggestions.Suggestions.Select(s => s.DisplayText.Text).OrderBy(x => x)); - Assert.Equal(expectedSuggestions, list); - Assert.True((responseIndex == 0) ^ testConnector.SendAsyncCalled); - - string networkTrace = testConnector._log.ToString(); - string queryPart = queryIndex switch - { - 0 => null, - 1 => "servers", - 2 => "databases?server=pfxdev-sql.database.windows.net", - 3 => "databases?server=default", - 4 => "v2/datasets/pfxdev-sql.database.windows.net,connectortest/procedures", - 5 => "v2/datasets/default,connectortest/procedures", - 6 => "v2/datasets/default,default/procedures", - _ => throw new NotImplementedException("Unknown index") - }; - - string expectedNetwork = queryIndex switch - { - 0 => string.Empty, - _ => -$@"POST https://tip1-shared-002.azure-apim.net/invoke - authority: tip1-shared-002.azure-apim.net - Authorization: Bearer eyJ0eXA... - path: /invoke - scheme: https - x-ms-client-environment-id: /providers/Microsoft.PowerApps/environments/a2df3fb8-e4a4-e5e6-905c-e3dff9f93b46 - x-ms-client-session-id: 8e67ebdc-d402-455a-b33a-304820832383 - x-ms-request-method: GET - x-ms-request-url: /apim/sql/5f57ec83acef477b8ccc769e52fa22cc/{queryPart} - x-ms-user-agent: PowerFx/{PowerPlatformConnectorClient.Version} -" - }; - - Assert.Equal(expectedNetwork.Replace("\r\n", "\n").Replace("\r", "\n"), networkTrace.Replace("\r\n", "\n").Replace("\r", "\n")); + httpClient) + { + SessionId = "8e67ebdc-d402-455a-b33a-304820832383" + }; + + config.AddActionConnector("SQL", apiDoc, new ConsoleLogger(_output)); + testConnector.SetResponseFromFile(responseIndex switch + { + 0 => null, + 1 => @"Responses\SQL Server Intellisense Response 1.json", + 2 => @"Responses\SQL Server Intellisense Response 2.json", + 3 => @"Responses\SQL Server Intellisense Response 3.json", + _ => null + }); + RecalcEngine engine = new RecalcEngine(config); + BasicServiceProvider serviceProvider = new BasicServiceProvider().AddRuntimeContext(new TestConnectorRuntimeContext("SQL", client, console: _output)); + + CheckResult checkResult = engine.Check(expression, symbolTable: null); + IIntellisenseResult suggestions = engine.Suggest(checkResult, expression.Length, serviceProvider); + + string list = string.Join("|", suggestions.Suggestions.Select(s => s.DisplayText.Text).OrderBy(x => x)); + Assert.Equal(expectedSuggestions, list); + Assert.True((responseIndex == 0) ^ testConnector.SendAsyncCalled); + + string networkTrace = testConnector._log.ToString(); + string queryPart = queryIndex switch + { + 0 => null, + 1 => "servers", + 2 => "databases?server=pfxdev-sql.database.windows.net", + 3 => "databases?server=default", + 4 => "v2/datasets/pfxdev-sql.database.windows.net,connectortest/procedures", + 5 => "v2/datasets/default,connectortest/procedures", + 6 => "v2/datasets/default,default/procedures", + _ => throw new NotImplementedException("Unknown index") + }; + + string expectedNetwork = queryIndex switch + { + 0 => string.Empty, + _ => +$@"POST https://tip1-shared-002.azure-apim.net/invoke + authority: tip1-shared-002.azure-apim.net + Authorization: Bearer eyJ0eXA... + path: /invoke + scheme: https + x-ms-client-environment-id: /providers/Microsoft.PowerApps/environments/a2df3fb8-e4a4-e5e6-905c-e3dff9f93b46 + x-ms-client-session-id: 8e67ebdc-d402-455a-b33a-304820832383 + x-ms-request-method: GET + x-ms-request-url: /apim/sql/5f57ec83acef477b8ccc769e52fa22cc/{queryPart} + x-ms-user-agent: PowerFx/{PowerPlatformConnectorClient.Version} +" + }; + + Assert.Equal(expectedNetwork.Replace("\r\n", "\n").Replace("\r", "\n"), networkTrace.Replace("\r\n", "\n").Replace("\r", "\n")); } - [Theory] + [Theory] [InlineData(1, 1, @"SQL.ExecuteProcedureV2(""default"", ""default"", ""sp_1"", ", @"p1|User")] // stored proc with 1 param, out of record [InlineData(2, 1, @"SQL.ExecuteProcedureV2(""default"", ""default"", ""sp_2"", ", @"p1|p2|User")] // stored proc with 2 params, out of record [InlineData(1, 1, @"SQL.ExecuteProcedureV2(""default"", ""default"", ""sp_1"", { ", "p1")] // in record, only suggest param names @@ -129,7 +129,7 @@ $@"POST https://tip1-shared-002.azure-apim.net/invoke { // These tests are exercising 'x-ms-dynamic-schema' extension property using LoggingTestServer testConnector = new LoggingTestServer(@"Swagger\SQL Server.json", _output); - OpenApiDocument apiDoc = testConnector._apiDocument; + OpenApiDocument apiDoc = testConnector._apiDocument; PowerFxConfig config = new PowerFxConfig(Features.PowerFxV1) { SymbolTable = UserInfoTestSetup.GetUserInfoSymbolTable() }; using HttpClient httpClient = new HttpClient(testConnector); @@ -154,17 +154,17 @@ $@"POST https://tip1-shared-002.azure-apim.net/invoke }); } - RecalcEngine engine = new RecalcEngine(config); - BasicServiceProvider serviceProvider = new BasicServiceProvider().AddRuntimeContext(new TestConnectorRuntimeContext("SQL", client, console: _output)); - + RecalcEngine engine = new RecalcEngine(config); + BasicServiceProvider serviceProvider = new BasicServiceProvider().AddRuntimeContext(new TestConnectorRuntimeContext("SQL", client, console: _output)); + CheckResult checkResult = engine.Check(expression, symbolTable: null); IIntellisenseResult suggestions = engine.Suggest(checkResult, expression.Length, serviceProvider); string list = string.Join("|", suggestions.Suggestions.Select(s => s.DisplayText.Text).OrderBy(x => x)); - Assert.Equal(expectedSuggestions, list); - + Assert.Equal(expectedSuggestions, list); + string networkTrace = testConnector._log.ToString(); - string expectedNetwork = networkCall == 0 ? string.Empty : + string expectedNetwork = networkCall == 0 ? string.Empty : $@"POST https://tip1-shared-002.azure-apim.net/invoke authority: tip1-shared-002.azure-apim.net Authorization: Bearer eyJ0eXA... @@ -175,15 +175,15 @@ $@"POST https://tip1-shared-002.azure-apim.net/invoke x-ms-request-method: GET x-ms-request-url: /apim/sql/5f57ec83acef477b8ccc769e52fa22cc/v2/$metadata.json/datasets/default,default/procedures/sp_{responseIndex} x-ms-user-agent: PowerFx/{PowerPlatformConnectorClient.Version} -"; - +"; + Assert.Equal(expectedNetwork.Replace("\r\n", "\n").Replace("\r", "\n"), networkTrace.Replace("\r\n", "\n").Replace("\r", "\n")); - } - - [Fact] + } + + [Fact] public void ConnectorIntellisenseTest_ServerError() - { - string expression = @"SQL.ExecuteProcedureV2(""default"", ""default"", ""sp_2"", { "; + { + string expression = @"SQL.ExecuteProcedureV2(""default"", ""default"", ""sp_2"", { "; using LoggingTestServer testConnector = new LoggingTestServer(@"Swagger\SQL Server.json", _output); OpenApiDocument apiDoc = testConnector._apiDocument; @@ -195,28 +195,28 @@ $@"POST https://tip1-shared-002.azure-apim.net/invoke SessionId = "8e67ebdc-d402-455a-b33a-304820832383" }; - config.AddActionConnector("SQL", apiDoc, new ConsoleLogger(_output)); - - // The response is invalid (malformed Json), this is to ensure we manage properly the exception coming from ExtractFromJson - testConnector.SetResponseFromFile(@"Responses\SQL Server Intellisense Response Error.json"); - - RecalcEngine engine = new RecalcEngine(config); - BasicServiceProvider serviceProvider = new BasicServiceProvider().AddRuntimeContext(new TestConnectorRuntimeContext("SQL", client, console: _output)); - - CheckResult checkResult = engine.Check(expression, symbolTable: null); - + config.AddActionConnector("SQL", apiDoc, new ConsoleLogger(_output)); + + // The response is invalid (malformed Json), this is to ensure we manage properly the exception coming from ExtractFromJson + testConnector.SetResponseFromFile(@"Responses\SQL Server Intellisense Response Error.json"); + + RecalcEngine engine = new RecalcEngine(config); + BasicServiceProvider serviceProvider = new BasicServiceProvider().AddRuntimeContext(new TestConnectorRuntimeContext("SQL", client, console: _output)); + + CheckResult checkResult = engine.Check(expression, symbolTable: null); + // This call should not throw an exception IIntellisenseResult suggestions = engine.Suggest(checkResult, expression.Length, serviceProvider); - + // We don't get any result as the response is invalid string list = string.Join("|", suggestions.Suggestions.Select(s => s.DisplayText.Text).OrderBy(x => x)); Assert.Equal(string.Empty, list); - } - - [Fact] + } + + [Fact] public void ConnectorIntellisenseTest_EmptyResponse() - { - string expression = @"SQL.ExecuteProcedureV2(""default"", ""default"", ""sp_2"", { "; + { + string expression = @"SQL.ExecuteProcedureV2(""default"", ""default"", ""sp_2"", { "; using LoggingTestServer testConnector = new LoggingTestServer(@"Swagger\SQL Server.json", _output); OpenApiDocument apiDoc = testConnector._apiDocument; @@ -228,25 +228,25 @@ $@"POST https://tip1-shared-002.azure-apim.net/invoke SessionId = "8e67ebdc-d402-455a-b33a-304820832383" }; - config.AddActionConnector("SQL", apiDoc, new ConsoleLogger(_output)); - - // The response is empty, this is to ensure we manage properly the exception coming from ExtractFromJson - testConnector.SetResponseFromFile(@"Responses\EmptyResponse.json"); - - RecalcEngine engine = new RecalcEngine(config); - BasicServiceProvider serviceProvider = new BasicServiceProvider().AddRuntimeContext(new TestConnectorRuntimeContext("SQL", client, console: _output)); - - CheckResult checkResult = engine.Check(expression, symbolTable: null); - + config.AddActionConnector("SQL", apiDoc, new ConsoleLogger(_output)); + + // The response is empty, this is to ensure we manage properly the exception coming from ExtractFromJson + testConnector.SetResponseFromFile(@"Responses\EmptyResponse.json"); + + RecalcEngine engine = new RecalcEngine(config); + BasicServiceProvider serviceProvider = new BasicServiceProvider().AddRuntimeContext(new TestConnectorRuntimeContext("SQL", client, console: _output)); + + CheckResult checkResult = engine.Check(expression, symbolTable: null); + // This call should not throw an exception IIntellisenseResult suggestions = engine.Suggest(checkResult, expression.Length, serviceProvider); - + // We don't get any result as the response is invalid string list = string.Join("|", suggestions.Suggestions.Select(s => s.DisplayText.Text).OrderBy(x => x)); Assert.Equal(string.Empty, list); - } - - [Theory] + } + + [Theory] [InlineData(1, 1, @"SQL.ExecuteProcedureV2(""default"", ""default"", ""sp_1"", ", @"p1")] // stored proc with 1 param, out of record public void ConnectorIntellisenseTestLSP(int responseIndex, int networkCall, string expression, string expectedSuggestions) { @@ -265,14 +265,14 @@ $@"POST https://tip1-shared-002.azure-apim.net/invoke { SessionId = "8e67ebdc-d402-455a-b33a-304820832383" }; - - // Real client comes at per-intellisense time. - // - Must still pass in some non-null client to initialize the invoker - // - client must have same base address as what we pass in at intellisense. - using var ignoreHttpClient = new HttpClient - { - BaseAddress = client.BaseAddress - }; + + // Real client comes at per-intellisense time. + // - Must still pass in some non-null client to initialize the invoker + // - client must have same base address as what we pass in at intellisense. + using var ignoreHttpClient = new HttpClient + { + BaseAddress = client.BaseAddress + }; const string cxNamespace = "SQL"; config.AddActionConnector(new ConnectorSettings(cxNamespace), apiDoc, new ConsoleLogger(_output)); if (networkCall > 0) @@ -285,17 +285,17 @@ $@"POST https://tip1-shared-002.azure-apim.net/invoke }); } - RecalcEngine engine = new RecalcEngine(config); - BasicServiceProvider services = new BasicServiceProvider().AddRuntimeContext(new TestConnectorRuntimeContext(cxNamespace, client, console: _output)); - - IPowerFxScope scope = new EditorContextScope((expr) => engine.Check(expression, symbolTable: null)) { Services = services }; - IIntellisenseResult suggestions = scope.Suggest(expression, expression.Length); - + RecalcEngine engine = new RecalcEngine(config); + BasicServiceProvider services = new BasicServiceProvider().AddRuntimeContext(new TestConnectorRuntimeContext(cxNamespace, client, console: _output)); + + IPowerFxScope scope = new EditorContextScope((expr) => engine.Check(expression, symbolTable: null)) { Services = services }; + IIntellisenseResult suggestions = scope.Suggest(expression, expression.Length); + string list = string.Join("|", suggestions.Suggestions.Select(s => s.DisplayText.Text).OrderBy(x => x)); - Assert.Equal(expectedSuggestions, list); - + Assert.Equal(expectedSuggestions, list); + string networkTrace = testConnector._log.ToString(); - string expectedNetwork = networkCall == 0 ? string.Empty : + string expectedNetwork = networkCall == 0 ? string.Empty : $@"POST https://tip1-shared-002.azure-apim.net/invoke authority: tip1-shared-002.azure-apim.net Authorization: Bearer eyJ0eXA... @@ -306,74 +306,74 @@ $@"POST https://tip1-shared-002.azure-apim.net/invoke x-ms-request-method: GET x-ms-request-url: /apim/sql/5f57ec83acef477b8ccc769e52fa22cc/v2/$metadata.json/datasets/default,default/procedures/sp_{responseIndex} x-ms-user-agent: PowerFx/{PowerPlatformConnectorClient.Version} -"; - +"; + Assert.Equal(expectedNetwork.Replace("\r\n", "\n").Replace("\r", "\n"), networkTrace.Replace("\r\n", "\n").Replace("\r", "\n")); - } - - [Fact] - public async Task ConnectorIntellisenseTest3() - { - using LoggingTestServer testConnector = new LoggingTestServer(@"Swagger\SharePoint.json", _output); - OpenApiDocument apiDoc = testConnector._apiDocument; - PowerFxConfig config = new PowerFxConfig(); - string token = @"eyJ0eXA..."; - - using HttpClient httpClient = new HttpClient(testConnector); - using PowerPlatformConnectorClient ppClient = new PowerPlatformConnectorClient("https://tip1-shared-002.azure-apim.net", "2f0cc19d-893e-e765-b15d-2906e3231c09" /* env */, "6fb0a1a8e2f5487eafbe306821d8377e" /* connId */, () => $"{token}", httpClient) { SessionId = "547d471f-c04c-4c4a-b3af-337ab0637a0d" }; - - List functions = OpenApiParser.GetFunctions("SP", apiDoc, new ConsoleLogger(_output)).OrderBy(f => f.Name).ToList(); - - // Total 101 functions, including 50 internal, 1 deprecated & 50 internal functions (and no deprecated+internal functions) - Assert.Equal(51, functions.Count); - Assert.Single(functions.Where(f => f.IsDeprecated)); - - // This is expected as internal functions (which are marked as internal) are not included - Assert.Empty(functions.Where(f => f.IsInternal)); - Assert.Empty(functions.Where(f => f.IsDeprecated && f.IsInternal)); - - IEnumerable funcInfos = config.AddActionConnector("SP", apiDoc, new ConsoleLogger(_output)); - RecalcEngine engine = new RecalcEngine(config); - - CheckResult checkResult = engine.Check("SP.", symbolTable: null); - IIntellisenseResult suggestions = engine.Suggest(checkResult, 3); - - // We only suggest 50 functions as we don't include deprecated & internal functions - Assert.Equal(50, suggestions.Suggestions.Count()); - } - - [Theory] - [InlineData(@"SQL.")] - [InlineData(@"SQ")] - public async Task ConnectorDoNotSuggestDeprecatedEndpoints(string expression) - { - // This Path can be found in the Swagger file SQL Server.json - var deprecatedFunctionExample = "SQL.ExecutePassThroughNativeQuery"; - using LoggingTestServer testConnector = new LoggingTestServer(@"Swagger\SQL Server.json", _output); - OpenApiDocument apiDoc = testConnector._apiDocument; - PowerFxConfig config = new PowerFxConfig(Features.PowerFxV1); - - using HttpClient httpClient = new HttpClient(testConnector); - using PowerPlatformConnectorClient client = new PowerPlatformConnectorClient( + } + + [Fact] + public async Task ConnectorIntellisenseTest3() + { + using LoggingTestServer testConnector = new LoggingTestServer(@"Swagger\SharePoint.json", _output); + OpenApiDocument apiDoc = testConnector._apiDocument; + PowerFxConfig config = new PowerFxConfig(); + string token = @"eyJ0eXA..."; + + using HttpClient httpClient = new HttpClient(testConnector); + using PowerPlatformConnectorClient ppClient = new PowerPlatformConnectorClient("https://tip1-shared-002.azure-apim.net", "2f0cc19d-893e-e765-b15d-2906e3231c09" /* env */, "6fb0a1a8e2f5487eafbe306821d8377e" /* connId */, () => $"{token}", httpClient) { SessionId = "547d471f-c04c-4c4a-b3af-337ab0637a0d" }; + + List functions = OpenApiParser.GetFunctions("SP", apiDoc, new ConsoleLogger(_output)).OrderBy(f => f.Name).ToList(); + + // Total 101 functions, including 50 internal, 1 deprecated & 50 internal functions (and no deprecated+internal functions) + Assert.Equal(51, functions.Count); + Assert.Single(functions.Where(f => f.IsDeprecated)); + + // This is expected as internal functions (which are marked as internal) are not included + Assert.Empty(functions.Where(f => f.IsInternal)); + Assert.Empty(functions.Where(f => f.IsDeprecated && f.IsInternal)); + + IEnumerable funcInfos = config.AddActionConnector("SP", apiDoc, new ConsoleLogger(_output)); + RecalcEngine engine = new RecalcEngine(config); + + CheckResult checkResult = engine.Check("SP.", symbolTable: null); + IIntellisenseResult suggestions = engine.Suggest(checkResult, 3); + + // We only suggest 50 functions as we don't include deprecated & internal functions + Assert.Equal(50, suggestions.Suggestions.Count()); + } + + [Theory] + [InlineData(@"SQL.")] + [InlineData(@"SQ")] + public async Task ConnectorDoNotSuggestDeprecatedEndpoints(string expression) + { + // This Path can be found in the Swagger file SQL Server.json + var deprecatedFunctionExample = "SQL.ExecutePassThroughNativeQuery"; + using LoggingTestServer testConnector = new LoggingTestServer(@"Swagger\SQL Server.json", _output); + OpenApiDocument apiDoc = testConnector._apiDocument; + PowerFxConfig config = new PowerFxConfig(Features.PowerFxV1); + + using HttpClient httpClient = new HttpClient(testConnector); + using PowerPlatformConnectorClient client = new PowerPlatformConnectorClient( "tip1-shared-002.azure-apim.net", // endpoint "a2df3fb8-e4a4-e5e6-905c-e3dff9f93b46", // environment "5f57ec83acef477b8ccc769e52fa22cc", // connectionId () => "eyJ0eXA...", - httpClient) - { - SessionId = "8e67ebdc-d402-455a-b33a-304820832383" - }; - - config.AddActionConnector("SQL", apiDoc, new ConsoleLogger(_output)); - RecalcEngine engine = new RecalcEngine(config); - BasicServiceProvider serviceProvider = new BasicServiceProvider().AddRuntimeContext(new TestConnectorRuntimeContext("SQL", client, console: _output)); - - CheckResult checkResult = engine.Check(expression, symbolTable: null); - IIntellisenseResult suggestions = engine.Suggest(checkResult, expression.Length, serviceProvider); - - Assert.DoesNotContain(suggestions.Suggestions, suggestion => suggestion.DisplayText.Text.Equals(deprecatedFunctionExample)); - } - } + httpClient) + { + SessionId = "8e67ebdc-d402-455a-b33a-304820832383" + }; + + config.AddActionConnector("SQL", apiDoc, new ConsoleLogger(_output)); + RecalcEngine engine = new RecalcEngine(config); + BasicServiceProvider serviceProvider = new BasicServiceProvider().AddRuntimeContext(new TestConnectorRuntimeContext("SQL", client, console: _output)); + + CheckResult checkResult = engine.Check(expression, symbolTable: null); + IIntellisenseResult suggestions = engine.Suggest(checkResult, expression.Length, serviceProvider); + + Assert.DoesNotContain(suggestions.Suggestions, suggestion => suggestion.DisplayText.Text.Equals(deprecatedFunctionExample)); + } + } } #pragma warning restore SA1515 // Single-line comment should be preceded by blank line diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/InternalTesting.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/InternalTesting.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/InternalTesting.cs rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/InternalTesting.cs diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/LivePublicSwaggerTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/LivePublicSwaggerTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/LivePublicSwaggerTests.cs rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/LivePublicSwaggerTests.cs diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/LoggerTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/LoggerTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/LoggerTests.cs rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/LoggerTests.cs diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/MergeRecordTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/MergeRecordTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/MergeRecordTests.cs rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/MergeRecordTests.cs diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Microsoft.PowerFx.Connectors.Tests.Shared.projitems b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Microsoft.PowerFx.Connectors.Tests.Shared.projitems new file mode 100644 index 000000000..441524806 --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Microsoft.PowerFx.Connectors.Tests.Shared.projitems @@ -0,0 +1,24 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + b91ccc91-2a8d-4411-9a50-709a11b61adc + + + Microsoft.PowerFx.Connectors.Tests.Shared + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Microsoft.PowerFx.Connectors.Tests.Shared.shproj b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Microsoft.PowerFx.Connectors.Tests.Shared.shproj new file mode 100644 index 000000000..3a37e5bd8 --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Microsoft.PowerFx.Connectors.Tests.Shared.shproj @@ -0,0 +1,13 @@ + + + + b91ccc91-2a8d-4411-9a50-709a11b61adc + 14.0 + + + + + + + + diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/O365GroupTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/O365GroupTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/O365GroupTests.cs rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/O365GroupTests.cs diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/O365OutlookTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/O365OutlookTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/O365OutlookTests.cs rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/O365OutlookTests.cs diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/O365UserTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/O365UserTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/O365UserTests.cs rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/O365UserTests.cs diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/ODataParametersTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/ODataParametersTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/ODataParametersTests.cs rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/ODataParametersTests.cs diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/OpenApiExtensionTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/OpenApiExtensionTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/OpenApiExtensionTests.cs rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/OpenApiExtensionTests.cs diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/OpenApiFormUrlEncoderTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/OpenApiFormUrlEncoderTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/OpenApiFormUrlEncoderTests.cs rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/OpenApiFormUrlEncoderTests.cs diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/OpenApiHelperFunctions.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/OpenApiHelperFunctions.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/OpenApiHelperFunctions.cs rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/OpenApiHelperFunctions.cs diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/OpenApiJsonSerializerTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/OpenApiJsonSerializerTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/OpenApiJsonSerializerTests.cs rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/OpenApiJsonSerializerTests.cs diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/OpenApiParserTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/OpenApiParserTests.cs similarity index 99% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/OpenApiParserTests.cs rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/OpenApiParserTests.cs index 18db58a2b..283aef067 100644 --- a/src/tests/Microsoft.PowerFx.Connectors.Tests/OpenApiParserTests.cs +++ b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/OpenApiParserTests.cs @@ -1,6 +1,6 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System.Collections.Generic; using System.Linq; using System.Net.Http; @@ -13,18 +13,18 @@ using Microsoft.PowerFx.Tests; using Microsoft.PowerFx.Types; using Newtonsoft.Json; using Xunit; -using Xunit.Abstractions; - +using Xunit.Abstractions; + namespace Microsoft.PowerFx.Connectors.Tests { public class OpenApiParserTests { - private readonly ITestOutputHelper _output; - - public OpenApiParserTests(ITestOutputHelper output) - { - _output = output; - } + private readonly ITestOutputHelper _output; + + public OpenApiParserTests(ITestOutputHelper output) + { + _output = output; + } [Fact] public void ACSL_GetFunctionNames() @@ -45,57 +45,57 @@ namespace Microsoft.PowerFx.Connectors.Tests Assert.Equal("Analyzes the input conversation utterance.", conversationAnalysisAnalyzeConversationConversation.Description); Assert.Equal("Conversations (CLU) (2022-05-01)", conversationAnalysisAnalyzeConversationConversation.Summary); Assert.Equal("/apim/cognitiveservicestextanalytics/{connectionId}/language/:analyze-conversations", conversationAnalysisAnalyzeConversationConversation.OperationPath); - Assert.Equal(HttpMethod.Post, conversationAnalysisAnalyzeConversationConversation.HttpMethod); - } - - [Fact] - public void ACSL_GetFunctionNames22() - { - OpenApiDocument doc = Helpers.ReadSwagger(@"Swagger\Azure Cognitive Service for Language v2.2.json", _output); - List functions = OpenApiParser.GetFunctions("ACSL", doc, new ConsoleLogger(_output)).OrderBy(cf => cf.Name).ToList(); - ConnectorFunction detectSentimentV3 = functions.First(cf => cf.Name == "DetectSentimentV3"); - - Assert.Equal("Documents", detectSentimentV3.OptionalParameters[0].Summary); - Assert.Equal("The documents to analyze.", detectSentimentV3.OptionalParameters[0].Description); - } - + Assert.Equal(HttpMethod.Post, conversationAnalysisAnalyzeConversationConversation.HttpMethod); + } + + [Fact] + public void ACSL_GetFunctionNames22() + { + OpenApiDocument doc = Helpers.ReadSwagger(@"Swagger\Azure Cognitive Service for Language v2.2.json", _output); + List functions = OpenApiParser.GetFunctions("ACSL", doc, new ConsoleLogger(_output)).OrderBy(cf => cf.Name).ToList(); + ConnectorFunction detectSentimentV3 = functions.First(cf => cf.Name == "DetectSentimentV3"); + + Assert.Equal("Documents", detectSentimentV3.OptionalParameters[0].Summary); + Assert.Equal("The documents to analyze.", detectSentimentV3.OptionalParameters[0].Description); + } + [Fact] public void ACSL_Load() { OpenApiDocument doc = Helpers.ReadSwagger(@"Swagger\Azure Cognitive Service for Language.json", _output); - (List connectorFunctions, List texlFunctions) = OpenApiParser.ParseInternal(new ConnectorSettings("ACSL"), doc, new ConsoleLogger(_output)); - Assert.Contains(connectorFunctions, func => func.Namespace == "ACSL" && func.Name == "ConversationAnalysisAnalyzeConversationConversation"); - Assert.Contains(texlFunctions, func => func.Namespace.Name.Value == "ACSL" && func.Name == "ConversationAnalysisAnalyzeConversationConversation"); - - ConnectorFunction func1 = connectorFunctions.First(f => f.Name == "AnalyzeTextSubmitJobCustomEntityRecognition"); - Assert.Equal("analysisInput:![documents:*[id:s, language:s, text:s]]|task:![parameters:![deploymentName:s, projectName:s, stringIndexType:s]]", string.Join("|", func1.RequiredParameters.Select(rp => $"{rp.Name}:{rp.FormulaType._type}"))); - Assert.Equal("displayName:s", string.Join("|", func1.OptionalParameters.Select(rp => $"{rp.Name}:{rp.FormulaType._type}"))); - - (connectorFunctions, texlFunctions) = OpenApiParser.ParseInternal(new ConnectorSettings("ACSL") { Compatibility = ConnectorCompatibility.SwaggerCompatibility }, doc, new ConsoleLogger(_output)); - Assert.Contains(connectorFunctions, func => func.Namespace == "ACSL" && func.Name == "ConversationAnalysisAnalyzeConversationConversation"); - Assert.Contains(texlFunctions, func => func.Namespace.Name.Value == "ACSL" && func.Name == "ConversationAnalysisAnalyzeConversationConversation"); - - func1 = connectorFunctions.First(f => f.Name == "AnalyzeTextSubmitJobCustomEntityRecognition"); - Assert.Equal("analysisInput:![documents:*[id:s, language:s, text:s]]|task:![parameters:![deploymentName:s, projectName:s]]", string.Join("|", func1.RequiredParameters.Select(rp => $"{rp.Name}:{rp.FormulaType._type}"))); - Assert.Empty(func1.OptionalParameters); - } - + (List connectorFunctions, List texlFunctions) = OpenApiParser.ParseInternal(new ConnectorSettings("ACSL"), doc, new ConsoleLogger(_output)); + Assert.Contains(connectorFunctions, func => func.Namespace == "ACSL" && func.Name == "ConversationAnalysisAnalyzeConversationConversation"); + Assert.Contains(texlFunctions, func => func.Namespace.Name.Value == "ACSL" && func.Name == "ConversationAnalysisAnalyzeConversationConversation"); + + ConnectorFunction func1 = connectorFunctions.First(f => f.Name == "AnalyzeTextSubmitJobCustomEntityRecognition"); + Assert.Equal("analysisInput:![documents:*[id:s, language:s, text:s]]|task:![parameters:![deploymentName:s, projectName:s, stringIndexType:s]]", string.Join("|", func1.RequiredParameters.Select(rp => $"{rp.Name}:{rp.FormulaType._type}"))); + Assert.Equal("displayName:s", string.Join("|", func1.OptionalParameters.Select(rp => $"{rp.Name}:{rp.FormulaType._type}"))); + + (connectorFunctions, texlFunctions) = OpenApiParser.ParseInternal(new ConnectorSettings("ACSL") { Compatibility = ConnectorCompatibility.SwaggerCompatibility }, doc, new ConsoleLogger(_output)); + Assert.Contains(connectorFunctions, func => func.Namespace == "ACSL" && func.Name == "ConversationAnalysisAnalyzeConversationConversation"); + Assert.Contains(texlFunctions, func => func.Namespace.Name.Value == "ACSL" && func.Name == "ConversationAnalysisAnalyzeConversationConversation"); + + func1 = connectorFunctions.First(f => f.Name == "AnalyzeTextSubmitJobCustomEntityRecognition"); + Assert.Equal("analysisInput:![documents:*[id:s, language:s, text:s]]|task:![parameters:![deploymentName:s, projectName:s]]", string.Join("|", func1.RequiredParameters.Select(rp => $"{rp.Name}:{rp.FormulaType._type}"))); + Assert.Empty(func1.OptionalParameters); + } + [Fact] public void SF_TextCsv() - { + { OpenApiDocument doc = Helpers.ReadSwagger(@"Swagger\SalesForce.json", _output); - (List connectorFunctions, List texlFunctions) = OpenApiParser.ParseInternal(new ConnectorSettings("SF") { Compatibility = ConnectorCompatibility.SwaggerCompatibility }, doc, new ConsoleLogger(_output)); - - // function returns text/csv - ConnectorFunction func1 = connectorFunctions.First(f => f.Name == "GetJobRecordResults"); - ConnectorType returnType = func1.ReturnParameterType; - - // returns a string - Assert.Equal("s", returnType.FormulaType._type.ToString()); - } - + (List connectorFunctions, List texlFunctions) = OpenApiParser.ParseInternal(new ConnectorSettings("SF") { Compatibility = ConnectorCompatibility.SwaggerCompatibility }, doc, new ConsoleLogger(_output)); + + // function returns text/csv + ConnectorFunction func1 = connectorFunctions.First(f => f.Name == "GetJobRecordResults"); + ConnectorType returnType = func1.ReturnParameterType; + + // returns a string + Assert.Equal("s", returnType.FormulaType._type.ToString()); + } + #pragma warning disable SA1118, SA1117, SA1119, SA1137 - + [Fact] public void ACSL_GetFunctionParameters_PowerAppsCompatibility() { @@ -112,18 +112,18 @@ namespace Microsoft.PowerFx.Connectors.Tests RecordType analysisInputRecordType = Extensions.MakeRecordType( ("conversationItem", Extensions.MakeRecordType( - ("language", FormulaType.String), + ("language", FormulaType.String), ("modality", FormulaType.String), ("text", FormulaType.String)))); RecordType parametersRecordType = Extensions.MakeRecordType( - ("deploymentName", FormulaType.String), - ("directTarget", FormulaType.String), + ("deploymentName", FormulaType.String), + ("directTarget", FormulaType.String), ("isLoggingEnabled", FormulaType.Boolean), - ("projectName", FormulaType.String), - ("stringIndexType", FormulaType.String), + ("projectName", FormulaType.String), + ("stringIndexType", FormulaType.String), ("targetProjectParameters", FormulaType.UntypedObject), ("verbose", FormulaType.Boolean)); - + // -- Parameter 1 -- Assert.Equal("kind", function.RequiredParameters[0].Name); Assert.Equal(FormulaType.String, function.RequiredParameters[0].FormulaType); @@ -134,20 +134,20 @@ namespace Microsoft.PowerFx.Connectors.Tests Assert.Equal("analysisInput", function.RequiredParameters[1].Name); Assert.Equal(analysisInputRecordType, function.RequiredParameters[1].FormulaType); Assert.Equal("A single conversational task to execute.", function.RequiredParameters[1].Description); - Assert.Null(function.RequiredParameters[1].DefaultValue); - Assert.NotNull(function.RequiredParameters[1].ConnectorType); - Assert.Equal("analysisInput", function.RequiredParameters[1].ConnectorType.Name); - Assert.Null(function.RequiredParameters[1].ConnectorType.DisplayName); - Assert.Equal("The input ConversationItem and its optional parameters", function.RequiredParameters[1].ConnectorType.Description); - Assert.Equal(analysisInputRecordType, function.RequiredParameters[1].ConnectorType.FormulaType); - Assert.True(function.RequiredParameters[1].ConnectorType.IsRequired); - Assert.Single(function.RequiredParameters[1].ConnectorType.Fields); - Assert.Equal("conversationItem", function.RequiredParameters[1].ConnectorType.Fields[0].Name); - Assert.Null(function.RequiredParameters[1].ConnectorType.Fields[0].DisplayName); - Assert.Equal("The abstract base for a user input formatted conversation (e.g., Text, Transcript).", function.RequiredParameters[1].ConnectorType.Fields[0].Description); - Assert.True(function.RequiredParameters[1].ConnectorType.Fields[0].IsRequired); - - // -- Parameter 3 -- + Assert.Null(function.RequiredParameters[1].DefaultValue); + Assert.NotNull(function.RequiredParameters[1].ConnectorType); + Assert.Equal("analysisInput", function.RequiredParameters[1].ConnectorType.Name); + Assert.Null(function.RequiredParameters[1].ConnectorType.DisplayName); + Assert.Equal("The input ConversationItem and its optional parameters", function.RequiredParameters[1].ConnectorType.Description); + Assert.Equal(analysisInputRecordType, function.RequiredParameters[1].ConnectorType.FormulaType); + Assert.True(function.RequiredParameters[1].ConnectorType.IsRequired); + Assert.Single(function.RequiredParameters[1].ConnectorType.Fields); + Assert.Equal("conversationItem", function.RequiredParameters[1].ConnectorType.Fields[0].Name); + Assert.Null(function.RequiredParameters[1].ConnectorType.Fields[0].DisplayName); + Assert.Equal("The abstract base for a user input formatted conversation (e.g., Text, Transcript).", function.RequiredParameters[1].ConnectorType.Fields[0].Description); + Assert.True(function.RequiredParameters[1].ConnectorType.Fields[0].IsRequired); + + // -- Parameter 3 -- Assert.Equal("parameters", function.RequiredParameters[2].Name); Assert.Equal(parametersRecordType, function.RequiredParameters[2].FormulaType); Assert.Equal("A single conversational task to execute.", function.RequiredParameters[2].Description); @@ -163,7 +163,7 @@ namespace Microsoft.PowerFx.Connectors.Tests Assert.Equal(FormulaType.String, function.HiddenRequiredParameters[0].FormulaType); Assert.Equal("Client API version.", function.HiddenRequiredParameters[0].Description); Assert.Equal("2022-05-01", function.HiddenRequiredParameters[0].DefaultValue.ToObject()); - + // -- Hidden Required Parameter 2 -- Assert.Equal("analysisInput", function.HiddenRequiredParameters[1].Name); Assert.Equal(analysisInputRecordTypeH, function.HiddenRequiredParameters[1].FormulaType); @@ -200,15 +200,15 @@ namespace Microsoft.PowerFx.Connectors.Tests string returnTypeName = expectedReturnType.ToStringWithDisplayNames(); Assert.Equal(rt3Name, returnTypeName); - Assert.True((FormulaType)expectedReturnType == returnType); - - ConnectorType connectorReturnType = function.ReturnParameterType; - Assert.NotNull(connectorReturnType); - Assert.Equal((FormulaType)expectedReturnType, connectorReturnType.FormulaType); - Assert.Equal(2, connectorReturnType.Fields.Length); - Assert.Equal("The results of a Conversation task.", connectorReturnType.Description); + Assert.True((FormulaType)expectedReturnType == returnType); + + ConnectorType connectorReturnType = function.ReturnParameterType; + Assert.NotNull(connectorReturnType); + Assert.Equal((FormulaType)expectedReturnType, connectorReturnType.FormulaType); + Assert.Equal(2, connectorReturnType.Fields.Length); + Assert.Equal("The results of a Conversation task.", connectorReturnType.Description); } - + [Fact] public void ACSL_GetFunctionParameters_SwaggerCompatibility() { @@ -217,17 +217,17 @@ namespace Microsoft.PowerFx.Connectors.Tests Assert.Equal("ConversationAnalysisAnalyzeConversationConversation", function.Name); Assert.Equal("ConversationAnalysis_AnalyzeConversation_Conversation", function.OriginalName); - Assert.Equal("/apim/cognitiveservicestextanalytics/{connectionId}/language/:analyze-conversations", function.OperationPath); - - RecordType analysisInputRecordType = Extensions.MakeRecordType( - ("conversationItem", Extensions.MakeRecordType( - ("language", FormulaType.String), + Assert.Equal("/apim/cognitiveservicestextanalytics/{connectionId}/language/:analyze-conversations", function.OperationPath); + + RecordType analysisInputRecordType = Extensions.MakeRecordType( + ("conversationItem", Extensions.MakeRecordType( + ("language", FormulaType.String), ("text", FormulaType.String)))); RecordType parametersRecordType = Extensions.MakeRecordType( - ("deploymentName", FormulaType.String), - ("projectName", FormulaType.String), + ("deploymentName", FormulaType.String), + ("projectName", FormulaType.String), ("verbose", FormulaType.Boolean)); - + Assert.Equal(2, function.RequiredParameters.Length); Assert.Equal(3, function.HiddenRequiredParameters.Length); Assert.Empty(function.OptionalParameters); @@ -236,20 +236,20 @@ namespace Microsoft.PowerFx.Connectors.Tests Assert.Equal("analysisInput", function.RequiredParameters[0].Name); Assert.Equal(analysisInputRecordType, function.RequiredParameters[0].FormulaType); Assert.Equal("A single conversational task to execute.", function.RequiredParameters[0].Description); - Assert.Null(function.RequiredParameters[0].DefaultValue); - Assert.NotNull(function.RequiredParameters[0].ConnectorType); - Assert.Equal("analysisInput", function.RequiredParameters[0].ConnectorType.Name); - Assert.Null(function.RequiredParameters[0].ConnectorType.DisplayName); - Assert.Equal("The input ConversationItem and its optional parameters", function.RequiredParameters[0].ConnectorType.Description); - Assert.Equal(analysisInputRecordType, function.RequiredParameters[0].ConnectorType.FormulaType); - Assert.True(function.RequiredParameters[0].ConnectorType.IsRequired); - Assert.Single(function.RequiredParameters[0].ConnectorType.Fields); - Assert.Equal("conversationItem", function.RequiredParameters[0].ConnectorType.Fields[0].Name); - Assert.Null(function.RequiredParameters[0].ConnectorType.Fields[0].DisplayName); - Assert.Equal("The abstract base for a user input formatted conversation (e.g., Text, Transcript).", function.RequiredParameters[0].ConnectorType.Fields[0].Description); - Assert.True(function.RequiredParameters[0].ConnectorType.Fields[0].IsRequired); - - // -- Parameter 2 -- + Assert.Null(function.RequiredParameters[0].DefaultValue); + Assert.NotNull(function.RequiredParameters[0].ConnectorType); + Assert.Equal("analysisInput", function.RequiredParameters[0].ConnectorType.Name); + Assert.Null(function.RequiredParameters[0].ConnectorType.DisplayName); + Assert.Equal("The input ConversationItem and its optional parameters", function.RequiredParameters[0].ConnectorType.Description); + Assert.Equal(analysisInputRecordType, function.RequiredParameters[0].ConnectorType.FormulaType); + Assert.True(function.RequiredParameters[0].ConnectorType.IsRequired); + Assert.Single(function.RequiredParameters[0].ConnectorType.Fields); + Assert.Equal("conversationItem", function.RequiredParameters[0].ConnectorType.Fields[0].Name); + Assert.Null(function.RequiredParameters[0].ConnectorType.Fields[0].DisplayName); + Assert.Equal("The abstract base for a user input formatted conversation (e.g., Text, Transcript).", function.RequiredParameters[0].ConnectorType.Fields[0].Description); + Assert.True(function.RequiredParameters[0].ConnectorType.Fields[0].IsRequired); + + // -- Parameter 2 -- Assert.Equal("parameters", function.RequiredParameters[1].Name); Assert.Equal(parametersRecordType, function.RequiredParameters[1].FormulaType); Assert.Equal("A single conversational task to execute.", function.RequiredParameters[1].Description); @@ -308,23 +308,23 @@ namespace Microsoft.PowerFx.Connectors.Tests string returnTypeName = expectedReturnType.ToStringWithDisplayNames(); Assert.Equal(rt3Name, returnTypeName); - Assert.True((FormulaType)expectedReturnType == returnType); - - ConnectorType connectorReturnType = function.ReturnParameterType; - Assert.NotNull(connectorReturnType); - Assert.Equal((FormulaType)expectedReturnType, connectorReturnType.FormulaType); - Assert.Equal(2, connectorReturnType.Fields.Length); - Assert.Equal("The results of a Conversation task.", connectorReturnType.Description); + Assert.True((FormulaType)expectedReturnType == returnType); + + ConnectorType connectorReturnType = function.ReturnParameterType; + Assert.NotNull(connectorReturnType); + Assert.Equal((FormulaType)expectedReturnType, connectorReturnType.FormulaType); + Assert.Equal(2, connectorReturnType.Fields.Length); + Assert.Equal("The results of a Conversation task.", connectorReturnType.Description); } - + [Fact] public async Task ACSL_InvokeFunction() { using var testConnector = new LoggingTestServer(@"Swagger\Azure Cognitive Service for Language.json", _output); OpenApiDocument apiDoc = testConnector._apiDocument; - ConsoleLogger logger = new ConsoleLogger(_output); + ConsoleLogger logger = new ConsoleLogger(_output); - PowerFxConfig pfxConfig = new PowerFxConfig(Features.PowerFxV1); + PowerFxConfig pfxConfig = new PowerFxConfig(Features.PowerFxV1); ConnectorFunction function = OpenApiParser.GetFunctions(new ConnectorSettings("ACSL") { Compatibility = ConnectorCompatibility.SwaggerCompatibility }, apiDoc).OrderBy(cf => cf.Name).ToList()[14]; Assert.Equal("ConversationAnalysisAnalyzeConversationConversation", function.Name); Assert.Equal("![kind:s, result:![detectedLanguage:s, prediction:![entities:*[category:s, confidenceScore:w, extraInformation:O, length:w, offset:w, resolutions:O, text:s], intents:*[category:s, confidenceScore:w], projectKind:s, topIntent:s], query:s]]", function.ReturnType.ToStringWithDisplayNames()); @@ -334,32 +334,32 @@ namespace Microsoft.PowerFx.Connectors.Tests string analysisInput = @"{ conversationItem: { modality: ""text"", language: ""en-us"", text: ""Book me a flight for Munich"" } }"; string parameters = @"{ deploymentName: ""deploy1"", projectName: ""project1"", verbose: true, stringIndexType: ""TextElement_V8"" }"; FormulaValue analysisInputParam = engine.Eval(analysisInput); - FormulaValue parametersParam = engine.Eval(parameters); + FormulaValue parametersParam = engine.Eval(parameters); FormulaValue kind = FormulaValue.New("Conversation"); - + using var httpClient = new HttpClient(testConnector); testConnector.SetResponseFromFile(@"Responses\Azure Cognitive Service for Language_Response.json"); using PowerPlatformConnectorClient client = new PowerPlatformConnectorClient("https://lucgen-apim.azure-api.net", "aaa373836ffd4915bf6eefd63d164adc" /* environment Id */, "16e7c181-2f8d-4cae-b1f0-179c5c4e4d8b" /* connectionId */, () => "No Auth", httpClient) { SessionId = "a41bd03b-6c3c-4509-a844-e8c51b61f878", - }; - - BaseRuntimeConnectorContext context = new TestConnectorRuntimeContext("ACSL", client, console: _output); - - FormulaValue httpResult = await function.InvokeAsync(new FormulaValue[] { analysisInputParam, parametersParam }, context, CancellationToken.None).ConfigureAwait(false); - httpClient.Dispose(); - client.Dispose(); - testConnector.Dispose(); - - using var testConnector2 = new LoggingTestServer(@"Swagger\Azure Cognitive Service for Language.json", _output); + }; + + BaseRuntimeConnectorContext context = new TestConnectorRuntimeContext("ACSL", client, console: _output); + + FormulaValue httpResult = await function.InvokeAsync(new FormulaValue[] { analysisInputParam, parametersParam }, context, CancellationToken.None).ConfigureAwait(false); + httpClient.Dispose(); + client.Dispose(); + testConnector.Dispose(); + + using var testConnector2 = new LoggingTestServer(@"Swagger\Azure Cognitive Service for Language.json", _output); using var httpClient2 = new HttpClient(testConnector2); testConnector2.SetResponseFromFile(@"Responses\Azure Cognitive Service for Language_Response.json"); using PowerPlatformConnectorClient client2 = new PowerPlatformConnectorClient("https://lucgen-apim.azure-api.net", "aaa373836ffd4915bf6eefd63d164adc" /* environment Id */, "16e7c181-2f8d-4cae-b1f0-179c5c4e4d8b" /* connectionId */, () => "No Auth", httpClient2) { SessionId = "a41bd03b-6c3c-4509-a844-e8c51b61f878", }; - - BaseRuntimeConnectorContext context2 = new TestConnectorRuntimeContext("ACSL", client2, console: _output); + + BaseRuntimeConnectorContext context2 = new TestConnectorRuntimeContext("ACSL", client2, console: _output); FormulaValue httpResult2 = await function.InvokeAsync(new FormulaValue[] { analysisInputParam, parametersParam }, context2, CancellationToken.None).ConfigureAwait(false); @@ -402,54 +402,54 @@ namespace Microsoft.PowerFx.Connectors.Tests "; Assert.Equal(expectedInput.Replace("\r\n", "\n").Replace("\r", "\n"), input.Replace("\r\n", "\n").Replace("\r", "\n")); - } - + } + [Fact] public async Task AzureOpenAiGetFunctions() { using var testConnector = new LoggingTestServer(@"Swagger\Azure Open AI.json", _output); OpenApiDocument apiDoc = testConnector._apiDocument; - PowerFxConfig pfxConfig = new PowerFxConfig(Features.PowerFxV1); - ConnectorFunction[] functions = OpenApiParser.GetFunctions("OpenAI", apiDoc, new ConsoleLogger(_output)).OrderBy(cf => cf.Name).ToArray(); - - Assert.Equal("ChatCompletionsCreate", functions[0].Name); - Assert.Equal("![choices:*[finish_reason:s, index:w, message:![content:s, role:s]], created:w, id:s, model:s, object:s, usage:![completion_tokens:w, prompt_tokens:w, total_tokens:w]]", functions[0].ReturnType.ToStringWithDisplayNames()); - - Assert.Equal("CompletionsCreate", functions[1].Name); - Assert.Equal("![choices:*[finish_reason:s, index:w, logprobs:![text_offset:*[Value:w], token_logprobs:*[Value:w], tokens:*[Value:s], top_logprobs:*[]], text:s], created:w, id:s, model:s, object:s, usage:![completion_tokens:w, prompt_tokens:w, total_tokens:w]]", functions[1].ReturnType.ToStringWithDisplayNames()); - - Assert.Equal("ExtensionsChatCompletionsCreate", functions[2].Name); - Assert.Equal("![choices:*[content_filter_results:![error:![code:s, message:s], hate:![filtered:b, severity:s], self_harm:![filtered:b, severity:s], sexual:![filtered:b, severity:s], violence:![filtered:b, severity:s]], finish_reason:s, index:w, messages:*[content:s, end_turn:b, index:w, recipient:s, role:s]], created:w, id:s, model:s, object:s, prompt_filter_results:*[content_filter_results:![error:![code:s, message:s], hate:![filtered:b, severity:s], self_harm:![filtered:b, severity:s], sexual:![filtered:b, severity:s], violence:![filtered:b, severity:s]], prompt_index:w], usage:![completion_tokens:w, prompt_tokens:w, total_tokens:w]]", functions[2].ReturnType.ToStringWithDisplayNames()); - } - + PowerFxConfig pfxConfig = new PowerFxConfig(Features.PowerFxV1); + ConnectorFunction[] functions = OpenApiParser.GetFunctions("OpenAI", apiDoc, new ConsoleLogger(_output)).OrderBy(cf => cf.Name).ToArray(); + + Assert.Equal("ChatCompletionsCreate", functions[0].Name); + Assert.Equal("![choices:*[finish_reason:s, index:w, message:![content:s, role:s]], created:w, id:s, model:s, object:s, usage:![completion_tokens:w, prompt_tokens:w, total_tokens:w]]", functions[0].ReturnType.ToStringWithDisplayNames()); + + Assert.Equal("CompletionsCreate", functions[1].Name); + Assert.Equal("![choices:*[finish_reason:s, index:w, logprobs:![text_offset:*[Value:w], token_logprobs:*[Value:w], tokens:*[Value:s], top_logprobs:*[]], text:s], created:w, id:s, model:s, object:s, usage:![completion_tokens:w, prompt_tokens:w, total_tokens:w]]", functions[1].ReturnType.ToStringWithDisplayNames()); + + Assert.Equal("ExtensionsChatCompletionsCreate", functions[2].Name); + Assert.Equal("![choices:*[content_filter_results:![error:![code:s, message:s], hate:![filtered:b, severity:s], self_harm:![filtered:b, severity:s], sexual:![filtered:b, severity:s], violence:![filtered:b, severity:s]], finish_reason:s, index:w, messages:*[content:s, end_turn:b, index:w, recipient:s, role:s]], created:w, id:s, model:s, object:s, prompt_filter_results:*[content_filter_results:![error:![code:s, message:s], hate:![filtered:b, severity:s], self_harm:![filtered:b, severity:s], sexual:![filtered:b, severity:s], violence:![filtered:b, severity:s]], prompt_index:w], usage:![completion_tokens:w, prompt_tokens:w, total_tokens:w]]", functions[2].ReturnType.ToStringWithDisplayNames()); + } + [Fact] public async Task ACSL_InvokeFunction_v21() { using var testConnector = new LoggingTestServer(@"Swagger\Azure Cognitive Service for Language v2.1.json", _output); OpenApiDocument apiDoc = testConnector._apiDocument; - ConsoleLogger logger = new ConsoleLogger(_output); + ConsoleLogger logger = new ConsoleLogger(_output); PowerFxConfig pfxConfig = new PowerFxConfig(Features.PowerFxV1); using var httpClient = new HttpClient(testConnector); testConnector.SetResponseFromFile(@"Responses\Azure Cognitive Service for Language v2.1_Response.json"); - + var xx = OpenApiParser.GetFunctions("ACSL", apiDoc, logger).OrderBy(cf => cf.Name).ToList(); ConnectorFunction function = OpenApiParser.GetFunctions("ACSL", apiDoc, logger).OrderBy(cf => cf.Name).ToList()[11]; Assert.Equal("ConversationAnalysisAnalyzeConversationConversation", function.Name); Assert.Equal("![kind:s, result:![detectedLanguage:s, prediction:![entities:*[category:s, confidenceScore:w, extraInformation:O, length:w, multipleResolutions:b, offset:w, resolutions:O, text:s, topResolution:O], intents:*[category:s, confidenceScore:w], projectKind:s, topIntent:s], query:s]]", function.ReturnType.ToStringWithDisplayNames()); - RecalcEngine engine = new RecalcEngine(pfxConfig); + RecalcEngine engine = new RecalcEngine(pfxConfig); string analysisInput = @"{ conversationItem: { modality: ""text"", language: ""en-us"", text: ""Book me a flight for Munich"" } }"; string parameters = @"{ deploymentName: ""deploy1"", projectName: ""project1"", verbose: true, stringIndexType: ""TextElement_V8"" }"; FormulaValue analysisInputParam = engine.Eval(analysisInput); - FormulaValue parametersParam = engine.Eval(parameters); + FormulaValue parametersParam = engine.Eval(parameters); FormulaValue kind = FormulaValue.New("Conversation"); - using PowerPlatformConnectorClient client = new PowerPlatformConnectorClient("https://lucgen-apim.azure-api.net", "aaa373836ffd4915bf6eefd63d164adc" /* environment Id */, "16e7c181-2f8d-4cae-b1f0-179c5c4e4d8b" /* connectionId */, () => "No Auth", httpClient) { SessionId = "a41bd03b-6c3c-4509-a844-e8c51b61f878", }; - BaseRuntimeConnectorContext context = new TestConnectorRuntimeContext("ACSL", client, console: _output); - + using PowerPlatformConnectorClient client = new PowerPlatformConnectorClient("https://lucgen-apim.azure-api.net", "aaa373836ffd4915bf6eefd63d164adc" /* environment Id */, "16e7c181-2f8d-4cae-b1f0-179c5c4e4d8b" /* connectionId */, () => "No Auth", httpClient) { SessionId = "a41bd03b-6c3c-4509-a844-e8c51b61f878", }; + BaseRuntimeConnectorContext context = new TestConnectorRuntimeContext("ACSL", client, console: _output); + FormulaValue httpResult = await function.InvokeAsync(new FormulaValue[] { kind, analysisInputParam, parametersParam }, context, CancellationToken.None).ConfigureAwait(false); Assert.NotNull(httpResult); @@ -465,7 +465,7 @@ namespace Microsoft.PowerFx.Connectors.Tests RecordValue entityValue2 = rows.Skip(1).First().Value; FormulaValue resolutionsValue = entityValue2.GetField("resolutions"); - Assert.True(resolutionsValue is UntypedObjectValue); + Assert.True(resolutionsValue is UntypedObjectValue); UOValueVisitor visitor1 = new UOValueVisitor(); resolutionsValue.Visit(visitor1); @@ -478,10 +478,10 @@ namespace Microsoft.PowerFx.Connectors.Tests topResolutionValue1.Visit(visitor2); Assert.Equal("{\"resolutionKind\":\"DateTimeResolution\",\"value\":\"2023-02-25\"}", visitor2.Result); - } - + } + internal class UOValueVisitor : IValueVisitor - { + { public string Result { get; private set; } public void Visit(BlankValue value) @@ -575,11 +575,11 @@ namespace Microsoft.PowerFx.Connectors.Tests Result = value.Value.ToString(); } - public void Visit(BlobValue value) - { - Result = value.Content.GetAsBase64Async(CancellationToken.None).Result; - } - + public void Visit(BlobValue value) + { + Result = value.Content.GetAsBase64Async(CancellationToken.None).Result; + } + private void Visit(IUntypedObject untypedObject) { var type = untypedObject.Type; @@ -600,8 +600,8 @@ namespace Microsoft.PowerFx.Connectors.Tests { if (externalType.Kind == ExternalTypeKind.Array) { - var rows = new List(); - + var rows = new List(); + for (var i = 0; i < untypedObject.GetArrayLength(); i++) { var row = untypedObject[i]; @@ -642,8 +642,8 @@ namespace Microsoft.PowerFx.Connectors.Tests public void LQA_Load() { OpenApiDocument doc = Helpers.ReadSwagger(@"Swagger\Language - Question Answering.json", _output); - (List connectorFunctions, List texlFunctions) = OpenApiParser.ParseInternal(new ConnectorSettings("LQA"), doc, new ConsoleLogger(_output)); - Assert.Contains(texlFunctions, func => func.Namespace.Name.Value == "LQA" && func.Name == "GetAnswersFromText"); + (List connectorFunctions, List texlFunctions) = OpenApiParser.ParseInternal(new ConnectorSettings("LQA"), doc, new ConsoleLogger(_output)); + Assert.Contains(texlFunctions, func => func.Namespace.Name.Value == "LQA" && func.Name == "GetAnswersFromText"); } [Fact] @@ -651,508 +651,508 @@ namespace Microsoft.PowerFx.Connectors.Tests { OpenApiDocument doc = Helpers.ReadSwagger(@"Swagger\SQL Server.json", _output); (List connectorFunctions, List texlFunctions) = OpenApiParser.ParseInternal(new ConnectorSettings("SQL") { IncludeInternalFunctions = true }, doc, new ConsoleLogger(_output)); - Assert.Contains(texlFunctions, func => func.Namespace.Name.Value == "SQL" && func.Name == "GetProcedureV2"); - } - - [Fact] - public void Dataverse_Sample() - { - OpenApiDocument doc = Helpers.ReadSwagger(@"Swagger\DataverseSample.json", _output); - ConnectorFunction[] functions = OpenApiParser.GetFunctions("DV", doc, new ConsoleLogger(_output)).ToArray(); - - Assert.NotNull(functions); - Assert.Equal(3, functions.Count()); - - Assert.Equal(new List() { "GetLead", "PostLead", "QualifyLead" }, functions.Select(f => f.Name).ToList()); - Assert.Equal(new List() { "GetLead", "PostLead", "QualifyLead" }, functions.Select(f => f.OriginalName).ToList()); - - // "x-ms-require-user-confirmation" - Assert.Equal(new List() { false, true, true }, functions.Select(f => f.RequiresUserConfirmation).ToList()); - - // "x-ms-explicit-input" in "QualifyLead" function parameters - Assert.Equal(4, functions[2].RequiredParameters.Length); - Assert.False(functions[2].RequiredParameters[0].ConnectorType.ExplicitInput); // "leadId" - Assert.True(functions[2].RequiredParameters[1].ConnectorType.ExplicitInput); // "CreateAccount" - Assert.True(functions[2].RequiredParameters[2].ConnectorType.ExplicitInput); // "CreateContact" - Assert.True(functions[2].RequiredParameters[3].ConnectorType.ExplicitInput); // "CreateOpportunity" - Assert.Single(functions[2].HiddenRequiredParameters); - Assert.False(functions[2].HiddenRequiredParameters[0].ConnectorType.ExplicitInput); // "Status" - Assert.Empty(functions[2].OptionalParameters); - - // "x-ms-visibility" - (0..3).ForAll(i => Assert.Equal(Visibility.None, functions[2].RequiredParameters[i].ConnectorType.Visibility)); - Assert.Equal(Visibility.Internal, functions[2].HiddenRequiredParameters[0].ConnectorType.Visibility); // "Status" - - // "enum" - Assert.Equal(FormulaType.Decimal, functions[1].OptionalParameters[2].ConnectorType.FormulaType); // "leadsourcecode" - Assert.True(functions[1].OptionalParameters[2].ConnectorType.IsEnum); - Assert.Equal(Enumerable.Range(1, 10).Select(i => (decimal)i).ToArray(), functions[1].OptionalParameters[2].ConnectorType.EnumValues.Select(fv => (decimal)fv.ToObject())); - Assert.Equal("Advertisement, Employee Referral, External Referral, Partner, Public Relations, Seminar, Trade Show, Web, Word of Mouth, Other", string.Join(", ", functions[1].OptionalParameters[2].ConnectorType.EnumDisplayNames)); - Assert.Equal(4m, functions[1].OptionalParameters[2].ConnectorType.Enum["Partner"].ToObject()); - - // "x-ms-enum-display-name" - Assert.NotNull(functions[1].OptionalParameters[2].ConnectorType.EnumDisplayNames); - Assert.Equal("Advertisement", functions[1].OptionalParameters[2].ConnectorType.EnumDisplayNames[0]); - Assert.Equal("Employee Referral", functions[1].OptionalParameters[2].ConnectorType.EnumDisplayNames[1]); - - Assert.True(functions[1].RequiredParameters[2].ConnectorType.IsEnum); // "msdyn_company@odata.bind" - Assert.Equal("2b629105-4a26-4607-97a5-0715059e0a55", functions[1].RequiredParameters[2].ConnectorType.EnumValues[0].ToObject()); - Assert.Equal("5cacddd3-d47f-4023-a68e-0ce3e0d401fb", functions[1].RequiredParameters[2].ConnectorType.EnumValues[1].ToObject()); - Assert.Equal("INMF", functions[1].RequiredParameters[2].ConnectorType.EnumDisplayNames[0]); - Assert.Equal("MYMF", functions[1].RequiredParameters[2].ConnectorType.EnumDisplayNames[1]); - } - - [Fact] - public void VisibilityTest() - { - OpenApiDocument doc = Helpers.ReadSwagger(@"Swagger\AzureBlobStorage.json", _output); - ConnectorFunction[] functions = OpenApiParser.GetFunctions("AzBlob", doc, new ConsoleLogger(_output)).ToArray(); - - ConnectorFunction createFileV2 = functions.First(f => f.Name == "CreateFileV2"); - - Assert.Equal(4, createFileV2.RequiredParameters.Length); - Assert.Equal(3, createFileV2.OptionalParameters.Length); - Assert.Empty(createFileV2.HiddenRequiredParameters); - - Assert.Equal("important", createFileV2.Visibility); - - Assert.Equal("dataset", createFileV2.RequiredParameters[0].Name); - Assert.Equal("folderPath", createFileV2.RequiredParameters[1].Name); - Assert.Equal("name", createFileV2.RequiredParameters[2].Name); - Assert.Equal("file", createFileV2.RequiredParameters[3].Name); - Assert.Equal(FormulaType.Blob, createFileV2.RequiredParameters[3].FormulaType); - (0..3).ForAll(i => Assert.Equal(Visibility.None, createFileV2.RequiredParameters[i].ConnectorType.Visibility)); - - Assert.Equal("queryParametersSingleEncoded", createFileV2.OptionalParameters[0].Name); - Assert.Equal("Content-Type", createFileV2.OptionalParameters[1].Name); - Assert.Equal("ReadFileMetadataFromServer", createFileV2.OptionalParameters[2].Name); - Assert.Equal(Visibility.Internal, createFileV2.OptionalParameters[0].ConnectorType.Visibility); - Assert.Equal(Visibility.Advanced, createFileV2.OptionalParameters[1].ConnectorType.Visibility); - Assert.Equal(Visibility.Internal, createFileV2.OptionalParameters[2].ConnectorType.Visibility); - - Assert.Equal(Visibility.None, createFileV2.ReturnParameterType.Visibility); - - ConnectorFunction listFolderV4 = functions.First(f => f.Name == "ListFolderV4"); - - Assert.Equal(Visibility.None, listFolderV4.ReturnParameterType.Visibility); - Assert.Equal(Visibility.None, listFolderV4.ReturnParameterType.Fields[0].Visibility); - Assert.Equal(Visibility.Advanced, listFolderV4.ReturnParameterType.Fields[1].Visibility); - Assert.Equal(Visibility.Advanced, listFolderV4.ReturnParameterType.Fields[2].Visibility); - } - - [Fact] - public void DynamicReturnValueTest() - { - using HttpClient httpClient = new (); - OpenApiDocument doc = Helpers.ReadSwagger(@"Swagger\SQL Server.json", _output); - ConnectorFunction[] functions = OpenApiParser.GetFunctions("SQL", doc, new ConsoleLogger(_output)).ToArray(); - - ConnectorFunction createFileV2 = functions.First(f => f.Name == "ExecuteProcedureV2"); - - Assert.Equal(4, createFileV2.RequiredParameters.Length); - Assert.Empty(createFileV2.OptionalParameters); - Assert.Empty(createFileV2.HiddenRequiredParameters); - - Assert.NotNull(createFileV2.DynamicReturnSchema); - Assert.Null(createFileV2.DynamicReturnProperty); - - Assert.Equal("GetProcedureV2", createFileV2.DynamicReturnSchema.OperationId); - Assert.NotNull(createFileV2.DynamicReturnSchema.ConnectorFunction); - Assert.Equal("GetProcedureV2", createFileV2.DynamicReturnSchema.ConnectorFunction.Name); - Assert.Equal("schema/procedureresultschema", createFileV2.DynamicReturnSchema.ValuePath); - Assert.Equal(3, createFileV2.DynamicReturnSchema.ParameterMap.Count); - - Assert.True(createFileV2.DynamicReturnSchema.ParameterMap["server"] is DynamicConnectorExtensionValue dv1 && dv1.Reference == "server"); - Assert.True(createFileV2.DynamicReturnSchema.ParameterMap["database"] is DynamicConnectorExtensionValue dv2 && dv2.Reference == "database"); - Assert.True(createFileV2.DynamicReturnSchema.ParameterMap["procedure"] is DynamicConnectorExtensionValue dv3 && dv3.Reference == "procedure"); - - ConnectorFunction executePassThroughNativeQueryV2 = functions.First(f => f.Name == "ExecutePassThroughNativeQueryV2"); - - Assert.Equal(2, executePassThroughNativeQueryV2.RequiredParameters.Length); - Assert.Equal(3, executePassThroughNativeQueryV2.OptionalParameters.Length); - Assert.Empty(executePassThroughNativeQueryV2.HiddenRequiredParameters); - - Assert.NotNull(executePassThroughNativeQueryV2.DynamicReturnSchema); - Assert.NotNull(executePassThroughNativeQueryV2.DynamicReturnProperty); - - Assert.Equal("GetPassThroughNativeQueryMetadataV2", executePassThroughNativeQueryV2.DynamicReturnSchema.OperationId); - Assert.NotNull(executePassThroughNativeQueryV2.DynamicReturnSchema.ConnectorFunction); - Assert.Equal("GetPassThroughNativeQueryMetadataV2", executePassThroughNativeQueryV2.DynamicReturnSchema.ConnectorFunction.Name); - Assert.Equal("schema/queryresults", executePassThroughNativeQueryV2.DynamicReturnSchema.ValuePath); - Assert.Equal(4, executePassThroughNativeQueryV2.DynamicReturnSchema.ParameterMap.Count); - Assert.True(executePassThroughNativeQueryV2.DynamicReturnSchema.ParameterMap["server"] is DynamicConnectorExtensionValue dv4 && dv4.Reference == "server"); - Assert.True(executePassThroughNativeQueryV2.DynamicReturnSchema.ParameterMap["database"] is DynamicConnectorExtensionValue dv5 && dv5.Reference == "database"); - Assert.True(executePassThroughNativeQueryV2.DynamicReturnSchema.ParameterMap["query"] is DynamicConnectorExtensionValue dv6 && dv6.Reference == "query"); - Assert.True(executePassThroughNativeQueryV2.DynamicReturnSchema.ParameterMap["formalParameters"] is DynamicConnectorExtensionValue dv7 && dv7.Reference == "formalParameters"); - - Assert.Equal("GetPassThroughNativeQueryMetadataV2", executePassThroughNativeQueryV2.DynamicReturnProperty.OperationId); - Assert.NotNull(executePassThroughNativeQueryV2.DynamicReturnProperty.ConnectorFunction); - Assert.Equal("GetPassThroughNativeQueryMetadataV2", executePassThroughNativeQueryV2.DynamicReturnProperty.ConnectorFunction.Name); - Assert.Equal("schema/queryresults", executePassThroughNativeQueryV2.DynamicReturnProperty.ItemValuePath); - Assert.Equal(4, executePassThroughNativeQueryV2.DynamicReturnProperty.ParameterMap.Count); - Assert.True(executePassThroughNativeQueryV2.DynamicReturnProperty.ParameterMap["server"] is DynamicConnectorExtensionValue dv8 && dv8.Reference == "server"); - Assert.True(executePassThroughNativeQueryV2.DynamicReturnProperty.ParameterMap["database"] is DynamicConnectorExtensionValue dv9 && dv9.Reference == "database"); - Assert.True(executePassThroughNativeQueryV2.DynamicReturnProperty.ParameterMap["query/query"] is DynamicConnectorExtensionValue dv10 && dv10.Reference == "query/query"); - Assert.True(executePassThroughNativeQueryV2.DynamicReturnProperty.ParameterMap["query/formalParameters"] is DynamicConnectorExtensionValue dv11 && dv11.Reference == "query/formalParameters"); - } - - [Fact] - public async Task DirectIntellisenseTest() - { - using var testConnector = new LoggingTestServer(@"Swagger\SQL Server.json", _output); - using var httpClient = new HttpClient(testConnector); - using PowerPlatformConnectorClient client = new PowerPlatformConnectorClient("https://tip1002-002.azure-apihub.net", "ddadf2c7-ebdd-ec01-a5d1-502dc07f04b4" /* environment Id */, "4bf9a87fc9054b6db3a4d07a1c1f5a5b" /* connectionId */, () => "eyJ0eXAi...", httpClient) { SessionId = "a41bd03b-6c3c-4509-a844-e8c51b61f878" }; - - BaseRuntimeConnectorContext runtimeContext = new TestConnectorRuntimeContext("SQL", client, console: _output); - - ConnectorFunction[] functions = OpenApiParser.GetFunctions("SQL", testConnector._apiDocument, new ConsoleLogger(_output)).ToArray(); - ConnectorFunction executeProcedureV2 = functions.First(f => f.Name == "ExecuteProcedureV2"); - - Assert.True(executeProcedureV2.RequiredParameters[0].SupportsDynamicIntellisense); - Assert.True(executeProcedureV2.RequiredParameters[1].SupportsDynamicIntellisense); - Assert.True(executeProcedureV2.RequiredParameters[2].SupportsDynamicIntellisense); - Assert.True(executeProcedureV2.RequiredParameters[3].SupportsDynamicIntellisense); - - // Keeping only for debugging - //FormulaValue result = await executeProcedureV2.InvokeAync(client, new FormulaValue[] - //{ - // FormulaValue.New("pfxdev-sql.database.windows.net"), - // FormulaValue.New("connectortest"), - // FormulaValue.New("sp_1"), - // FormulaValue.NewRecordFromFields(new NamedValue[] { new NamedValue("p1", FormulaValue.New(38)) }) - //}, CancellationToken.None).ConfigureAwait(false); - - testConnector.SetResponseFromFile(@"Responses\SQL Server Intellisense Response 3.json"); - ConnectorParameters parameters1 = await executeProcedureV2.GetParameterSuggestionsAsync( - new NamedValue[] - { - new NamedValue("server", FormulaValue.New("pfxdev-sql.database.windows.net")), - new NamedValue("database", FormulaValue.New("connectortest")) - }, - executeProcedureV2.RequiredParameters[2], // procedure - runtimeContext, - CancellationToken.None).ConfigureAwait(false); - - ConnectorParameterWithSuggestions suggestions1 = parameters1.ParametersWithSuggestions[2]; - Assert.NotNull(suggestions1); - Assert.NotNull(suggestions1.Suggestions); - Assert.Equal(2, suggestions1.Suggestions.Count()); - - testConnector.SetResponseFromFile(@"Responses\SQL Server Intellisense Response2 1.json"); - ConnectorParameters parameters2 = await executeProcedureV2.GetParameterSuggestionsAsync( - new NamedValue[] - { - new NamedValue("server", FormulaValue.New("pfxdev-sql.database.windows.net")), - new NamedValue("database", FormulaValue.New("connectortest")), - new NamedValue("procedure", FormulaValue.New("sp_1")) - }, - executeProcedureV2.RequiredParameters[3], // parameters - runtimeContext, - CancellationToken.None).ConfigureAwait(false); - - ConnectorParameterWithSuggestions suggestions2 = parameters2.ParametersWithSuggestions[3]; - Assert.NotNull(suggestions2); - Assert.NotNull(suggestions2.Suggestions); - Assert.Single(suggestions2.Suggestions); - - Assert.True(executeProcedureV2.ReturnParameterType.SupportsDynamicIntellisense); - - testConnector.SetResponseFromFile(@"Responses\SQL Server Intellisense Response2 1.json"); - ConnectorType returnType = await executeProcedureV2.GetConnectorReturnTypeAsync( - new NamedValue[] - { - new NamedValue("server", FormulaValue.New("pfxdev-sql.database.windows.net")), - new NamedValue("database", FormulaValue.New("connectortest")), - new NamedValue("procedure", FormulaValue.New("sp_1")) - }, - runtimeContext, - CancellationToken.None).ConfigureAwait(false); - - Assert.NotNull(returnType); - Assert.True(returnType.FormulaType is RecordType); - - string input = testConnector._log.ToString(); - var version = PowerPlatformConnectorClient.Version; - string expected = $@"POST https://tip1002-002.azure-apihub.net/invoke - authority: tip1002-002.azure-apihub.net - Authorization: Bearer eyJ0eXAi... - path: /invoke - scheme: https - x-ms-client-environment-id: /providers/Microsoft.PowerApps/environments/ddadf2c7-ebdd-ec01-a5d1-502dc07f04b4 - x-ms-client-session-id: a41bd03b-6c3c-4509-a844-e8c51b61f878 - x-ms-request-method: GET - x-ms-request-url: /apim/sql/4bf9a87fc9054b6db3a4d07a1c1f5a5b/v2/datasets/pfxdev-sql.database.windows.net,connectortest/procedures - x-ms-user-agent: PowerFx/{version} -POST https://tip1002-002.azure-apihub.net/invoke - authority: tip1002-002.azure-apihub.net - Authorization: Bearer eyJ0eXAi... - path: /invoke - scheme: https - x-ms-client-environment-id: /providers/Microsoft.PowerApps/environments/ddadf2c7-ebdd-ec01-a5d1-502dc07f04b4 - x-ms-client-session-id: a41bd03b-6c3c-4509-a844-e8c51b61f878 - x-ms-request-method: GET - x-ms-request-url: /apim/sql/4bf9a87fc9054b6db3a4d07a1c1f5a5b/v2/$metadata.json/datasets/pfxdev-sql.database.windows.net,connectortest/procedures/sp_1 - x-ms-user-agent: PowerFx/{version} -POST https://tip1002-002.azure-apihub.net/invoke - authority: tip1002-002.azure-apihub.net - Authorization: Bearer eyJ0eXAi... - path: /invoke - scheme: https - x-ms-client-environment-id: /providers/Microsoft.PowerApps/environments/ddadf2c7-ebdd-ec01-a5d1-502dc07f04b4 - x-ms-client-session-id: a41bd03b-6c3c-4509-a844-e8c51b61f878 - x-ms-request-method: GET - x-ms-request-url: /apim/sql/4bf9a87fc9054b6db3a4d07a1c1f5a5b/v2/$metadata.json/datasets/pfxdev-sql.database.windows.net,connectortest/procedures/sp_1 - x-ms-user-agent: PowerFx/{version} -"; - - Assert.Equal(expected, input); - } - - [Fact] - public async Task DataverseTest() - { - using var testConnector = new LoggingTestServer(@"Swagger\Dataverse.json", _output); - using var httpClient = new HttpClient(testConnector); - using PowerPlatformConnectorClient client = new PowerPlatformConnectorClient("https://tip1-shared.azure-apim.net", "Default-9f6be790-4a16-4dd6-9850-44a0d2649aef" /* environment Id */, "461a30624723445c9ba87313d8bbefa3" /* connectionId */, () => "eyJ0eXAiO...", httpClient) { SessionId = "a41bd03b-6c3c-4509-a844-e8c51b61f878" }; - - BaseRuntimeConnectorContext runtimeContext = new TestConnectorRuntimeContext("DV", client, console: _output); - - ConnectorFunction[] functions = OpenApiParser.GetFunctions(new ConnectorSettings("DV") { Compatibility = ConnectorCompatibility.SwaggerCompatibility }, testConnector._apiDocument).ToArray(); - ConnectorFunction createRecord = functions.First(f => f.Name == "CreateRecordWithOrganization"); - - testConnector.SetResponseFromFile(@"Responses\Dataverse_Response_1.json"); - ConnectorParameters parameters1 = await createRecord.GetParameterSuggestionsAsync( - new NamedValue[] - { - new NamedValue("organization", FormulaValue.New("https://org283e9949.crm10.dynamics.com")) - }, - createRecord.RequiredParameters[1], // entityName - runtimeContext, - CancellationToken.None).ConfigureAwait(false); - - ConnectorParameterWithSuggestions suggestions1 = parameters1.ParametersWithSuggestions[1]; - Assert.Equal(651, suggestions1.Suggestions.Count); - Assert.Equal("AAD Users", suggestions1.Suggestions[0].DisplayName); - Assert.Equal("aadusers", ((StringValue)suggestions1.Suggestions[0].Suggestion).Value); - - testConnector.SetResponseFromFile(@"Responses\Dataverse_Response_2.json"); - ConnectorParameters parameters2 = await createRecord.GetParameterSuggestionsAsync( - new NamedValue[] - { - new NamedValue("organization", FormulaValue.New("https://org283e9949.crm10.dynamics.com")), - new NamedValue("entityName", FormulaValue.New("accounts")) - }, - createRecord.RequiredParameters[2], // item - runtimeContext, - CancellationToken.None).ConfigureAwait(false); - - ConnectorParameterWithSuggestions suggestions2 = parameters2.ParametersWithSuggestions[2]; - Assert.Equal(119, suggestions2.Suggestions.Count); - Assert.Equal("accountcategorycode", suggestions2.Suggestions[0].DisplayName); - Assert.Equal("Decimal", suggestions2.Suggestions[0].Suggestion.Type.ToString()); - - string input = testConnector._log.ToString(); - var version = PowerPlatformConnectorClient.Version; - string expected = @$"POST https://tip1-shared.azure-apim.net/invoke - authority: tip1-shared.azure-apim.net - Authorization: Bearer eyJ0eXAiO... - organization: https://org283e9949.crm10.dynamics.com - path: /invoke - scheme: https - x-ms-client-environment-id: /providers/Microsoft.PowerApps/environments/Default-9f6be790-4a16-4dd6-9850-44a0d2649aef - x-ms-client-session-id: a41bd03b-6c3c-4509-a844-e8c51b61f878 - x-ms-request-method: POST - x-ms-request-url: /apim/commondataserviceforapps/461a30624723445c9ba87313d8bbefa3/v1.0/$metadata.json/GetEntityListEnum/GetEntitiesWithOrganization - x-ms-user-agent: PowerFx/{version} -POST https://tip1-shared.azure-apim.net/invoke - authority: tip1-shared.azure-apim.net - Authorization: Bearer eyJ0eXAiO... - organization: https://org283e9949.crm10.dynamics.com - path: /invoke - scheme: https - x-ms-client-environment-id: /providers/Microsoft.PowerApps/environments/Default-9f6be790-4a16-4dd6-9850-44a0d2649aef - x-ms-client-session-id: a41bd03b-6c3c-4509-a844-e8c51b61f878 - x-ms-request-method: GET - x-ms-request-url: /apim/commondataserviceforapps/461a30624723445c9ba87313d8bbefa3/v1.0/$metadata.json/entities/accounts/postitem - x-ms-user-agent: PowerFx/{version} -"; - - Assert.Equal(expected, input); - } - - [Theory] - - // Very slow -- [InlineData(@"Swagger\Dataverse 2.json")] - // Very slow -- [InlineData(@"Swagger\Dataverse 3.json")] - [InlineData(@"Swagger\PowerPlatformForAdmins.json")] - public async Task DataverseTest2(string swaggerFile) - { - PowerFxConfig powerFxConfig = new PowerFxConfig(); - OpenApiDocument doc = Helpers.ReadSwagger(swaggerFile, _output); - - OpenApiParser.GetFunctions("namespace", doc); // missing logger - powerFxConfig.AddActionConnector("namespace", doc); - } - - [Fact] - public async Task CardsForPowerApps_Invoke() - { - using var testConnector = new LoggingTestServer(@"Swagger\CardsForPowerApps.json", _output); - using var httpClient = new HttpClient(testConnector); - using PowerPlatformConnectorClient client = new PowerPlatformConnectorClient("https://tip1002-002.azure-apihub.net", "7592282b-e371-e3f6-8e04-e8f23e64227c" /* environment Id */, "shared-cardsforpower-eafc4fa0-c560-4eba-a5b2-3e1ebc63193a" /* connectionId */, () => "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dC...", httpClient) { SessionId = "a41bd03b-6c3c-4509-a844-e8c51b61f878" }; - - BaseRuntimeConnectorContext runtimeContext = new TestConnectorRuntimeContext("DV", client, console: _output); - - ConnectorFunction[] functions = OpenApiParser.GetFunctions( - new ConnectorSettings("DV") - { - Compatibility = ConnectorCompatibility.SwaggerCompatibility, - ReturnUnknownRecordFieldsAsUntypedObjects = true - }, - testConnector._apiDocument).ToArray(); - ConnectorFunction createCardInstance = functions.First(f => f.Name == "CreateCardInstance"); - - testConnector.SetResponseFromFile(@"Responses\CardsForPowerApps_CreateCardInstance.json"); - var result = await createCardInstance.InvokeAsync( - new FormulaValue[] - { - FormulaValue.New("card"), - FormulaValue.NewRecordFromFields( - new NamedValue("inputs", FormulaValue.NewRecordFromFields( - new NamedValue("property1", FormulaValue.New("test1")), - new NamedValue("property2", FormulaValue.New("test2"))))), - }, - runtimeContext, - CancellationToken.None).ConfigureAwait(false); - - string input = testConnector._log.ToString(); - Assert.Equal("AdaptiveCard", (((RecordValue)result).GetField("type") as UntypedObjectValue).Impl.GetString()); - Assert.Equal( - $@"POST https://tip1002-002.azure-apihub.net/invoke - authority: tip1002-002.azure-apihub.net - Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dC... - path: /invoke - scheme: https - x-ms-client-environment-id: /providers/Microsoft.PowerApps/environments/7592282b-e371-e3f6-8e04-e8f23e64227c - x-ms-client-session-id: a41bd03b-6c3c-4509-a844-e8c51b61f878 - x-ms-request-method: POST - x-ms-request-url: /apim/cardsforpowerapps/shared-cardsforpower-eafc4fa0-c560-4eba-a5b2-3e1ebc63193a/cards/cards/card/instances - x-ms-user-agent: PowerFx/{PowerPlatformConnectorClient.Version} - [content-header] Content-Type: application/json; charset=utf-8 - [body] {{""inputs"":{{""property1"":""test1"",""property2"":""test2""}}}} -", input); - } - - [Fact] - public async Task CardsForPowerApps_Suggestion() - { - using var testConnector = new LoggingTestServer(@"Swagger\CardsForPowerApps.json", _output); - using var httpClient = new HttpClient(testConnector); - using PowerPlatformConnectorClient client = new PowerPlatformConnectorClient("https://tip1002-002.azure-apihub.net", "7592282b-e371-e3f6-8e04-e8f23e64227c" /* environment Id */, "shared-cardsforpower-eafc4fa0-c560-4eba-a5b2-3e1ebc63193a" /* connectionId */, () => "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dC...", httpClient) { SessionId = "a41bd03b-6c3c-4509-a844-e8c51b61f878" }; - - BaseRuntimeConnectorContext runtimeContext = new TestConnectorRuntimeContext("DV", client, console: _output); - - ConnectorFunction[] functions = OpenApiParser.GetFunctions(new ConnectorSettings("DV") { Compatibility = ConnectorCompatibility.SwaggerCompatibility }, testConnector._apiDocument).ToArray(); - ConnectorFunction createCardInstance = functions.First(f => f.Name == "CreateCardInstance"); - - testConnector.SetResponseFromFile(@"Responses\CardsForPowerApps_Suggestions.json"); - ConnectorParameters parameters = await createCardInstance.GetParameterSuggestionsAsync( - new NamedValue[] - { - }, - createCardInstance.RequiredParameters[0], // cardid - runtimeContext, - CancellationToken.None).ConfigureAwait(false); - - ConnectorParameterWithSuggestions suggestions = parameters.ParametersWithSuggestions[0]; - Assert.Equal(2, suggestions.Suggestions.Count); - Assert.Equal("test", suggestions.Suggestions[0].DisplayName); - Assert.Equal("testWithInputs", suggestions.Suggestions[1].DisplayName); - } - - [Fact] - public async Task Teams_GetMessageDetails_WithComplexParameterReference() - { - using var testConnector = new LoggingTestServer(@"Swagger\Teams.json", _output); - using var httpClient = new HttpClient(testConnector); - using PowerPlatformConnectorClient client = new PowerPlatformConnectorClient("https://tip1002-002.azure-apihub.net", "7592282b-e371-e3f6-8e04-e8f23e64227c" /* environment Id */, "shared-cardsforpower-eafc4fa0-c560-4eba-a5b2-3e1ebc63193a" /* connectionId */, () => "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dC...", httpClient) { SessionId = "a41bd03b-6c3c-4509-a844-e8c51b61f878" }; - - BaseRuntimeConnectorContext runtimeContext = new TestConnectorRuntimeContext("DV", client, console: _output); - - ConnectorFunction[] functions = OpenApiParser.GetFunctions(new ConnectorSettings("DV") { Compatibility = ConnectorCompatibility.SwaggerCompatibility }, testConnector._apiDocument).ToArray(); - ConnectorFunction getMessageDetails = functions.First(f => f.Name == "GetMessageDetails"); - - testConnector.SetResponseFromFile(@"Responses\Teams_GetMessageDetails_InputType.json"); - ConnectorParameters parameters = await getMessageDetails.GetParameterSuggestionsAsync( - new NamedValue[] - { - new NamedValue("messageId", FormulaValue.New("messageId")), - new NamedValue("threadType", FormulaValue.New("channel")), - }, - getMessageDetails.RequiredParameters[2], // body - runtimeContext, - CancellationToken.None).ConfigureAwait(false); - - var bodyConnectorType = parameters.ParametersWithSuggestions[2].ConnectorType; - - ConnectorParameterWithSuggestions suggestions = parameters.ParametersWithSuggestions[2]; - testConnector.SetResponseFromFile(@"Responses\Teams_GetMessageDetails_GetSuggestionsForChannel.json"); - - var connectorTypeWithSuggestions = await getMessageDetails.GetConnectorSuggestionsAsync( - new NamedValue[] - { - new NamedValue("messageId", FormulaValue.New("messageId")), - new NamedValue("threadType", FormulaValue.New("channel")), - new NamedValue("body", FormulaValue.NewRecordFromFields( - new NamedValue("recipient", FormulaValue.NewRecordFromFields( - new NamedValue("groupId", FormulaValue.New("groupIdValue")))))), - }, - bodyConnectorType.Fields[0].Fields[1], // channelId - runtimeContext, - CancellationToken.None).ConfigureAwait(false); - - Assert.Equal(2, connectorTypeWithSuggestions.ConnectorSuggestions.Suggestions.Count); - Assert.Equal("channelName", connectorTypeWithSuggestions.ConnectorSuggestions.Suggestions[0].DisplayName); - Assert.Equal("channelName2", connectorTypeWithSuggestions.ConnectorSuggestions.Suggestions[1].DisplayName); - } - } - - public static class Extensions - { - public static RecordType MakeRecordType(params (string, FormulaType)[] columns) - { - RecordType rt = RecordType.Empty(); - - foreach ((string name, FormulaType type) in columns) - { - rt = rt.Add(name, type); - } - - return rt; - } - - public static TableType MakeTableType(params (string, FormulaType)[] columns) - { - TableType tt = TableType.Empty(); - - foreach ((string name, FormulaType type) in columns) - { - tt = tt.Add(name, type); - } - - return tt; - } - - public static OptionSetValueType MakeOptionSetType(string name, params string[] names) - { - return MakeOptionSet(name, names).FormulaType; - } - - public static OptionSet MakeOptionSet(string name, params string[] names) - { - return new OptionSet(name, DisplayNameUtility.MakeUnique(names.ToDictionary(n => n, n => n))); - } - } - + Assert.Contains(texlFunctions, func => func.Namespace.Name.Value == "SQL" && func.Name == "GetProcedureV2"); + } + + [Fact] + public void Dataverse_Sample() + { + OpenApiDocument doc = Helpers.ReadSwagger(@"Swagger\DataverseSample.json", _output); + ConnectorFunction[] functions = OpenApiParser.GetFunctions("DV", doc, new ConsoleLogger(_output)).ToArray(); + + Assert.NotNull(functions); + Assert.Equal(3, functions.Count()); + + Assert.Equal(new List() { "GetLead", "PostLead", "QualifyLead" }, functions.Select(f => f.Name).ToList()); + Assert.Equal(new List() { "GetLead", "PostLead", "QualifyLead" }, functions.Select(f => f.OriginalName).ToList()); + + // "x-ms-require-user-confirmation" + Assert.Equal(new List() { false, true, true }, functions.Select(f => f.RequiresUserConfirmation).ToList()); + + // "x-ms-explicit-input" in "QualifyLead" function parameters + Assert.Equal(4, functions[2].RequiredParameters.Length); + Assert.False(functions[2].RequiredParameters[0].ConnectorType.ExplicitInput); // "leadId" + Assert.True(functions[2].RequiredParameters[1].ConnectorType.ExplicitInput); // "CreateAccount" + Assert.True(functions[2].RequiredParameters[2].ConnectorType.ExplicitInput); // "CreateContact" + Assert.True(functions[2].RequiredParameters[3].ConnectorType.ExplicitInput); // "CreateOpportunity" + Assert.Single(functions[2].HiddenRequiredParameters); + Assert.False(functions[2].HiddenRequiredParameters[0].ConnectorType.ExplicitInput); // "Status" + Assert.Empty(functions[2].OptionalParameters); + + // "x-ms-visibility" + (0..3).ForAll(i => Assert.Equal(Visibility.None, functions[2].RequiredParameters[i].ConnectorType.Visibility)); + Assert.Equal(Visibility.Internal, functions[2].HiddenRequiredParameters[0].ConnectorType.Visibility); // "Status" + + // "enum" + Assert.Equal(FormulaType.Decimal, functions[1].OptionalParameters[2].ConnectorType.FormulaType); // "leadsourcecode" + Assert.True(functions[1].OptionalParameters[2].ConnectorType.IsEnum); + Assert.Equal(Enumerable.Range(1, 10).Select(i => (decimal)i).ToArray(), functions[1].OptionalParameters[2].ConnectorType.EnumValues.Select(fv => (decimal)fv.ToObject())); + Assert.Equal("Advertisement, Employee Referral, External Referral, Partner, Public Relations, Seminar, Trade Show, Web, Word of Mouth, Other", string.Join(", ", functions[1].OptionalParameters[2].ConnectorType.EnumDisplayNames)); + Assert.Equal(4m, functions[1].OptionalParameters[2].ConnectorType.Enum["Partner"].ToObject()); + + // "x-ms-enum-display-name" + Assert.NotNull(functions[1].OptionalParameters[2].ConnectorType.EnumDisplayNames); + Assert.Equal("Advertisement", functions[1].OptionalParameters[2].ConnectorType.EnumDisplayNames[0]); + Assert.Equal("Employee Referral", functions[1].OptionalParameters[2].ConnectorType.EnumDisplayNames[1]); + + Assert.True(functions[1].RequiredParameters[2].ConnectorType.IsEnum); // "msdyn_company@odata.bind" + Assert.Equal("2b629105-4a26-4607-97a5-0715059e0a55", functions[1].RequiredParameters[2].ConnectorType.EnumValues[0].ToObject()); + Assert.Equal("5cacddd3-d47f-4023-a68e-0ce3e0d401fb", functions[1].RequiredParameters[2].ConnectorType.EnumValues[1].ToObject()); + Assert.Equal("INMF", functions[1].RequiredParameters[2].ConnectorType.EnumDisplayNames[0]); + Assert.Equal("MYMF", functions[1].RequiredParameters[2].ConnectorType.EnumDisplayNames[1]); + } + + [Fact] + public void VisibilityTest() + { + OpenApiDocument doc = Helpers.ReadSwagger(@"Swagger\AzureBlobStorage.json", _output); + ConnectorFunction[] functions = OpenApiParser.GetFunctions("AzBlob", doc, new ConsoleLogger(_output)).ToArray(); + + ConnectorFunction createFileV2 = functions.First(f => f.Name == "CreateFileV2"); + + Assert.Equal(4, createFileV2.RequiredParameters.Length); + Assert.Equal(3, createFileV2.OptionalParameters.Length); + Assert.Empty(createFileV2.HiddenRequiredParameters); + + Assert.Equal("important", createFileV2.Visibility); + + Assert.Equal("dataset", createFileV2.RequiredParameters[0].Name); + Assert.Equal("folderPath", createFileV2.RequiredParameters[1].Name); + Assert.Equal("name", createFileV2.RequiredParameters[2].Name); + Assert.Equal("file", createFileV2.RequiredParameters[3].Name); + Assert.Equal(FormulaType.Blob, createFileV2.RequiredParameters[3].FormulaType); + (0..3).ForAll(i => Assert.Equal(Visibility.None, createFileV2.RequiredParameters[i].ConnectorType.Visibility)); + + Assert.Equal("queryParametersSingleEncoded", createFileV2.OptionalParameters[0].Name); + Assert.Equal("Content-Type", createFileV2.OptionalParameters[1].Name); + Assert.Equal("ReadFileMetadataFromServer", createFileV2.OptionalParameters[2].Name); + Assert.Equal(Visibility.Internal, createFileV2.OptionalParameters[0].ConnectorType.Visibility); + Assert.Equal(Visibility.Advanced, createFileV2.OptionalParameters[1].ConnectorType.Visibility); + Assert.Equal(Visibility.Internal, createFileV2.OptionalParameters[2].ConnectorType.Visibility); + + Assert.Equal(Visibility.None, createFileV2.ReturnParameterType.Visibility); + + ConnectorFunction listFolderV4 = functions.First(f => f.Name == "ListFolderV4"); + + Assert.Equal(Visibility.None, listFolderV4.ReturnParameterType.Visibility); + Assert.Equal(Visibility.None, listFolderV4.ReturnParameterType.Fields[0].Visibility); + Assert.Equal(Visibility.Advanced, listFolderV4.ReturnParameterType.Fields[1].Visibility); + Assert.Equal(Visibility.Advanced, listFolderV4.ReturnParameterType.Fields[2].Visibility); + } + + [Fact] + public void DynamicReturnValueTest() + { + using HttpClient httpClient = new (); + OpenApiDocument doc = Helpers.ReadSwagger(@"Swagger\SQL Server.json", _output); + ConnectorFunction[] functions = OpenApiParser.GetFunctions("SQL", doc, new ConsoleLogger(_output)).ToArray(); + + ConnectorFunction createFileV2 = functions.First(f => f.Name == "ExecuteProcedureV2"); + + Assert.Equal(4, createFileV2.RequiredParameters.Length); + Assert.Empty(createFileV2.OptionalParameters); + Assert.Empty(createFileV2.HiddenRequiredParameters); + + Assert.NotNull(createFileV2.DynamicReturnSchema); + Assert.Null(createFileV2.DynamicReturnProperty); + + Assert.Equal("GetProcedureV2", createFileV2.DynamicReturnSchema.OperationId); + Assert.NotNull(createFileV2.DynamicReturnSchema.ConnectorFunction); + Assert.Equal("GetProcedureV2", createFileV2.DynamicReturnSchema.ConnectorFunction.Name); + Assert.Equal("schema/procedureresultschema", createFileV2.DynamicReturnSchema.ValuePath); + Assert.Equal(3, createFileV2.DynamicReturnSchema.ParameterMap.Count); + + Assert.True(createFileV2.DynamicReturnSchema.ParameterMap["server"] is DynamicConnectorExtensionValue dv1 && dv1.Reference == "server"); + Assert.True(createFileV2.DynamicReturnSchema.ParameterMap["database"] is DynamicConnectorExtensionValue dv2 && dv2.Reference == "database"); + Assert.True(createFileV2.DynamicReturnSchema.ParameterMap["procedure"] is DynamicConnectorExtensionValue dv3 && dv3.Reference == "procedure"); + + ConnectorFunction executePassThroughNativeQueryV2 = functions.First(f => f.Name == "ExecutePassThroughNativeQueryV2"); + + Assert.Equal(2, executePassThroughNativeQueryV2.RequiredParameters.Length); + Assert.Equal(3, executePassThroughNativeQueryV2.OptionalParameters.Length); + Assert.Empty(executePassThroughNativeQueryV2.HiddenRequiredParameters); + + Assert.NotNull(executePassThroughNativeQueryV2.DynamicReturnSchema); + Assert.NotNull(executePassThroughNativeQueryV2.DynamicReturnProperty); + + Assert.Equal("GetPassThroughNativeQueryMetadataV2", executePassThroughNativeQueryV2.DynamicReturnSchema.OperationId); + Assert.NotNull(executePassThroughNativeQueryV2.DynamicReturnSchema.ConnectorFunction); + Assert.Equal("GetPassThroughNativeQueryMetadataV2", executePassThroughNativeQueryV2.DynamicReturnSchema.ConnectorFunction.Name); + Assert.Equal("schema/queryresults", executePassThroughNativeQueryV2.DynamicReturnSchema.ValuePath); + Assert.Equal(4, executePassThroughNativeQueryV2.DynamicReturnSchema.ParameterMap.Count); + Assert.True(executePassThroughNativeQueryV2.DynamicReturnSchema.ParameterMap["server"] is DynamicConnectorExtensionValue dv4 && dv4.Reference == "server"); + Assert.True(executePassThroughNativeQueryV2.DynamicReturnSchema.ParameterMap["database"] is DynamicConnectorExtensionValue dv5 && dv5.Reference == "database"); + Assert.True(executePassThroughNativeQueryV2.DynamicReturnSchema.ParameterMap["query"] is DynamicConnectorExtensionValue dv6 && dv6.Reference == "query"); + Assert.True(executePassThroughNativeQueryV2.DynamicReturnSchema.ParameterMap["formalParameters"] is DynamicConnectorExtensionValue dv7 && dv7.Reference == "formalParameters"); + + Assert.Equal("GetPassThroughNativeQueryMetadataV2", executePassThroughNativeQueryV2.DynamicReturnProperty.OperationId); + Assert.NotNull(executePassThroughNativeQueryV2.DynamicReturnProperty.ConnectorFunction); + Assert.Equal("GetPassThroughNativeQueryMetadataV2", executePassThroughNativeQueryV2.DynamicReturnProperty.ConnectorFunction.Name); + Assert.Equal("schema/queryresults", executePassThroughNativeQueryV2.DynamicReturnProperty.ItemValuePath); + Assert.Equal(4, executePassThroughNativeQueryV2.DynamicReturnProperty.ParameterMap.Count); + Assert.True(executePassThroughNativeQueryV2.DynamicReturnProperty.ParameterMap["server"] is DynamicConnectorExtensionValue dv8 && dv8.Reference == "server"); + Assert.True(executePassThroughNativeQueryV2.DynamicReturnProperty.ParameterMap["database"] is DynamicConnectorExtensionValue dv9 && dv9.Reference == "database"); + Assert.True(executePassThroughNativeQueryV2.DynamicReturnProperty.ParameterMap["query/query"] is DynamicConnectorExtensionValue dv10 && dv10.Reference == "query/query"); + Assert.True(executePassThroughNativeQueryV2.DynamicReturnProperty.ParameterMap["query/formalParameters"] is DynamicConnectorExtensionValue dv11 && dv11.Reference == "query/formalParameters"); + } + + [Fact] + public async Task DirectIntellisenseTest() + { + using var testConnector = new LoggingTestServer(@"Swagger\SQL Server.json", _output); + using var httpClient = new HttpClient(testConnector); + using PowerPlatformConnectorClient client = new PowerPlatformConnectorClient("https://tip1002-002.azure-apihub.net", "ddadf2c7-ebdd-ec01-a5d1-502dc07f04b4" /* environment Id */, "4bf9a87fc9054b6db3a4d07a1c1f5a5b" /* connectionId */, () => "eyJ0eXAi...", httpClient) { SessionId = "a41bd03b-6c3c-4509-a844-e8c51b61f878" }; + + BaseRuntimeConnectorContext runtimeContext = new TestConnectorRuntimeContext("SQL", client, console: _output); + + ConnectorFunction[] functions = OpenApiParser.GetFunctions("SQL", testConnector._apiDocument, new ConsoleLogger(_output)).ToArray(); + ConnectorFunction executeProcedureV2 = functions.First(f => f.Name == "ExecuteProcedureV2"); + + Assert.True(executeProcedureV2.RequiredParameters[0].SupportsDynamicIntellisense); + Assert.True(executeProcedureV2.RequiredParameters[1].SupportsDynamicIntellisense); + Assert.True(executeProcedureV2.RequiredParameters[2].SupportsDynamicIntellisense); + Assert.True(executeProcedureV2.RequiredParameters[3].SupportsDynamicIntellisense); + + // Keeping only for debugging + //FormulaValue result = await executeProcedureV2.InvokeAync(client, new FormulaValue[] + //{ + // FormulaValue.New("pfxdev-sql.database.windows.net"), + // FormulaValue.New("connectortest"), + // FormulaValue.New("sp_1"), + // FormulaValue.NewRecordFromFields(new NamedValue[] { new NamedValue("p1", FormulaValue.New(38)) }) + //}, CancellationToken.None).ConfigureAwait(false); + + testConnector.SetResponseFromFile(@"Responses\SQL Server Intellisense Response 3.json"); + ConnectorParameters parameters1 = await executeProcedureV2.GetParameterSuggestionsAsync( + new NamedValue[] + { + new NamedValue("server", FormulaValue.New("pfxdev-sql.database.windows.net")), + new NamedValue("database", FormulaValue.New("connectortest")) + }, + executeProcedureV2.RequiredParameters[2], // procedure + runtimeContext, + CancellationToken.None).ConfigureAwait(false); + + ConnectorParameterWithSuggestions suggestions1 = parameters1.ParametersWithSuggestions[2]; + Assert.NotNull(suggestions1); + Assert.NotNull(suggestions1.Suggestions); + Assert.Equal(2, suggestions1.Suggestions.Count()); + + testConnector.SetResponseFromFile(@"Responses\SQL Server Intellisense Response2 1.json"); + ConnectorParameters parameters2 = await executeProcedureV2.GetParameterSuggestionsAsync( + new NamedValue[] + { + new NamedValue("server", FormulaValue.New("pfxdev-sql.database.windows.net")), + new NamedValue("database", FormulaValue.New("connectortest")), + new NamedValue("procedure", FormulaValue.New("sp_1")) + }, + executeProcedureV2.RequiredParameters[3], // parameters + runtimeContext, + CancellationToken.None).ConfigureAwait(false); + + ConnectorParameterWithSuggestions suggestions2 = parameters2.ParametersWithSuggestions[3]; + Assert.NotNull(suggestions2); + Assert.NotNull(suggestions2.Suggestions); + Assert.Single(suggestions2.Suggestions); + + Assert.True(executeProcedureV2.ReturnParameterType.SupportsDynamicIntellisense); + + testConnector.SetResponseFromFile(@"Responses\SQL Server Intellisense Response2 1.json"); + ConnectorType returnType = await executeProcedureV2.GetConnectorReturnTypeAsync( + new NamedValue[] + { + new NamedValue("server", FormulaValue.New("pfxdev-sql.database.windows.net")), + new NamedValue("database", FormulaValue.New("connectortest")), + new NamedValue("procedure", FormulaValue.New("sp_1")) + }, + runtimeContext, + CancellationToken.None).ConfigureAwait(false); + + Assert.NotNull(returnType); + Assert.True(returnType.FormulaType is RecordType); + + string input = testConnector._log.ToString(); + var version = PowerPlatformConnectorClient.Version; + string expected = $@"POST https://tip1002-002.azure-apihub.net/invoke + authority: tip1002-002.azure-apihub.net + Authorization: Bearer eyJ0eXAi... + path: /invoke + scheme: https + x-ms-client-environment-id: /providers/Microsoft.PowerApps/environments/ddadf2c7-ebdd-ec01-a5d1-502dc07f04b4 + x-ms-client-session-id: a41bd03b-6c3c-4509-a844-e8c51b61f878 + x-ms-request-method: GET + x-ms-request-url: /apim/sql/4bf9a87fc9054b6db3a4d07a1c1f5a5b/v2/datasets/pfxdev-sql.database.windows.net,connectortest/procedures + x-ms-user-agent: PowerFx/{version} +POST https://tip1002-002.azure-apihub.net/invoke + authority: tip1002-002.azure-apihub.net + Authorization: Bearer eyJ0eXAi... + path: /invoke + scheme: https + x-ms-client-environment-id: /providers/Microsoft.PowerApps/environments/ddadf2c7-ebdd-ec01-a5d1-502dc07f04b4 + x-ms-client-session-id: a41bd03b-6c3c-4509-a844-e8c51b61f878 + x-ms-request-method: GET + x-ms-request-url: /apim/sql/4bf9a87fc9054b6db3a4d07a1c1f5a5b/v2/$metadata.json/datasets/pfxdev-sql.database.windows.net,connectortest/procedures/sp_1 + x-ms-user-agent: PowerFx/{version} +POST https://tip1002-002.azure-apihub.net/invoke + authority: tip1002-002.azure-apihub.net + Authorization: Bearer eyJ0eXAi... + path: /invoke + scheme: https + x-ms-client-environment-id: /providers/Microsoft.PowerApps/environments/ddadf2c7-ebdd-ec01-a5d1-502dc07f04b4 + x-ms-client-session-id: a41bd03b-6c3c-4509-a844-e8c51b61f878 + x-ms-request-method: GET + x-ms-request-url: /apim/sql/4bf9a87fc9054b6db3a4d07a1c1f5a5b/v2/$metadata.json/datasets/pfxdev-sql.database.windows.net,connectortest/procedures/sp_1 + x-ms-user-agent: PowerFx/{version} +"; + + Assert.Equal(expected, input); + } + + [Fact] + public async Task DataverseTest() + { + using var testConnector = new LoggingTestServer(@"Swagger\Dataverse.json", _output); + using var httpClient = new HttpClient(testConnector); + using PowerPlatformConnectorClient client = new PowerPlatformConnectorClient("https://tip1-shared.azure-apim.net", "Default-9f6be790-4a16-4dd6-9850-44a0d2649aef" /* environment Id */, "461a30624723445c9ba87313d8bbefa3" /* connectionId */, () => "eyJ0eXAiO...", httpClient) { SessionId = "a41bd03b-6c3c-4509-a844-e8c51b61f878" }; + + BaseRuntimeConnectorContext runtimeContext = new TestConnectorRuntimeContext("DV", client, console: _output); + + ConnectorFunction[] functions = OpenApiParser.GetFunctions(new ConnectorSettings("DV") { Compatibility = ConnectorCompatibility.SwaggerCompatibility }, testConnector._apiDocument).ToArray(); + ConnectorFunction createRecord = functions.First(f => f.Name == "CreateRecordWithOrganization"); + + testConnector.SetResponseFromFile(@"Responses\Dataverse_Response_1.json"); + ConnectorParameters parameters1 = await createRecord.GetParameterSuggestionsAsync( + new NamedValue[] + { + new NamedValue("organization", FormulaValue.New("https://org283e9949.crm10.dynamics.com")) + }, + createRecord.RequiredParameters[1], // entityName + runtimeContext, + CancellationToken.None).ConfigureAwait(false); + + ConnectorParameterWithSuggestions suggestions1 = parameters1.ParametersWithSuggestions[1]; + Assert.Equal(651, suggestions1.Suggestions.Count); + Assert.Equal("AAD Users", suggestions1.Suggestions[0].DisplayName); + Assert.Equal("aadusers", ((StringValue)suggestions1.Suggestions[0].Suggestion).Value); + + testConnector.SetResponseFromFile(@"Responses\Dataverse_Response_2.json"); + ConnectorParameters parameters2 = await createRecord.GetParameterSuggestionsAsync( + new NamedValue[] + { + new NamedValue("organization", FormulaValue.New("https://org283e9949.crm10.dynamics.com")), + new NamedValue("entityName", FormulaValue.New("accounts")) + }, + createRecord.RequiredParameters[2], // item + runtimeContext, + CancellationToken.None).ConfigureAwait(false); + + ConnectorParameterWithSuggestions suggestions2 = parameters2.ParametersWithSuggestions[2]; + Assert.Equal(119, suggestions2.Suggestions.Count); + Assert.Equal("accountcategorycode", suggestions2.Suggestions[0].DisplayName); + Assert.Equal("Decimal", suggestions2.Suggestions[0].Suggestion.Type.ToString()); + + string input = testConnector._log.ToString(); + var version = PowerPlatformConnectorClient.Version; + string expected = @$"POST https://tip1-shared.azure-apim.net/invoke + authority: tip1-shared.azure-apim.net + Authorization: Bearer eyJ0eXAiO... + organization: https://org283e9949.crm10.dynamics.com + path: /invoke + scheme: https + x-ms-client-environment-id: /providers/Microsoft.PowerApps/environments/Default-9f6be790-4a16-4dd6-9850-44a0d2649aef + x-ms-client-session-id: a41bd03b-6c3c-4509-a844-e8c51b61f878 + x-ms-request-method: POST + x-ms-request-url: /apim/commondataserviceforapps/461a30624723445c9ba87313d8bbefa3/v1.0/$metadata.json/GetEntityListEnum/GetEntitiesWithOrganization + x-ms-user-agent: PowerFx/{version} +POST https://tip1-shared.azure-apim.net/invoke + authority: tip1-shared.azure-apim.net + Authorization: Bearer eyJ0eXAiO... + organization: https://org283e9949.crm10.dynamics.com + path: /invoke + scheme: https + x-ms-client-environment-id: /providers/Microsoft.PowerApps/environments/Default-9f6be790-4a16-4dd6-9850-44a0d2649aef + x-ms-client-session-id: a41bd03b-6c3c-4509-a844-e8c51b61f878 + x-ms-request-method: GET + x-ms-request-url: /apim/commondataserviceforapps/461a30624723445c9ba87313d8bbefa3/v1.0/$metadata.json/entities/accounts/postitem + x-ms-user-agent: PowerFx/{version} +"; + + Assert.Equal(expected, input); + } + + [Theory] + + // Very slow -- [InlineData(@"Swagger\Dataverse 2.json")] + // Very slow -- [InlineData(@"Swagger\Dataverse 3.json")] + [InlineData(@"Swagger\PowerPlatformForAdmins.json")] + public async Task DataverseTest2(string swaggerFile) + { + PowerFxConfig powerFxConfig = new PowerFxConfig(); + OpenApiDocument doc = Helpers.ReadSwagger(swaggerFile, _output); + + OpenApiParser.GetFunctions("namespace", doc); // missing logger + powerFxConfig.AddActionConnector("namespace", doc); + } + + [Fact] + public async Task CardsForPowerApps_Invoke() + { + using var testConnector = new LoggingTestServer(@"Swagger\CardsForPowerApps.json", _output); + using var httpClient = new HttpClient(testConnector); + using PowerPlatformConnectorClient client = new PowerPlatformConnectorClient("https://tip1002-002.azure-apihub.net", "7592282b-e371-e3f6-8e04-e8f23e64227c" /* environment Id */, "shared-cardsforpower-eafc4fa0-c560-4eba-a5b2-3e1ebc63193a" /* connectionId */, () => "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dC...", httpClient) { SessionId = "a41bd03b-6c3c-4509-a844-e8c51b61f878" }; + + BaseRuntimeConnectorContext runtimeContext = new TestConnectorRuntimeContext("DV", client, console: _output); + + ConnectorFunction[] functions = OpenApiParser.GetFunctions( + new ConnectorSettings("DV") + { + Compatibility = ConnectorCompatibility.SwaggerCompatibility, + ReturnUnknownRecordFieldsAsUntypedObjects = true + }, + testConnector._apiDocument).ToArray(); + ConnectorFunction createCardInstance = functions.First(f => f.Name == "CreateCardInstance"); + + testConnector.SetResponseFromFile(@"Responses\CardsForPowerApps_CreateCardInstance.json"); + var result = await createCardInstance.InvokeAsync( + new FormulaValue[] + { + FormulaValue.New("card"), + FormulaValue.NewRecordFromFields( + new NamedValue("inputs", FormulaValue.NewRecordFromFields( + new NamedValue("property1", FormulaValue.New("test1")), + new NamedValue("property2", FormulaValue.New("test2"))))), + }, + runtimeContext, + CancellationToken.None).ConfigureAwait(false); + + string input = testConnector._log.ToString(); + Assert.Equal("AdaptiveCard", (((RecordValue)result).GetField("type") as UntypedObjectValue).Impl.GetString()); + Assert.Equal( + $@"POST https://tip1002-002.azure-apihub.net/invoke + authority: tip1002-002.azure-apihub.net + Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dC... + path: /invoke + scheme: https + x-ms-client-environment-id: /providers/Microsoft.PowerApps/environments/7592282b-e371-e3f6-8e04-e8f23e64227c + x-ms-client-session-id: a41bd03b-6c3c-4509-a844-e8c51b61f878 + x-ms-request-method: POST + x-ms-request-url: /apim/cardsforpowerapps/shared-cardsforpower-eafc4fa0-c560-4eba-a5b2-3e1ebc63193a/cards/cards/card/instances + x-ms-user-agent: PowerFx/{PowerPlatformConnectorClient.Version} + [content-header] Content-Type: application/json; charset=utf-8 + [body] {{""inputs"":{{""property1"":""test1"",""property2"":""test2""}}}} +", input); + } + + [Fact] + public async Task CardsForPowerApps_Suggestion() + { + using var testConnector = new LoggingTestServer(@"Swagger\CardsForPowerApps.json", _output); + using var httpClient = new HttpClient(testConnector); + using PowerPlatformConnectorClient client = new PowerPlatformConnectorClient("https://tip1002-002.azure-apihub.net", "7592282b-e371-e3f6-8e04-e8f23e64227c" /* environment Id */, "shared-cardsforpower-eafc4fa0-c560-4eba-a5b2-3e1ebc63193a" /* connectionId */, () => "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dC...", httpClient) { SessionId = "a41bd03b-6c3c-4509-a844-e8c51b61f878" }; + + BaseRuntimeConnectorContext runtimeContext = new TestConnectorRuntimeContext("DV", client, console: _output); + + ConnectorFunction[] functions = OpenApiParser.GetFunctions(new ConnectorSettings("DV") { Compatibility = ConnectorCompatibility.SwaggerCompatibility }, testConnector._apiDocument).ToArray(); + ConnectorFunction createCardInstance = functions.First(f => f.Name == "CreateCardInstance"); + + testConnector.SetResponseFromFile(@"Responses\CardsForPowerApps_Suggestions.json"); + ConnectorParameters parameters = await createCardInstance.GetParameterSuggestionsAsync( + new NamedValue[] + { + }, + createCardInstance.RequiredParameters[0], // cardid + runtimeContext, + CancellationToken.None).ConfigureAwait(false); + + ConnectorParameterWithSuggestions suggestions = parameters.ParametersWithSuggestions[0]; + Assert.Equal(2, suggestions.Suggestions.Count); + Assert.Equal("test", suggestions.Suggestions[0].DisplayName); + Assert.Equal("testWithInputs", suggestions.Suggestions[1].DisplayName); + } + + [Fact] + public async Task Teams_GetMessageDetails_WithComplexParameterReference() + { + using var testConnector = new LoggingTestServer(@"Swagger\Teams.json", _output); + using var httpClient = new HttpClient(testConnector); + using PowerPlatformConnectorClient client = new PowerPlatformConnectorClient("https://tip1002-002.azure-apihub.net", "7592282b-e371-e3f6-8e04-e8f23e64227c" /* environment Id */, "shared-cardsforpower-eafc4fa0-c560-4eba-a5b2-3e1ebc63193a" /* connectionId */, () => "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dC...", httpClient) { SessionId = "a41bd03b-6c3c-4509-a844-e8c51b61f878" }; + + BaseRuntimeConnectorContext runtimeContext = new TestConnectorRuntimeContext("DV", client, console: _output); + + ConnectorFunction[] functions = OpenApiParser.GetFunctions(new ConnectorSettings("DV") { Compatibility = ConnectorCompatibility.SwaggerCompatibility }, testConnector._apiDocument).ToArray(); + ConnectorFunction getMessageDetails = functions.First(f => f.Name == "GetMessageDetails"); + + testConnector.SetResponseFromFile(@"Responses\Teams_GetMessageDetails_InputType.json"); + ConnectorParameters parameters = await getMessageDetails.GetParameterSuggestionsAsync( + new NamedValue[] + { + new NamedValue("messageId", FormulaValue.New("messageId")), + new NamedValue("threadType", FormulaValue.New("channel")), + }, + getMessageDetails.RequiredParameters[2], // body + runtimeContext, + CancellationToken.None).ConfigureAwait(false); + + var bodyConnectorType = parameters.ParametersWithSuggestions[2].ConnectorType; + + ConnectorParameterWithSuggestions suggestions = parameters.ParametersWithSuggestions[2]; + testConnector.SetResponseFromFile(@"Responses\Teams_GetMessageDetails_GetSuggestionsForChannel.json"); + + var connectorTypeWithSuggestions = await getMessageDetails.GetConnectorSuggestionsAsync( + new NamedValue[] + { + new NamedValue("messageId", FormulaValue.New("messageId")), + new NamedValue("threadType", FormulaValue.New("channel")), + new NamedValue("body", FormulaValue.NewRecordFromFields( + new NamedValue("recipient", FormulaValue.NewRecordFromFields( + new NamedValue("groupId", FormulaValue.New("groupIdValue")))))), + }, + bodyConnectorType.Fields[0].Fields[1], // channelId + runtimeContext, + CancellationToken.None).ConfigureAwait(false); + + Assert.Equal(2, connectorTypeWithSuggestions.ConnectorSuggestions.Suggestions.Count); + Assert.Equal("channelName", connectorTypeWithSuggestions.ConnectorSuggestions.Suggestions[0].DisplayName); + Assert.Equal("channelName2", connectorTypeWithSuggestions.ConnectorSuggestions.Suggestions[1].DisplayName); + } + } + + public static class Extensions + { + public static RecordType MakeRecordType(params (string, FormulaType)[] columns) + { + RecordType rt = RecordType.Empty(); + + foreach ((string name, FormulaType type) in columns) + { + rt = rt.Add(name, type); + } + + return rt; + } + + public static TableType MakeTableType(params (string, FormulaType)[] columns) + { + TableType tt = TableType.Empty(); + + foreach ((string name, FormulaType type) in columns) + { + tt = tt.Add(name, type); + } + + return tt; + } + + public static OptionSetValueType MakeOptionSetType(string name, params string[] names) + { + return MakeOptionSet(name, names).FormulaType; + } + + public static OptionSet MakeOptionSet(string name, params string[] names) + { + return new OptionSet(name, DisplayNameUtility.MakeUnique(names.ToDictionary(n => n, n => n))); + } + } + #pragma warning restore SA1118, SA1117, SA1119, SA1137 } diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/PowerAppsForMakersTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PowerAppsForMakersTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/PowerAppsForMakersTests.cs rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PowerAppsForMakersTests.cs diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/PowerPlatformConnectorClientTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PowerPlatformConnectorClientTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/PowerPlatformConnectorClientTests.cs rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PowerPlatformConnectorClientTests.cs diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/PowerPlatformConnectorTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PowerPlatformConnectorTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/PowerPlatformConnectorTests.cs rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PowerPlatformConnectorTests.cs diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/PowerPlatformTabularTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PowerPlatformTabularTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/PowerPlatformTabularTests.cs rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PowerPlatformTabularTests.cs diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/PriorityOrderer.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PriorityOrderer.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/PriorityOrderer.cs rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PriorityOrderer.cs diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/PriorityOrdererTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PriorityOrdererTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/PriorityOrdererTests.cs rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PriorityOrdererTests.cs diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/PublicSurfaceTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PublicSurfaceTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/PublicSurfaceTests.cs rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PublicSurfaceTests.cs diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Azure Cognitive Service for Language v2.1_Response.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Azure Cognitive Service for Language v2.1_Response.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Azure Cognitive Service for Language v2.1_Response.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Azure Cognitive Service for Language v2.1_Response.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Azure Cognitive Service for Language_Response.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Azure Cognitive Service for Language_Response.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Azure Cognitive Service for Language_Response.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Azure Cognitive Service for Language_Response.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/AzureBlobStorage_GetFileContentV2.raw b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/AzureBlobStorage_GetFileContentV2.raw similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/AzureBlobStorage_GetFileContentV2.raw rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/AzureBlobStorage_GetFileContentV2.raw diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/AzureBlobStorage_ListRootFolderV3_response.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/AzureBlobStorage_ListRootFolderV3_response.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/AzureBlobStorage_ListRootFolderV3_response.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/AzureBlobStorage_ListRootFolderV3_response.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/AzureBlobStorage_Paging_Response1.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/AzureBlobStorage_Paging_Response1.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/AzureBlobStorage_Paging_Response1.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/AzureBlobStorage_Paging_Response1.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/AzureBlobStorage_Paging_Response2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/AzureBlobStorage_Paging_Response2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/AzureBlobStorage_Paging_Response2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/AzureBlobStorage_Paging_Response2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/AzureBlobStorage_Paging_Response3.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/AzureBlobStorage_Paging_Response3.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/AzureBlobStorage_Paging_Response3.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/AzureBlobStorage_Paging_Response3.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/AzureBlobStorage_Response.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/AzureBlobStorage_Response.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/AzureBlobStorage_Response.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/AzureBlobStorage_Response.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/AzureBlobStorage_Response2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/AzureBlobStorage_Response2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/AzureBlobStorage_Response2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/AzureBlobStorage_Response2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Bing Maps GetRouteV3.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Bing Maps GetRouteV3.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Bing Maps GetRouteV3.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Bing Maps GetRouteV3.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/CardsForPowerApps_CreateCardInstance.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/CardsForPowerApps_CreateCardInstance.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/CardsForPowerApps_CreateCardInstance.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/CardsForPowerApps_CreateCardInstance.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/CardsForPowerApps_Suggestions.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/CardsForPowerApps_Suggestions.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/CardsForPowerApps_Suggestions.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/CardsForPowerApps_Suggestions.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Dataverse_Response_1.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Dataverse_Response_1.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Dataverse_Response_1.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Dataverse_Response_1.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Dataverse_Response_2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Dataverse_Response_2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Dataverse_Response_2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Dataverse_Response_2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Dataverse_Response_3.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Dataverse_Response_3.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Dataverse_Response_3.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Dataverse_Response_3.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/EXO_Response1.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/EXO_Response1.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/EXO_Response1.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/EXO_Response1.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/EXO_Response2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/EXO_Response2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/EXO_Response2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/EXO_Response2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/EXO_Response3.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/EXO_Response3.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/EXO_Response3.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/EXO_Response3.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/EXO_Response4.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/EXO_Response4.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/EXO_Response4.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/EXO_Response4.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/EXO_Response5.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/EXO_Response5.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/EXO_Response5.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/EXO_Response5.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/EmptyResponse.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/EmptyResponse.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/EmptyResponse.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/EmptyResponse.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/HttpCall_1.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/HttpCall_1.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/HttpCall_1.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/HttpCall_1.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Invalid.txt b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Invalid.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Invalid.txt rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Invalid.txt diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/MSNWeather_Response.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/MSNWeather_Response.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/MSNWeather_Response.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/MSNWeather_Response.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/MSNWeather_Response2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/MSNWeather_Response2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/MSNWeather_Response2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/MSNWeather_Response2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Office 365 Outlook ExportEmailV2.txt b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Office 365 Outlook ExportEmailV2.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Office 365 Outlook ExportEmailV2.txt rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Office 365 Outlook ExportEmailV2.txt diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Office 365 Outlook FindMeetingTimesV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Office 365 Outlook FindMeetingTimesV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Office 365 Outlook FindMeetingTimesV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Office 365 Outlook FindMeetingTimesV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Office 365 Outlook FlagV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Office 365 Outlook FlagV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Office 365 Outlook FlagV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Office 365 Outlook FlagV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Office 365 Outlook GetEmails.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Office 365 Outlook GetEmails.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Office 365 Outlook GetEmails.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Office 365 Outlook GetEmails.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Office 365 Outlook GetEmailsV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Office 365 Outlook GetEmailsV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Office 365 Outlook GetEmailsV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Office 365 Outlook GetEmailsV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Office 365 Outlook GetMailTipsV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Office 365 Outlook GetMailTipsV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Office 365 Outlook GetMailTipsV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Office 365 Outlook GetMailTipsV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Office 365 Outlook GetRoomListsV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Office 365 Outlook GetRoomListsV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Office 365 Outlook GetRoomListsV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Office 365 Outlook GetRoomListsV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Office 365 Outlook GetRooms.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Office 365 Outlook GetRooms.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Office 365 Outlook GetRooms.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Office 365 Outlook GetRooms.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Office 365 Outlook V4CalendarPostItem.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Office 365 Outlook V4CalendarPostItem.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Office 365 Outlook V4CalendarPostItem.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Office 365 Outlook V4CalendarPostItem.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Office365_DirectsV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Office365_DirectsV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Office365_DirectsV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Office365_DirectsV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Office365_SearchV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Office365_SearchV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Office365_SearchV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Office365_SearchV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Office365_UserProfileV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Office365_UserProfileV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Office365_UserProfileV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Office365_UserProfileV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_00.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_00.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_00.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_00.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_01.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_01.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_01.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_01.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_02.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_02.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_02.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_02.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_03.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_03.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_03.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_03.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_04.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_04.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_04.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_04.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_05.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_05.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_05.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_05.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_06.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_06.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_06.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_06.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_07.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_07.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_07.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_07.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_08.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_08.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_08.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_08.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_09.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_09.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_09.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_09.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_10.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_10.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_10.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_10.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_11.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_11.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_11.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_11.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_12.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_12.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_12.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_12.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_13.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_13.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_13.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_13.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_14.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_14.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_14.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_14.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_15.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_15.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_15.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_15.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_16.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_16.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_16.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_16.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_17.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_17.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_17.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_17.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_18.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_18.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_18.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_18.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_19.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_19.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_19.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_19.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_20.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_20.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_20.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_20.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_21.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_21.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_21.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_21.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_22.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_22.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_DVReturnType_22.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_DVReturnType_22.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Groups_AddMemberToGroup.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Groups_AddMemberToGroup.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Groups_AddMemberToGroup.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Groups_AddMemberToGroup.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Groups_CreateCalendarEvent.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Groups_CreateCalendarEvent.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Groups_CreateCalendarEvent.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Groups_CreateCalendarEvent.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Groups_CreateCalendarEventV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Groups_CreateCalendarEventV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Groups_CreateCalendarEventV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Groups_CreateCalendarEventV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Groups_CreateCalendarEvent_ToDelete.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Groups_CreateCalendarEvent_ToDelete.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Groups_CreateCalendarEvent_ToDelete.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Groups_CreateCalendarEvent_ToDelete.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Groups_CreateCalendarEvent_ToUpdate.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Groups_CreateCalendarEvent_ToUpdate.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Groups_CreateCalendarEvent_ToUpdate.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Groups_CreateCalendarEvent_ToUpdate.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Groups_HttpRequest.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Groups_HttpRequest.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Groups_HttpRequest.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Groups_HttpRequest.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Groups_HttpRequestV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Groups_HttpRequestV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Groups_HttpRequestV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Groups_HttpRequestV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Groups_ListGroupMembers.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Groups_ListGroupMembers.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Groups_ListGroupMembers.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Groups_ListGroupMembers.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Groups_ListGroupsWithFilter.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Groups_ListGroupsWithFilter.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Groups_ListGroupsWithFilter.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Groups_ListGroupsWithFilter.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Groups_ListGroups_01.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Groups_ListGroups_01.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Groups_ListGroups_01.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Groups_ListGroups_01.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Groups_ListGroups_02.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Groups_ListGroups_02.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Groups_ListGroups_02.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Groups_ListGroups_02.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Groups_ListOwnedGroups.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Groups_ListOwnedGroups.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Groups_ListOwnedGroups.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Groups_ListOwnedGroups.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Groups_ListOwnedGroupsV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Groups_ListOwnedGroupsV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Groups_ListOwnedGroupsV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Groups_ListOwnedGroupsV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Groups_ListOwnedGroupsV3.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Groups_ListOwnedGroupsV3.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Groups_ListOwnedGroupsV3.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Groups_ListOwnedGroupsV3.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Groups_UpdateCalendarEvent.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Groups_UpdateCalendarEvent.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Groups_UpdateCalendarEvent.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Groups_UpdateCalendarEvent.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_CalendarDeleteItem_NotFound.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_CalendarDeleteItem_NotFound.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_CalendarDeleteItem_NotFound.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_CalendarDeleteItem_NotFound.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_CalendarGetItem.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_CalendarGetItem.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_CalendarGetItem.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_CalendarGetItem.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_CalendarGetItems.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_CalendarGetItems.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_CalendarGetItems.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_CalendarGetItems.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_CalendarGetTable.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_CalendarGetTable.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_CalendarGetTable.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_CalendarGetTable.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_CalendarGetTables.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_CalendarGetTables.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_CalendarGetTables.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_CalendarGetTables.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_CalendarGetTablesV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_CalendarGetTablesV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_CalendarGetTablesV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_CalendarGetTablesV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_CalendarPatchItem.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_CalendarPatchItem.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_CalendarPatchItem.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_CalendarPatchItem.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_CalendarPostItem.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_CalendarPostItem.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_CalendarPostItem.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_CalendarPostItem.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_ContactGetItem.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_ContactGetItem.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_ContactGetItem.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_ContactGetItem.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_ContactGetItemV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_ContactGetItemV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_ContactGetItemV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_ContactGetItemV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_ContactGetItems.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_ContactGetItems.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_ContactGetItems.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_ContactGetItems.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_ContactGetItemsV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_ContactGetItemsV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_ContactGetItemsV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_ContactGetItemsV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_ContactGetTable.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_ContactGetTable.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_ContactGetTable.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_ContactGetTable.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_ContactGetTables.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_ContactGetTables.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_ContactGetTables.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_ContactGetTables.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_ContactGetTablesV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_ContactGetTablesV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_ContactGetTablesV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_ContactGetTablesV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_ContactPatchItem.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_ContactPatchItem.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_ContactPatchItem.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_ContactPatchItem.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_ContactPatchItemV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_ContactPatchItemV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_ContactPatchItemV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_ContactPatchItemV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_ContactPostItem.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_ContactPostItem.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_ContactPostItem.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_ContactPostItem.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_ContactPostItemV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_ContactPostItemV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_ContactPostItemV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_ContactPostItemV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_ExportEmail.eml b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_ExportEmail.eml similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_ExportEmail.eml rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_ExportEmail.eml diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_FindMeetingTimes.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_FindMeetingTimes.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_FindMeetingTimes.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_FindMeetingTimes.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_FindMeetingTimesV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_FindMeetingTimesV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_FindMeetingTimesV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_FindMeetingTimesV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_FlagV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_FlagV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_FlagV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_FlagV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetAttachment.png b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetAttachment.png similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetAttachment.png rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetAttachment.png diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetAttachmentV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetAttachmentV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetAttachmentV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetAttachmentV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetDataSets.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetDataSets.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetDataSets.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetDataSets.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetDataSetsMetadata.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetDataSetsMetadata.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetDataSetsMetadata.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetDataSetsMetadata.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetEmail.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetEmail.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetEmail.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetEmail.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetEmailV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetEmailV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetEmailV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetEmailV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetEmails.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetEmails.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetEmails.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetEmails.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetEmailsV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetEmailsV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetEmailsV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetEmailsV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetEmailsV3.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetEmailsV3.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetEmailsV3.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetEmailsV3.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetEventsCalendarView.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetEventsCalendarView.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetEventsCalendarView.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetEventsCalendarView.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetEventsCalendarViewV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetEventsCalendarViewV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetEventsCalendarViewV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetEventsCalendarViewV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetEventsCalendarViewV3.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetEventsCalendarViewV3.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetEventsCalendarViewV3.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetEventsCalendarViewV3.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetEventsCalendarView_Error.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetEventsCalendarView_Error.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetEventsCalendarView_Error.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetEventsCalendarView_Error.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetMailTips.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetMailTips.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetMailTips.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetMailTips.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetMailTipsV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetMailTipsV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetMailTipsV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetMailTipsV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetRoomLists.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetRoomLists.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetRoomLists.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetRoomLists.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetRoomListsV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetRoomListsV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetRoomListsV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetRoomListsV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetRooms.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetRooms.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetRooms.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetRooms.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetRoomsInRoomList.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetRoomsInRoomList.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetRoomsInRoomList.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetRoomsInRoomList.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetRoomsInRoomListV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetRoomsInRoomListV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetRoomsInRoomListV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetRoomsInRoomListV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetRoomsV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetRoomsV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetRoomsV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetRoomsV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetSensitivityLabels.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetSensitivityLabels.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_GetSensitivityLabels.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_GetSensitivityLabels.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_HttpRequest.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_HttpRequest.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_HttpRequest.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_HttpRequest.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_MarkAsReadV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_MarkAsReadV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_MarkAsReadV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_MarkAsReadV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_MarkAsReadV3.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_MarkAsReadV3.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_MarkAsReadV3.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_MarkAsReadV3.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_Move.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_Move.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_Move.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_Move.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_MoveV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_MoveV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_MoveV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_MoveV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_SetAutomaticRepliesSettingV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_SetAutomaticRepliesSettingV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_SetAutomaticRepliesSettingV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_SetAutomaticRepliesSettingV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_V2CalendarGetItem.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_V2CalendarGetItem.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_V2CalendarGetItem.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_V2CalendarGetItem.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_V2CalendarGetItems.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_V2CalendarGetItems.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_V2CalendarGetItems.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_V2CalendarGetItems.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_V2CalendarGetItems2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_V2CalendarGetItems2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_V2CalendarGetItems2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_V2CalendarGetItems2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_V2CalendarPatchItem.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_V2CalendarPatchItem.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_V2CalendarPatchItem.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_V2CalendarPatchItem.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_V2CalendarPostItem.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_V2CalendarPostItem.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_V2CalendarPostItem.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_V2CalendarPostItem.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_V3CalendarGetItem.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_V3CalendarGetItem.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_V3CalendarGetItem.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_V3CalendarGetItem.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_V3CalendarGetItems.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_V3CalendarGetItems.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_V3CalendarGetItems.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_V3CalendarGetItems.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_V3CalendarPatchItem.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_V3CalendarPatchItem.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_V3CalendarPatchItem.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_V3CalendarPatchItem.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_V3CalendarPostItem.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_V3CalendarPostItem.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_V3CalendarPostItem.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_V3CalendarPostItem.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_V4CalendarGetItems.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_V4CalendarGetItems.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_V4CalendarGetItems.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_V4CalendarGetItems.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_V4CalendarPatchItem.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_V4CalendarPatchItem.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_V4CalendarPatchItem.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_V4CalendarPatchItem.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_V4CalendarPostItem.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_V4CalendarPostItem.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Outlook_V4CalendarPostItem.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Outlook_V4CalendarPostItem.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_DirectReportsV2_03.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_DirectReportsV2_03.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_DirectReportsV2_03.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_DirectReportsV2_03.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_DirectReports_01.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_DirectReports_01.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_DirectReports_01.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_DirectReports_01.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_DirectReports_02.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_DirectReports_02.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_DirectReports_02.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_DirectReports_02.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_DirectReports_03.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_DirectReports_03.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_DirectReports_03.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_DirectReports_03.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_HttpRequest.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_HttpRequest.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_HttpRequest.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_HttpRequest.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_Manager.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_Manager.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_Manager.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_Manager.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_Manager404.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_Manager404.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_Manager404.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_Manager404.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_ManagerV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_ManagerV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_ManagerV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_ManagerV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_MyProfile.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_MyProfile.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_MyProfile.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_MyProfile.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_MyProfileV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_MyProfileV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_MyProfileV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_MyProfileV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_MyProfileV2A.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_MyProfileV2A.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_MyProfileV2A.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_MyProfileV2A.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_MyTrendingDocuments.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_MyTrendingDocuments.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_MyTrendingDocuments.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_MyTrendingDocuments.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_RelevantPeople.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_RelevantPeople.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_RelevantPeople.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_RelevantPeople.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_SearchUser.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_SearchUser.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_SearchUser.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_SearchUser.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_SearchUser2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_SearchUser2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_SearchUser2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_SearchUser2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_SearchUserV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_SearchUserV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_SearchUserV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_SearchUserV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_TrendingDocuments.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_TrendingDocuments.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_TrendingDocuments.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_TrendingDocuments.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_UserPhotoMetadata.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_UserPhotoMetadata.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_UserPhotoMetadata.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_UserPhotoMetadata.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_UserProfile.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_UserProfile.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_UserProfile.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_UserProfile.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_UserProfileV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_UserProfileV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_UserProfileV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_UserProfileV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_UserProfileV2A.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_UserProfileV2A.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_O365Users_UserProfileV2A.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_O365Users_UserProfileV2A.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_ServiceNowReturnType.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_ServiceNowReturnType.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Response_ServiceNowReturnType.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Response_ServiceNowReturnType.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SF GetData.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SF GetData.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SF GetData.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SF GetData.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SF GetDatasetsMetadata.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SF GetDatasetsMetadata.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SF GetDatasetsMetadata.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SF GetDatasetsMetadata.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SF GetSchema.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SF GetSchema.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SF GetSchema.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SF GetSchema.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SF GetTables.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SF GetTables.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SF GetTables.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SF GetTables.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SP GetData.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SP GetData.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SP GetData.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SP GetData.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SP GetDatasetsMetadata.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SP GetDatasetsMetadata.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SP GetDatasetsMetadata.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SP GetDatasetsMetadata.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SP GetTable.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SP GetTable.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SP GetTable.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SP GetTable.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SP GetTables.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SP GetTables.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SP GetTables.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SP GetTables.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SPO_Response1.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SPO_Response1.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SPO_Response1.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SPO_Response1.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SPO_Response2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SPO_Response2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SPO_Response2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SPO_Response2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SPO_Response3.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SPO_Response3.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SPO_Response3.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SPO_Response3.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SPO_Response4.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SPO_Response4.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SPO_Response4.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SPO_Response4.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SPO_Response5.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SPO_Response5.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SPO_Response5.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SPO_Response5.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SQL GetDatasetsMetadata.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SQL GetDatasetsMetadata.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SQL GetDatasetsMetadata.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SQL GetDatasetsMetadata.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SQL GetTables.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SQL GetTables.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SQL GetTables.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SQL GetTables.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SQL Server ExecuteStoredProcedureV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SQL Server ExecuteStoredProcedureV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SQL Server ExecuteStoredProcedureV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SQL Server ExecuteStoredProcedureV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SQL Server Get First Customers.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SQL Server Get First Customers.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SQL Server Get First Customers.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SQL Server Get First Customers.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SQL Server GetProceduresV2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SQL Server GetProceduresV2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SQL Server GetProceduresV2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SQL Server GetProceduresV2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SQL Server Intellisense Error.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SQL Server Intellisense Error.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SQL Server Intellisense Error.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SQL Server Intellisense Error.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SQL Server Intellisense Response 1.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SQL Server Intellisense Response 1.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SQL Server Intellisense Response 1.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SQL Server Intellisense Response 1.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SQL Server Intellisense Response 2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SQL Server Intellisense Response 2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SQL Server Intellisense Response 2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SQL Server Intellisense Response 2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SQL Server Intellisense Response 3.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SQL Server Intellisense Response 3.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SQL Server Intellisense Response 3.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SQL Server Intellisense Response 3.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SQL Server Intellisense Response Error.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SQL Server Intellisense Response Error.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SQL Server Intellisense Response Error.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SQL Server Intellisense Response Error.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SQL Server Intellisense Response2 1.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SQL Server Intellisense Response2 1.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SQL Server Intellisense Response2 1.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SQL Server Intellisense Response2 1.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SQL Server Intellisense Response2 2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SQL Server Intellisense Response2 2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SQL Server Intellisense Response2 2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SQL Server Intellisense Response2 2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SQL Server Load Customers DB.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SQL Server Load Customers DB.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SQL Server Load Customers DB.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SQL Server Load Customers DB.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SQL Server TestAllFunctions.jsonSet b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SQL Server TestAllFunctions.jsonSet similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/SQL Server TestAllFunctions.jsonSet rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SQL Server TestAllFunctions.jsonSet diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Teams_GetMessageDetails_GetSuggestionsForChannel.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Teams_GetMessageDetails_GetSuggestionsForChannel.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Teams_GetMessageDetails_GetSuggestionsForChannel.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Teams_GetMessageDetails_GetSuggestionsForChannel.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Teams_GetMessageDetails_InputType.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Teams_GetMessageDetails_InputType.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Responses/Teams_GetMessageDetails_InputType.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Teams_GetMessageDetails_InputType.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/Azure Cognitive Service for Language v2.1.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/Azure Cognitive Service for Language v2.1.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/Azure Cognitive Service for Language v2.1.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/Azure Cognitive Service for Language v2.1.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/Azure Cognitive Service for Language v2.2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/Azure Cognitive Service for Language v2.2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/Azure Cognitive Service for Language v2.2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/Azure Cognitive Service for Language v2.2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/Azure Cognitive Service for Language.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/Azure Cognitive Service for Language.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/Azure Cognitive Service for Language.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/Azure Cognitive Service for Language.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/Azure Open AI.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/Azure Open AI.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/Azure Open AI.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/Azure Open AI.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/AzureAppService.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/AzureAppService.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/AzureAppService.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/AzureAppService.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/AzureBlobStorage.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/AzureBlobStorage.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/AzureBlobStorage.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/AzureBlobStorage.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/Bing_Maps.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/Bing_Maps.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/Bing_Maps.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/Bing_Maps.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/CardsForPowerApps.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/CardsForPowerApps.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/CardsForPowerApps.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/CardsForPowerApps.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/DadJokes.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/DadJokes.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/DadJokes.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/DadJokes.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/Dataverse 2.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/Dataverse 2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/Dataverse 2.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/Dataverse 2.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/Dataverse 3.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/Dataverse 3.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/Dataverse 3.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/Dataverse 3.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/Dataverse.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/Dataverse.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/Dataverse.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/Dataverse.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/DataverseSample.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/DataverseSample.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/DataverseSample.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/DataverseSample.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/Eden AI.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/Eden AI.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/Eden AI.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/Eden AI.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/ExcelOnlineBusiness.swagger.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/ExcelOnlineBusiness.swagger.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/ExcelOnlineBusiness.swagger.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/ExcelOnlineBusiness.swagger.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/Language - Question Answering.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/Language - Question Answering.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/Language - Question Answering.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/Language - Question Answering.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/MSNWeather.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/MSNWeather.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/MSNWeather.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/MSNWeather.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/Office_365_Groups.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/Office_365_Groups.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/Office_365_Groups.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/Office_365_Groups.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/Office_365_Outlook.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/Office_365_Outlook.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/Office_365_Outlook.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/Office_365_Outlook.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/Office_365_Users.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/Office_365_Users.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/Office_365_Users.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/Office_365_Users.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/PetStore.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/PetStore.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/PetStore.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/PetStore.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/PowerAppsForMakers.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/PowerAppsForMakers.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/PowerAppsForMakers.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/PowerAppsForMakers.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/PowerPlatformForAdmins.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/PowerPlatformForAdmins.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/PowerPlatformForAdmins.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/PowerPlatformForAdmins.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/SQL Server.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/SQL Server.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/SQL Server.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/SQL Server.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/SalesForce.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/SalesForce.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/SalesForce.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/SalesForce.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/ServiceNow.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/ServiceNow.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/ServiceNow.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/ServiceNow.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/SharePoint.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/SharePoint.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/SharePoint.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/SharePoint.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/Teams.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/Teams.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/Teams.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/Teams.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/TestConnector12.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/TestConnector12.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/TestConnector12.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/TestConnector12.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/TestOpenAPI.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/TestOpenAPI.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/TestOpenAPI.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/TestOpenAPI.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/ZenDesk.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/ZenDesk.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/ZenDesk.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/ZenDesk.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/shared_sendmail.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/shared_sendmail.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/Swagger/shared_sendmail.json rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Swagger/shared_sendmail.json diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/SwaggerFailureTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/SwaggerFailureTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/SwaggerFailureTests.cs rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/SwaggerFailureTests.cs diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/TestConnectorRuntimeContext.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/TestConnectorRuntimeContext.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/TestConnectorRuntimeContext.cs rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/TestConnectorRuntimeContext.cs diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests/ThreadingTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/ThreadingTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Connectors.Tests/ThreadingTests.cs rename to src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/ThreadingTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/AnalyzeThreadSafety.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/AnalyzeThreadSafety.cs similarity index 98% rename from src/tests/Microsoft.PowerFx.Core.Tests/AnalyzeThreadSafety.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/AnalyzeThreadSafety.cs index b51ae8e32..6e4c520b7 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests/AnalyzeThreadSafety.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/AnalyzeThreadSafety.cs @@ -2,14 +2,14 @@ // Licensed under the MIT license. using System; -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; +using System.Diagnostics; +using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; -using Microsoft.PowerFx.Core.Texl.Builtins; +using Microsoft.PowerFx.Core.Texl.Builtins; using Xunit; namespace Microsoft.PowerFx.Core.Tests @@ -18,84 +18,84 @@ namespace Microsoft.PowerFx.Core.Tests /// Analyze assemblies for thread safety issues. /// public class AnalyzeThreadSafety - { - // Return true if safe, false on error. - public static bool VerifyThreadSafeImmutable(Type t) - { - int errors = 0; - - var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly; - - // Check out all fields and properties. - foreach (var prop in t.GetProperties(flags)) - { - var name = prop.Name; - if (prop.CanWrite) - { - var isInitKeyword = prop.SetMethod.ReturnParameter.GetRequiredCustomModifiers().Contains(typeof(System.Runtime.CompilerServices.IsExternalInit)); - if (!isInitKeyword) - { - // No mutable properties allowed. Init only ok. - Debugger.Log(0, string.Empty, $"{t.Name}.{name} has setter\r\n"); - errors++; - } - } - - Assert.True(prop.CanRead); - - var propType = prop.PropertyType; - - if (!IsTypeImmutable(propType)) - { - // valuetypes are copies, so no contention - if (!prop.PropertyType.IsValueType) - { - // Fail. - Debugger.Log(0, string.Empty, $"{t.Name}.{name} returns mutable value\r\n"); - errors++; - } - } - } - - foreach (var field in t.GetFields(flags)) - { - var name = field.Name; - - if (name.StartsWith("<")) - { - // Ignore compile generated fields. - continue; - } - - // ReadOnly - if (!field.IsInitOnly) - { - Debugger.Log(0, string.Empty, $"{t.Name}.{name} is not readonly\r\n"); - errors++; - } - - if (field.GetCustomAttributes().Any() || - IsTypeConcurrent(field.FieldType)) - { - continue; - } - - if (!IsTypeImmutable(field.FieldType)) - { - // Fail. - Debugger.Log(0, string.Empty, $"{t.Name}.{name} returns mutable value\r\n"); - errors++; - } - } - - if (errors > 0) - { - Debugger.Log(0, string.Empty, $"\r\n"); - return false; - } - - return true; - } + { + // Return true if safe, false on error. + public static bool VerifyThreadSafeImmutable(Type t) + { + int errors = 0; + + var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly; + + // Check out all fields and properties. + foreach (var prop in t.GetProperties(flags)) + { + var name = prop.Name; + if (prop.CanWrite) + { + var isInitKeyword = prop.SetMethod.ReturnParameter.GetRequiredCustomModifiers().Contains(typeof(System.Runtime.CompilerServices.IsExternalInit)); + if (!isInitKeyword) + { + // No mutable properties allowed. Init only ok. + Debugger.Log(0, string.Empty, $"{t.Name}.{name} has setter\r\n"); + errors++; + } + } + + Assert.True(prop.CanRead); + + var propType = prop.PropertyType; + + if (!IsTypeImmutable(propType)) + { + // valuetypes are copies, so no contention + if (!prop.PropertyType.IsValueType) + { + // Fail. + Debugger.Log(0, string.Empty, $"{t.Name}.{name} returns mutable value\r\n"); + errors++; + } + } + } + + foreach (var field in t.GetFields(flags)) + { + var name = field.Name; + + if (name.StartsWith("<")) + { + // Ignore compile generated fields. + continue; + } + + // ReadOnly + if (!field.IsInitOnly) + { + Debugger.Log(0, string.Empty, $"{t.Name}.{name} is not readonly\r\n"); + errors++; + } + + if (field.GetCustomAttributes().Any() || + IsTypeConcurrent(field.FieldType)) + { + continue; + } + + if (!IsTypeImmutable(field.FieldType)) + { + // Fail. + Debugger.Log(0, string.Empty, $"{t.Name}.{name} returns mutable value\r\n"); + errors++; + } + } + + if (errors > 0) + { + Debugger.Log(0, string.Empty, $"\r\n"); + return false; + } + + return true; + } // Verify there are no "unsafe" static fields that could be threading issues. // Bugs - list of field types types that don't work. This should be driven to 0. @@ -186,65 +186,65 @@ namespace Microsoft.PowerFx.Core.Tests // Batch up errors so we can see all at once. Assert.Empty(errors); - } - - // $$$ Supersedes ImmutabilityTests. - // This is more aggressive (includes private fields), but they don't all pass. So assert is disabled. - public static void CheckImmutableTypes(Assembly[] assemblies, bool enableAssert = false) - { - foreach (var assembly in assemblies) - { - foreach (Type type in assembly.GetTypes()) - { + } + + // $$$ Supersedes ImmutabilityTests. + // This is more aggressive (includes private fields), but they don't all pass. So assert is disabled. + public static void CheckImmutableTypes(Assembly[] assemblies, bool enableAssert = false) + { + foreach (var assembly in assemblies) + { + foreach (Type type in assembly.GetTypes()) + { if (type.Name.StartsWith("<", StringComparison.OrdinalIgnoreCase)) { continue; // exclude compiler generated closures. - } - - // includes base types - var attr = type.GetInterfaces().Select(x => x.GetCustomAttributes().OfType()); - if (attr == null) - { - continue; - } - - // Common pattern is a writeable derived type (like Dict vs. IReadOnlyDict). - var attrNotSafe = type.GetCustomAttribute(inherit: false); - if (attrNotSafe != null) - { - var attribute = type.GetCustomAttribute(inherit: false); - if (attribute != null) - { - Assert.True(false); // Class can't have both safe & unsafe together. - } - - continue; - } - - bool ok = AnalyzeThreadSafety.VerifyThreadSafeImmutable(type); - - // Enable this, per https://github.com/microsoft/Power-Fx/issues/1519 - if (enableAssert) - { - Assert.True(ok); - } - } - } + } + + // includes base types + var attr = type.GetInterfaces().Select(x => x.GetCustomAttributes().OfType()); + if (attr == null) + { + continue; + } + + // Common pattern is a writeable derived type (like Dict vs. IReadOnlyDict). + var attrNotSafe = type.GetCustomAttribute(inherit: false); + if (attrNotSafe != null) + { + var attribute = type.GetCustomAttribute(inherit: false); + if (attribute != null) + { + Assert.True(false); // Class can't have both safe & unsafe together. + } + + continue; + } + + bool ok = AnalyzeThreadSafety.VerifyThreadSafeImmutable(type); + + // Enable this, per https://github.com/microsoft/Power-Fx/issues/1519 + if (enableAssert) + { + Assert.True(ok); + } + } + } } - - private static bool IsTypeConcurrent(Type type) - { + + private static bool IsTypeConcurrent(Type type) + { if (type.IsGenericType) { - var genericDef = type.GetGenericTypeDefinition(); - if (genericDef == typeof(ConcurrentDictionary<,>)) - { - return true; - } - } - - return false; - } + var genericDef = type.GetGenericTypeDefinition(); + if (genericDef == typeof(ConcurrentDictionary<,>)) + { + return true; + } + } + + return false; + } private static bool IsFieldVolatile(FieldInfo field) { @@ -259,23 +259,23 @@ namespace Microsoft.PowerFx.Core.Tests { // Primitives typeof(object), - typeof(string), + typeof(string), typeof(System.Type), typeof(Random), typeof(DateTime), typeof(System.Text.RegularExpressions.Regex), - typeof(System.Numerics.BigInteger), - typeof(NumberFormatInfo), - - // Generics - typeof(IReadOnlyDictionary<,>), - typeof(IReadOnlyCollection<>), - typeof(IReadOnlyList<>), - typeof(Nullable<>), - typeof(IEnumerable<>), - typeof(KeyValuePair<,>), - typeof(ISet<>) - }; + typeof(System.Numerics.BigInteger), + typeof(NumberFormatInfo), + + // Generics + typeof(IReadOnlyDictionary<,>), + typeof(IReadOnlyCollection<>), + typeof(IReadOnlyList<>), + typeof(Nullable<>), + typeof(IEnumerable<>), + typeof(KeyValuePair<,>), + typeof(ISet<>) + }; // If the instance is readonly, is the type itself immutable ? internal static bool IsTypeImmutable(Type t) @@ -300,20 +300,20 @@ namespace Microsoft.PowerFx.Core.Tests // Collection classes should be a IReadOnly. Verify their T is also safe. if (t.IsGenericType) { - var genericDef = t.GetGenericTypeDefinition(); - if (_knownImmutableTypes.Contains(genericDef)) - { - var typeArgs = t.GetGenericArguments(); - foreach (var arg in typeArgs) - { - var isArgSafe = IsTypeImmutable(arg) || arg.IsValueType; - if (!isArgSafe) - { - return false; - } - } + var genericDef = t.GetGenericTypeDefinition(); + if (_knownImmutableTypes.Contains(genericDef)) + { + var typeArgs = t.GetGenericArguments(); + foreach (var arg in typeArgs) + { + var isArgSafe = IsTypeImmutable(arg) || arg.IsValueType; + if (!isArgSafe) + { + return false; + } + } - return true; + return true; } } diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/AssemblyProperties.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/AssemblyProperties.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/AssemblyProperties.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/AssemblyProperties.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/AssociatedDataSourcesTests/TestDVEntity.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/AssociatedDataSourcesTests/TestDVEntity.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/AssociatedDataSourcesTests/TestDVEntity.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/AssociatedDataSourcesTests/TestDVEntity.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/AssociatedDataSourcesTests/TestDelegationValidation.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/AssociatedDataSourcesTests/TestDelegationValidation.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/AssociatedDataSourcesTests/TestDelegationValidation.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/AssociatedDataSourcesTests/TestDelegationValidation.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/AssociatedDataSourcesTests/TestUpdateDataQuerySelects.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/AssociatedDataSourcesTests/TestUpdateDataQuerySelects.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/AssociatedDataSourcesTests/TestUpdateDataQuerySelects.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/AssociatedDataSourcesTests/TestUpdateDataQuerySelects.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/AttributeParserTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/AttributeParserTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/AttributeParserTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/AttributeParserTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/BinderTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/BinderTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/BinderTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/BinderTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/BinderUtilsTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/BinderUtilsTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/BinderUtilsTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/BinderUtilsTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/BindingEngineTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/BindingEngineTests.cs similarity index 98% rename from src/tests/Microsoft.PowerFx.Core.Tests/BindingEngineTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/BindingEngineTests.cs index b2601f3cb..565e0428b 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests/BindingEngineTests.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/BindingEngineTests.cs @@ -1,25 +1,25 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -using System; -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; -using Microsoft.PowerFx.Core.Functions; -using Microsoft.PowerFx.Core.Localization; +using Microsoft.PowerFx.Core.Functions; +using Microsoft.PowerFx.Core.Localization; using Microsoft.PowerFx.Core.Tests; -using Microsoft.PowerFx.Core.Texl.Builtins; -using Microsoft.PowerFx.Core.Types; -using Microsoft.PowerFx.Core.Utils; -using Microsoft.PowerFx.Syntax; +using Microsoft.PowerFx.Core.Texl.Builtins; +using Microsoft.PowerFx.Core.Types; +using Microsoft.PowerFx.Core.Utils; +using Microsoft.PowerFx.Syntax; using Microsoft.PowerFx.Types; using Xunit; -using Xunit.Sdk; - +using Xunit.Sdk; + namespace Microsoft.PowerFx.Tests { public class BindingEngineTests : PowerFxTest - { + { [Fact] public void CheckSuccess() { @@ -54,8 +54,8 @@ namespace Microsoft.PowerFx.Tests Assert.Equal("3 * x", str); var r = RecordType.Empty().Add( - new NamedFormulaType("x", FormulaType.Number)); - + new NamedFormulaType("x", FormulaType.Number)); + var check = engine.Check(parse, r); Assert.True(check.IsSuccess); @@ -69,7 +69,7 @@ namespace Microsoft.PowerFx.Tests public void CheckChainingParseSuccess() { var opts = new ParserOptions - { + { AllowsSideEffects = true }; @@ -94,8 +94,8 @@ namespace Microsoft.PowerFx.Tests var result = engine.Parse("3*1+"); Assert.True(result.HasError); - Assert.Single(result.Errors); - + Assert.Single(result.Errors); + AssertContainsError(result, "Error 4-4: Expected an operand"); } @@ -109,8 +109,8 @@ namespace Microsoft.PowerFx.Tests Assert.False(result.IsSuccess); Assert.True(result.Errors.Count() >= 1); AssertContainsError(result, "Error 4-4: Expected an operand"); - } - + } + [Fact] public void CheckParseErrorCommaSeparatedLocale() { @@ -136,14 +136,14 @@ namespace Microsoft.PowerFx.Tests var result = Engine.Parse("Function(args; separated; by; semicolons) + 123,456", options: new ParserOptions(CultureInfo.GetCultureInfo("de-DE"))); Assert.True(result.IsSuccess); - } - - [Fact] - public void CheckNullRef() - { - var engine = new Engine(new PowerFxConfig()); - Assert.Throws(() => engine.Check((string)null)); - } + } + + [Fact] + public void CheckNullRef() + { + var engine = new Engine(new PowerFxConfig()); + Assert.Throws(() => engine.Check((string)null)); + } [Fact] public void CheckBindError() @@ -154,22 +154,22 @@ namespace Microsoft.PowerFx.Tests Assert.False(result.IsSuccess); AssertContainsError(result, "Error 2-5: Name isn't valid. 'foo' isn't recognized"); - } - - [Theory] - - // Binding errors - [InlineData("3+foo+2", "Error 2-5: Il nome non è valido. \"foo\" non riconosciuto.", "it-IT")] - [InlineData("Foo()", "Error 0-5: 'Foo' est une fonction inconnue ou non prise en charge.", "fr-FR")] - [InlineData("AAA", "Error 0-3: O nome não é válido. 'AAA' não é reconhecido.", "pt-BR")] - [InlineData("Bar()", "Error 0-5: \"Bar\" — неизвестная или неподдерживаемая функция.", "ru-RU")] - [InlineData("Table({a:BB})", "Error 9-11: Name isn't valid. 'BB' isn't recognized.", "en-US")] - - // Parse errors - [InlineData("2e.5", "Error 1-2: È previsto un operatore. A questo punto della formula è previsto un operatore, ad esempio +, * o &.", "it-IT")] - [InlineData(".2.3", "Error 0-1: Caractères inattendus. Des caractères sont utilisés dans la formule de manière inattendue.", "fr-FR")] - [InlineData("2EEE5", "Error 1-5: Operador esperado. Esperamos um operador como +, * ou & neste ponto na fórmula.", "pt-BR")] - [InlineData("7E1111111", "Error 0-9: Numerická hodnota je príliš veľká.", "sk-SK")] + } + + [Theory] + + // Binding errors + [InlineData("3+foo+2", "Error 2-5: Il nome non è valido. \"foo\" non riconosciuto.", "it-IT")] + [InlineData("Foo()", "Error 0-5: 'Foo' est une fonction inconnue ou non prise en charge.", "fr-FR")] + [InlineData("AAA", "Error 0-3: O nome não é válido. 'AAA' não é reconhecido.", "pt-BR")] + [InlineData("Bar()", "Error 0-5: \"Bar\" — неизвестная или неподдерживаемая функция.", "ru-RU")] + [InlineData("Table({a:BB})", "Error 9-11: Name isn't valid. 'BB' isn't recognized.", "en-US")] + + // Parse errors + [InlineData("2e.5", "Error 1-2: È previsto un operatore. A questo punto della formula è previsto un operatore, ad esempio +, * o &.", "it-IT")] + [InlineData(".2.3", "Error 0-1: Caractères inattendus. Des caractères sont utilisés dans la formule de manière inattendue.", "fr-FR")] + [InlineData("2EEE5", "Error 1-5: Operador esperado. Esperamos um operador como +, * ou & neste ponto na fórmula.", "pt-BR")] + [InlineData("7E1111111", "Error 0-9: Numerická hodnota je príliš veľká.", "sk-SK")] [InlineData("4E88888", "Error 0-7: Numeric value is too large.", "en-US")] public void CheckBindError2(string expression, string expected, string locale) { @@ -214,15 +214,15 @@ namespace Microsoft.PowerFx.Tests Assert.False(result.IsSuccess); Assert.Single(result.Errors); AssertContainsError(result, "Error 31-34: Name isn't valid. 'foo' isn't recognized"); - } - + } + [Fact] public void CheckDottedBindError() { var config = new PowerFxConfig(); var engine = new Engine(config); var result = engine.Check("First([1,2,3]).foo"); - Assert.False(result.IsSuccess); + Assert.False(result.IsSuccess); Assert.Single(result.Errors); AssertContainsError(result, "Error 14-18: Name isn't valid. 'foo' isn't recognized."); } @@ -233,7 +233,7 @@ namespace Microsoft.PowerFx.Tests var config = new PowerFxConfig(); var engine = new Engine(config); var result = engine.Check("[1,2,3].foo"); - Assert.False(result.IsSuccess); + Assert.False(result.IsSuccess); Assert.Single(result.Errors); AssertContainsError(result, "Error 7-11: Deprecated use of '.'. Please use the 'ShowColumns' function instead."); } @@ -258,229 +258,229 @@ namespace Microsoft.PowerFx.Tests var engine = new Engine(config); var formula = "Behavior(); Behavior()"; - var options = new ParserOptions { AllowsSideEffects = true }; - + var options = new ParserOptions { AllowsSideEffects = true }; + var result1 = engine.Check(formula, options: options); Assert.True(result1.IsSuccess); var parseResult2 = engine.Parse(formula, options); var result2 = engine.Check(parseResult2); Assert.True(result2.IsSuccess); - } - - [Fact] - public void CheckRecursiveCustomType() - { + } + + [Fact] + public void CheckRecursiveCustomType() + { var config = new PowerFxConfig(); - var engine = new Engine(config); - - var lazyTypeInstance = new LazyRecursiveRecordType(); - - var result = engine.Check( - "Loop.Loop.Loop.Loop.Loop.Loop.Loop.Loop.Loop" + - ".Loop.Loop.Loop.Loop.Loop.Loop.Loop.Loop.Loop" + - ".Loop.Loop.Loop.Loop.Loop.Loop.Loop.Loop.Loop" + - ".Loop.Loop.Loop.Loop.Loop.Loop.Loop.Loop.Loop", lazyTypeInstance); - + var engine = new Engine(config); + + var lazyTypeInstance = new LazyRecursiveRecordType(); + + var result = engine.Check( + "Loop.Loop.Loop.Loop.Loop.Loop.Loop.Loop.Loop" + + ".Loop.Loop.Loop.Loop.Loop.Loop.Loop.Loop.Loop" + + ".Loop.Loop.Loop.Loop.Loop.Loop.Loop.Loop.Loop" + + ".Loop.Loop.Loop.Loop.Loop.Loop.Loop.Loop.Loop", lazyTypeInstance); + Assert.True(result.IsSuccess); - Assert.IsType(result.ReturnType); - Assert.Equal(lazyTypeInstance, result.ReturnType); - - // We never needed to iterate the fields of the lazy type + Assert.IsType(result.ReturnType); + Assert.Equal(lazyTypeInstance, result.ReturnType); + + // We never needed to iterate the fields of the lazy type Assert.False(lazyTypeInstance.EnumerableIterated); - } - - [Fact] - public void CheckFunctionsWithColumnIdentifierAndLambda_OneFunction() - { - var config = new PowerFxConfig(); - var gotException = false; - - try - { - config.AddFunction(new LambdaAndColumnIdentifierFunction()); - } - catch (ArgumentException ex) - { - Assert.Equal("This function is ambiguous, it contains lambda expressions and column identifiers for the same argument.", ex.Message); - gotException = true; - } - finally - { - Assert.True(gotException); - } - } - - [Fact] - public void CheckFunctionsWithColumnIdentifierAndLambda_Overloads_TwoFunctions() - { - var funcs = new TexlFunction[] { new LambdaFunction(0x1), new ColumnIdentifierFunction(0x1) }; - - for (var i = 0; i < 2; i++) - { - var config = new PowerFxConfig(); - var gotException = false; - - config.AddFunction(funcs[i % 2]); - - try - { - config.AddFunction(funcs[(i + 1) % 2]); - } - catch (ArgumentException ex) - { - Assert.Equal("This function is ambiguous, it contains lambda expressions and column identifiers for the same argument.", ex.Message); - gotException = true; - } - finally - { - Assert.True(gotException); - } - } - } - - [Fact] - public void CheckFunctionsWithColumnIdentifierAndLambda_Overloads_NoConflict() - { - var funcs = new TexlFunction[] { new LambdaFunction(0x1), new ColumnIdentifierFunction(0x2) }; - - for (var i = 0; i < 2; i++) - { - var config = new PowerFxConfig(); - - config.AddFunction(funcs[i % 2]); - config.AddFunction(funcs[(i + 1) % 2]); - } - } - - [Fact] - public void CheckFunctionsWithColumnIdentifierAndLambda_Overloads_NoConflict2() - { - var funcs = new TexlFunction[] { new LambdaFunction(0x1), new ColumnIdentifierFunction(0x2), new LambdaFunction(0x4), new ColumnIdentifierFunction(0x8) }; - - for (var i = 0; i < 4; i++) - { - var config = new PowerFxConfig(); - - config.AddFunction(funcs[i % 4]); - config.AddFunction(funcs[(i + 1) % 4]); - config.AddFunction(funcs[(i + 2) % 4]); - config.AddFunction(funcs[(i + 3) % 4]); - } - } - - [Fact] - public void CheckFunctionsWithColumnIdentifierAndLambda_Overloads_Conflict() - { - var funcs = new TexlFunction[] { new LambdaFunction(0x1), new ColumnIdentifierFunction(0x2), new ColumnIdentifierFunction(0x1) }; - - for (var i = 0; i < 3; i++) - { - var config = new PowerFxConfig(); - var gotException = false; - - try - { - config.AddFunction(funcs[i % 3]); - config.AddFunction(funcs[(i + 1) % 3]); - config.AddFunction(funcs[(i + 2) % 3]); - } - catch (ArgumentException ex) - { - Assert.Equal("This function is ambiguous, it contains lambda expressions and column identifiers for the same argument.", ex.Message); - gotException = true; - } - finally - { - Assert.True(gotException); - } - } - } - - internal class LazyRecursiveRecordType : RecordType - { - public override IEnumerable FieldNames => GetFieldNames(); - - public bool EnumerableIterated = false; - - public LazyRecursiveRecordType() - : base() - { - } - - public override bool TryGetFieldType(string name, out FormulaType type) - { - switch (name) - { - case "SomeString": - type = FormulaType.String; - return true; - case "Loop": - type = this; - return true; - default: - type = FormulaType.Blank; - return false; - } - } - - private IEnumerable GetFieldNames() - { - EnumerableIterated = true; - - yield return "SomeString"; - yield return "Loop"; - } - - public override bool Equals(object other) - { - return other is LazyRecursiveRecordType; // All the same - } - - public override int GetHashCode() - { - return 1; - } - } - - [Fact] - public void CheckTypeUnionLazy() - { + } + + [Fact] + public void CheckFunctionsWithColumnIdentifierAndLambda_OneFunction() + { var config = new PowerFxConfig(); - var engine = new Engine(config); - - var lazyTypeInstance = new LazyRecursiveRecordType(); - - var result = engine.Check("First(Table(Loop, {A: SomeString}))", lazyTypeInstance); - + var gotException = false; + + try + { + config.AddFunction(new LambdaAndColumnIdentifierFunction()); + } + catch (ArgumentException ex) + { + Assert.Equal("This function is ambiguous, it contains lambda expressions and column identifiers for the same argument.", ex.Message); + gotException = true; + } + finally + { + Assert.True(gotException); + } + } + + [Fact] + public void CheckFunctionsWithColumnIdentifierAndLambda_Overloads_TwoFunctions() + { + var funcs = new TexlFunction[] { new LambdaFunction(0x1), new ColumnIdentifierFunction(0x1) }; + + for (var i = 0; i < 2; i++) + { + var config = new PowerFxConfig(); + var gotException = false; + + config.AddFunction(funcs[i % 2]); + + try + { + config.AddFunction(funcs[(i + 1) % 2]); + } + catch (ArgumentException ex) + { + Assert.Equal("This function is ambiguous, it contains lambda expressions and column identifiers for the same argument.", ex.Message); + gotException = true; + } + finally + { + Assert.True(gotException); + } + } + } + + [Fact] + public void CheckFunctionsWithColumnIdentifierAndLambda_Overloads_NoConflict() + { + var funcs = new TexlFunction[] { new LambdaFunction(0x1), new ColumnIdentifierFunction(0x2) }; + + for (var i = 0; i < 2; i++) + { + var config = new PowerFxConfig(); + + config.AddFunction(funcs[i % 2]); + config.AddFunction(funcs[(i + 1) % 2]); + } + } + + [Fact] + public void CheckFunctionsWithColumnIdentifierAndLambda_Overloads_NoConflict2() + { + var funcs = new TexlFunction[] { new LambdaFunction(0x1), new ColumnIdentifierFunction(0x2), new LambdaFunction(0x4), new ColumnIdentifierFunction(0x8) }; + + for (var i = 0; i < 4; i++) + { + var config = new PowerFxConfig(); + + config.AddFunction(funcs[i % 4]); + config.AddFunction(funcs[(i + 1) % 4]); + config.AddFunction(funcs[(i + 2) % 4]); + config.AddFunction(funcs[(i + 3) % 4]); + } + } + + [Fact] + public void CheckFunctionsWithColumnIdentifierAndLambda_Overloads_Conflict() + { + var funcs = new TexlFunction[] { new LambdaFunction(0x1), new ColumnIdentifierFunction(0x2), new ColumnIdentifierFunction(0x1) }; + + for (var i = 0; i < 3; i++) + { + var config = new PowerFxConfig(); + var gotException = false; + + try + { + config.AddFunction(funcs[i % 3]); + config.AddFunction(funcs[(i + 1) % 3]); + config.AddFunction(funcs[(i + 2) % 3]); + } + catch (ArgumentException ex) + { + Assert.Equal("This function is ambiguous, it contains lambda expressions and column identifiers for the same argument.", ex.Message); + gotException = true; + } + finally + { + Assert.True(gotException); + } + } + } + + internal class LazyRecursiveRecordType : RecordType + { + public override IEnumerable FieldNames => GetFieldNames(); + + public bool EnumerableIterated = false; + + public LazyRecursiveRecordType() + : base() + { + } + + public override bool TryGetFieldType(string name, out FormulaType type) + { + switch (name) + { + case "SomeString": + type = FormulaType.String; + return true; + case "Loop": + type = this; + return true; + default: + type = FormulaType.Blank; + return false; + } + } + + private IEnumerable GetFieldNames() + { + EnumerableIterated = true; + + yield return "SomeString"; + yield return "Loop"; + } + + public override bool Equals(object other) + { + return other is LazyRecursiveRecordType; // All the same + } + + public override int GetHashCode() + { + return 1; + } + } + + [Fact] + public void CheckTypeUnionLazy() + { + var config = new PowerFxConfig(); + var engine = new Engine(config); + + var lazyTypeInstance = new LazyRecursiveRecordType(); + + var result = engine.Check("First(Table(Loop, {A: SomeString}))", lazyTypeInstance); + Assert.True(result.IsSuccess); - Assert.IsType(result.ReturnType); - - Assert.Equal("![A:s, Loop:r!, SomeString:s]", result.ReturnType._type.ToString()); - - // Union operations require iterating fields + Assert.IsType(result.ReturnType); + + Assert.Equal("![A:s, Loop:r!, SomeString:s]", result.ReturnType._type.ToString()); + + // Union operations require iterating fields Assert.True(lazyTypeInstance.EnumerableIterated); - } - - [Fact] - public void CheckShuffleLazyTable() - { + } + + [Fact] + public void CheckShuffleLazyTable() + { var config = new PowerFxConfig(); - var engine = new Engine(config); - - var lazyTypeInstance = new LazyRecursiveRecordType().ToTable(); - - var result = engine.Check("Shuffle(Table)", RecordType.Empty().Add("Table", lazyTypeInstance)); - Assert.True(result.IsSuccess); - + var engine = new Engine(config); + + var lazyTypeInstance = new LazyRecursiveRecordType().ToTable(); + + var result = engine.Check("Shuffle(Table)", RecordType.Empty().Add("Table", lazyTypeInstance)); + Assert.True(result.IsSuccess); + var tableType = Assert.IsType(result.ReturnType); Assert.IsType(tableType.ToRecord()); - } + } - /// - /// A function with behavior/side-effects used in testing. + /// + /// A function with behavior/side-effects used in testing. /// - internal class BehaviorFunction : TexlFunction + internal class BehaviorFunction : TexlFunction { public BehaviorFunction() : base( @@ -494,18 +494,18 @@ namespace Microsoft.PowerFx.Tests 0, // no args 0) { - } - - public override bool IsSelfContained => false; - - public override IEnumerable GetSignatures() - { - yield break; - } - } - - // Example of function that requires AI disclaimer. - internal class AISummarizeFunction : TexlFunction + } + + public override bool IsSelfContained => false; + + public override IEnumerable GetSignatures() + { + yield break; + } + } + + // Example of function that requires AI disclaimer. + internal class AISummarizeFunction : TexlFunction { public AISummarizeFunction() : base( @@ -519,99 +519,99 @@ namespace Microsoft.PowerFx.Tests 0, // no args 0) { - } - - public override bool IsSelfContained => false; - - public override IEnumerable GetSignatures() - { - yield break; - } - } - - internal class LambdaAndColumnIdentifierFunction : TexlFunction - { - public LambdaAndColumnIdentifierFunction() - : base(DPath.Root, "LambdaAndColumnIdentifierFunction", "LambdaAndColumnIdentifierFunction", TexlStrings.AboutSet, FunctionCategories.Text, DType.Boolean, 0x1, 1, 1) - { - } - - public override bool HasLambdas => true; - - public override bool HasColumnIdentifiers => true; - - public override bool IsSelfContained => false; - - public override IEnumerable GetSignatures() - { - yield break; - } - - public override bool IsLambdaParam(TexlNode node, int index) - { - return true; - } - - public override ParamIdentifierStatus GetIdentifierParamStatus(TexlNode node, Features features, int index) - { - return ParamIdentifierStatus.AlwaysIdentifier; - } - } - - internal class LambdaFunction : TexlFunction - { - private readonly int _mask = 0; - - public LambdaFunction(int mask) - : base(DPath.Root, "TestFunction1", "TestFunction1", TexlStrings.AboutSet, FunctionCategories.Text, DType.Boolean, mask, 1, 1) - { - _mask = mask; - } - - public override bool HasLambdas => true; - - public override bool HasColumnIdentifiers => false; - - public override bool IsSelfContained => false; - - public override IEnumerable GetSignatures() - { - yield break; - } - - public override bool IsLambdaParam(TexlNode node, int index) - { - return (_mask & (1 << index)) != 0; - } - } - - internal class ColumnIdentifierFunction : TexlFunction - { - private readonly int _mask = 0; - - public ColumnIdentifierFunction(int mask) - : base(DPath.Root, "TestFunction1", "TestFunction1", TexlStrings.AboutSet, FunctionCategories.Text, DType.Boolean, 0x0, 1, 1) - { - _mask = mask; - } - - public override bool HasLambdas => false; - - public override bool HasColumnIdentifiers => true; - - public override bool IsSelfContained => false; - - public override IEnumerable GetSignatures() - { - yield break; - } - - public override ParamIdentifierStatus GetIdentifierParamStatus(TexlNode node, Features features, int index) - { - return (_mask & (1 << index)) != 0 ? ParamIdentifierStatus.AlwaysIdentifier : ParamIdentifierStatus.NeverIdentifier; - } - } - + } + + public override bool IsSelfContained => false; + + public override IEnumerable GetSignatures() + { + yield break; + } + } + + internal class LambdaAndColumnIdentifierFunction : TexlFunction + { + public LambdaAndColumnIdentifierFunction() + : base(DPath.Root, "LambdaAndColumnIdentifierFunction", "LambdaAndColumnIdentifierFunction", TexlStrings.AboutSet, FunctionCategories.Text, DType.Boolean, 0x1, 1, 1) + { + } + + public override bool HasLambdas => true; + + public override bool HasColumnIdentifiers => true; + + public override bool IsSelfContained => false; + + public override IEnumerable GetSignatures() + { + yield break; + } + + public override bool IsLambdaParam(TexlNode node, int index) + { + return true; + } + + public override ParamIdentifierStatus GetIdentifierParamStatus(TexlNode node, Features features, int index) + { + return ParamIdentifierStatus.AlwaysIdentifier; + } + } + + internal class LambdaFunction : TexlFunction + { + private readonly int _mask = 0; + + public LambdaFunction(int mask) + : base(DPath.Root, "TestFunction1", "TestFunction1", TexlStrings.AboutSet, FunctionCategories.Text, DType.Boolean, mask, 1, 1) + { + _mask = mask; + } + + public override bool HasLambdas => true; + + public override bool HasColumnIdentifiers => false; + + public override bool IsSelfContained => false; + + public override IEnumerable GetSignatures() + { + yield break; + } + + public override bool IsLambdaParam(TexlNode node, int index) + { + return (_mask & (1 << index)) != 0; + } + } + + internal class ColumnIdentifierFunction : TexlFunction + { + private readonly int _mask = 0; + + public ColumnIdentifierFunction(int mask) + : base(DPath.Root, "TestFunction1", "TestFunction1", TexlStrings.AboutSet, FunctionCategories.Text, DType.Boolean, 0x0, 1, 1) + { + _mask = mask; + } + + public override bool HasLambdas => false; + + public override bool HasColumnIdentifiers => true; + + public override bool IsSelfContained => false; + + public override IEnumerable GetSignatures() + { + yield break; + } + + public override ParamIdentifierStatus GetIdentifierParamStatus(TexlNode node, Features features, int index) + { + return (_mask & (1 << index)) != 0 ? ParamIdentifierStatus.AlwaysIdentifier : ParamIdentifierStatus.NeverIdentifier; + } + } + private void AssertContainsError(IOperationStatus result, string errorMessage) { Assert.Contains(result.Errors, x => x.ToString().StartsWith(errorMessage)); diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/BlobTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/BlobTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/BlobTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/BlobTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/CharacterUtilsTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/CharacterUtilsTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/CharacterUtilsTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/CharacterUtilsTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/CheckResultTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/CheckResultTests.cs similarity index 99% rename from src/tests/Microsoft.PowerFx.Core.Tests/CheckResultTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/CheckResultTests.cs index 948fbdcd6..f0a438b8d 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests/CheckResultTests.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/CheckResultTests.cs @@ -4,11 +4,11 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.Linq; -using Microsoft.PowerFx.Core.Tests.Helpers; +using System.Linq; +using Microsoft.PowerFx.Core.Tests.Helpers; using Microsoft.PowerFx.Core.Utils; using Microsoft.PowerFx.Syntax; -using Microsoft.PowerFx.Types; +using Microsoft.PowerFx.Types; using Xunit; namespace Microsoft.PowerFx.Core.Tests @@ -144,36 +144,36 @@ namespace Microsoft.PowerFx.Core.Tests Assert.Throws(() => check.SetText(parseResult)); Assert.Same(parseResult, check.Parse); - } - - [Theory] - - [InlineData("\"Something\"", "Something", true)] - [InlineData("1", 1d, true)] - [InlineData("true", true, true)] - [InlineData("GUID(\"ac98f780-0df8-427d-8c09-50f09b5f9cf5\")", "ac98f780-0df8-427d-8c09-50f09b5f9cf5", true)] - [InlineData("GUID()", null, false)] - [InlineData("Abs(2)", null, false)] + } + + [Theory] + + [InlineData("\"Something\"", "Something", true)] + [InlineData("1", 1d, true)] + [InlineData("true", true, true)] + [InlineData("GUID(\"ac98f780-0df8-427d-8c09-50f09b5f9cf5\")", "ac98f780-0df8-427d-8c09-50f09b5f9cf5", true)] + [InlineData("GUID()", null, false)] + [InlineData("Abs(2)", null, false)] public void GetParseLiteralsTests(string expression, object expected, bool canGetAsLiteral) { var check = new CheckResult(new Engine()); - var parseResult = Engine.Parse(expression, options: new PowerFx.ParserOptions() { NumberIsFloat = true }); + var parseResult = Engine.Parse(expression, options: new PowerFx.ParserOptions() { NumberIsFloat = true }); check.SetText(parseResult); - check.ApplyParse(); - + check.ApplyParse(); + var gotLiteral = check.TryGetAsLiteral(out var value); - Assert.Equal(canGetAsLiteral, gotLiteral); - - if (gotLiteral) - { - if (expression.StartsWith("GUID(")) - { - expected = Guid.Parse((string)expected); - } - - Assert.Equal(expected, value); + Assert.Equal(canGetAsLiteral, gotLiteral); + + if (gotLiteral) + { + if (expression.StartsWith("GUID(")) + { + expected = Guid.Parse((string)expected); + } + + Assert.Equal(expected, value); } } @@ -198,8 +198,8 @@ namespace Microsoft.PowerFx.Core.Tests [InlineData("1+", false)] public void BasicParseErrors2(string expr, bool numberIsFloat) { - var check = new CheckResult(new Engine()); - check.SetText(expr, numberIsFloat ? _numberIsFloatOpts : null); // parse error + var check = new CheckResult(new Engine()); + check.SetText(expr, numberIsFloat ? _numberIsFloatOpts : null); // parse error // Can still try to bind even with parse errors. // But some information like Returntype isn't computed. @@ -216,9 +216,9 @@ namespace Microsoft.PowerFx.Core.Tests // Still assign some types var node = ((BinaryOpNode)parse.Root).Left; - var type = check.GetNodeType(node); + var type = check.GetNodeType(node); Assert.Equal(numberIsFloat ? FormulaType.Number : FormulaType.Decimal, type); - } + } // Ensure we can pass in ParserOptions. [Fact] @@ -291,96 +291,96 @@ namespace Microsoft.PowerFx.Core.Tests check.ApplyParse(); Assert.Throws(() => check.ApplyBinding()); - } - - [Fact] - public void GetFunctionInfoTest() - { - var check = new CheckResult(new Engine()) - .SetText("Power(3,2)") - .SetBindingInfo(); - - var node = (CallNode)check.ApplyParse().Root; - - var info = check.GetFunctionInfo(node); - - Assert.Equal("Power", info.Name); - var sigs = info.Signatures.Single(); - Assert.Equal("Power(base, exponent)", sigs.DebugToString()); - } - - [Fact] - public void GetFunctionInfoTestNull() - { - var check = new CheckResult(new Engine()) - .SetText("Power(3,2)") - .SetBindingInfo(); - - Assert.Throws(() => check.GetFunctionInfo(null)); - } - - [Fact] - public void GetFunctionMissingTest() - { - var check = new CheckResult(new Engine()) - .SetText("Missing(3,2)") - .SetBindingInfo(); - - var node = (CallNode)check.ApplyParse().Root; - - // No function Info. - var info = check.GetFunctionInfo(node); - Assert.Null(info); } - [Theory] - + [Fact] + public void GetFunctionInfoTest() + { + var check = new CheckResult(new Engine()) + .SetText("Power(3,2)") + .SetBindingInfo(); + + var node = (CallNode)check.ApplyParse().Root; + + var info = check.GetFunctionInfo(node); + + Assert.Equal("Power", info.Name); + var sigs = info.Signatures.Single(); + Assert.Equal("Power(base, exponent)", sigs.DebugToString()); + } + + [Fact] + public void GetFunctionInfoTestNull() + { + var check = new CheckResult(new Engine()) + .SetText("Power(3,2)") + .SetBindingInfo(); + + Assert.Throws(() => check.GetFunctionInfo(null)); + } + + [Fact] + public void GetFunctionMissingTest() + { + var check = new CheckResult(new Engine()) + .SetText("Missing(3,2)") + .SetBindingInfo(); + + var node = (CallNode)check.ApplyParse().Root; + + // No function Info. + var info = check.GetFunctionInfo(node); + Assert.Null(info); + } + + [Theory] + // ****When Coercion is not allowed.**** - [InlineData("\"test string\"", false, true, "s", "")] - + [InlineData("\"test string\"", false, true, "s", "")] + // (even with coercion not allowed, we coerce numerics.) - [InlineData("Float(12)", false, true, "w", "")] - [InlineData("Float(12)", false, true, "n", "")] - [InlineData("Decimal(12)", false, true, "w", "")] - [InlineData("Decimal(12)", false, true, "n", "")] - [InlineData("{a:12, b:15}", false, false, "n", "Type mismatch between source and target types. Expected Number; Found Record.")] - - // (even with coercion not allowed, we coerce numerics.) - [InlineData("{a:Float(12), b:Float(15)}", false, true, "![a:w,b:n]", "")] - [InlineData("{a:Decimal(12), b:Decimal(15)}", false, true, "![a:w,b:n]", "")] - - // missing field - [InlineData("{a:Decimal(12), b:Decimal(15)}", false, false, "![a:w,b:n,c:s]", "Type mismatch between source and target record types. Given type has missing fields: c.")] - - // extra field - [InlineData("{a:Decimal(12), b:Decimal(15)}", false, false, "![a:w]", "Type mismatch between source and target record types. Given type has extra fields: b.")] - [InlineData("{a:12, b:15}", false, false, "*[a:w,b:w]", "Type mismatch between source and target types. Expected Table; Found Record.")] - [InlineData("{a:12, b:15}", false, false, "![a:w,b:s]", "Type mismatch between source and target record types. Field name: b Expected Text; Found Decimal.")] - - // ****When Coercion is allowed**** - [InlineData("\"test string\"", true, true, "s", "")] + [InlineData("Float(12)", false, true, "w", "")] + [InlineData("Float(12)", false, true, "n", "")] + [InlineData("Decimal(12)", false, true, "w", "")] + [InlineData("Decimal(12)", false, true, "n", "")] + [InlineData("{a:12, b:15}", false, false, "n", "Type mismatch between source and target types. Expected Number; Found Record.")] + + // (even with coercion not allowed, we coerce numerics.) + [InlineData("{a:Float(12), b:Float(15)}", false, true, "![a:w,b:n]", "")] + [InlineData("{a:Decimal(12), b:Decimal(15)}", false, true, "![a:w,b:n]", "")] + + // missing field + [InlineData("{a:Decimal(12), b:Decimal(15)}", false, false, "![a:w,b:n,c:s]", "Type mismatch between source and target record types. Given type has missing fields: c.")] + + // extra field + [InlineData("{a:Decimal(12), b:Decimal(15)}", false, false, "![a:w]", "Type mismatch between source and target record types. Given type has extra fields: b.")] + [InlineData("{a:12, b:15}", false, false, "*[a:w,b:w]", "Type mismatch between source and target types. Expected Table; Found Record.")] + [InlineData("{a:12, b:15}", false, false, "![a:w,b:s]", "Type mismatch between source and target record types. Field name: b Expected Text; Found Decimal.")] + + // ****When Coercion is allowed**** + [InlineData("\"test string\"", true, true, "s", "")] [InlineData("\"test string\"", true, true, "n", "")] - [InlineData("12", true, true, "w", "")] - [InlineData("12", true, true, "n", "")] + [InlineData("12", true, true, "w", "")] + [InlineData("12", true, true, "n", "")] [InlineData("{a:12, b:15}", true, false, "n", "Given Record type cannot be coerced to source type Number.")] - [InlineData("{a:12, b:15}", true, true, "![a:n,b:n]", "")] - [InlineData("{a:12, b:15}", true, false, "*[a:w,b:n]", "Type mismatch between source and target types. Expected Table; Found Record.")] - [InlineData("{a:Float(12), b:Float(15)}", true, true, "![a:w,b:n]", "")] - [InlineData("{a:Decimal(12), b:Decimal(15)}", true, true, "![a:w,b:n]", "")] - - // (with coercion allowed, aggregate's field also coerces) - [InlineData("{a:12, b:15}", true, true, "![a:w,b:s]", "")] - - // missing field - [InlineData("{a:Decimal(12), b:Decimal(15)}", true, false, "![a:w,b:n,c:s]", "Type mismatch between source and target record types. Given type has missing fields: c.")] - - // extra field + [InlineData("{a:12, b:15}", true, true, "![a:n,b:n]", "")] + [InlineData("{a:12, b:15}", true, false, "*[a:w,b:n]", "Type mismatch between source and target types. Expected Table; Found Record.")] + [InlineData("{a:Float(12), b:Float(15)}", true, true, "![a:w,b:n]", "")] + [InlineData("{a:Decimal(12), b:Decimal(15)}", true, true, "![a:w,b:n]", "")] + + // (with coercion allowed, aggregate's field also coerces) + [InlineData("{a:12, b:15}", true, true, "![a:w,b:s]", "")] + + // missing field + [InlineData("{a:Decimal(12), b:Decimal(15)}", true, false, "![a:w,b:n,c:s]", "Type mismatch between source and target record types. Given type has missing fields: c.")] + + // extra field [InlineData("{a:Decimal(12), b:Decimal(15)}", true, false, "![a:w]", "Type mismatch between source and target record types. Given type has extra fields: b.")] public void CheckResultExpectedReturnValueString(string inputExpr, bool allowCoerceTo, bool isSuccess, string expectedType, string errorMsg) - { - var expectedFormulaType = FormulaType.Build(TestUtils.DT(expectedType)); + { + var expectedFormulaType = FormulaType.Build(TestUtils.DT(expectedType)); CheckResultExpectedReturnValue(inputExpr, allowCoerceTo, isSuccess, errorMsg, expectedFormulaType); - } + } [Fact] public void BindingSetRecordType() @@ -522,11 +522,11 @@ namespace Microsoft.PowerFx.Core.Tests [InlineData("123+abc", "#$decimal$# + #$firstname$#", true)] // display names [InlineData("123+", "#$decimal$# + #$error$#", false)] // error [InlineData("123,456", "#$decimal$#", true)] // locales - [InlineData("Power(2,3)", "Power(#$decimal$#)", true)] // functions aren't Pii - - // Unkown public function are PII - [InlineData("MadeUpFunction(1)", "#$function$#(#$decimal$#)", true)] - [InlineData("Power(MadeUpFunction(1))", "Power(#$function$#(#$decimal$#))", true)] + [InlineData("Power(2,3)", "Power(#$decimal$#)", true)] // functions aren't Pii + + // Unkown public function are PII + [InlineData("MadeUpFunction(1)", "#$function$#(#$decimal$#)", true)] + [InlineData("Power(MadeUpFunction(1))", "Power(#$function$#(#$decimal$#))", true)] [InlineData("Power(Clear(1))", "Power(Clear(#$decimal$#))", true)] public void TestApplyGetLogging(string expr, string execptedLog, bool success) { @@ -539,85 +539,85 @@ namespace Microsoft.PowerFx.Core.Tests var log = check.ApplyGetLogging(); Assert.Equal(success, check.IsSuccess); Assert.Equal(execptedLog, log); - } - - [Theory] - [InlineData("ShowColumns(Table, Name)")] - public void ApplyLoggingWithBinding(string expr) - { - var tableType = TableType.Empty().Add("Name", FormulaType.String); - var symbolTable = new SymbolTable(); - symbolTable.AddVariable("Table", tableType); - - CheckResult check = new CheckResult(new Engine()) - .SetText(expr) - .SetBindingInfo(symbolTable); - - var errors = check.ApplyErrors(); - Assert.Empty(errors); - - var anon = check.ApplyGetLogging(); - } - - [Fact] - public void TestSummary() - { - var check = new CheckResult(new Engine()); - + } + + [Theory] + [InlineData("ShowColumns(Table, Name)")] + public void ApplyLoggingWithBinding(string expr) + { + var tableType = TableType.Empty().Add("Name", FormulaType.String); + var symbolTable = new SymbolTable(); + symbolTable.AddVariable("Table", tableType); + + CheckResult check = new CheckResult(new Engine()) + .SetText(expr) + .SetBindingInfo(symbolTable); + + var errors = check.ApplyErrors(); + Assert.Empty(errors); + + var anon = check.ApplyGetLogging(); + } + + [Fact] + public void TestSummary() + { + var check = new CheckResult(new Engine()); + var r1 = RecordType.Empty() .Add(new NamedFormulaType("new_field", FormulaType.Number, "Field")); check.SetText("1", new PowerFx.ParserOptions { AllowsSideEffects = true }); - check.SetBindingInfo(r1); + check.SetBindingInfo(r1); + + var summary = check.ApplyGetContextSummary(); + + Assert.True(summary.AllowsSideEffects); + Assert.False(summary.IsPreV1Semantics); + Assert.Null(summary.ExpectedReturnType); + Assert.Single(summary.SuggestedSymbols); + + var sym1 = summary.SuggestedSymbols.First(); + + Assert.Equal("Field", sym1.DisplayName); + Assert.Equal("Field", sym1.BestName); + Assert.Equal("new_field", sym1.Name); + Assert.Equal(FormulaType.Number, sym1.Type); + Assert.False(sym1.Properties.CanSet); + Assert.False(sym1.Properties.CanMutate); + + var type1 = sym1.Slot.Owner.GetTypeFromSlot(sym1.Slot); + Assert.Equal(FormulaType.Number, type1); + } - var summary = check.ApplyGetContextSummary(); - - Assert.True(summary.AllowsSideEffects); - Assert.False(summary.IsPreV1Semantics); - Assert.Null(summary.ExpectedReturnType); - Assert.Single(summary.SuggestedSymbols); - - var sym1 = summary.SuggestedSymbols.First(); - - Assert.Equal("Field", sym1.DisplayName); - Assert.Equal("Field", sym1.BestName); - Assert.Equal("new_field", sym1.Name); - Assert.Equal(FormulaType.Number, sym1.Type); - Assert.False(sym1.Properties.CanSet); - Assert.False(sym1.Properties.CanMutate); - - var type1 = sym1.Slot.Owner.GetTypeFromSlot(sym1.Slot); - Assert.Equal(FormulaType.Number, type1); - } - private void CheckResultExpectedReturnValue(string inputExpr, bool allowCoerceTo, bool isSuccess, string errorMsg, FormulaType expectedReturnType) { var check = new CheckResult(PrimitiveValueConversionsTests.GetEngineWithFeatureGatedFunctions()) .SetText(inputExpr) .SetBindingInfo() - .SetExpectedReturnValue(expectedReturnType, allowCoerceTo); - - check.ApplyBinding(); - + .SetExpectedReturnValue(expectedReturnType, allowCoerceTo); + + check.ApplyBinding(); + CheckExpectedReturn(check, isSuccess, errorMsg, expectedReturnType); - } - + } + private void CheckResultExpectedReturnTypes(string inputExpr, bool isSuccess, string errorMsg, FormulaType[] returnTypes, FormulaType expectedType) - { -#pragma warning disable CS0618 // Type or member is obsolete + { +#pragma warning disable CS0618 // Type or member is obsolete var check = new CheckResult(new Engine()) .SetText(inputExpr) .SetBindingInfo() - .SetExpectedReturnValue(returnTypes); -#pragma warning restore CS0618 // Type or member is obsolete - - check.ApplyBinding(); - + .SetExpectedReturnValue(returnTypes); +#pragma warning restore CS0618 // Type or member is obsolete + + check.ApplyBinding(); + CheckExpectedReturn(check, isSuccess, errorMsg, expectedType); - } - - private void CheckExpectedReturn(CheckResult check, bool isSuccess, string errorMsg, FormulaType expectedType) - { + } + + private void CheckExpectedReturn(CheckResult check, bool isSuccess, string errorMsg, FormulaType expectedType) + { if (isSuccess) { Assert.True(check.IsSuccess); @@ -628,7 +628,7 @@ namespace Microsoft.PowerFx.Core.Tests try { - var errors = check.ApplyErrors(); + var errors = check.ApplyErrors(); exMsg = errors.First().Message; Assert.False(check.IsSuccess); } @@ -638,7 +638,7 @@ namespace Microsoft.PowerFx.Core.Tests } Assert.True(errorMsg.Contains(exMsg), exMsg); - } + } } } } diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/CompatTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/CompatTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/CompatTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/CompatTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/CoreUtilExtensionTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/CoreUtilExtensionTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/CoreUtilExtensionTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/CoreUtilExtensionTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/DNameTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/DNameTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/DNameTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/DNameTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/DPathTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/DPathTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/DPathTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/DPathTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/DerivedEngineTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/DerivedEngineTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/DerivedEngineTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/DerivedEngineTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/DisplayNameOptionSetTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/DisplayNameOptionSetTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/DisplayNameOptionSetTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/DisplayNameOptionSetTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/DisplayNameTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/DisplayNameTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/DisplayNameTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/DisplayNameTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/DisplayNameUtilTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/DisplayNameUtilTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/DisplayNameUtilTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/DisplayNameUtilTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/Docs/EngineSchemaChecker.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/Docs/EngineSchemaChecker.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/Docs/EngineSchemaChecker.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/Docs/EngineSchemaChecker.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/EngineTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/EngineTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/EngineTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/EngineTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/EnumSyncTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/EnumSyncTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/EnumSyncTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/EnumSyncTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionErrorTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionErrorTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionErrorTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionErrorTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Abs.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Abs.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Abs.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Abs.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/AbsT.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/AbsT.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/AbsT.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/AbsT.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Acumatica.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Acumatica.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Acumatica.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Acumatica.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/AddColumns.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/AddColumns.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/AddColumns.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/AddColumns.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/AddColumns_NumberIsFloatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/AddColumns_NumberIsFloatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/AddColumns_NumberIsFloatDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/AddColumns_NumberIsFloatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/AddColumns_SupportColumnNamesAsIdentifiers.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/AddColumns_SupportColumnNamesAsIdentifiers.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/AddColumns_SupportColumnNamesAsIdentifiers.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/AddColumns_SupportColumnNamesAsIdentifiers.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/AddColumns_SupportColumnNamesAsIdentifiers_NumberIsFloatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/AddColumns_SupportColumnNamesAsIdentifiers_NumberIsFloatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/AddColumns_SupportColumnNamesAsIdentifiers_NumberIsFloatDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/AddColumns_SupportColumnNamesAsIdentifiers_NumberIsFloatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/AndOrCases.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/AndOrCases.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/AndOrCases.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/AndOrCases.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ArgCoercion.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ArgCoercion.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ArgCoercion.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ArgCoercion.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ArgCoercionT.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ArgCoercionT.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ArgCoercionT.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ArgCoercionT.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ArgCoercionT_ConsistentOneColumnTableResultDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ArgCoercionT_ConsistentOneColumnTableResultDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ArgCoercionT_ConsistentOneColumnTableResultDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ArgCoercionT_ConsistentOneColumnTableResultDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ArgCoercion_V1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ArgCoercion_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ArgCoercion_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ArgCoercion_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ArgCoercion_V1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ArgCoercion_V1CompatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ArgCoercion_V1CompatDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ArgCoercion_V1CompatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Average.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Average.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Average.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Average.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/BasicCoercion.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/BasicCoercion.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/BasicCoercion.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/BasicCoercion.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/BasicCoercion_V1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/BasicCoercion_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/BasicCoercion_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/BasicCoercion_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/BasicCoercion_V1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/BasicCoercion_V1CompatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/BasicCoercion_V1CompatDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/BasicCoercion_V1CompatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Blank.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Blank.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Blank.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Blank.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Blob.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Blob.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Blob.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Blob.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Boolean.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Boolean.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Boolean.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Boolean.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/BooleanT.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/BooleanT.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/BooleanT.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/BooleanT.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Chaining.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Chaining.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Chaining.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Chaining.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Char.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Char.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Char.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Char.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/CharT.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/CharT.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/CharT.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/CharT.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/CharT_ConsistentOneColumnTableResultDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/CharT_ConsistentOneColumnTableResultDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/CharT_ConsistentOneColumnTableResultDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/CharT_ConsistentOneColumnTableResultDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Clear.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Clear.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Clear.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Clear.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Clear_V1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Clear_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Clear_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Clear_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Clear_V1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Clear_V1CompatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Clear_V1CompatDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Clear_V1CompatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Coalesce.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Coalesce.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Coalesce.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Coalesce.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Coalesce_V1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Coalesce_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Coalesce_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Coalesce_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Coalesce_V1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Coalesce_V1CompatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Coalesce_V1CompatDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Coalesce_V1CompatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Collect.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Collect.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Collect.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Collect.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Collect_V1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Collect_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Collect_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Collect_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Collect_V1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Collect_V1CompatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Collect_V1CompatDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Collect_V1CompatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Color.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Color.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Color.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Color.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ColorFade.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ColorFade.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ColorFade.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ColorFade.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ColorFadeT.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ColorFadeT.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ColorFadeT.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ColorFadeT.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Color_StronglyTypedEnums.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Color_StronglyTypedEnums.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Color_StronglyTypedEnums.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Color_StronglyTypedEnums.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Color_StronglyTypedEnumsDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Color_StronglyTypedEnumsDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Color_StronglyTypedEnumsDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Color_StronglyTypedEnumsDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ColumnNames.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ColumnNames.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ColumnNames.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ColumnNames.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Concat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Concat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Concat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Concat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Concatenate.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Concatenate.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Concatenate.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Concatenate.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ConcatenateT.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ConcatenateT.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ConcatenateT.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ConcatenateT.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ConcatenateT_ConsistentOneColumnTableResultDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ConcatenateT_ConsistentOneColumnTableResultDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ConcatenateT_ConsistentOneColumnTableResultDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ConcatenateT_ConsistentOneColumnTableResultDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Count.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Count.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Count.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Count.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/CountA.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/CountA.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/CountA.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/CountA.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/CountIf.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/CountIf.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/CountIf.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/CountIf.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/CountRows.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/CountRows.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/CountRows.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/CountRows.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Date.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Date.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Date.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Date.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateAdd.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateAdd.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateAdd.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateAdd.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateAdd_StronglyTypedBuiltinEnums.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateAdd_StronglyTypedBuiltinEnums.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateAdd_StronglyTypedBuiltinEnums.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateAdd_StronglyTypedBuiltinEnums.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateAdd_StronglyTypedEnumsDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateAdd_StronglyTypedEnumsDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateAdd_StronglyTypedEnumsDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateAdd_StronglyTypedEnumsDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateAdd_TimeZone_Beirut.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateAdd_TimeZone_Beirut.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateAdd_TimeZone_Beirut.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateAdd_TimeZone_Beirut.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateAdd_TimeZone_London.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateAdd_TimeZone_London.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateAdd_TimeZone_London.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateAdd_TimeZone_London.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateAdd_TimeZone_Seattle.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateAdd_TimeZone_Seattle.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateAdd_TimeZone_Seattle.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateAdd_TimeZone_Seattle.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateAdd_TimeZone_UTC.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateAdd_TimeZone_UTC.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateAdd_TimeZone_UTC.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateAdd_TimeZone_UTC.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateDiff.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateDiff.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateDiff.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateDiff.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateDiff_StronglyTypedBuiltinEnums.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateDiff_StronglyTypedBuiltinEnums.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateDiff_StronglyTypedBuiltinEnums.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateDiff_StronglyTypedBuiltinEnums.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateDiff_StronglyTypedEnumsDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateDiff_StronglyTypedEnumsDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateDiff_StronglyTypedEnumsDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateDiff_StronglyTypedEnumsDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateDiff_TimeZone_Lord_Howe_Island.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateDiff_TimeZone_Lord_Howe_Island.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateDiff_TimeZone_Lord_Howe_Island.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateDiff_TimeZone_Lord_Howe_Island.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateDiff_TimeZone_Seattle.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateDiff_TimeZone_Seattle.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateDiff_TimeZone_Seattle.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateDiff_TimeZone_Seattle.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateTime.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateTime.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateTime.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateTime.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateTimeValue_TimeZone_Paris.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateTimeValue_TimeZone_Paris.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateTimeValue_TimeZone_Paris.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateTimeValue_TimeZone_Paris.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateTimeValue_TimeZone_Seattle.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateTimeValue_TimeZone_Seattle.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateTimeValue_TimeZone_Seattle.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateTimeValue_TimeZone_Seattle.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateValue.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateValue.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateValue.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateValue.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateValue_TimeZone_London.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateValue_TimeZone_London.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateValue_TimeZone_London.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateValue_TimeZone_London.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateValue_TimeZone_Paris.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateValue_TimeZone_Paris.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateValue_TimeZone_Paris.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateValue_TimeZone_Paris.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateValue_TimeZone_Seattle.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateValue_TimeZone_Seattle.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DateValue_TimeZone_Seattle.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DateValue_TimeZone_Seattle.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Date_TimeZone_Paris.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Date_TimeZone_Paris.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Date_TimeZone_Paris.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Date_TimeZone_Paris.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Date_TimeZone_Seattle.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Date_TimeZone_Seattle.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Date_TimeZone_Seattle.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Date_TimeZone_Seattle.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Date_TimeZone_UTC.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Date_TimeZone_UTC.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Date_TimeZone_UTC.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Date_TimeZone_UTC.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Dec2Hex.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Dec2Hex.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Dec2Hex.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Dec2Hex.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Decimal.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Decimal.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Decimal.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Decimal.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DecimalBoot.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DecimalBoot.txt similarity index 96% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DecimalBoot.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DecimalBoot.txt index 69469a8d6..32ae83d20 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DecimalBoot.txt +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DecimalBoot.txt @@ -1,56 +1,56 @@ -#SETUP: EnableExpressionChaining,MutationFunctionsTestSetup,ConsistentOneColumnTableResult -#SETUP: NumberIsFloat - -// Tests that can't be run in decimal yet and have #SKIP directives -// Once decimal is done, this #SKIPs can be removed and this file can be removed - ->> Boolean(If(1/0<2,[1])) -Error({Kind:ErrorKind.Div0}) - ->> Boolean(If(1<0,[1])) -Blank() - ->> Boolean(Filter([1,2,3], Value > 10)) -Table() - ->> ForAll(Boolean([3, 1/0, 0, Sqrt(-1)]), IfError(Text(Value), $"ErrorKind={FirstError.Kind}")) -Table({Value:"true"},{Value:"ErrorKind=13"},{Value:"false"},{Value:"ErrorKind=24"}) - ->> Switch(2,1,"a",Sqrt(-1),"b","c") -Error({Kind:ErrorKind.Numeric}) - -// Errors not evaluated are not returned ->> Switch(2,1,"a",2,"b",Sqrt(-1),"c","d") -"b" - ->> Abs(Table({a:1/0},{a:Power(-3,2)})) -Table({Value:Error({Kind:ErrorKind.Div0})},{Value:9}) - -// Multiple errors ->> Count(Table({a:1/0},{a:Sqrt(-1)})) -Error({Kind:ErrorKind.Div0}) - -// Multiple errors ->> Count(Table({a:Sqrt(-1)},{a:1/0})) -Error({Kind:ErrorKind.Numeric}) - -// All error values ->> CountA(Table({a:1/0},{a:Sqrt(-1)})) -Error({Kind:ErrorKind.Div0}) - -// All error values ->> CountA(Table({a:Sqrt(-1)},{a:1/0})) -Error({Kind:ErrorKind.Numeric}) - ->> CountRows(Table({a:1/0},{a:Sqrt(-1)})) -2 - -// Distinct values where expression handles errors ->> Distinct(Table({a:1},{a:2},{a:If(1/0<2,{a:2}).a},{a:7},{a:Sqrt(-1)},{a:-13},{a:2}), IfError(a,-FirstError.Kind)) -Table({Value:1},{Value:2},{Value:-13},{Value:7},{Value:-24}) - -// Distinct which ignores columns with errors ->> Distinct(Table({a:10,b:1/0},{a:9,b:Sqrt(-1)},{a:8,b:3},{a:10,b:1/0},{a:8,b:Ln(-1)}), a) -Table({Value:10},{Value:9},{Value:8}) - - +#SETUP: EnableExpressionChaining,MutationFunctionsTestSetup,ConsistentOneColumnTableResult +#SETUP: NumberIsFloat + +// Tests that can't be run in decimal yet and have #SKIP directives +// Once decimal is done, this #SKIPs can be removed and this file can be removed + +>> Boolean(If(1/0<2,[1])) +Error({Kind:ErrorKind.Div0}) + +>> Boolean(If(1<0,[1])) +Blank() + +>> Boolean(Filter([1,2,3], Value > 10)) +Table() + +>> ForAll(Boolean([3, 1/0, 0, Sqrt(-1)]), IfError(Text(Value), $"ErrorKind={FirstError.Kind}")) +Table({Value:"true"},{Value:"ErrorKind=13"},{Value:"false"},{Value:"ErrorKind=24"}) + +>> Switch(2,1,"a",Sqrt(-1),"b","c") +Error({Kind:ErrorKind.Numeric}) + +// Errors not evaluated are not returned +>> Switch(2,1,"a",2,"b",Sqrt(-1),"c","d") +"b" + +>> Abs(Table({a:1/0},{a:Power(-3,2)})) +Table({Value:Error({Kind:ErrorKind.Div0})},{Value:9}) + +// Multiple errors +>> Count(Table({a:1/0},{a:Sqrt(-1)})) +Error({Kind:ErrorKind.Div0}) + +// Multiple errors +>> Count(Table({a:Sqrt(-1)},{a:1/0})) +Error({Kind:ErrorKind.Numeric}) + +// All error values +>> CountA(Table({a:1/0},{a:Sqrt(-1)})) +Error({Kind:ErrorKind.Div0}) + +// All error values +>> CountA(Table({a:Sqrt(-1)},{a:1/0})) +Error({Kind:ErrorKind.Numeric}) + +>> CountRows(Table({a:1/0},{a:Sqrt(-1)})) +2 + +// Distinct values where expression handles errors +>> Distinct(Table({a:1},{a:2},{a:If(1/0<2,{a:2}).a},{a:7},{a:Sqrt(-1)},{a:-13},{a:2}), IfError(a,-FirstError.Kind)) +Table({Value:1},{Value:2},{Value:-13},{Value:7},{Value:-24}) + +// Distinct which ignores columns with errors +>> Distinct(Table({a:10,b:1/0},{a:9,b:Sqrt(-1)},{a:8,b:3},{a:10,b:1/0},{a:8,b:Ln(-1)}), a) +Table({Value:10},{Value:9},{Value:8}) + + diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DecimalDotnetRuntime.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DecimalDotnetRuntime.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DecimalDotnetRuntime.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DecimalDotnetRuntime.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DecimalIntReturn.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DecimalIntReturn.txt similarity index 97% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DecimalIntReturn.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DecimalIntReturn.txt index 75a5725cf..06469ec28 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DecimalIntReturn.txt +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DecimalIntReturn.txt @@ -1,78 +1,78 @@ -#SETUP: disable:NumberIsFloat - -// Some functions return integer results, that are typed decimal or float based on NumberIsFloat - ->> Len( "abc") + 1.0000000000000000000000001 -4.0000000000000000000000001 - ->> ForAll( Len( ["abc","defg"] ), Value * 1.0000000000000000000000001 ) -Table({Value:3.0000000000000000000000003},{Value:4.0000000000000000000000004}) - ->> Count( [1,2,3,Blank()] ) + 1.0000000000000000000000001 -4.0000000000000000000000001 - ->> CountA( ["a","b","c",Blank()] ) + 1.0000000000000000000000001 -4.0000000000000000000000001 - ->> CountRows( ["a","b","c",Blank()] ) + 1.0000000000000000000000001 -5.0000000000000000000000001 - ->> CountIf( ["a","b","c","ee",Blank()], Len(Value) = 1 ) + 1.0000000000000000000000001 -4.0000000000000000000000001 - ->> DateDiff( DateTime(1990,11,2,5,20,54), DateTime(2000,2,9,8,30,23), TimeUnit.Years ) + 1.0000000000000000000001 -11.0000000000000000000001 - ->> DateDiff( DateTime(1990,11,2,5,20,54), DateTime(2000,2,9,8,30,23), TimeUnit.Months ) + 1.0000000000000000000001 -112.0000000000000000000001 - ->> DateDiff( DateTime(1990,11,2,5,20,54), DateTime(2000,2,9,8,30,23), TimeUnit.Quarters ) + 1.0000000000000000000001 -38.0000000000000000000001 - ->> DateDiff( DateTime(1990,11,2,5,20,54), DateTime(2000,2,9,8,30,23), TimeUnit.Days ) + 1.0000000000000000000001 -3387.0000000000000000000001 - ->> DateDiff( DateTime(1990,11,2,5,20,54), DateTime(2000,2,9,8,30,23), TimeUnit.Hours ) + 1.0000000000000000000001 -81268.0000000000000000000001 - ->> DateDiff( DateTime(1990,11,2,5,20,54), DateTime(2000,2,9,8,30,23), TimeUnit.Minutes ) + 1.0000000000000000000001 -4876031.0000000000000000000001 - ->> DateDiff( DateTime(1990,11,2,5,20,54), DateTime(2000,2,9,8,30,23), TimeUnit.Seconds ) + 1.00000000000000001 -292561770.00000000000000001 - ->> DateDiff( DateTime(1990,11,2,5,20,54), DateTime(2000,2,9,8,30,23), TimeUnit.Milliseconds ) + 1.00000000000000001 -292561769001.00000000000000001 - ->> Year( DateTime(1990,3,2,5,20,54) ) + 1.0000000000000000000001 -1991.0000000000000000000001 - ->> Month( DateTime(1990,3,2,5,20,54) ) + 1.0000000000000000000001 -4.0000000000000000000001 - ->> Day( DateTime(1990,3,2,5,20,54) ) + 1.0000000000000000000001 -3.0000000000000000000001 - ->> Hour( DateTime(1990,3,2,5,20,54) ) + 1.0000000000000000000001 -6.0000000000000000000001 - ->> Minute( DateTime(1990,3,2,5,20,54) ) + 1.0000000000000000000001 -21.0000000000000000000001 - ->> Second( DateTime(1990,3,2,5,20,54) ) + 1.0000000000000000000001 -55.0000000000000000000001 - ->> RandBetween(11,11) + 1.00000000000000000000000001 -12.00000000000000000000000001 - ->> Hex2Dec( "1234BEEF" ) + 1.00000000000000000001 -305446640.00000000000000000001 - ->> Hex2Dec( "1234BEEF" ) + 0.99999999999999999999 -305446639.99999999999999999999 - - - - - - +#SETUP: disable:NumberIsFloat + +// Some functions return integer results, that are typed decimal or float based on NumberIsFloat + +>> Len( "abc") + 1.0000000000000000000000001 +4.0000000000000000000000001 + +>> ForAll( Len( ["abc","defg"] ), Value * 1.0000000000000000000000001 ) +Table({Value:3.0000000000000000000000003},{Value:4.0000000000000000000000004}) + +>> Count( [1,2,3,Blank()] ) + 1.0000000000000000000000001 +4.0000000000000000000000001 + +>> CountA( ["a","b","c",Blank()] ) + 1.0000000000000000000000001 +4.0000000000000000000000001 + +>> CountRows( ["a","b","c",Blank()] ) + 1.0000000000000000000000001 +5.0000000000000000000000001 + +>> CountIf( ["a","b","c","ee",Blank()], Len(Value) = 1 ) + 1.0000000000000000000000001 +4.0000000000000000000000001 + +>> DateDiff( DateTime(1990,11,2,5,20,54), DateTime(2000,2,9,8,30,23), TimeUnit.Years ) + 1.0000000000000000000001 +11.0000000000000000000001 + +>> DateDiff( DateTime(1990,11,2,5,20,54), DateTime(2000,2,9,8,30,23), TimeUnit.Months ) + 1.0000000000000000000001 +112.0000000000000000000001 + +>> DateDiff( DateTime(1990,11,2,5,20,54), DateTime(2000,2,9,8,30,23), TimeUnit.Quarters ) + 1.0000000000000000000001 +38.0000000000000000000001 + +>> DateDiff( DateTime(1990,11,2,5,20,54), DateTime(2000,2,9,8,30,23), TimeUnit.Days ) + 1.0000000000000000000001 +3387.0000000000000000000001 + +>> DateDiff( DateTime(1990,11,2,5,20,54), DateTime(2000,2,9,8,30,23), TimeUnit.Hours ) + 1.0000000000000000000001 +81268.0000000000000000000001 + +>> DateDiff( DateTime(1990,11,2,5,20,54), DateTime(2000,2,9,8,30,23), TimeUnit.Minutes ) + 1.0000000000000000000001 +4876031.0000000000000000000001 + +>> DateDiff( DateTime(1990,11,2,5,20,54), DateTime(2000,2,9,8,30,23), TimeUnit.Seconds ) + 1.00000000000000001 +292561770.00000000000000001 + +>> DateDiff( DateTime(1990,11,2,5,20,54), DateTime(2000,2,9,8,30,23), TimeUnit.Milliseconds ) + 1.00000000000000001 +292561769001.00000000000000001 + +>> Year( DateTime(1990,3,2,5,20,54) ) + 1.0000000000000000000001 +1991.0000000000000000000001 + +>> Month( DateTime(1990,3,2,5,20,54) ) + 1.0000000000000000000001 +4.0000000000000000000001 + +>> Day( DateTime(1990,3,2,5,20,54) ) + 1.0000000000000000000001 +3.0000000000000000000001 + +>> Hour( DateTime(1990,3,2,5,20,54) ) + 1.0000000000000000000001 +6.0000000000000000000001 + +>> Minute( DateTime(1990,3,2,5,20,54) ) + 1.0000000000000000000001 +21.0000000000000000000001 + +>> Second( DateTime(1990,3,2,5,20,54) ) + 1.0000000000000000000001 +55.0000000000000000000001 + +>> RandBetween(11,11) + 1.00000000000000000000000001 +12.00000000000000000000000001 + +>> Hex2Dec( "1234BEEF" ) + 1.00000000000000000001 +305446640.00000000000000000001 + +>> Hex2Dec( "1234BEEF" ) + 0.99999999999999999999 +305446639.99999999999999999999 + + + + + + diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DecimalIntReturn_DVDecimal.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DecimalIntReturn_DVDecimal.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DecimalIntReturn_DVDecimal.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DecimalIntReturn_DVDecimal.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DecimalMathFuncs_NumberIsFloat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DecimalMathFuncs_NumberIsFloat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DecimalMathFuncs_NumberIsFloat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DecimalMathFuncs_NumberIsFloat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DecimalMathFuncs_NumberIsFloatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DecimalMathFuncs_NumberIsFloatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DecimalMathFuncs_NumberIsFloatDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DecimalMathFuncs_NumberIsFloatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DecimalMathFuncs_NumberIsFloatDisabled_DVDecimal.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DecimalMathFuncs_NumberIsFloatDisabled_DVDecimal.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DecimalMathFuncs_NumberIsFloatDisabled_DVDecimal.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DecimalMathFuncs_NumberIsFloatDisabled_DVDecimal.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DecimalMathFuncs_NumberIsFloat_Constructors.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DecimalMathFuncs_NumberIsFloat_Constructors.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DecimalMathFuncs_NumberIsFloat_Constructors.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DecimalMathFuncs_NumberIsFloat_Constructors.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DecimalMathFuncs_NumberIsFloat_Constructors_DecimalSupport.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DecimalMathFuncs_NumberIsFloat_Constructors_DecimalSupport.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DecimalMathFuncs_NumberIsFloat_Constructors_DecimalSupport.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DecimalMathFuncs_NumberIsFloat_Constructors_DecimalSupport.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DecimalMathFuncs_NumberIsFloat_Constructors_DecimalSupport_PowerFxV1.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DecimalMathFuncs_NumberIsFloat_Constructors_DecimalSupport_PowerFxV1.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DecimalMathFuncs_NumberIsFloat_Constructors_DecimalSupport_PowerFxV1.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DecimalMathFuncs_NumberIsFloat_Constructors_DecimalSupport_PowerFxV1.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DecimalMathFuncs_NumberIsFloat_Constructors_DecimalSupport_PowerFxV1Disabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DecimalMathFuncs_NumberIsFloat_Constructors_DecimalSupport_PowerFxV1Disabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DecimalMathFuncs_NumberIsFloat_Constructors_DecimalSupport_PowerFxV1Disabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DecimalMathFuncs_NumberIsFloat_Constructors_DecimalSupport_PowerFxV1Disabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DecimalNonMathFuncs.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DecimalNonMathFuncs.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DecimalNonMathFuncs.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DecimalNonMathFuncs.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DecimalNonMathFuncs_DVDecimal.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DecimalNonMathFuncs_DVDecimal.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DecimalNonMathFuncs_DVDecimal.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DecimalNonMathFuncs_DVDecimal.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DecimalOps.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DecimalOps.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DecimalOps.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DecimalOps.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DecimalOps_DVDecimal.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DecimalOps_DVDecimal.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DecimalOps_DVDecimal.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DecimalOps_DVDecimal.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DecimalOverflow.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DecimalOverflow.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DecimalOverflow.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DecimalOverflow.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Disable_ConsistentOneColumnTableResult.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Disable_ConsistentOneColumnTableResult.txt similarity index 84% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Disable_ConsistentOneColumnTableResult.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Disable_ConsistentOneColumnTableResult.txt index 1bdf40a32..57b869abd 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Disable_ConsistentOneColumnTableResult.txt +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Disable_ConsistentOneColumnTableResult.txt @@ -1,10 +1,10 @@ -#SETUP: disable:ConsistentOneColumnTableResult - ->> Char(["55", "56", "57"]) -Table({Result:"7"},{Result:"8"},{Result:"9"}) - ->> Concatenate(-1, ["456", "abc"] ) -Table({Result:"-1456"},{Result:"-1abc"}) - ->> Concatenate(Table({a: -1}, {a: 123}), Table({a: "456"}, {a: "abc"})) +#SETUP: disable:ConsistentOneColumnTableResult + +>> Char(["55", "56", "57"]) +Table({Result:"7"},{Result:"8"},{Result:"9"}) + +>> Concatenate(-1, ["456", "abc"] ) +Table({Result:"-1456"},{Result:"-1abc"}) + +>> Concatenate(Table({a: -1}, {a: 123}), Table({a: "456"}, {a: "abc"})) Table({Result:"-1456"},{Result:"123abc"}) \ No newline at end of file diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Distinct.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Distinct.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Distinct.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Distinct.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Distinct_ConsistentOneColumnTableResultDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Distinct_ConsistentOneColumnTableResultDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Distinct_ConsistentOneColumnTableResultDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Distinct_ConsistentOneColumnTableResultDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Distinct_V1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Distinct_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Distinct_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Distinct_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Distinct_V1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Distinct_V1CompatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Distinct_V1CompatDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Distinct_V1CompatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DropColumns_SupportColumnNamesAsIdentifiers.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DropColumns_SupportColumnNamesAsIdentifiers.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DropColumns_SupportColumnNamesAsIdentifiers.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DropColumns_SupportColumnNamesAsIdentifiers.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DropColumns_SupportColumnNamesAsIdentifiersDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DropColumns_SupportColumnNamesAsIdentifiersDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/DropColumns_SupportColumnNamesAsIdentifiersDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/DropColumns_SupportColumnNamesAsIdentifiersDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/EDateEOMonth.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/EDateEOMonth.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/EDateEOMonth.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/EDateEOMonth.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/EncodeHTML.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/EncodeHTML.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/EncodeHTML.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/EncodeHTML.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/EncodeURL.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/EncodeURL.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/EncodeURL.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/EncodeURL.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/EndsWith.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/EndsWith.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/EndsWith.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/EndsWith.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Equality.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Equality.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Equality.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Equality.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Error.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Error.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Error.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Error.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ErrorKinds.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ErrorKinds.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ErrorKinds.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ErrorKinds.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ErrorKinds_ColumnNamesAsIdentifiers.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ErrorKinds_ColumnNamesAsIdentifiers.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ErrorKinds_ColumnNamesAsIdentifiers.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ErrorKinds_ColumnNamesAsIdentifiers.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ErrorKinds_ColumnNamesAsIdentifiersDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ErrorKinds_ColumnNamesAsIdentifiersDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ErrorKinds_ColumnNamesAsIdentifiersDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ErrorKinds_ColumnNamesAsIdentifiersDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ErrorKinds_StronlyTypedBuiltinEnumsDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ErrorKinds_StronlyTypedBuiltinEnumsDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ErrorKinds_StronlyTypedBuiltinEnumsDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ErrorKinds_StronlyTypedBuiltinEnumsDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Escaping.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Escaping.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Escaping.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Escaping.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Exp.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Exp.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Exp.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Exp.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ExpT.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ExpT.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ExpT.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ExpT.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/FilterFunctions.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/FilterFunctions.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/FilterFunctions.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/FilterFunctions.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/FilterFunctions_V1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/FilterFunctions_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/FilterFunctions_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/FilterFunctions_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/FilterFunctions_V1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/FilterFunctions_V1CompatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/FilterFunctions_V1CompatDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/FilterFunctions_V1CompatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/FilterLookUp_TwoArg_V1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/FilterLookUp_TwoArg_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/FilterLookUp_TwoArg_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/FilterLookUp_TwoArg_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Find.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Find.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Find.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Find.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/FindT.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/FindT.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/FindT.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/FindT.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/FindT_ConsistentOneColumnTableResultDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/FindT_ConsistentOneColumnTableResultDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/FindT_ConsistentOneColumnTableResultDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/FindT_ConsistentOneColumnTableResultDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Find_StronglyTypedBuiltinEnums.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Find_StronglyTypedBuiltinEnums.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Find_StronglyTypedBuiltinEnums.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Find_StronglyTypedBuiltinEnums.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Find_StronglyTypedBuiltinEnumsDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Find_StronglyTypedBuiltinEnumsDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Find_StronglyTypedBuiltinEnumsDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Find_StronglyTypedBuiltinEnumsDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/FirstLast.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/FirstLast.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/FirstLast.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/FirstLast.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/FirstLastN.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/FirstLastN.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/FirstLastN.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/FirstLastN.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/FirstLastN_RequiredSecondArgument.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/FirstLastN_RequiredSecondArgument.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/FirstLastN_RequiredSecondArgument.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/FirstLastN_RequiredSecondArgument.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/FirstLastN_RequiredSecondArgumentDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/FirstLastN_RequiredSecondArgumentDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/FirstLastN_RequiredSecondArgumentDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/FirstLastN_RequiredSecondArgumentDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/FirstLastN_V1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/FirstLastN_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/FirstLastN_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/FirstLastN_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/FirstLastN_V1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/FirstLastN_V1CompatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/FirstLastN_V1CompatDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/FirstLastN_V1CompatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/FirstLast_V1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/FirstLast_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/FirstLast_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/FirstLast_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/FirstLast_V1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/FirstLast_V1CompatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/FirstLast_V1CompatDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/FirstLast_V1CompatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Float.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Float.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Float.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Float.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/FloatLarge.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/FloatLarge.txt similarity index 97% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/FloatLarge.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/FloatLarge.txt index c9769d942..155962a43 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/FloatLarge.txt +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/FloatLarge.txt @@ -1,120 +1,120 @@ -#SETUP: NumberIsFloat - -// Tests that take or result in large floating point values, outside the range of Decimal (~ +/- 1E29) -// These cause problems with the serialization/deserialization test in BaseRunner.cs/RunAsync2 - ->> Abs(1E+308) -100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 - ->> Abs(-1E+308) -100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 - ->> Abs(Value("1E+308")) -100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 - ->> Abs(Value("-1E+308")) -100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 - ->> Value( "1e100" ) -1E+100 - ->> 1e100 -1E+100 - -// Excel returns 5.5799E+186 ->> Exp(430) -5579910311786366000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 - ->> Int(Value("1E+308")) -100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 - ->> Int(Value("-1E+308")) --100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 - ->> Power(Exp(42.5),2) -8223012714622878000000000000000000000 - ->> Round(Value("1E+308"),10) -1E+308 - -// Excel shows typo in the formula and suggest to change the formula to Sqrt(E1+308) ->> Sqrt(Value("1E+308")) -10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 - -//PowerFx shows error message "Argument to SQRT function should be non-negative number", while Excel shows typo in the formula and suggest to change the formula to Sqrt(-E1+308) ->> Sqrt(-1E+308) -Error({Kind:ErrorKind.Numeric}) - ->> 1E+308 * 1000 -Error({Kind:ErrorKind.Numeric}) - ->> 1E-308 / 1000 -0 - ->> Sum(1E+308,1E+308) -Error({Kind:ErrorKind.Numeric}) - -// Overflow in calculation ->> Sum(1e308, 1e308, 1e308, 1e308, 1e308) -Error({Kind:ErrorKind.Numeric}) - -// Overflow in calculation ->> VarP(1e200, 2e200, 3e200, 4e200) -Error({Kind:ErrorKind.Numeric}) - ->> Round(1E+308,10) -1E+308 - ->> Max(1E+308, 1E+308) -100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 - ->> Min(1E+308, 1E+308) -100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 - ->> Mod(1E+300,1E-20) -6.0682893566359166E-21 - ->> Dec2Hex([1,1e-45,1e45,2]) -Table({Value:"1"},{Value:"0"},{Value:Error({Kind:ErrorKind.Numeric})},{Value:"2"}) - -// Infinity ->> StdevP(-1.7e308,1.7e308) -Error({Kind:ErrorKind.Numeric}) - ->> Acot(1e100) -0 - ->> Acot(-1e100) -3.141592654 - ->> Atan(1e100) -1.570796327 - ->> Atan(-1e100) --1.570796327 - ->> Substitute("HelloHelloHello", "He", "Je", 5e100) -"HelloHelloHello" - ->> Replace("abcd", 5e100, 1, "P") -"abcdP" - -// Excel shows typo in the formula and suggest to change the formula to Ln(E1+308) ->> Ln(1E+308) -709.19620864 - -// Excel shows typo in the formula and suggest to change the formula to Ln(E1+308) ->> Ln(-1E+308) -Error({Kind:ErrorKind.Numeric}) - -// Overflow in calculation ->> StdevP(1e200, 2e200, 3e200, 4e200) -Error({Kind:ErrorKind.Numeric}) - ->> Round(Atan([0, 1e100, -1e100, 1, -1, Blank()]), 4) -Table({Value:0},{Value:1.5708},{Value:-1.5708},{Value:0.7854},{Value:-0.7854},{Value:0}) - ->> Round(Acot([0, 1e100, -1e100, 1, -1, Blank()]), 4) -Table({Value:1.5708},{Value:0},{Value:3.1416},{Value:0.7854},{Value:2.3562},{Value:1.5708}) - - +#SETUP: NumberIsFloat + +// Tests that take or result in large floating point values, outside the range of Decimal (~ +/- 1E29) +// These cause problems with the serialization/deserialization test in BaseRunner.cs/RunAsync2 + +>> Abs(1E+308) +100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + +>> Abs(-1E+308) +100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + +>> Abs(Value("1E+308")) +100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + +>> Abs(Value("-1E+308")) +100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + +>> Value( "1e100" ) +1E+100 + +>> 1e100 +1E+100 + +// Excel returns 5.5799E+186 +>> Exp(430) +5579910311786366000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + +>> Int(Value("1E+308")) +100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + +>> Int(Value("-1E+308")) +-100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + +>> Power(Exp(42.5),2) +8223012714622878000000000000000000000 + +>> Round(Value("1E+308"),10) +1E+308 + +// Excel shows typo in the formula and suggest to change the formula to Sqrt(E1+308) +>> Sqrt(Value("1E+308")) +10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + +//PowerFx shows error message "Argument to SQRT function should be non-negative number", while Excel shows typo in the formula and suggest to change the formula to Sqrt(-E1+308) +>> Sqrt(-1E+308) +Error({Kind:ErrorKind.Numeric}) + +>> 1E+308 * 1000 +Error({Kind:ErrorKind.Numeric}) + +>> 1E-308 / 1000 +0 + +>> Sum(1E+308,1E+308) +Error({Kind:ErrorKind.Numeric}) + +// Overflow in calculation +>> Sum(1e308, 1e308, 1e308, 1e308, 1e308) +Error({Kind:ErrorKind.Numeric}) + +// Overflow in calculation +>> VarP(1e200, 2e200, 3e200, 4e200) +Error({Kind:ErrorKind.Numeric}) + +>> Round(1E+308,10) +1E+308 + +>> Max(1E+308, 1E+308) +100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + +>> Min(1E+308, 1E+308) +100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + +>> Mod(1E+300,1E-20) +6.0682893566359166E-21 + +>> Dec2Hex([1,1e-45,1e45,2]) +Table({Value:"1"},{Value:"0"},{Value:Error({Kind:ErrorKind.Numeric})},{Value:"2"}) + +// Infinity +>> StdevP(-1.7e308,1.7e308) +Error({Kind:ErrorKind.Numeric}) + +>> Acot(1e100) +0 + +>> Acot(-1e100) +3.141592654 + +>> Atan(1e100) +1.570796327 + +>> Atan(-1e100) +-1.570796327 + +>> Substitute("HelloHelloHello", "He", "Je", 5e100) +"HelloHelloHello" + +>> Replace("abcd", 5e100, 1, "P") +"abcdP" + +// Excel shows typo in the formula and suggest to change the formula to Ln(E1+308) +>> Ln(1E+308) +709.19620864 + +// Excel shows typo in the formula and suggest to change the formula to Ln(E1+308) +>> Ln(-1E+308) +Error({Kind:ErrorKind.Numeric}) + +// Overflow in calculation +>> StdevP(1e200, 2e200, 3e200, 4e200) +Error({Kind:ErrorKind.Numeric}) + +>> Round(Atan([0, 1e100, -1e100, 1, -1, Blank()]), 4) +Table({Value:0},{Value:1.5708},{Value:-1.5708},{Value:0.7854},{Value:-0.7854},{Value:0}) + +>> Round(Acot([0, 1e100, -1e100, 1, -1, Blank()]), 4) +Table({Value:1.5708},{Value:0},{Value:3.1416},{Value:0.7854},{Value:2.3562},{Value:1.5708}) + + diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ForAll.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ForAll.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ForAll.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ForAll.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ForAll_V1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ForAll_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ForAll_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ForAll_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ForAll_V1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ForAll_V1CompatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ForAll_V1CompatDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ForAll_V1CompatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/GUID.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/GUID.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/GUID.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/GUID.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Hex2Dec.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Hex2Dec.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Hex2Dec.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Hex2Dec.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/If.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/If.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/If.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/If.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/IfError.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/IfError.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/IfError.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/IfError.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/IfError_V1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/IfError_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/IfError_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/IfError_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/IfError_V1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/IfError_V1CompatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/IfError_V1CompatDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/IfError_V1CompatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/If_AllowsSideEffects.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/If_AllowsSideEffects.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/If_AllowsSideEffects.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/If_AllowsSideEffects.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/If_V1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/If_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/If_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/If_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/If_V1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/If_V1CompatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/If_V1CompatDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/If_V1CompatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Index.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Index.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Index.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Index.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Index_TableSyntaxDoesntWrapRecordsDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Index_TableSyntaxDoesntWrapRecordsDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Index_TableSyntaxDoesntWrapRecordsDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Index_TableSyntaxDoesntWrapRecordsDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Int.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Int.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Int.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Int.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/IntT.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/IntT.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/IntT.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/IntT.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/IntT_ConsistentOneColumnTableResultDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/IntT_ConsistentOneColumnTableResultDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/IntT_ConsistentOneColumnTableResultDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/IntT_ConsistentOneColumnTableResultDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/IsBlankOrError.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/IsBlankOrError.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/IsBlankOrError.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/IsBlankOrError.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/IsEmpty.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/IsEmpty.txt similarity index 94% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/IsEmpty.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/IsEmpty.txt index e6e13eef1..303594466 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/IsEmpty.txt +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/IsEmpty.txt @@ -1,128 +1,128 @@ -#SETUP: RestrictedIsEmptyArguments - -// NUMERIC RECORDS - ->> IsEmpty([1234]) -false - ->> IsEmpty([1, 2, 3, 4, 5]) -false - - -// BOOLEAN RECORDS - ->> IsEmpty([false]) -false - ->> IsEmpty( ["Hello"]) -false - - -// STRING RECORDS - ->> IsEmpty([""]) -false - ->> IsEmpty(Table({a:"1"},{a:"two"},{a:"three"},{a:"four"},{a:"five"})) -false - - -// DATE/TIME/DATETIMEVALUE RECORDS - ->> IsEmpty([Date(2022,12,12)]) -false - ->> IsEmpty([Time(6,30,30)]) -false - ->> IsEmpty(Table({a:DateTimeValue("5/12/2022 6:30:30 PM")},{a:DateTimeValue("May 19, 2022")})) -false - - -// BLANK RECORDS - ->> IsEmpty([]) -true - ->> IsEmpty(Blank()) -true - ->> IsEmpty(If(1<0,[1,2,3])) -true - ->> IsEmpty([Blank()]) -false - ->> IsEmpty([Blank(),Blank(),Blank(),Blank(),Blank()]) -false - ->> IsEmpty(Table(Blank())) -false - ->> IsEmpty(Table({a:Blank()})) -false - ->> IsEmpty(Table({a:Blank()},{a:Blank()},{a:Blank()})) -false - ->> IsEmpty(LastN([1,2,3,4], Blank())) -true - - -// ERROR RECORDS - ->> IsEmpty([1/0]) -false - ->> IsEmpty(Table({a:1/0},{a:Sqrt(-1)})) -false - - -// EMPTY TABLES OF DIFFERENT DATATYPES - -// Numeric Properties ->> IsEmpty(Filter([1, 2, 3], Value > 10)) -true - -// String Properties ->> IsEmpty(Filter(["one", "two"], Len(Value) > 5)) -true - -// Boolean Properties ->> IsEmpty(Filter([true, false], Len(Value) > 5)) -true - -// Date Properties ->> IsEmpty(Filter([Date(2022,12,1), Date(2022,2,2)], Value > Date(2022,12,4))) -true - -// Time Properties ->> IsEmpty(Filter([Time(4,0,0), Time(4,30,0)], Value > Time(5,0,0))) -true - -// DateTime Properties ->> IsEmpty(Filter([DateTime(2022,6,19,16,0,0), DateTime(2022,6,18,4,30,0)], Value > DateTime(2022,6,19,16,0,0))) -true - -// Multiple Datatype Properties ->> IsEmpty( - Filter( - Table( - {a:1,b:true,c:Date(2022,12,1),d:Time(12,34,56),e:DateTime(2012,12,12,12,12,12)}, - {a:5,b:false,c:Date(2022,12,1),d:Time(5,0,0),e:DateTime(2012,12,12,12,12,12)}, - {a:6,b:true,c:Date(2022,12,1),d:Time(12,34,56),e:DateTime(2022,6,19,16,0,0)} - ),a > 10)) -true - -// INVALID ARGUMENTS ->> IsEmpty("") -Errors: Error 8-10: Invalid argument type (Text). Expecting a Table value instead. - ->> IsEmpty({}) -Errors: Error 8-10: Invalid argument type (Record). Expecting a Table value instead. - ->> IsEmpty(0) -Errors: Error 8-9: Invalid argument type (Decimal). Expecting a Table value instead. - ->> IsEmpty(false) -Errors: Error 8-13: Invalid argument type (Boolean). Expecting a Table value instead. +#SETUP: RestrictedIsEmptyArguments + +// NUMERIC RECORDS + +>> IsEmpty([1234]) +false + +>> IsEmpty([1, 2, 3, 4, 5]) +false + + +// BOOLEAN RECORDS + +>> IsEmpty([false]) +false + +>> IsEmpty( ["Hello"]) +false + + +// STRING RECORDS + +>> IsEmpty([""]) +false + +>> IsEmpty(Table({a:"1"},{a:"two"},{a:"three"},{a:"four"},{a:"five"})) +false + + +// DATE/TIME/DATETIMEVALUE RECORDS + +>> IsEmpty([Date(2022,12,12)]) +false + +>> IsEmpty([Time(6,30,30)]) +false + +>> IsEmpty(Table({a:DateTimeValue("5/12/2022 6:30:30 PM")},{a:DateTimeValue("May 19, 2022")})) +false + + +// BLANK RECORDS + +>> IsEmpty([]) +true + +>> IsEmpty(Blank()) +true + +>> IsEmpty(If(1<0,[1,2,3])) +true + +>> IsEmpty([Blank()]) +false + +>> IsEmpty([Blank(),Blank(),Blank(),Blank(),Blank()]) +false + +>> IsEmpty(Table(Blank())) +false + +>> IsEmpty(Table({a:Blank()})) +false + +>> IsEmpty(Table({a:Blank()},{a:Blank()},{a:Blank()})) +false + +>> IsEmpty(LastN([1,2,3,4], Blank())) +true + + +// ERROR RECORDS + +>> IsEmpty([1/0]) +false + +>> IsEmpty(Table({a:1/0},{a:Sqrt(-1)})) +false + + +// EMPTY TABLES OF DIFFERENT DATATYPES + +// Numeric Properties +>> IsEmpty(Filter([1, 2, 3], Value > 10)) +true + +// String Properties +>> IsEmpty(Filter(["one", "two"], Len(Value) > 5)) +true + +// Boolean Properties +>> IsEmpty(Filter([true, false], Len(Value) > 5)) +true + +// Date Properties +>> IsEmpty(Filter([Date(2022,12,1), Date(2022,2,2)], Value > Date(2022,12,4))) +true + +// Time Properties +>> IsEmpty(Filter([Time(4,0,0), Time(4,30,0)], Value > Time(5,0,0))) +true + +// DateTime Properties +>> IsEmpty(Filter([DateTime(2022,6,19,16,0,0), DateTime(2022,6,18,4,30,0)], Value > DateTime(2022,6,19,16,0,0))) +true + +// Multiple Datatype Properties +>> IsEmpty( + Filter( + Table( + {a:1,b:true,c:Date(2022,12,1),d:Time(12,34,56),e:DateTime(2012,12,12,12,12,12)}, + {a:5,b:false,c:Date(2022,12,1),d:Time(5,0,0),e:DateTime(2012,12,12,12,12,12)}, + {a:6,b:true,c:Date(2022,12,1),d:Time(12,34,56),e:DateTime(2022,6,19,16,0,0)} + ),a > 10)) +true + +// INVALID ARGUMENTS +>> IsEmpty("") +Errors: Error 8-10: Invalid argument type (Text). Expecting a Table value instead. + +>> IsEmpty({}) +Errors: Error 8-10: Invalid argument type (Record). Expecting a Table value instead. + +>> IsEmpty(0) +Errors: Error 8-9: Invalid argument type (Decimal). Expecting a Table value instead. + +>> IsEmpty(false) +Errors: Error 8-13: Invalid argument type (Boolean). Expecting a Table value instead. diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/IsMatch.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/IsMatch.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/IsMatch.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/IsMatch.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/IsMatch_StronglyTypedEnumsDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/IsMatch_StronglyTypedEnumsDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/IsMatch_StronglyTypedEnumsDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/IsMatch_StronglyTypedEnumsDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/IsNumeric.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/IsNumeric.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/IsNumeric.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/IsNumeric.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/IsToday.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/IsToday.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/IsToday.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/IsToday.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/JSON.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/JSON.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/JSON.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/JSON.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/JSON_FR.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/JSON_FR.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/JSON_FR.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/JSON_FR.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/JSON_OptionSets.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/JSON_OptionSets.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/JSON_OptionSets.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/JSON_OptionSets.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/JSON_US.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/JSON_US.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/JSON_US.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/JSON_US.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/JSON_V1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/JSON_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/JSON_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/JSON_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/JSON_V1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/JSON_V1CompatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/JSON_V1CompatDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/JSON_V1CompatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Language.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Language.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Language.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Language.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Left.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Left.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Left.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Left.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/LeftRightT.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/LeftRightT.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/LeftRightT.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/LeftRightT.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/LeftRightT_ConsistentOneColumnTableResultDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/LeftRightT_ConsistentOneColumnTableResultDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/LeftRightT_ConsistentOneColumnTableResultDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/LeftRightT_ConsistentOneColumnTableResultDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Len.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Len.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Len.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Len.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/LenT.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/LenT.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/LenT.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/LenT.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/LenT_ConsistentOneColumnTableResultDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/LenT_ConsistentOneColumnTableResultDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/LenT_ConsistentOneColumnTableResultDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/LenT_ConsistentOneColumnTableResultDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Ln.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Ln.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Ln.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Ln.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/LnT.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/LnT.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/LnT.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/LnT.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/LnT_ConsistentOneColumnTableResultDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/LnT_ConsistentOneColumnTableResultDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/LnT_ConsistentOneColumnTableResultDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/LnT_ConsistentOneColumnTableResultDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Log.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Log.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Log.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Log.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Lookup.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Lookup.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Lookup.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Lookup.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Lookup_V1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Lookup_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Lookup_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Lookup_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Lookup_V1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Lookup_V1CompatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Lookup_V1CompatDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Lookup_V1CompatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Lower.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Lower.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Lower.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Lower.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Match.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Match.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Match.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Match.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/MatchAll.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/MatchAll.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/MatchAll.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/MatchAll.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/MatchAll_StronglyTypedEnumsDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/MatchAll_StronglyTypedEnumsDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/MatchAll_StronglyTypedEnumsDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/MatchAll_StronglyTypedEnumsDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Match_StronglyTypedEnumsDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Match_StronglyTypedEnumsDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Match_StronglyTypedEnumsDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Match_StronglyTypedEnumsDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Mid.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Mid.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Mid.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Mid.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/MidT.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/MidT.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/MidT.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/MidT.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/MidT_ConsistentOneColumnTableResultDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/MidT_ConsistentOneColumnTableResultDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/MidT_ConsistentOneColumnTableResultDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/MidT_ConsistentOneColumnTableResultDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/MinMax.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/MinMax.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/MinMax.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/MinMax.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/MinMax_NumberIsFloat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/MinMax_NumberIsFloat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/MinMax_NumberIsFloat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/MinMax_NumberIsFloat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/MinMax_V1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/MinMax_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/MinMax_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/MinMax_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/MinMax_V1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/MinMax_V1CompatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/MinMax_V1CompatDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/MinMax_V1CompatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Mod.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Mod.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Mod.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Mod.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ModT.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ModT.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ModT.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ModT.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ModT_ConsistentOneColumnTableResultDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ModT_ConsistentOneColumnTableResultDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ModT_ConsistentOneColumnTableResultDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ModT_ConsistentOneColumnTableResultDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ModT_Decimal.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ModT_Decimal.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ModT_Decimal.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ModT_Decimal.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ModT_Float.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ModT_Float.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ModT_Float.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ModT_Float.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Mod_Decimal.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Mod_Decimal.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Mod_Decimal.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Mod_Decimal.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Mod_Float.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Mod_Float.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Mod_Float.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Mod_Float.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/NotYetReady/Find.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/NotYetReady/Find.txt similarity index 97% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/NotYetReady/Find.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/NotYetReady/Find.txt index fea1a2780..3d66812a5 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/NotYetReady/Find.txt +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/NotYetReady/Find.txt @@ -1,256 +1,256 @@ -// ************************* SCALAR PARAMETERS ************************* - ->> Find(",", "LastName,FirstName", Blank()) -Blank() - -// ************************* TABLE PARAMETERS ************************* - -// ======================== SCENARIO 1 ======================== -// findText: null, withinText: null, startIndex: [{ start: 1 }] - ->> Find(If(1<0,["string"]),If(1<0,["string"]),Table({start:1})) -Table({Result:1}) - -// ======================== SCENARIO 2 ======================== -// findText: null, withinText: null, startIndex: [{ start: 2 }] - ->> Find(If(1<0,["string"]),If(1<0,["string"]),Table({start:2})) -Table({Result:Error({Kind:ErrorKind.InvalidArgument})}) - -// ======================== SCENARIO 3 ======================== -// findText: null, withinText: "textToBeSearchedIn", startIndex: [{ start: 5 }] - ->> Find(If(1<0,["string"]),"textToBeSearchedIn",Table({start:5})) -Table({Result:5}) - -// ======================== SCENARIO 4 ======================== -// findText: "textToSearch", withinText: null, startIndex: [{ start: 1 }] - ->> Find("textToSearch",If(1<0,["string"]),[1]) -Table({Result:Blank()}) - -// ======================== SCENARIO 5 ======================== -// findText: [{ find: null }, { find: '' }, { find: ',' }], withinText: null, startIndex: 1 - ->> Find(Table( - {find: Blank()}, - {find: ""}, - {find: ","}), - If(1<0,["string"]),1) -Table({Result:1},{Result:1},{Result:Blank()}) - -// ======================== SCENARIO 6 ======================== -// findText: [{ find: null }, { find: '' }, { find: ',' }], withinText: null, startIndex: [{ start: 1 }, { start: 2 }, { start: 3 }] - ->> Find(Table( - {find: Blank()}, - {find: ""}, - {find: ","}), - If(1<0,["string"]),[1,2,3]) -Table({Result:1},{Result:Error({Kind:ErrorKind.InvalidArgument})},{Result:Error({Kind:ErrorKind.InvalidArgument})}) - -// ======================== SCENARIO 7 ======================== -// findText: null, withinText: [{ within: null }, { within: '' }, { within: 'textToBeSearchedIn' }], startIndex: 1 - ->> Find(If(1<0,["string"]), - Table( - {within: Blank()}, - {within: ""}, - {within: "textToBeSearchedIn"}), - 1) -Table({Result:1},{Result:1},{Result:1}) -// Question: Is the StartIndex parameter correct? - -// ======================== SCENARIO 8 ======================== -// findText: null, withinText: [{ within: null }, { within: '' }, { within: 'textToBeSearchedIn' }], startIndex: [{ start: 1 }, { start: 2 }, { start: 3 }] - ->> Find(If(1<0,["string"]), - Table( - {within: Blank()}, - {within: ""}, - {within: "textToBeSearchedIn"}), - [1,2,3]) -Table({Result:1},{Result:Error({Kind:ErrorKind.InvalidArgument})},{Result:3}) - -// ======================== SCENARIO 15 ======================== -// findText: [{ find: null }, { find: '' }, { find: ',' }], withinText: null, - ->> Find( - Table({find: Blank()},{find: ""},{find: ","}), - If(1<0,["string"])) -Table({Result:1},{Result:1},{Result:Blank()}) - -// ======================== SCENARIO 16 ======================== -// findText: null, withinText: [{ within: null }, { within: '' }, { within: 'textToBeSearchedIn' }] - ->> Find( - If(1<0,["string"]), - Table({within: Blank()},{within: ""},{within: "textToBeSearchedIn"})) -Table({Result:1},{Result:1},{Result:1}) - -// ======================== SCENARIO 20 ======================== -// findText: [{ find: null }, { find: ',' }] -// withinText: [{ within: 'lastName,firstName' },{ within: 'lastName,firstName,' },{ within: 'lastName,firstName' },{ within: null }] - ->> Find( - Table({find: Blank()},{find: ","}), - Table({within: "lastName,firstName"},{within: "lastName,firstName,"},{within: "lastName,firstName"},{within: Blank()}) - ) -Table({Result:1},{Result:9},{Result:1},{Result:1}) - -// ======================== SCENARIO 21 ======================== -// findText: [{ find: null }, { find: ',' }, { find: '' }, { find: 'findMe' }], withinText: [{ within: null }, { within: 'lastName,firstName' }] - ->> Find( - Table({find: Blank()},{find: ","},{find: ""},{find: "findMe"}), - Table({within: Blank()},{within: "lastName,firstName"}) - ) -Table({Result:1},{Result:9},{Result:1},{Result:Blank()}) - -// ======================== SCENARIO 22 ======================== -// findText: [{ find: null }, { find: ',' }], WithinText: [{ within: 'lastName,firstName' }], startIndex: [{ start: 9 }, { start: 2 }, { start: 3 }] - ->> Find( - Table({find: Blank()},{find: ","}), - Table({within: "lastName,firstName"}), - [9,2,3] - ) -Table({Result:9},{Result:Error({Kind:ErrorKind.InvalidArgument})},{Result:Error({Kind:ErrorKind.InvalidArgument})}) - -// ======================== SCENARIO 23 ======================== -// findText: [{ find: null }, { find: ',' }, { find: '' }, { find: 'findMe' }] -// withinText: [{ within: null }, { within: 'lastName,firstName' }] -// startIndex: 10 - ->> Find( - Table({find: Blank()},{find: ","},{find: ""},{find: "findMe"}), - Table({within: Blank()},{within: "lastName,firstName"}), - 10 - ) -Table({Result:Error({Kind:ErrorKind.InvalidArgument})},{Result:Blank()},{Result:Error({Kind:ErrorKind.InvalidArgument})},{Result:Error({Kind:ErrorKind.InvalidArgument})}) - -// ======================== SCENARIO 24 ======================== -// findText: [{ find: ',' }, { find: null }], withinText: 'lastName,firstName', startIndex: [{ start: 1 }, { start: 2 }, { start: 3 }] - ->> Find( - Table({find: ","},{find: Blank()}), - "lastName,firstName", - [1,2,3] - ) -Table({Result:9},{Result:2},{Result:3}) - -// ======================== SCENARIO 25 ======================== -// findText: ',', withinText: [{ within: null }, { within: 'lastName,firstName' }, { within: 'lastName,firstName' }], startIndex: [{ start: 1 }, { start: 2 }], - ->> Find( - ",", - Table({within:Blank()},{within:"lastName,firstName"},{within:"lastName,firstName"}), - [1,2] - ) -Table({Result:Blank()},{Result:9},{Result:Blank()}) - -// ======================== SCENARIO 31 ======================== -// findText: [],withinText: [{ within: null }, { within: 'lastName,firstName' }, { within: 'lastName,firstName' }], startIndex: 2 - ->> Find( - Filter(["string"], Len(Value) > 10), - Table({within:Blank()},{within:"lastName,firstName"},{within:"lastName,firstName"}), - 2) -Table() - -// ======================== SCENARIO 32 ======================== -// findText: [], withinText: 'textToSearch', startIndex: [{ start: 1 }, { start: 2 }], - ->> Find( - Filter(["string"], Len(Value) > 10), - "textToSearch", - [1,2]) -Table() - -// ======================== SCENARIO 33 ======================== -// findText: [{ find: ',' }, { find: null }, { find: '' }], withinText: [], startIndex: 2 - ->> Find( - Table({find:","},{find:Blank()},{find:""}), - Filter(["string"], Len(Value) > 10), - 2) -Table() - -// ======================== SCENARIO 34 ======================== -// findText: ',', withinText: [], startIndex: [{ start: 1 }, { start: 1 }], - ->> Find( - ",", - Filter(["string"], Len(Value) > 10), - [1,1]) -Table() - -// ======================== SCENARIO 35 ======================== -// findText: [{ find: null }, { find: ',' }, { find: '' }, { find: 'findMe' }], -// withinText: [{ within: null }, { within: 'lastName,firstName' }, { within: 'lastName,firstName' }], -// startIndex: [], - ->> Find( - Table({find: Blank()},{find: ","},{find: ""},{find: "findMe"}), - Table({within:Blank()},{within:"lastName,firstName"},{within:"lastName,firstName"}), - Filter([1, 2, 3], Value > 100) - ) -Table() - -// ======================== SCENARIO 36 ======================== -// findText: ',', withinText: [{ within: null }, { within: 'lastName,firstName' }], startIndex: [] - ->> Find( - ",", - Table({within: Blank()},{within: "lastName,firstName"}), - Filter([1, 2, 3], Value > 100) - ) -Table() - -// ======================== SCENARIO 37 ======================== -// findText: 'c', withinText: [{ within: 'abc' }, { within: anError }, { within: 'cde' }], - ->> Find( - "c", - Table({within: "abc"},{within: Error({Kind: Validation})}, {within: "cde"}) - ) -Table({Result:3},{Result:Error({Kind:ErrorKind.Validation})},{Result:1}) - -// ======================== SCENARIO 38 ======================== -// findText: 'c', withinText: [{ within: 'abc' }, anError, { within: 'cde' }], - ->> Find( - "c", - Table({within: "abc"},{within: Error({Kind: Validation})}, {within: "cde"}), - [3,1/0,1] - ) -Table({Result:3},{Result:Error({Kind:ErrorKind.Validation})},{Result:1}) - -// ======================== SCENARIO 39 ======================== -// findText: [{ find: 'a' }, { find: anError }, { find: 'b' }], withinText: 'abc', - ->> Find( - Table({find:"a"},{find:Error({Kind: Validation})},{find:"b"}), - "abc", - [1,1/0,2] - ) -Table({Result:1},{Result:Error({Kind:ErrorKind.Validation})},{Result:2}) - -// ======================== SCENARIO 40 ======================== -// findText: [{ find: 'a' }, { find: anError }, { find: 'b' }, anError, { find: 'c' }], -// withinText: [{ within: 'abc' }, anError, { within: anError }, anError, { within: 'abc' }], - ->> Find( - Table({find:"a"},{find:Error({Kind: Validation})},{find:"b"},{find:Error({Kind: InvalidArgument})},{find:"c"}), - Table({within:"abc"}, Error({Kind: InvalidArgument}), {within:Error({Kind: Validation})}, Error({Kind: Div0}), {within:"abc"}) - ) -Table({Result:1},Error({Kind:ErrorKind.InvalidArgument}),{Result:Error({Kind:ErrorKind.Validation})},Error({Kind:ErrorKind.Div0}),{Result:3}) - -// ======================== SCENARIO 42 ======================== -// findText: anError, withinText: [{ within: 'First' }, { within: 'Second' }] - ->> Find( - Text(1/0), - Table({within:"First"},{within:"Second"}), - ) -Table({Result:Error({Kind:ErrorKind.Div0})},{Result:Error({Kind:ErrorKind.Div0})}) +// ************************* SCALAR PARAMETERS ************************* + +>> Find(",", "LastName,FirstName", Blank()) +Blank() + +// ************************* TABLE PARAMETERS ************************* + +// ======================== SCENARIO 1 ======================== +// findText: null, withinText: null, startIndex: [{ start: 1 }] + +>> Find(If(1<0,["string"]),If(1<0,["string"]),Table({start:1})) +Table({Result:1}) + +// ======================== SCENARIO 2 ======================== +// findText: null, withinText: null, startIndex: [{ start: 2 }] + +>> Find(If(1<0,["string"]),If(1<0,["string"]),Table({start:2})) +Table({Result:Error({Kind:ErrorKind.InvalidArgument})}) + +// ======================== SCENARIO 3 ======================== +// findText: null, withinText: "textToBeSearchedIn", startIndex: [{ start: 5 }] + +>> Find(If(1<0,["string"]),"textToBeSearchedIn",Table({start:5})) +Table({Result:5}) + +// ======================== SCENARIO 4 ======================== +// findText: "textToSearch", withinText: null, startIndex: [{ start: 1 }] + +>> Find("textToSearch",If(1<0,["string"]),[1]) +Table({Result:Blank()}) + +// ======================== SCENARIO 5 ======================== +// findText: [{ find: null }, { find: '' }, { find: ',' }], withinText: null, startIndex: 1 + +>> Find(Table( + {find: Blank()}, + {find: ""}, + {find: ","}), + If(1<0,["string"]),1) +Table({Result:1},{Result:1},{Result:Blank()}) + +// ======================== SCENARIO 6 ======================== +// findText: [{ find: null }, { find: '' }, { find: ',' }], withinText: null, startIndex: [{ start: 1 }, { start: 2 }, { start: 3 }] + +>> Find(Table( + {find: Blank()}, + {find: ""}, + {find: ","}), + If(1<0,["string"]),[1,2,3]) +Table({Result:1},{Result:Error({Kind:ErrorKind.InvalidArgument})},{Result:Error({Kind:ErrorKind.InvalidArgument})}) + +// ======================== SCENARIO 7 ======================== +// findText: null, withinText: [{ within: null }, { within: '' }, { within: 'textToBeSearchedIn' }], startIndex: 1 + +>> Find(If(1<0,["string"]), + Table( + {within: Blank()}, + {within: ""}, + {within: "textToBeSearchedIn"}), + 1) +Table({Result:1},{Result:1},{Result:1}) +// Question: Is the StartIndex parameter correct? + +// ======================== SCENARIO 8 ======================== +// findText: null, withinText: [{ within: null }, { within: '' }, { within: 'textToBeSearchedIn' }], startIndex: [{ start: 1 }, { start: 2 }, { start: 3 }] + +>> Find(If(1<0,["string"]), + Table( + {within: Blank()}, + {within: ""}, + {within: "textToBeSearchedIn"}), + [1,2,3]) +Table({Result:1},{Result:Error({Kind:ErrorKind.InvalidArgument})},{Result:3}) + +// ======================== SCENARIO 15 ======================== +// findText: [{ find: null }, { find: '' }, { find: ',' }], withinText: null, + +>> Find( + Table({find: Blank()},{find: ""},{find: ","}), + If(1<0,["string"])) +Table({Result:1},{Result:1},{Result:Blank()}) + +// ======================== SCENARIO 16 ======================== +// findText: null, withinText: [{ within: null }, { within: '' }, { within: 'textToBeSearchedIn' }] + +>> Find( + If(1<0,["string"]), + Table({within: Blank()},{within: ""},{within: "textToBeSearchedIn"})) +Table({Result:1},{Result:1},{Result:1}) + +// ======================== SCENARIO 20 ======================== +// findText: [{ find: null }, { find: ',' }] +// withinText: [{ within: 'lastName,firstName' },{ within: 'lastName,firstName,' },{ within: 'lastName,firstName' },{ within: null }] + +>> Find( + Table({find: Blank()},{find: ","}), + Table({within: "lastName,firstName"},{within: "lastName,firstName,"},{within: "lastName,firstName"},{within: Blank()}) + ) +Table({Result:1},{Result:9},{Result:1},{Result:1}) + +// ======================== SCENARIO 21 ======================== +// findText: [{ find: null }, { find: ',' }, { find: '' }, { find: 'findMe' }], withinText: [{ within: null }, { within: 'lastName,firstName' }] + +>> Find( + Table({find: Blank()},{find: ","},{find: ""},{find: "findMe"}), + Table({within: Blank()},{within: "lastName,firstName"}) + ) +Table({Result:1},{Result:9},{Result:1},{Result:Blank()}) + +// ======================== SCENARIO 22 ======================== +// findText: [{ find: null }, { find: ',' }], WithinText: [{ within: 'lastName,firstName' }], startIndex: [{ start: 9 }, { start: 2 }, { start: 3 }] + +>> Find( + Table({find: Blank()},{find: ","}), + Table({within: "lastName,firstName"}), + [9,2,3] + ) +Table({Result:9},{Result:Error({Kind:ErrorKind.InvalidArgument})},{Result:Error({Kind:ErrorKind.InvalidArgument})}) + +// ======================== SCENARIO 23 ======================== +// findText: [{ find: null }, { find: ',' }, { find: '' }, { find: 'findMe' }] +// withinText: [{ within: null }, { within: 'lastName,firstName' }] +// startIndex: 10 + +>> Find( + Table({find: Blank()},{find: ","},{find: ""},{find: "findMe"}), + Table({within: Blank()},{within: "lastName,firstName"}), + 10 + ) +Table({Result:Error({Kind:ErrorKind.InvalidArgument})},{Result:Blank()},{Result:Error({Kind:ErrorKind.InvalidArgument})},{Result:Error({Kind:ErrorKind.InvalidArgument})}) + +// ======================== SCENARIO 24 ======================== +// findText: [{ find: ',' }, { find: null }], withinText: 'lastName,firstName', startIndex: [{ start: 1 }, { start: 2 }, { start: 3 }] + +>> Find( + Table({find: ","},{find: Blank()}), + "lastName,firstName", + [1,2,3] + ) +Table({Result:9},{Result:2},{Result:3}) + +// ======================== SCENARIO 25 ======================== +// findText: ',', withinText: [{ within: null }, { within: 'lastName,firstName' }, { within: 'lastName,firstName' }], startIndex: [{ start: 1 }, { start: 2 }], + +>> Find( + ",", + Table({within:Blank()},{within:"lastName,firstName"},{within:"lastName,firstName"}), + [1,2] + ) +Table({Result:Blank()},{Result:9},{Result:Blank()}) + +// ======================== SCENARIO 31 ======================== +// findText: [],withinText: [{ within: null }, { within: 'lastName,firstName' }, { within: 'lastName,firstName' }], startIndex: 2 + +>> Find( + Filter(["string"], Len(Value) > 10), + Table({within:Blank()},{within:"lastName,firstName"},{within:"lastName,firstName"}), + 2) +Table() + +// ======================== SCENARIO 32 ======================== +// findText: [], withinText: 'textToSearch', startIndex: [{ start: 1 }, { start: 2 }], + +>> Find( + Filter(["string"], Len(Value) > 10), + "textToSearch", + [1,2]) +Table() + +// ======================== SCENARIO 33 ======================== +// findText: [{ find: ',' }, { find: null }, { find: '' }], withinText: [], startIndex: 2 + +>> Find( + Table({find:","},{find:Blank()},{find:""}), + Filter(["string"], Len(Value) > 10), + 2) +Table() + +// ======================== SCENARIO 34 ======================== +// findText: ',', withinText: [], startIndex: [{ start: 1 }, { start: 1 }], + +>> Find( + ",", + Filter(["string"], Len(Value) > 10), + [1,1]) +Table() + +// ======================== SCENARIO 35 ======================== +// findText: [{ find: null }, { find: ',' }, { find: '' }, { find: 'findMe' }], +// withinText: [{ within: null }, { within: 'lastName,firstName' }, { within: 'lastName,firstName' }], +// startIndex: [], + +>> Find( + Table({find: Blank()},{find: ","},{find: ""},{find: "findMe"}), + Table({within:Blank()},{within:"lastName,firstName"},{within:"lastName,firstName"}), + Filter([1, 2, 3], Value > 100) + ) +Table() + +// ======================== SCENARIO 36 ======================== +// findText: ',', withinText: [{ within: null }, { within: 'lastName,firstName' }], startIndex: [] + +>> Find( + ",", + Table({within: Blank()},{within: "lastName,firstName"}), + Filter([1, 2, 3], Value > 100) + ) +Table() + +// ======================== SCENARIO 37 ======================== +// findText: 'c', withinText: [{ within: 'abc' }, { within: anError }, { within: 'cde' }], + +>> Find( + "c", + Table({within: "abc"},{within: Error({Kind: Validation})}, {within: "cde"}) + ) +Table({Result:3},{Result:Error({Kind:ErrorKind.Validation})},{Result:1}) + +// ======================== SCENARIO 38 ======================== +// findText: 'c', withinText: [{ within: 'abc' }, anError, { within: 'cde' }], + +>> Find( + "c", + Table({within: "abc"},{within: Error({Kind: Validation})}, {within: "cde"}), + [3,1/0,1] + ) +Table({Result:3},{Result:Error({Kind:ErrorKind.Validation})},{Result:1}) + +// ======================== SCENARIO 39 ======================== +// findText: [{ find: 'a' }, { find: anError }, { find: 'b' }], withinText: 'abc', + +>> Find( + Table({find:"a"},{find:Error({Kind: Validation})},{find:"b"}), + "abc", + [1,1/0,2] + ) +Table({Result:1},{Result:Error({Kind:ErrorKind.Validation})},{Result:2}) + +// ======================== SCENARIO 40 ======================== +// findText: [{ find: 'a' }, { find: anError }, { find: 'b' }, anError, { find: 'c' }], +// withinText: [{ within: 'abc' }, anError, { within: anError }, anError, { within: 'abc' }], + +>> Find( + Table({find:"a"},{find:Error({Kind: Validation})},{find:"b"},{find:Error({Kind: InvalidArgument})},{find:"c"}), + Table({within:"abc"}, Error({Kind: InvalidArgument}), {within:Error({Kind: Validation})}, Error({Kind: Div0}), {within:"abc"}) + ) +Table({Result:1},Error({Kind:ErrorKind.InvalidArgument}),{Result:Error({Kind:ErrorKind.Validation})},Error({Kind:ErrorKind.Div0}),{Result:3}) + +// ======================== SCENARIO 42 ======================== +// findText: anError, withinText: [{ within: 'First' }, { within: 'Second' }] + +>> Find( + Text(1/0), + Table({within:"First"},{within:"Second"}), + ) +Table({Result:Error({Kind:ErrorKind.Div0})},{Result:Error({Kind:ErrorKind.Div0})}) diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/NotYetReady/ISOWeekNum.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/NotYetReady/ISOWeekNum.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/NotYetReady/ISOWeekNum.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/NotYetReady/ISOWeekNum.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/NotYetReady/If.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/NotYetReady/If.txt similarity index 96% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/NotYetReady/If.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/NotYetReady/If.txt index c460bbb7d..72a0f3b2a 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/NotYetReady/If.txt +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/NotYetReady/If.txt @@ -1,145 +1,145 @@ -// ************************************** FIRST CONDITION TRUE CASES ************************************** - -// The first condition is true, and the corresponding Date ThenResult is returned. ->> If(true, Date(2022,4,22), Date(2022,4,23)) -Date(4,22,2022) - -// The first condition is true, and the corresponding Time ThenResult is returned. -//Excel doesn't ignore the seconds part and returns 12:50:00 PM ->> If(Time(12,50,50)>Time(12,49,00), Time(12,50,50), Time(12,49,00)) -12:50 PM - -// The first condition is true, and the corresponding DateTimeValue ThenResult is returned. ->> If(DateTimeValue("1/1/2000 12:00:00:00 AM")>Time(12,49,00), DateTimeValue("1/1/2000 12:00:00:00 AM"), Time(12,49,00)) -1/1/2000 12:00 AM - -//Excel returns error ->> If("a", 2, 3) -3 - -// ************************************** FIRST CONDITION FALSE CASES ************************************** - -// Both the first and second conditions are false, a DefaultResult was provided, and it's returned ->> If( 5>25, "Result1", IsNumeric("25"), "Result2", "Result3") -"Result3" - -// Both the first and second conditions are false, a DefaultResult isn't provided, hence returned Blank ->> If( 5>25, "Result1", IsNumeric("25"), "Result2") -Blank() - -// ***************** FIRST ARGUMENT NUMBER, SECOND ARGUMENT OTHER DATAYPE ***************** - -//Number to Date Coercion ->> If(false,1,Date(2012,12,14)) -1355472000000 - -//Number to Date Coercion ->> If(false,1,DateValue("12/14/2012")) -1355472000000 - -//Number to Time Coercion ->> If(false,1,Time(12,35,55)) -74155000 - -//Number to Time Coercion ->> If(false,1,TimeValue("12:35:55")) -74155000 - -//Number to DateTime Coercion ->> If(false,1,DateTimeValue("1/1/2000 12:00:00:00 AM")) -946713600000 - -// ***************** FIRST ARGUMENT STRING, SECOND ARGUMENT OTHER DATAYPE ***************** - -//String to Date Coercion ->> If(false,"Hello",DateValue("12/14/2012")) -Date(12,14,2012) - -//String to DateTime Coercion ->> If(false,"Hello",DateTimeValue("1/1/2000 12:00:00:00 AM")) -"1/1/2000 12:00 AM" - -// ***************** FIRST ARGUMENT BOOLEAN, SECOND ARGUMENT OTHER DATAYPE ***************** - -//Boolean to String Coercion -////Excel returns "Good morning" ->> If(false,true,"Good morning") -false - -// ***************** FIRST ARGUMENT DATE, SECOND ARGUMENT OTHER DATAYPE ***************** - -//Date to Date Coercion ->> If(false,Date(2012,12,14),DateValue("1/1/2014")) -Date(1,1,2014) - -//Date to Date Coercion ->> If(false,Date(2012,12,14),Date(2014,1,1)) -Date(1,1,2014) - -//Date to Number Coercion ->> If(false,Date(2012,12,14),1) -Date(12,31,1969) - -//Date to Time Coercion ->> If(false,Date(2012,12,14),Time(12,35,55)) -1/1/1970 12:35 PM - -//Date to Time Coercion ->> If(false,Date(2012,12,14),TimeValue("12:35:55")) -1/1/1970 12:35 PM - -//Date to DateTime Coercion ->> If(false,Date(2012,12,14),DateTimeValue("1/1/2000 12:00:00:00 AM")) -1/1/2000 12:00 AM - -// ***************** FIRST ARGUMENT TIME, SECOND ARGUMENT OTHER DATAYPE ***************** - -//Time to Time Coercion ->> If(false,Time(12,35,55),Time(5,20,40)) -5:20 AM - -//Time to Time Coercion ->> If(false,Time(12,35,55),TimeValue("5:20:40")) -5:20 AM - -//Time to Number Coercion ->> If(false,Time(12,35,55),1) -4:00 PM - -//Time to Date Coercion ->> If(false,Time(12,35,55),Date(2014,2,15)) -2/15/2014 12:00 AM - -//Time to Date Coercion ->> If(false,Time(12,35,55),DateValue("2/15/2014")) -2/15/2014 12:00 AM - -//Time to DateTime Coercion ->> If(false,Time(12,35,55),DateTimeValue("1/1/2000 12:00:00:00 AM")) -1/1/2000 12:00 AM - -// ***************** FIRST ARGUMENT DATETIME, SECOND ARGUMENT OTHER DATAYPE ***************** - -//DateTime to DateTime Coercion ->> If(false,DateTimeValue("11/1/2000 12:00:00:00 AM"),DateTimeValue("12/1/2004 12:00:00:00 AM")) -12/1/2004 12:00 AM - -//DateTime to Number Coercion ->> If(false,DateTimeValue("11/1/2000 12:00:00:00 AM"),1) -12/31/1969 4:00 PM - -//DateTime to Date Coercion ->> If(false,DateTimeValue("11/1/2000 12:00:00:00 AM"),Date(2014,2,15)) -2/15/2014 12:00 AM - -//DateTime to Date Coercion ->> If(false,DateTimeValue("11/1/2000 12:00:00:00 AM"),DateValue("2/15/2014")) -2/15/2014 12:00 AM - -//DateTime to Time Coercion ->> If(false,DateTimeValue("11/1/2000 12:00:00:00 AM"),TimeValue("12:35:55")) -1/1/1970 12:35 PM - -//DateTime to Time Coercion ->> If(false,DateTimeValue("11/1/2000 12:00:00:00 AM"),Time(12,35,55)) +// ************************************** FIRST CONDITION TRUE CASES ************************************** + +// The first condition is true, and the corresponding Date ThenResult is returned. +>> If(true, Date(2022,4,22), Date(2022,4,23)) +Date(4,22,2022) + +// The first condition is true, and the corresponding Time ThenResult is returned. +//Excel doesn't ignore the seconds part and returns 12:50:00 PM +>> If(Time(12,50,50)>Time(12,49,00), Time(12,50,50), Time(12,49,00)) +12:50 PM + +// The first condition is true, and the corresponding DateTimeValue ThenResult is returned. +>> If(DateTimeValue("1/1/2000 12:00:00:00 AM")>Time(12,49,00), DateTimeValue("1/1/2000 12:00:00:00 AM"), Time(12,49,00)) +1/1/2000 12:00 AM + +//Excel returns error +>> If("a", 2, 3) +3 + +// ************************************** FIRST CONDITION FALSE CASES ************************************** + +// Both the first and second conditions are false, a DefaultResult was provided, and it's returned +>> If( 5>25, "Result1", IsNumeric("25"), "Result2", "Result3") +"Result3" + +// Both the first and second conditions are false, a DefaultResult isn't provided, hence returned Blank +>> If( 5>25, "Result1", IsNumeric("25"), "Result2") +Blank() + +// ***************** FIRST ARGUMENT NUMBER, SECOND ARGUMENT OTHER DATAYPE ***************** + +//Number to Date Coercion +>> If(false,1,Date(2012,12,14)) +1355472000000 + +//Number to Date Coercion +>> If(false,1,DateValue("12/14/2012")) +1355472000000 + +//Number to Time Coercion +>> If(false,1,Time(12,35,55)) +74155000 + +//Number to Time Coercion +>> If(false,1,TimeValue("12:35:55")) +74155000 + +//Number to DateTime Coercion +>> If(false,1,DateTimeValue("1/1/2000 12:00:00:00 AM")) +946713600000 + +// ***************** FIRST ARGUMENT STRING, SECOND ARGUMENT OTHER DATAYPE ***************** + +//String to Date Coercion +>> If(false,"Hello",DateValue("12/14/2012")) +Date(12,14,2012) + +//String to DateTime Coercion +>> If(false,"Hello",DateTimeValue("1/1/2000 12:00:00:00 AM")) +"1/1/2000 12:00 AM" + +// ***************** FIRST ARGUMENT BOOLEAN, SECOND ARGUMENT OTHER DATAYPE ***************** + +//Boolean to String Coercion +////Excel returns "Good morning" +>> If(false,true,"Good morning") +false + +// ***************** FIRST ARGUMENT DATE, SECOND ARGUMENT OTHER DATAYPE ***************** + +//Date to Date Coercion +>> If(false,Date(2012,12,14),DateValue("1/1/2014")) +Date(1,1,2014) + +//Date to Date Coercion +>> If(false,Date(2012,12,14),Date(2014,1,1)) +Date(1,1,2014) + +//Date to Number Coercion +>> If(false,Date(2012,12,14),1) +Date(12,31,1969) + +//Date to Time Coercion +>> If(false,Date(2012,12,14),Time(12,35,55)) +1/1/1970 12:35 PM + +//Date to Time Coercion +>> If(false,Date(2012,12,14),TimeValue("12:35:55")) +1/1/1970 12:35 PM + +//Date to DateTime Coercion +>> If(false,Date(2012,12,14),DateTimeValue("1/1/2000 12:00:00:00 AM")) +1/1/2000 12:00 AM + +// ***************** FIRST ARGUMENT TIME, SECOND ARGUMENT OTHER DATAYPE ***************** + +//Time to Time Coercion +>> If(false,Time(12,35,55),Time(5,20,40)) +5:20 AM + +//Time to Time Coercion +>> If(false,Time(12,35,55),TimeValue("5:20:40")) +5:20 AM + +//Time to Number Coercion +>> If(false,Time(12,35,55),1) +4:00 PM + +//Time to Date Coercion +>> If(false,Time(12,35,55),Date(2014,2,15)) +2/15/2014 12:00 AM + +//Time to Date Coercion +>> If(false,Time(12,35,55),DateValue("2/15/2014")) +2/15/2014 12:00 AM + +//Time to DateTime Coercion +>> If(false,Time(12,35,55),DateTimeValue("1/1/2000 12:00:00:00 AM")) +1/1/2000 12:00 AM + +// ***************** FIRST ARGUMENT DATETIME, SECOND ARGUMENT OTHER DATAYPE ***************** + +//DateTime to DateTime Coercion +>> If(false,DateTimeValue("11/1/2000 12:00:00:00 AM"),DateTimeValue("12/1/2004 12:00:00:00 AM")) +12/1/2004 12:00 AM + +//DateTime to Number Coercion +>> If(false,DateTimeValue("11/1/2000 12:00:00:00 AM"),1) +12/31/1969 4:00 PM + +//DateTime to Date Coercion +>> If(false,DateTimeValue("11/1/2000 12:00:00:00 AM"),Date(2014,2,15)) +2/15/2014 12:00 AM + +//DateTime to Date Coercion +>> If(false,DateTimeValue("11/1/2000 12:00:00:00 AM"),DateValue("2/15/2014")) +2/15/2014 12:00 AM + +//DateTime to Time Coercion +>> If(false,DateTimeValue("11/1/2000 12:00:00:00 AM"),TimeValue("12:35:55")) +1/1/1970 12:35 PM + +//DateTime to Time Coercion +>> If(false,DateTimeValue("11/1/2000 12:00:00:00 AM"),Time(12,35,55)) 1/1/1970 12:35 PM \ No newline at end of file diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/NotYetReady/Sort.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/NotYetReady/Sort.txt similarity index 99% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/NotYetReady/Sort.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/NotYetReady/Sort.txt index 527fd72c0..f561a2f7f 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/NotYetReady/Sort.txt +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/NotYetReady/Sort.txt @@ -1,432 +1,432 @@ -// ******** NUMBER PARAMETERS ******** - ->> Sort([-2, -1, 5, 1, 2], Value) -[-2,-1,1,2,5] - ->> Sort([-2, -1, 5, 1, 2], Value, SortOrder.Descending) -[5,2,1,-1,-2] - ->> Sort([-2, -1, -1E+308, 1, 2], Value) -[-1e+308,-2,-1,1,2] - ->> Sort([-2, -1, 1E+308, 1, 2], Value) -[-2,-1,1,2,1e+308] - ->> Sort([{a:8, b:-1}, {a:2}, {b:5}], Value.a) -[{a:2},{a:8,b:-1},{b:5}] - ->> Sort([{a:8, b:-1}, {a:2}, {b:5}], Value.b, SortOrder.Descending) -[{b:5},{a:8,b:-1},{a:2}] - -// ******** STRING PARAMETERS ******** - ->> Sort(["a", "b", "D", "x", "J", "C"], Value) -["a","b","C","D","J","x"] - ->> Sort(["a", "b", "D", "x", "J", "C"], Value, SortOrder.Descending) -["x","J","D","C","b","a"] - ->> Sort(["a", "b", "B", "x", "D", "C"], Value) -["a","b","B","C","D","x"] - ->> Sort(["Hello", "hellO", "hello", "heLLo", "HELLO", "hElLo"], Value) -["hello","hellO","heLLo","hElLo","Hello","HELLO"] - ->> Sort(["Hello", "hellO", "1", "%", "2", "hElLo"], Value) -["%","1","2","hellO","hElLo","Hello"] - ->> Sort(["a", "b", Blank(), "x", "J", "C"], Value) -["a","b","C","J","x",Blank()] - ->> Sort(["a", "b", Blank(), "x", "J", "C"], Value, SortOrder.Descending) -["x","J","C","b","a",Blank()] - -// ******** BOOLEAN PARAMETERS ******** - ->> Sort([true,false,true,false,true], Value) -[false,false,true,true,true] - ->> Sort([true,false,true,false,true], Value, SortOrder.Descending) -[true,true,true,false,false] - ->> Sort([true,false,Blank(),false,true], Value) -[false,false,true,true,Blank()] - ->> Sort([true,false,Blank(),false,true], Value, SortOrder.Descending) -[true,true,false,false,Blank()] - -// ******** DATE PARAMETERS ******** - ->> Sort([Date(2020, 01, 05), Date(2020, 01, 01), Date(1995, 01, 01)], Value) -[Date(1995,1,1),Date(2020,1,1),Date(2020,1,5)] - ->> Sort([Date(2020, 01, 05), Date(2012,12,14), Date(2022,1,1)], Value) -[Date(2012,12,14),Date(2020,1,5),Date(2022,1,1)] - ->> Sort([Date(2020, 01, 05), Date(2020, 01, 01), Date(1995, 01, 01)], Value, SortOrder.Descending) -[Date(2020,1,5),Date(2020,1,1),Date(1995,1,1)] - ->> Sort([Date(2020, 01, 05), Date(2012,12,14), Blank(), Date(2022,1,1)], Value, SortOrder.Descending) -[Date(2022,1,1),Date(2020,1,5),Date(2012,12,14),Blank()] - -// ******** TIME PARAMETERS ******** - ->> Sort([Time(12,35,55), Time(23, 01, 01), Time(5,30,50)], Value) -["05:30:50.000","12:35:55.000","23:01:01.000"] - ->> Sort([Time(12,35,55), Time(23, 01, 01), Time(5,30,50)], Value, SortOrder.Descending) -["23:01:01.000","12:35:55.000","05:30:50.000"] - ->> Sort([Time(17,29,0), Blank(), Time(5,30,50)], Value) -["05:30:50.000","17:29:00.000",Blank()] - -// ******** DATETIME PARAMETERS ******** - ->> Sort([DateTime(2019,5,16,8,0,0), DateTime(2019,5,16,20,0,0), DateTime(2019,5,16,20,0,1)], Value) -[DateTime(2019,5,16,8,0,0,0),DateTime(2019,5,16,20,0,0,0),DateTime(2019,5,16,20,0,1,0)] - ->> Sort([DateTime(2019,5,16,8,0,0), DateTime(2019,5,16,20,0,0), DateTime(2019,5,16,20,0,1)], Value, SortOrder.Descending) -[DateTime(2019,5,16,20,0,1,0),DateTime(2019,5,16,20,0,0,0),DateTime(2019,5,16,8,0,0,0)] - -// ******** NULL and ERROR PARAMETERS ******** - ->> Sort([Blank()],true) -[Blank()] - -//Blank passed as the table record ->> Sort([Blank()], Value) -[Blank()] - ->> Sort([Blank(),Blank()], Value, SortOrder.Descending) -[Blank(),Blank()] - ->> Sort([-2, Blank(), 5, 1, 2], Value) -[-2,1,2,5,Blank()] - ->> Sort([-2, Blank(), 5, 1, 2], Value, SortOrder.Descending) -[5,2,1,-2,Blank()] - ->> Sort([-2, -Blank(), 5, 1, 2], Value) -[-2,0,1,2,5] - -//Blank sort formula parameter ->> Sort([Blank()], Blank()) -[Blank()] - ->> Sort([-2, -1, 5, 1, 2], Blank()) -[-2,-1,5,1,2] - ->> Sort([-2, -1, 5, 1, 2]) -[-2,-1,5,1,2] - -// ******** MULTI COLUMN SORT CASES ******** - -// SORT ON NUMERIC COLUMN "AGE" - ASCENDING ORDER - ->> Sort(Table( - { First: "Bob", Last: Blank(), Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: "Liam", Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Fiona", Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, - { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, - { First: "Helio", Last: Blank(), Age: 63, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, - { First: "Titan", Last: Blank(), Age: 79, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, - { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, - { First: "Ivor", Last: Blank(), Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }),Age) -Table({Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:Blank(),Vaccinated:true},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:63,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:79,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:Blank(),First:"Liam",Gender:"Male",HasDog:Blank(),Last:"Clark",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Allen",Vaccinated:Blank()},{Age:Blank(),First:"Fiona",Gender:"Female",HasDog:Blank(),Last:"Brown",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Walker",Vaccinated:Blank()},{Age:Blank(),First:"Ivor",Gender:"Female",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()}) - - -// SORT ON NUMERIC COLUMN "AGE" - DESCENDING ORDER - ->> Sort(Table( - { First: "Bob", Last: Blank(), Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: "Liam", Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Fiona", Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, - { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, - { First: "Helio", Last: Blank(), Age: 63, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, - { First: "Titan", Last: Blank(), Age: 79, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, - { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, - { First: "Ivor", Last: Blank(), Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }),Age, SortOrder.Descending) -Table({Age:79,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:63,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:Blank(),Vaccinated:true},{Age:Blank(),First:"Liam",Gender:"Male",HasDog:Blank(),Last:"Clark",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Allen",Vaccinated:Blank()},{Age:Blank(),First:"Fiona",Gender:"Female",HasDog:Blank(),Last:"Brown",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Walker",Vaccinated:Blank()},{Age:Blank(),First:"Ivor",Gender:"Female",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()}) - -// SORT ON BOOLEAN COLUMN "Vaccinated" - ASCENDING ORDER - ->> Sort(Table( - { First: "Bob", Last: Blank(), Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: "Liam", Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Fiona", Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, - { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, - { First: "Helio", Last: Blank(), Age: 63, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, - { First: "Titan", Last: Blank(), Age: 79, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, - { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, - { First: "Ivor", Last: Blank(), Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }), Vaccinated) -Table({Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:Blank(),Vaccinated:true},{Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true},{Age:Blank(),First:"Liam",Gender:"Male",HasDog:Blank(),Last:"Clark",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Allen",Vaccinated:Blank()},{Age:Blank(),First:"Fiona",Gender:"Female",HasDog:Blank(),Last:"Brown",Vaccinated:Blank()},{Age:63,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:79,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Walker",Vaccinated:Blank()},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:Blank(),First:"Ivor",Gender:"Female",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()}) - - -// SORT ON BOOLEAN COLUMN "Vaccinated" - DESCENDING ORDER - ->> Sort(Table( - { First: "Bob", Last: Blank(), Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: "Liam", Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Fiona", Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, - { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, - { First: "Helio", Last: Blank(), Age: 63, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, - { First: "Titan", Last: Blank(), Age: 79, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, - { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, - { First: "Ivor", Last: Blank(), Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }), Vaccinated, SortOrder.Descending) -Table({Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:Blank(),Vaccinated:true},{Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true},{Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:Blank(),First:"Liam",Gender:"Male",HasDog:Blank(),Last:"Clark",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Allen",Vaccinated:Blank()},{Age:Blank(),First:"Fiona",Gender:"Female",HasDog:Blank(),Last:"Brown",Vaccinated:Blank()},{Age:63,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:79,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Walker",Vaccinated:Blank()},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:Blank(),First:"Ivor",Gender:"Female",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()}) - -// SORT ON STRING COLUMN "First" - ASCENDING ORDER - ->> Sort(Table( - { First: "Bob", Last: Blank(), Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: "Liam", Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Fiona", Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, - { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, - { First: "Helio", Last: Blank(), Age: 63, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, - { First: "Titan", Last: Blank(), Age: 79, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, - { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, - { First: "Ivor", Last: Blank(), Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }),First) -Table({Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:Blank(),Vaccinated:true},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:Blank(),First:"Fiona",Gender:"Female",HasDog:Blank(),Last:"Brown",Vaccinated:Blank()},{Age:63,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:Blank(),First:"Ivor",Gender:"Female",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true},{Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:Blank(),First:"Liam",Gender:"Male",HasDog:Blank(),Last:"Clark",Vaccinated:Blank()},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:79,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Allen",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Walker",Vaccinated:Blank()}) - - -// SORT ON STRING COLUMN "First" - DESCENDING ORDER - ->> Sort(Table( - { First: "Bob", Last: Blank(), Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: "Liam", Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Fiona", Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, - { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, - { First: "Helio", Last: Blank(), Age: 63, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, - { First: "Titan", Last: Blank(), Age: 79, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, - { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, - { First: "Ivor", Last: Blank(), Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }), First, SortOrder.Descending) -Table({Age:79,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:Blank(),First:"Liam",Gender:"Male",HasDog:Blank(),Last:"Clark",Vaccinated:Blank()},{Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true},{Age:Blank(),First:"Ivor",Gender:"Female",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:63,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:Blank(),First:"Fiona",Gender:"Female",HasDog:Blank(),Last:"Brown",Vaccinated:Blank()},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:Blank(),Vaccinated:true},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Allen",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Walker",Vaccinated:Blank()}) - -// EMBEDDED SORT - FIRST ON STRING COLUMN "First" AND THEN ON "Last" - ASCENDING ORDER - ->> Sort(Sort(Table( - { First: "Bob", Last: Blank(), Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: "Liam", Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Fiona", Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, - { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, - { First: "Helio", Last: Blank(), Age: 63, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, - { First: "Titan", Last: Blank(), Age: 79, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, - { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, - { First: "Ivor", Last: Blank(), Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }),Last),First) -Table({Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:263,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true},{Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:792,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Allen",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:Blank(),Last:"Brown",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Clark",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:Blank(),Last:"Rodriguez",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Walker",Vaccinated:Blank()}) - - -// EMBEDDED SORT - FIRST ON STRING COLUMN "First" AND THEN ON "Last" - DESCENDING ORDER - ->> Sort(Sort(Table( - { First: "Bob", Last: Blank(), Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: "Liam", Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Fiona", Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, - { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, - { First: "Helio", Last: Blank(), Age: 63, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, - { First: "Titan", Last: Blank(), Age: 79, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, - { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, - { First: "Ivor", Last: Blank(), Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }),Last,SortOrder.Descending),First,SortOrder.Descending) -Table({Age:792,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true},{Age:263,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Walker",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:Blank(),Last:"Rodriguez",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Clark",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:Blank(),Last:"Brown",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Allen",Vaccinated:Blank()}) - - -// Blank() PASSED AS SORT FORMULA - ASCENDING ORDER - ->> Sort(Table( - { First: "Bob", Last: Blank(), Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: "Liam", Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Fiona", Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, - { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, - { First: "Helio", Last: Blank(), Age: 63, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, - { First: "Titan", Last: Blank(), Age: 79, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, - { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, - { First: "Ivor", Last: Blank(), Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }), Blank()) -Table({Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:Blank(),Vaccinated:true},{Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:Blank(),First:"Liam",Gender:"Male",HasDog:Blank(),Last:"Clark",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Allen",Vaccinated:Blank()},{Age:Blank(),First:"Fiona",Gender:"Female",HasDog:Blank(),Last:"Brown",Vaccinated:Blank()},{Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:63,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:79,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Walker",Vaccinated:Blank()},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:Blank(),First:"Ivor",Gender:"Female",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true}) - - -// Blank() PASSED AS SORT FORMULA - DESCENDING ORDER - ->> Sort(Table( - { First: "Bob", Last: Blank(), Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: "Liam", Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Fiona", Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, - { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, - { First: "Helio", Last: Blank(), Age: 63, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, - { First: "Titan", Last: Blank(), Age: 79, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, - { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, - { First: "Ivor", Last: Blank(), Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }), Blank(),SortOrder.Descending) -Table({Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:Blank(),Vaccinated:true},{Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:Blank(),First:"Liam",Gender:"Male",HasDog:Blank(),Last:"Clark",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Allen",Vaccinated:Blank()},{Age:Blank(),First:"Fiona",Gender:"Female",HasDog:Blank(),Last:"Brown",Vaccinated:Blank()},{Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:63,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:79,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Walker",Vaccinated:Blank()},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:Blank(),First:"Ivor",Gender:"Female",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true}) - - -// SORT A TABLE WITH ERROR ROWS - ASCENDING ORDER - -//Sort by First Name ->> Sort(Table( - { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, - { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, - If(Left("Hello", -1) = "", { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }), - { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false } - ),If(IsBlankOrError(ThisRecord), Blank(), First)) -Table({First:"Alice",Last:"Smith",Age:5,Gender:"Female",Vaccinated:true,HasDog:true},{First:"Bob",Last:"Smith",Age:2,Gender:"Male",Vaccinated:true,HasDog:true},{First:"Dora",Last:"TheExplorer",Age:4,Gender:"Female",Vaccinated:false,HasDog:true},{First:"John",Last:"Batali",Age:17,Gender:"Male",Vaccinated:false,HasDog:false},Error({Kind:ErrorKind.InvalidArgument})) - - -// SORT A TABLE WITH BLANK ROWS - ASCENDING ORDER - ->> Sort(Table( - { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, - Blank(), - Blank(), - Blank(), - { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, - { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, - { First: "Helio", Last: Blank(), Age: 263, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, - { First: "Titan", Last: Blank(), Age: 792, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - Blank(), - { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, - { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, - Blank(), - { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }),If(IsBlank(Value),Blank(),Value.First)) -Table({Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:263,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true},{Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:792,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},Blank(),Blank(),Blank(),Blank(),Blank()) - -// SORT ON TABLE WITH WITH ERROR VALUES FOR SOME COLUMNS, BUT DOESN'T TOUCH ERROR - ASCENDING ORDER - ->> Sort(Table( - { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, - { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, - { First: "Helio", Last: Left("Hello", -1), Age: 263, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, - { First: "Titan", Last: Left("Hello", -1), Age: 792, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }),First) -Table({First:"Alice",Last:"Smith",Age:5,Gender:"Female",Vaccinated:true,HasDog:true},{First:"Bob",Last:"Smith",Age:2,Gender:"Male",Vaccinated:true,HasDog:true},{First:"Emily",Last:"Jones",Age:29,Gender:"Female",Vaccinated:true,HasDog:false},{First:"Helio",Last:Error({Kind:ErrorKind.InvalidArgument}),Age:263,Gender:"Unknown",Vaccinated:Blank(),HasDog:false},{First:"John",Last:"Batali",Age:17,Gender:"Male",Vaccinated:false,HasDog:false},{First:"Mary",Last:"Harris",Age:48,Gender:"Female",Vaccinated:false,HasDog:false},{First:"Titan",Last:Error({Kind:ErrorKind.InvalidArgument}),Age:792,Gender:"Unknown",Vaccinated:Blank(),HasDog:false}) - - -// SORT ON TABLE WITH ERROR VALUES FOR SOME COLUMNS, BUT THE ERROR IS HANDLED - ASCENDING ORDER - ->> Sort(Table( - { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, - { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, - { First: "Helio", Last: Left("Hello", -1), Age: 263, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, - { First: "Titan", Last: Left("Hello", -1), Age: 792, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }),If(IsBlankOrError(Last),"",Last)) -Table({First:"Helio",Last:Error({Kind:ErrorKind.InvalidArgument}),Age:263,Gender:"Unknown",Vaccinated:Blank(),HasDog:false},{First:"Titan",Last:Error({Kind:ErrorKind.InvalidArgument}),Age:792,Gender:"Unknown",Vaccinated:Blank(),HasDog:false},{First:"John",Last:"Batali",Age:17,Gender:"Male",Vaccinated:false,HasDog:false},{First:"Mary",Last:"Harris",Age:48,Gender:"Female",Vaccinated:false,HasDog:false},{First:"Emily",Last:"Jones",Age:29,Gender:"Female",Vaccinated:true,HasDog:false},{First:"Bob",Last:"Smith",Age:2,Gender:"Male",Vaccinated:true,HasDog:true},{First:"Alice",Last:"Smith",Age:5,Gender:"Female",Vaccinated:true,HasDog:true}) - -// SORT HIERARCHICAL DATA ON BOOL EXPRESSION - ASCENDING ORDER - ->> Sort(Table( - { First: "Bob", Age: 2, Properties: { Vaccinated: true, HasDog: true }}, - { First: "Alice", Age: 5, Properties: { Vaccinated: true, HasDog: true }}, - { First: "John", Age: 17, Properties: { Vaccinated: false, HasDog: false }}, - { First: "Emily", Age: 29, Properties: { Vaccinated: true, HasDog: Blank() }}, - { First: "Helio", Age: 63, Properties: { Vaccinated: Blank(), HasDog: Blank() }}, - { First: "Mary", Age: 48, Properties: Blank()}, - { First: "Titan", Age: 79, Properties: Blank() }),Coalesce(Properties.HasDog, false)) -Table({Age:17,First:"John",Properties:{HasDog:false,Vaccinated:false}},{Age:2,First:"Bob",Properties:{HasDog:true,Vaccinated:true}},{Age:5,First:"Alice",Properties:{HasDog:true,Vaccinated:true}},{Age:29,First:"Emily",Properties:{HasDog:Blank(),Vaccinated:true}},{Age:63,First:"Helio",Properties:{HasDog:Blank(),Vaccinated:Blank()}},{Age:48,First:"Mary",Properties:Blank()},{Age:79,First:"Titan",Properties:Blank()}) +// ******** NUMBER PARAMETERS ******** + +>> Sort([-2, -1, 5, 1, 2], Value) +[-2,-1,1,2,5] + +>> Sort([-2, -1, 5, 1, 2], Value, SortOrder.Descending) +[5,2,1,-1,-2] + +>> Sort([-2, -1, -1E+308, 1, 2], Value) +[-1e+308,-2,-1,1,2] + +>> Sort([-2, -1, 1E+308, 1, 2], Value) +[-2,-1,1,2,1e+308] + +>> Sort([{a:8, b:-1}, {a:2}, {b:5}], Value.a) +[{a:2},{a:8,b:-1},{b:5}] + +>> Sort([{a:8, b:-1}, {a:2}, {b:5}], Value.b, SortOrder.Descending) +[{b:5},{a:8,b:-1},{a:2}] + +// ******** STRING PARAMETERS ******** + +>> Sort(["a", "b", "D", "x", "J", "C"], Value) +["a","b","C","D","J","x"] + +>> Sort(["a", "b", "D", "x", "J", "C"], Value, SortOrder.Descending) +["x","J","D","C","b","a"] + +>> Sort(["a", "b", "B", "x", "D", "C"], Value) +["a","b","B","C","D","x"] + +>> Sort(["Hello", "hellO", "hello", "heLLo", "HELLO", "hElLo"], Value) +["hello","hellO","heLLo","hElLo","Hello","HELLO"] + +>> Sort(["Hello", "hellO", "1", "%", "2", "hElLo"], Value) +["%","1","2","hellO","hElLo","Hello"] + +>> Sort(["a", "b", Blank(), "x", "J", "C"], Value) +["a","b","C","J","x",Blank()] + +>> Sort(["a", "b", Blank(), "x", "J", "C"], Value, SortOrder.Descending) +["x","J","C","b","a",Blank()] + +// ******** BOOLEAN PARAMETERS ******** + +>> Sort([true,false,true,false,true], Value) +[false,false,true,true,true] + +>> Sort([true,false,true,false,true], Value, SortOrder.Descending) +[true,true,true,false,false] + +>> Sort([true,false,Blank(),false,true], Value) +[false,false,true,true,Blank()] + +>> Sort([true,false,Blank(),false,true], Value, SortOrder.Descending) +[true,true,false,false,Blank()] + +// ******** DATE PARAMETERS ******** + +>> Sort([Date(2020, 01, 05), Date(2020, 01, 01), Date(1995, 01, 01)], Value) +[Date(1995,1,1),Date(2020,1,1),Date(2020,1,5)] + +>> Sort([Date(2020, 01, 05), Date(2012,12,14), Date(2022,1,1)], Value) +[Date(2012,12,14),Date(2020,1,5),Date(2022,1,1)] + +>> Sort([Date(2020, 01, 05), Date(2020, 01, 01), Date(1995, 01, 01)], Value, SortOrder.Descending) +[Date(2020,1,5),Date(2020,1,1),Date(1995,1,1)] + +>> Sort([Date(2020, 01, 05), Date(2012,12,14), Blank(), Date(2022,1,1)], Value, SortOrder.Descending) +[Date(2022,1,1),Date(2020,1,5),Date(2012,12,14),Blank()] + +// ******** TIME PARAMETERS ******** + +>> Sort([Time(12,35,55), Time(23, 01, 01), Time(5,30,50)], Value) +["05:30:50.000","12:35:55.000","23:01:01.000"] + +>> Sort([Time(12,35,55), Time(23, 01, 01), Time(5,30,50)], Value, SortOrder.Descending) +["23:01:01.000","12:35:55.000","05:30:50.000"] + +>> Sort([Time(17,29,0), Blank(), Time(5,30,50)], Value) +["05:30:50.000","17:29:00.000",Blank()] + +// ******** DATETIME PARAMETERS ******** + +>> Sort([DateTime(2019,5,16,8,0,0), DateTime(2019,5,16,20,0,0), DateTime(2019,5,16,20,0,1)], Value) +[DateTime(2019,5,16,8,0,0,0),DateTime(2019,5,16,20,0,0,0),DateTime(2019,5,16,20,0,1,0)] + +>> Sort([DateTime(2019,5,16,8,0,0), DateTime(2019,5,16,20,0,0), DateTime(2019,5,16,20,0,1)], Value, SortOrder.Descending) +[DateTime(2019,5,16,20,0,1,0),DateTime(2019,5,16,20,0,0,0),DateTime(2019,5,16,8,0,0,0)] + +// ******** NULL and ERROR PARAMETERS ******** + +>> Sort([Blank()],true) +[Blank()] + +//Blank passed as the table record +>> Sort([Blank()], Value) +[Blank()] + +>> Sort([Blank(),Blank()], Value, SortOrder.Descending) +[Blank(),Blank()] + +>> Sort([-2, Blank(), 5, 1, 2], Value) +[-2,1,2,5,Blank()] + +>> Sort([-2, Blank(), 5, 1, 2], Value, SortOrder.Descending) +[5,2,1,-2,Blank()] + +>> Sort([-2, -Blank(), 5, 1, 2], Value) +[-2,0,1,2,5] + +//Blank sort formula parameter +>> Sort([Blank()], Blank()) +[Blank()] + +>> Sort([-2, -1, 5, 1, 2], Blank()) +[-2,-1,5,1,2] + +>> Sort([-2, -1, 5, 1, 2]) +[-2,-1,5,1,2] + +// ******** MULTI COLUMN SORT CASES ******** + +// SORT ON NUMERIC COLUMN "AGE" - ASCENDING ORDER + +>> Sort(Table( + { First: "Bob", Last: Blank(), Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: "Liam", Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Fiona", Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, + { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, + { First: "Helio", Last: Blank(), Age: 63, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, + { First: "Titan", Last: Blank(), Age: 79, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, + { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, + { First: "Ivor", Last: Blank(), Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }),Age) +Table({Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:Blank(),Vaccinated:true},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:63,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:79,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:Blank(),First:"Liam",Gender:"Male",HasDog:Blank(),Last:"Clark",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Allen",Vaccinated:Blank()},{Age:Blank(),First:"Fiona",Gender:"Female",HasDog:Blank(),Last:"Brown",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Walker",Vaccinated:Blank()},{Age:Blank(),First:"Ivor",Gender:"Female",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()}) + + +// SORT ON NUMERIC COLUMN "AGE" - DESCENDING ORDER + +>> Sort(Table( + { First: "Bob", Last: Blank(), Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: "Liam", Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Fiona", Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, + { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, + { First: "Helio", Last: Blank(), Age: 63, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, + { First: "Titan", Last: Blank(), Age: 79, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, + { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, + { First: "Ivor", Last: Blank(), Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }),Age, SortOrder.Descending) +Table({Age:79,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:63,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:Blank(),Vaccinated:true},{Age:Blank(),First:"Liam",Gender:"Male",HasDog:Blank(),Last:"Clark",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Allen",Vaccinated:Blank()},{Age:Blank(),First:"Fiona",Gender:"Female",HasDog:Blank(),Last:"Brown",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Walker",Vaccinated:Blank()},{Age:Blank(),First:"Ivor",Gender:"Female",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()}) + +// SORT ON BOOLEAN COLUMN "Vaccinated" - ASCENDING ORDER + +>> Sort(Table( + { First: "Bob", Last: Blank(), Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: "Liam", Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Fiona", Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, + { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, + { First: "Helio", Last: Blank(), Age: 63, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, + { First: "Titan", Last: Blank(), Age: 79, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, + { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, + { First: "Ivor", Last: Blank(), Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }), Vaccinated) +Table({Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:Blank(),Vaccinated:true},{Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true},{Age:Blank(),First:"Liam",Gender:"Male",HasDog:Blank(),Last:"Clark",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Allen",Vaccinated:Blank()},{Age:Blank(),First:"Fiona",Gender:"Female",HasDog:Blank(),Last:"Brown",Vaccinated:Blank()},{Age:63,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:79,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Walker",Vaccinated:Blank()},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:Blank(),First:"Ivor",Gender:"Female",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()}) + + +// SORT ON BOOLEAN COLUMN "Vaccinated" - DESCENDING ORDER + +>> Sort(Table( + { First: "Bob", Last: Blank(), Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: "Liam", Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Fiona", Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, + { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, + { First: "Helio", Last: Blank(), Age: 63, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, + { First: "Titan", Last: Blank(), Age: 79, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, + { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, + { First: "Ivor", Last: Blank(), Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }), Vaccinated, SortOrder.Descending) +Table({Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:Blank(),Vaccinated:true},{Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true},{Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:Blank(),First:"Liam",Gender:"Male",HasDog:Blank(),Last:"Clark",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Allen",Vaccinated:Blank()},{Age:Blank(),First:"Fiona",Gender:"Female",HasDog:Blank(),Last:"Brown",Vaccinated:Blank()},{Age:63,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:79,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Walker",Vaccinated:Blank()},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:Blank(),First:"Ivor",Gender:"Female",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()}) + +// SORT ON STRING COLUMN "First" - ASCENDING ORDER + +>> Sort(Table( + { First: "Bob", Last: Blank(), Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: "Liam", Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Fiona", Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, + { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, + { First: "Helio", Last: Blank(), Age: 63, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, + { First: "Titan", Last: Blank(), Age: 79, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, + { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, + { First: "Ivor", Last: Blank(), Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }),First) +Table({Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:Blank(),Vaccinated:true},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:Blank(),First:"Fiona",Gender:"Female",HasDog:Blank(),Last:"Brown",Vaccinated:Blank()},{Age:63,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:Blank(),First:"Ivor",Gender:"Female",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true},{Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:Blank(),First:"Liam",Gender:"Male",HasDog:Blank(),Last:"Clark",Vaccinated:Blank()},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:79,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Allen",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Walker",Vaccinated:Blank()}) + + +// SORT ON STRING COLUMN "First" - DESCENDING ORDER + +>> Sort(Table( + { First: "Bob", Last: Blank(), Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: "Liam", Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Fiona", Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, + { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, + { First: "Helio", Last: Blank(), Age: 63, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, + { First: "Titan", Last: Blank(), Age: 79, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, + { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, + { First: "Ivor", Last: Blank(), Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }), First, SortOrder.Descending) +Table({Age:79,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:Blank(),First:"Liam",Gender:"Male",HasDog:Blank(),Last:"Clark",Vaccinated:Blank()},{Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true},{Age:Blank(),First:"Ivor",Gender:"Female",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:63,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:Blank(),First:"Fiona",Gender:"Female",HasDog:Blank(),Last:"Brown",Vaccinated:Blank()},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:Blank(),Vaccinated:true},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Allen",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Walker",Vaccinated:Blank()}) + +// EMBEDDED SORT - FIRST ON STRING COLUMN "First" AND THEN ON "Last" - ASCENDING ORDER + +>> Sort(Sort(Table( + { First: "Bob", Last: Blank(), Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: "Liam", Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Fiona", Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, + { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, + { First: "Helio", Last: Blank(), Age: 63, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, + { First: "Titan", Last: Blank(), Age: 79, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, + { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, + { First: "Ivor", Last: Blank(), Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }),Last),First) +Table({Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:263,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true},{Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:792,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Allen",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:Blank(),Last:"Brown",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Clark",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:Blank(),Last:"Rodriguez",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Walker",Vaccinated:Blank()}) + + +// EMBEDDED SORT - FIRST ON STRING COLUMN "First" AND THEN ON "Last" - DESCENDING ORDER + +>> Sort(Sort(Table( + { First: "Bob", Last: Blank(), Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: "Liam", Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Fiona", Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, + { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, + { First: "Helio", Last: Blank(), Age: 63, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, + { First: "Titan", Last: Blank(), Age: 79, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, + { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, + { First: "Ivor", Last: Blank(), Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }),Last,SortOrder.Descending),First,SortOrder.Descending) +Table({Age:792,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true},{Age:263,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Walker",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:Blank(),Last:"Rodriguez",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Clark",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:Blank(),Last:"Brown",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Allen",Vaccinated:Blank()}) + + +// Blank() PASSED AS SORT FORMULA - ASCENDING ORDER + +>> Sort(Table( + { First: "Bob", Last: Blank(), Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: "Liam", Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Fiona", Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, + { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, + { First: "Helio", Last: Blank(), Age: 63, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, + { First: "Titan", Last: Blank(), Age: 79, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, + { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, + { First: "Ivor", Last: Blank(), Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }), Blank()) +Table({Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:Blank(),Vaccinated:true},{Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:Blank(),First:"Liam",Gender:"Male",HasDog:Blank(),Last:"Clark",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Allen",Vaccinated:Blank()},{Age:Blank(),First:"Fiona",Gender:"Female",HasDog:Blank(),Last:"Brown",Vaccinated:Blank()},{Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:63,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:79,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Walker",Vaccinated:Blank()},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:Blank(),First:"Ivor",Gender:"Female",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true}) + + +// Blank() PASSED AS SORT FORMULA - DESCENDING ORDER + +>> Sort(Table( + { First: "Bob", Last: Blank(), Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: "Liam", Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Fiona", Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, + { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, + { First: "Helio", Last: Blank(), Age: 63, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, + { First: "Titan", Last: Blank(), Age: 79, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, + { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, + { First: "Ivor", Last: Blank(), Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }), Blank(),SortOrder.Descending) +Table({Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:Blank(),Vaccinated:true},{Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:Blank(),First:"Liam",Gender:"Male",HasDog:Blank(),Last:"Clark",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Allen",Vaccinated:Blank()},{Age:Blank(),First:"Fiona",Gender:"Female",HasDog:Blank(),Last:"Brown",Vaccinated:Blank()},{Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:63,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:79,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Walker",Vaccinated:Blank()},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:Blank(),First:"Ivor",Gender:"Female",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true}) + + +// SORT A TABLE WITH ERROR ROWS - ASCENDING ORDER + +//Sort by First Name +>> Sort(Table( + { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, + { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, + If(Left("Hello", -1) = "", { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }), + { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false } + ),If(IsBlankOrError(ThisRecord), Blank(), First)) +Table({First:"Alice",Last:"Smith",Age:5,Gender:"Female",Vaccinated:true,HasDog:true},{First:"Bob",Last:"Smith",Age:2,Gender:"Male",Vaccinated:true,HasDog:true},{First:"Dora",Last:"TheExplorer",Age:4,Gender:"Female",Vaccinated:false,HasDog:true},{First:"John",Last:"Batali",Age:17,Gender:"Male",Vaccinated:false,HasDog:false},Error({Kind:ErrorKind.InvalidArgument})) + + +// SORT A TABLE WITH BLANK ROWS - ASCENDING ORDER + +>> Sort(Table( + { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, + Blank(), + Blank(), + Blank(), + { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, + { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, + { First: "Helio", Last: Blank(), Age: 263, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, + { First: "Titan", Last: Blank(), Age: 792, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + Blank(), + { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, + { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, + Blank(), + { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }),If(IsBlank(Value),Blank(),Value.First)) +Table({Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:263,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true},{Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:792,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},Blank(),Blank(),Blank(),Blank(),Blank()) + +// SORT ON TABLE WITH WITH ERROR VALUES FOR SOME COLUMNS, BUT DOESN'T TOUCH ERROR - ASCENDING ORDER + +>> Sort(Table( + { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, + { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, + { First: "Helio", Last: Left("Hello", -1), Age: 263, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, + { First: "Titan", Last: Left("Hello", -1), Age: 792, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }),First) +Table({First:"Alice",Last:"Smith",Age:5,Gender:"Female",Vaccinated:true,HasDog:true},{First:"Bob",Last:"Smith",Age:2,Gender:"Male",Vaccinated:true,HasDog:true},{First:"Emily",Last:"Jones",Age:29,Gender:"Female",Vaccinated:true,HasDog:false},{First:"Helio",Last:Error({Kind:ErrorKind.InvalidArgument}),Age:263,Gender:"Unknown",Vaccinated:Blank(),HasDog:false},{First:"John",Last:"Batali",Age:17,Gender:"Male",Vaccinated:false,HasDog:false},{First:"Mary",Last:"Harris",Age:48,Gender:"Female",Vaccinated:false,HasDog:false},{First:"Titan",Last:Error({Kind:ErrorKind.InvalidArgument}),Age:792,Gender:"Unknown",Vaccinated:Blank(),HasDog:false}) + + +// SORT ON TABLE WITH ERROR VALUES FOR SOME COLUMNS, BUT THE ERROR IS HANDLED - ASCENDING ORDER + +>> Sort(Table( + { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, + { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, + { First: "Helio", Last: Left("Hello", -1), Age: 263, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, + { First: "Titan", Last: Left("Hello", -1), Age: 792, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }),If(IsBlankOrError(Last),"",Last)) +Table({First:"Helio",Last:Error({Kind:ErrorKind.InvalidArgument}),Age:263,Gender:"Unknown",Vaccinated:Blank(),HasDog:false},{First:"Titan",Last:Error({Kind:ErrorKind.InvalidArgument}),Age:792,Gender:"Unknown",Vaccinated:Blank(),HasDog:false},{First:"John",Last:"Batali",Age:17,Gender:"Male",Vaccinated:false,HasDog:false},{First:"Mary",Last:"Harris",Age:48,Gender:"Female",Vaccinated:false,HasDog:false},{First:"Emily",Last:"Jones",Age:29,Gender:"Female",Vaccinated:true,HasDog:false},{First:"Bob",Last:"Smith",Age:2,Gender:"Male",Vaccinated:true,HasDog:true},{First:"Alice",Last:"Smith",Age:5,Gender:"Female",Vaccinated:true,HasDog:true}) + +// SORT HIERARCHICAL DATA ON BOOL EXPRESSION - ASCENDING ORDER + +>> Sort(Table( + { First: "Bob", Age: 2, Properties: { Vaccinated: true, HasDog: true }}, + { First: "Alice", Age: 5, Properties: { Vaccinated: true, HasDog: true }}, + { First: "John", Age: 17, Properties: { Vaccinated: false, HasDog: false }}, + { First: "Emily", Age: 29, Properties: { Vaccinated: true, HasDog: Blank() }}, + { First: "Helio", Age: 63, Properties: { Vaccinated: Blank(), HasDog: Blank() }}, + { First: "Mary", Age: 48, Properties: Blank()}, + { First: "Titan", Age: 79, Properties: Blank() }),Coalesce(Properties.HasDog, false)) +Table({Age:17,First:"John",Properties:{HasDog:false,Vaccinated:false}},{Age:2,First:"Bob",Properties:{HasDog:true,Vaccinated:true}},{Age:5,First:"Alice",Properties:{HasDog:true,Vaccinated:true}},{Age:29,First:"Emily",Properties:{HasDog:Blank(),Vaccinated:true}},{Age:63,First:"Helio",Properties:{HasDog:Blank(),Vaccinated:Blank()}},{Age:48,First:"Mary",Properties:Blank()},{Age:79,First:"Titan",Properties:Blank()}) diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/NotYetReady/Switch.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/NotYetReady/Switch.txt similarity index 96% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/NotYetReady/Switch.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/NotYetReady/Switch.txt index 53955ae2f..2966504cd 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/NotYetReady/Switch.txt +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/NotYetReady/Switch.txt @@ -1,192 +1,192 @@ -// =========================Null, Empty String and Error cases ========================= - -// Switch case with Error as the condition and as exact match ->> Switch(1/0,1,0,1/0,3,2,5,2,7,11) -Error({Kind:ErrorKind.Div0}) - -// =========================Null, Empty String and Error cases ========================= - ->> Switch(1/0,"zero","one","two")", -Error({Kind:ErrorKind.Div0}) - ->> Switch("zero",1/0,"one","two")", -Error({Kind:ErrorKind.Div0}) - ->> Switch("zero","one",1/0,"two")", -"two" - ->> Switch("zero","one","two",1/0)", -Error({Kind:ErrorKind.Div0}) - -// ====================== FIRST RESULT NUMBER, SECOND RESULT OTHER DATAYPE ====================== - -//Number-Date ->> Switch("Case1","Case2",1,"Case1",Date(2000,1,4)) -946972800000 - ->> Switch("Case1","Case2",1,"Case1",DateValue("1/10/2000")) -947491200000 - -//Number-Time ->> Switch("Case1","Case2",1,"Case1",Time(6,30,40)) -52240000 - ->> Switch("Case1","Case2",1,"Case1",TimeValue("6:00:00")) -50400000 - -//Number-DateTime ->> Switch("Case1","Case2",1,"Case1",DateTimeValue("4/1/2001 10:00:00")) -986144400000 - -// ====================== FIRST RESULT STRING, SECOND RESULT OTHER DATAYPE ====================== - ->> Switch("Case1","Case2","1","Case1",DateValue("1/10/2000")) -Date(2000,1,10) - -//String-Time ->> Switch("Case1","Case2","1","Case1",Time(6,30,40)) -52240000 - ->> Switch("Case1","Case2","1","Case1",TimeValue("6:00:00")) -50400000 - -//String-DateTime ->> Switch("Case1","Case2","1","Case1",DateTimeValue("4/1/2001 10:00:00")) -986144400000 - -// ====================== FIRST RESULT BOOLEAN, SECOND RESULT OTHER DATAYPE ====================== - ->> Switch("Case1","Case2",true,"Case1","1") -false - ->> Switch("Case1","Case2",true,"Case1","AB$%^") -false - -//Boolean-Date ->> Switch("Case1","Case2",true,"Case1",Date(2000,1,4)) -false - ->> Switch("Case1","Case2",false,"Case1",DateValue("1/10/2000")) -false - -//Boolean-Time ->> Switch("Case1","Case2",true,"Case1",Time(6,30,40)) -false - ->> Switch("Case1","Case2",false,"Case1",TimeValue("6:00:00")) -false - -//Boolean-DateTime ->> Switch("Case1","Case2",true,"Case1",DateTimeValue("4/1/2001 10:00:00")) -false - -// ====================== FIRST RESULT DATE, SECOND RESULT OTHER DATAYPE ====================== - -//Date-Number ->> Switch("Case1","Case2",Date(2000,1,4),"Case1",0) -Date(1969,12,31) - ->> Switch("Case1","Case2",Date(2000,1,4),"Case1",-23.5) -Date(1969,12,31) - ->> Switch("Case1","Case2",Date(2000,1,4),"Case1",100) -Date(1969,12,31) - -//Date-String ->> Switch("Case1","Case2",Date(2000,1,4),"Case1","1") -Date(2001,1,1) - ->> Switch("Case1","Case2",Date(2000,1,4),"Case1","200") -Date(200,1,1) - ->> Switch("Case1","Case2",Date(2000,1,4),"Case1","-12.5") -Date(2001,12,5) - -//Date-Boolean ->> Switch("Case1","Case2",Date(2000,1,4),"Case1",false) -Error({Kind:ErrorKind.InvalidArgument}) - ->> Switch("Case1","Case2",Date(2000,1,4),"Case1",true) -Error({Kind:ErrorKind.InvalidArgument}) - ->> Switch("Case1","Case2",Date(2000,1,4),"Case1",DateValue("1/10/2000")) -Date(2000,1,10) - -//Date-Time ->> Switch("Case1","Case2",Date(2000,1,4),"Case1",Time(6,30,40)) -1/1/1970 6:30 AM - ->> Switch("Case1","Case2",Date(2000,1,4),"Case1",TimeValue("6:00:00")) -1/1/1970 6:00 AM - -// ====================== FIRST RESULT TIME, SECOND RESULT OTHER DATAYPE ====================== - -//Time-Number ->> Switch("Case1","Case2",Time(6,30,30),"Case1",0) -4:00 PM - ->> Switch("Case1","Case2",Time(6,30,30),"Case1",-23.5) -3:59 PM - ->> Switch("Case1","Case2",Time(6,30,30),"Case1",100) -4:00 PM - ->> Switch("Case1","Case2",Time(6,30,30),"Case1","-12.5") -12:00 AM - -//Time-Boolean ->> Switch("Case1","Case2",Time(6,30,30),"Case1",false) -Error({Kind:ErrorKind.Div0}) - ->> Switch("Case1","Case2",Time(6,30,30),"Case1",true) -Error({Kind:ErrorKind.Div0}) - -//Time-Date ->> Switch("Case1","Case2",Time(6,30,30),"Case1",Date(2000,1,5)) -1/5/2000 12:00 AM - ->> Switch("Case1","Case2",Time(6,30,30),"Case1",DateValue("1/10/2000")) -1/10/2000 12:00 AM - -// ====================== FIRST RESULT DATETIME, SECOND RESULT OTHER DATAYPE ====================== - -//DateTime-Number ->> Switch("Case1","Case2",DateTimeValue("4/1/2001 10:00:00"),"Case1",0) -12/31/1969 4:00 PM - ->> Switch("Case1","Case2",DateTimeValue("4/1/2001 10:00:00"),"Case1",-23.5) -12/31/1969 3:59 PM - ->> Switch("Case1","Case2",DateTimeValue("4/1/2001 10:00:00"),"Case1",100) -12/31/1969 4:00 PM - -//DateTime-String ->> Switch("Case1","Case2",DateTimeValue("4/1/2001 10:00:00"),"Case1","1") -1/1/2001 12:00 AM - ->> Switch("Case1","Case2",DateTimeValue("4/1/2001 10:00:00"),"Case1","200") -1/1/200 12:00 AM - ->> Switch("Case1","Case2",DateTimeValue("4/1/2001 10:00:00"),"Case1","-12.5") -12/5/2001 12:00 AM - -//DateTime-Boolean ->> Switch("Case1","Case2",DateTimeValue("4/1/2001 10:00:00"),"Case1",false) -Error({Kind:ErrorKind.Div0}) - ->> Switch("Case1","Case2",DateTimeValue("4/1/2001 10:00:00"),"Case1",true) -Error({Kind:ErrorKind.Div0}) - -//DateTime-Date ->> Switch("Case1","Case2",DateTimeValue("4/1/2001 10:00:00"),"Case1",Date(2000,1,5)) -1/5/2000 12:00 AM - ->> Switch("Case1","Case2",DateTimeValue("4/1/2001 10:00:00"),"Case1",DateValue("1/10/2000")) -1/10/2000 12:00 AM - -//DateTime-Time ->> Switch("Case1","Case2",DateTimeValue("4/1/2001 10:00:00"),"Case1",Time(6,30,40)) -1/1/1970 6:30 AM - ->> Switch("Case1","Case2",DateTimeValue("4/1/2001 10:00:00"),"Case1",TimeValue("6:00:00")) -1/1/1970 6:00 AM +// =========================Null, Empty String and Error cases ========================= + +// Switch case with Error as the condition and as exact match +>> Switch(1/0,1,0,1/0,3,2,5,2,7,11) +Error({Kind:ErrorKind.Div0}) + +// =========================Null, Empty String and Error cases ========================= + +>> Switch(1/0,"zero","one","two")", +Error({Kind:ErrorKind.Div0}) + +>> Switch("zero",1/0,"one","two")", +Error({Kind:ErrorKind.Div0}) + +>> Switch("zero","one",1/0,"two")", +"two" + +>> Switch("zero","one","two",1/0)", +Error({Kind:ErrorKind.Div0}) + +// ====================== FIRST RESULT NUMBER, SECOND RESULT OTHER DATAYPE ====================== + +//Number-Date +>> Switch("Case1","Case2",1,"Case1",Date(2000,1,4)) +946972800000 + +>> Switch("Case1","Case2",1,"Case1",DateValue("1/10/2000")) +947491200000 + +//Number-Time +>> Switch("Case1","Case2",1,"Case1",Time(6,30,40)) +52240000 + +>> Switch("Case1","Case2",1,"Case1",TimeValue("6:00:00")) +50400000 + +//Number-DateTime +>> Switch("Case1","Case2",1,"Case1",DateTimeValue("4/1/2001 10:00:00")) +986144400000 + +// ====================== FIRST RESULT STRING, SECOND RESULT OTHER DATAYPE ====================== + +>> Switch("Case1","Case2","1","Case1",DateValue("1/10/2000")) +Date(2000,1,10) + +//String-Time +>> Switch("Case1","Case2","1","Case1",Time(6,30,40)) +52240000 + +>> Switch("Case1","Case2","1","Case1",TimeValue("6:00:00")) +50400000 + +//String-DateTime +>> Switch("Case1","Case2","1","Case1",DateTimeValue("4/1/2001 10:00:00")) +986144400000 + +// ====================== FIRST RESULT BOOLEAN, SECOND RESULT OTHER DATAYPE ====================== + +>> Switch("Case1","Case2",true,"Case1","1") +false + +>> Switch("Case1","Case2",true,"Case1","AB$%^") +false + +//Boolean-Date +>> Switch("Case1","Case2",true,"Case1",Date(2000,1,4)) +false + +>> Switch("Case1","Case2",false,"Case1",DateValue("1/10/2000")) +false + +//Boolean-Time +>> Switch("Case1","Case2",true,"Case1",Time(6,30,40)) +false + +>> Switch("Case1","Case2",false,"Case1",TimeValue("6:00:00")) +false + +//Boolean-DateTime +>> Switch("Case1","Case2",true,"Case1",DateTimeValue("4/1/2001 10:00:00")) +false + +// ====================== FIRST RESULT DATE, SECOND RESULT OTHER DATAYPE ====================== + +//Date-Number +>> Switch("Case1","Case2",Date(2000,1,4),"Case1",0) +Date(1969,12,31) + +>> Switch("Case1","Case2",Date(2000,1,4),"Case1",-23.5) +Date(1969,12,31) + +>> Switch("Case1","Case2",Date(2000,1,4),"Case1",100) +Date(1969,12,31) + +//Date-String +>> Switch("Case1","Case2",Date(2000,1,4),"Case1","1") +Date(2001,1,1) + +>> Switch("Case1","Case2",Date(2000,1,4),"Case1","200") +Date(200,1,1) + +>> Switch("Case1","Case2",Date(2000,1,4),"Case1","-12.5") +Date(2001,12,5) + +//Date-Boolean +>> Switch("Case1","Case2",Date(2000,1,4),"Case1",false) +Error({Kind:ErrorKind.InvalidArgument}) + +>> Switch("Case1","Case2",Date(2000,1,4),"Case1",true) +Error({Kind:ErrorKind.InvalidArgument}) + +>> Switch("Case1","Case2",Date(2000,1,4),"Case1",DateValue("1/10/2000")) +Date(2000,1,10) + +//Date-Time +>> Switch("Case1","Case2",Date(2000,1,4),"Case1",Time(6,30,40)) +1/1/1970 6:30 AM + +>> Switch("Case1","Case2",Date(2000,1,4),"Case1",TimeValue("6:00:00")) +1/1/1970 6:00 AM + +// ====================== FIRST RESULT TIME, SECOND RESULT OTHER DATAYPE ====================== + +//Time-Number +>> Switch("Case1","Case2",Time(6,30,30),"Case1",0) +4:00 PM + +>> Switch("Case1","Case2",Time(6,30,30),"Case1",-23.5) +3:59 PM + +>> Switch("Case1","Case2",Time(6,30,30),"Case1",100) +4:00 PM + +>> Switch("Case1","Case2",Time(6,30,30),"Case1","-12.5") +12:00 AM + +//Time-Boolean +>> Switch("Case1","Case2",Time(6,30,30),"Case1",false) +Error({Kind:ErrorKind.Div0}) + +>> Switch("Case1","Case2",Time(6,30,30),"Case1",true) +Error({Kind:ErrorKind.Div0}) + +//Time-Date +>> Switch("Case1","Case2",Time(6,30,30),"Case1",Date(2000,1,5)) +1/5/2000 12:00 AM + +>> Switch("Case1","Case2",Time(6,30,30),"Case1",DateValue("1/10/2000")) +1/10/2000 12:00 AM + +// ====================== FIRST RESULT DATETIME, SECOND RESULT OTHER DATAYPE ====================== + +//DateTime-Number +>> Switch("Case1","Case2",DateTimeValue("4/1/2001 10:00:00"),"Case1",0) +12/31/1969 4:00 PM + +>> Switch("Case1","Case2",DateTimeValue("4/1/2001 10:00:00"),"Case1",-23.5) +12/31/1969 3:59 PM + +>> Switch("Case1","Case2",DateTimeValue("4/1/2001 10:00:00"),"Case1",100) +12/31/1969 4:00 PM + +//DateTime-String +>> Switch("Case1","Case2",DateTimeValue("4/1/2001 10:00:00"),"Case1","1") +1/1/2001 12:00 AM + +>> Switch("Case1","Case2",DateTimeValue("4/1/2001 10:00:00"),"Case1","200") +1/1/200 12:00 AM + +>> Switch("Case1","Case2",DateTimeValue("4/1/2001 10:00:00"),"Case1","-12.5") +12/5/2001 12:00 AM + +//DateTime-Boolean +>> Switch("Case1","Case2",DateTimeValue("4/1/2001 10:00:00"),"Case1",false) +Error({Kind:ErrorKind.Div0}) + +>> Switch("Case1","Case2",DateTimeValue("4/1/2001 10:00:00"),"Case1",true) +Error({Kind:ErrorKind.Div0}) + +//DateTime-Date +>> Switch("Case1","Case2",DateTimeValue("4/1/2001 10:00:00"),"Case1",Date(2000,1,5)) +1/5/2000 12:00 AM + +>> Switch("Case1","Case2",DateTimeValue("4/1/2001 10:00:00"),"Case1",DateValue("1/10/2000")) +1/10/2000 12:00 AM + +//DateTime-Time +>> Switch("Case1","Case2",DateTimeValue("4/1/2001 10:00:00"),"Case1",Time(6,30,40)) +1/1/1970 6:30 AM + +>> Switch("Case1","Case2",DateTimeValue("4/1/2001 10:00:00"),"Case1",TimeValue("6:00:00")) +1/1/1970 6:00 AM diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/NotYetReady/Value.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/NotYetReady/Value.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/NotYetReady/Value.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/NotYetReady/Value.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/NotYetReady/WeekNum.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/NotYetReady/WeekNum.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/NotYetReady/WeekNum.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/NotYetReady/WeekNum.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Div_Decimal.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Div_Decimal.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Div_Decimal.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Div_Decimal.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Div_Float.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Div_Float.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Div_Float.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Div_Float.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Div_Float_DecimalSupport.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Div_Float_DecimalSupport.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Div_Float_DecimalSupport.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Div_Float_DecimalSupport.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Eq_Decimal.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Eq_Decimal.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Eq_Decimal.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Eq_Decimal.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Eq_Decimal_V1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Eq_Decimal_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Eq_Decimal_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Eq_Decimal_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Eq_Decimal_V1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Eq_Decimal_V1CompatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Eq_Decimal_V1CompatDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Eq_Decimal_V1CompatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Eq_DifferentTypes_V1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Eq_DifferentTypes_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Eq_DifferentTypes_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Eq_DifferentTypes_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Eq_DifferentTypes_V1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Eq_DifferentTypes_V1CompatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Eq_DifferentTypes_V1CompatDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Eq_DifferentTypes_V1CompatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Eq_Float.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Eq_Float.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Eq_Float.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Eq_Float.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Eq_Float_DecimalSupport.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Eq_Float_DecimalSupport.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Eq_Float_DecimalSupport.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Eq_Float_DecimalSupport.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Eq_Float_V1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Eq_Float_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Eq_Float_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Eq_Float_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Eq_Float_V1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Eq_Float_V1CompatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Eq_Float_V1CompatDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Eq_Float_V1CompatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Eq_Float_V1Compat_DecimalSupport.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Eq_Float_V1Compat_DecimalSupport.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Eq_Float_V1Compat_DecimalSupport.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Eq_Float_V1Compat_DecimalSupport.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Exp_Decimal.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Exp_Decimal.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Exp_Decimal.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Exp_Decimal.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Exp_Float.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Exp_Float.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Exp_Float.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Exp_Float.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Exp_Float_DecimalSupport.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Exp_Float_DecimalSupport.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Exp_Float_DecimalSupport.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Exp_Float_DecimalSupport.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Geq_Decimal.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Geq_Decimal.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Geq_Decimal.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Geq_Decimal.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Geq_Float.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Geq_Float.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Geq_Float.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Geq_Float.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Geq_Float_DecimalSupport.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Geq_Float_DecimalSupport.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Geq_Float_DecimalSupport.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Geq_Float_DecimalSupport.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Geq_Float_V1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Geq_Float_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Geq_Float_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Geq_Float_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Geq_Float_V1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Geq_Float_V1CompatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Geq_Float_V1CompatDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Geq_Float_V1CompatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Gt_Decimal.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Gt_Decimal.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Gt_Decimal.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Gt_Decimal.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Gt_Float.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Gt_Float.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Gt_Float.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Gt_Float.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Gt_Float_DecimalSupport.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Gt_Float_DecimalSupport.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Gt_Float_DecimalSupport.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Gt_Float_DecimalSupport.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Gt_Float_V1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Gt_Float_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Gt_Float_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Gt_Float_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Gt_Float_V1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Gt_Float_V1CompatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Gt_Float_V1CompatDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Gt_Float_V1CompatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Leq_Decimal.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Leq_Decimal.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Leq_Decimal.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Leq_Decimal.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Leq_Float.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Leq_Float.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Leq_Float.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Leq_Float.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Leq_Float_DecimalSupport.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Leq_Float_DecimalSupport.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Leq_Float_DecimalSupport.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Leq_Float_DecimalSupport.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Leq_Float_V1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Leq_Float_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Leq_Float_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Leq_Float_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Leq_Float_V1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Leq_Float_V1CompatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Leq_Float_V1CompatDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Leq_Float_V1CompatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Lt_Decimal.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Lt_Decimal.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Lt_Decimal.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Lt_Decimal.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Lt_Float.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Lt_Float.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Lt_Float.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Lt_Float.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Lt_Float_DecimalSupport.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Lt_Float_DecimalSupport.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Lt_Float_DecimalSupport.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Lt_Float_DecimalSupport.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Lt_Float_V1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Lt_Float_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Lt_Float_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Lt_Float_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Lt_Float_V1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Lt_Float_V1CompatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Lt_Float_V1CompatDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Lt_Float_V1CompatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Minus_Decimal.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Minus_Decimal.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Minus_Decimal.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Minus_Decimal.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Minus_Float.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Minus_Float.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Minus_Float.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Minus_Float.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Minus_Float_DecimalSupport.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Minus_Float_DecimalSupport.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Minus_Float_DecimalSupport.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Minus_Float_DecimalSupport.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Mod_Decimal.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Mod_Decimal.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Mod_Decimal.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Mod_Decimal.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Mod_DecimalSupport.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Mod_DecimalSupport.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Mod_DecimalSupport.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Mod_DecimalSupport.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Mod_Float.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Mod_Float.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Mod_Float.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Mod_Float.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Mul_Decimal.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Mul_Decimal.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Mul_Decimal.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Mul_Decimal.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Mul_Float.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Mul_Float.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Mul_Float.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Mul_Float.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Mul_Float_DecimalSupport.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Mul_Float_DecimalSupport.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Mul_Float_DecimalSupport.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Mul_Float_DecimalSupport.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Neq_Decimal.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Neq_Decimal.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Neq_Decimal.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Neq_Decimal.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Neq_Decimal_V1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Neq_Decimal_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Neq_Decimal_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Neq_Decimal_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Neq_Decimal_V1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Neq_Decimal_V1CompatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Neq_Decimal_V1CompatDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Neq_Decimal_V1CompatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Neq_Float.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Neq_Float.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Neq_Float.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Neq_Float.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Neq_Float_DecimalSupport.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Neq_Float_DecimalSupport.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Neq_Float_DecimalSupport.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Neq_Float_DecimalSupport.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Neq_Float_V1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Neq_Float_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Neq_Float_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Neq_Float_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Neq_Float_V1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Neq_Float_V1CompatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Neq_Float_V1CompatDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Neq_Float_V1CompatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Neq_Float_V1Compat_DecimalSupport.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Neq_Float_V1Compat_DecimalSupport.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Neq_Float_V1Compat_DecimalSupport.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Neq_Float_V1Compat_DecimalSupport.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Plus_Decimal.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Plus_Decimal.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Plus_Decimal.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Plus_Decimal.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Plus_Float.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Plus_Float.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Plus_Float.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Plus_Float.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Plus_Float_DecimalSupport.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Plus_Float_DecimalSupport.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Plus_Float_DecimalSupport.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Plus_Float_DecimalSupport.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Unary_Decimal.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Unary_Decimal.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Unary_Decimal.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Unary_Decimal.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Unary_Float.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Unary_Float.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Unary_Float.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Unary_Float.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Unary_Float_DecimalSupport.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Unary_Float_DecimalSupport.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OpMatrix_Unary_Float_DecimalSupport.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OpMatrix_Unary_Float_DecimalSupport.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OptionSet.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OptionSet.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/OptionSet.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/OptionSet.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ParseJson.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ParseJson.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ParseJson.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ParseJson.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ParseJson_Arithmetic.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ParseJson_Arithmetic.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ParseJson_Arithmetic.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ParseJson_Arithmetic.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ParseJson_Coercions.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ParseJson_Coercions.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ParseJson_Coercions.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ParseJson_Coercions.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Patch_PatchRecord.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Patch_PatchRecord.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Patch_PatchRecord.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Patch_PatchRecord.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Patch_V1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Patch_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Patch_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Patch_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/PlainText.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/PlainText.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/PlainText.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/PlainText.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Power.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Power.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Power.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Power.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Proper.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Proper.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Proper.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Proper.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/README.md b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/README.md similarity index 97% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/README.md rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/README.md index ac5f66d0d..7b8a88901 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/README.md +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/README.md @@ -1,98 +1,98 @@ -# Expression Tests - -This directory contains Power Fx expression tests in a simple .txt file format. - -Tests are in the form: -``` ->> test expression -test result or #SKIP directive -blank line -``` - -Test expression can be multiple lines using significant white space at the beginning of continuation lines (like Python and YAML). - -## Configuration testing - -These tests are run with different configurations of features and parser options. -Most tests are not impacted by these differences and should be placed in a top level file -with no suffix and few #SEUTP directives for features that are not -directly being tested. - -There may be some tests that are sensitive to the configuration. These should be broken -out into their own file and the suffix reflects roughly the configuration that is needed. - -For example: -- **MinMax.txt**: Tests common Min and Max function scenarios that are not sensitive to the configuration. - For example, `Max(1,2)` is always `2`. -- **MinMax_NumberIsFloat.txt**: Tests that are specific to floating point operation and would not work properly with - Decimal numbers. For example, `Max(1e300,1.1e300)`. This file contains a `#SETUP: NumberIsFloat` directive. -- **MinMax_V1Compat.txt**: Tests that have the old behavior before PowerFxV1CompatibilityRules was introduced. - This file contains a `#SETUP: PowerFxV1CompaitilibyRules` to prevent it from being run if that switch is disabled. -- **MinMax_V1CompatDisabled.txt**: Tests for the new PowerFxV1CompatibilityRules behavior. - This file contains a `#SETUP: disable:PowerFxV1CompaitilibyRules` to prevent it from being run if that switch is disabled. - -## File Directives - -File directives appear at the top of the file. - -Each direcive may be followed by an end of line comment. - -Multiple directives are allowed in a file, except for #OVERRIDE. - -### SETUP - -``` -#SETUP: handler | [disable:]flag -``` - -Specifies the setup handler, engine Features, and ParserOption flags required by this test. - -If the test environment cannot satisfy the needs of #SETUP, the test file is skipped. - -It is OK to turn on flags that are not directly being tested. For example, `#SETUP: TableSyntaxDoesntWrapRecords` is common -as many tests are written using [ ] notation for defining tables. - -### OVERRIDE - -``` -#OVERRIDE: filename -``` - -Test results or #SKIP in this file override the same test in the named file. - -### DISABLE - -``` -#DISABLE: filename -``` - -Disables a test file. All #DISABLE directives are handled before any tests are run. - -## Test Directives - -### SKIP - -``` -#SKIP: comment -``` - -Use this directive in place of a result to skip a test. -Comment should include why the test was skipped, ideally with a link an issue filed for repair and the expected result after the repair. - -For example -``` ->> Abs(Table({a:1/0},{a:Power(-3,2)})) -#SKIP: waiting on https://github.com/microsoft/Power-Fx/issues/1204 expected: Table({Value:Error({Kind:ErrorKind.Div0})},{Value:9}) -``` - -## Numbers - -Since Power Fx works with both Decimal and Float numbers, when not directly testing the limits of these, -it is best to pick numbers in the range -1e28 to 1e28 and to not have infinitely repeating results. -For example, instead of testing with 1/3 which has a different precision in decimal and floating point, stick to -1/4, 1/8, or 1/2 that have an exact representation within 12 decimal places. - -Do not use the Float or Decimal functions at this time. Not all hosts have these functions yet. -If writing a test specifically for NumberIsFloat, use the Value function instead. -All the tests are run with both Decimal and Float configurations, so there is no need to have tests -that vary on just this one difference. +# Expression Tests + +This directory contains Power Fx expression tests in a simple .txt file format. + +Tests are in the form: +``` +>> test expression +test result or #SKIP directive +blank line +``` + +Test expression can be multiple lines using significant white space at the beginning of continuation lines (like Python and YAML). + +## Configuration testing + +These tests are run with different configurations of features and parser options. +Most tests are not impacted by these differences and should be placed in a top level file +with no suffix and few #SEUTP directives for features that are not +directly being tested. + +There may be some tests that are sensitive to the configuration. These should be broken +out into their own file and the suffix reflects roughly the configuration that is needed. + +For example: +- **MinMax.txt**: Tests common Min and Max function scenarios that are not sensitive to the configuration. + For example, `Max(1,2)` is always `2`. +- **MinMax_NumberIsFloat.txt**: Tests that are specific to floating point operation and would not work properly with + Decimal numbers. For example, `Max(1e300,1.1e300)`. This file contains a `#SETUP: NumberIsFloat` directive. +- **MinMax_V1Compat.txt**: Tests that have the old behavior before PowerFxV1CompatibilityRules was introduced. + This file contains a `#SETUP: PowerFxV1CompaitilibyRules` to prevent it from being run if that switch is disabled. +- **MinMax_V1CompatDisabled.txt**: Tests for the new PowerFxV1CompatibilityRules behavior. + This file contains a `#SETUP: disable:PowerFxV1CompaitilibyRules` to prevent it from being run if that switch is disabled. + +## File Directives + +File directives appear at the top of the file. + +Each direcive may be followed by an end of line comment. + +Multiple directives are allowed in a file, except for #OVERRIDE. + +### SETUP + +``` +#SETUP: handler | [disable:]flag +``` + +Specifies the setup handler, engine Features, and ParserOption flags required by this test. + +If the test environment cannot satisfy the needs of #SETUP, the test file is skipped. + +It is OK to turn on flags that are not directly being tested. For example, `#SETUP: TableSyntaxDoesntWrapRecords` is common +as many tests are written using [ ] notation for defining tables. + +### OVERRIDE + +``` +#OVERRIDE: filename +``` + +Test results or #SKIP in this file override the same test in the named file. + +### DISABLE + +``` +#DISABLE: filename +``` + +Disables a test file. All #DISABLE directives are handled before any tests are run. + +## Test Directives + +### SKIP + +``` +#SKIP: comment +``` + +Use this directive in place of a result to skip a test. +Comment should include why the test was skipped, ideally with a link an issue filed for repair and the expected result after the repair. + +For example +``` +>> Abs(Table({a:1/0},{a:Power(-3,2)})) +#SKIP: waiting on https://github.com/microsoft/Power-Fx/issues/1204 expected: Table({Value:Error({Kind:ErrorKind.Div0})},{Value:9}) +``` + +## Numbers + +Since Power Fx works with both Decimal and Float numbers, when not directly testing the limits of these, +it is best to pick numbers in the range -1e28 to 1e28 and to not have infinitely repeating results. +For example, instead of testing with 1/3 which has a different precision in decimal and floating point, stick to +1/4, 1/8, or 1/2 that have an exact representation within 12 decimal places. + +Do not use the Float or Decimal functions at this time. Not all hosts have these functions yet. +If writing a test specifically for NumberIsFloat, use the Value function instead. +All the tests are run with both Decimal and Float configurations, so there is no need to have tests +that vary on just this one difference. diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/RGBA.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/RGBA.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/RGBA.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/RGBA.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Rand.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Rand.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Rand.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Rand.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Rand_Decimal.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Rand_Decimal.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Rand_Decimal.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Rand_Decimal.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Rand_DecimalSupport.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Rand_DecimalSupport.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Rand_DecimalSupport.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Rand_DecimalSupport.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Rand_Float.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Rand_Float.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Rand_Float.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Rand_Float.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Record.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Record.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Record.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Record.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Record_TableSyntaxDoesntWrapRecordsDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Record_TableSyntaxDoesntWrapRecordsDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Record_TableSyntaxDoesntWrapRecordsDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Record_TableSyntaxDoesntWrapRecordsDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Remove.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Remove.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Remove.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Remove.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Remove_V1Compact.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Remove_V1Compact.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Remove_V1Compact.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Remove_V1Compact.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/RenameColumns_SupportColumnNamesAsIdentifiers.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/RenameColumns_SupportColumnNamesAsIdentifiers.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/RenameColumns_SupportColumnNamesAsIdentifiers.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/RenameColumns_SupportColumnNamesAsIdentifiers.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/RenameColumns_SupportColumnNamesAsIdentifiersDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/RenameColumns_SupportColumnNamesAsIdentifiersDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/RenameColumns_SupportColumnNamesAsIdentifiersDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/RenameColumns_SupportColumnNamesAsIdentifiersDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Replace.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Replace.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Replace.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Replace.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ReservedKeyword.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ReservedKeyword.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ReservedKeyword.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ReservedKeyword.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ReservedKeyword_Disabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ReservedKeyword_Disabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ReservedKeyword_Disabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ReservedKeyword_Disabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ReservedKeyword_Enabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ReservedKeyword_Enabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ReservedKeyword_Enabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ReservedKeyword_Enabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Right.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Right.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Right.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Right.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Round.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Round.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Round.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Round.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/RoundDown.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/RoundDown.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/RoundDown.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/RoundDown.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/RoundDownT.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/RoundDownT.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/RoundDownT.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/RoundDownT.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/RoundDownT_ConsistentOneColumnTableResultDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/RoundDownT_ConsistentOneColumnTableResultDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/RoundDownT_ConsistentOneColumnTableResultDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/RoundDownT_ConsistentOneColumnTableResultDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/RoundT.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/RoundT.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/RoundT.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/RoundT.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/RoundT_ConsistentOneColumnTableResultDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/RoundT_ConsistentOneColumnTableResultDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/RoundT_ConsistentOneColumnTableResultDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/RoundT_ConsistentOneColumnTableResultDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/RoundUp.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/RoundUp.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/RoundUp.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/RoundUp.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/RoundUpT.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/RoundUpT.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/RoundUpT.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/RoundUpT.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/RoundUpT_ConsistentOneColumnTableResultDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/RoundUpT_ConsistentOneColumnTableResultDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/RoundUpT_ConsistentOneColumnTableResultDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/RoundUpT_ConsistentOneColumnTableResultDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/RoundingOps.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/RoundingOps.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/RoundingOps.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/RoundingOps.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/SQLCompiler.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/SQLCompiler.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/SQLCompiler.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/SQLCompiler.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Search.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Search.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Search.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Search.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Search_SupportColumnNamesAsIdentifiersDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Search_SupportColumnNamesAsIdentifiersDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Search_SupportColumnNamesAsIdentifiersDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Search_SupportColumnNamesAsIdentifiersDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Sequence.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Sequence.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Sequence.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Sequence.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Sequence_Decimal.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Sequence_Decimal.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Sequence_Decimal.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Sequence_Decimal.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Sequence_Decimal_DVDecimal.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Sequence_Decimal_DVDecimal.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Sequence_Decimal_DVDecimal.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Sequence_Decimal_DVDecimal.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Sequence_Float.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Sequence_Float.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Sequence_Float.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Sequence_Float.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ShowColumns.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ShowColumns.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ShowColumns.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ShowColumns.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ShowColumns_SupportColumnNamesAsIdentifiersDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ShowColumns_SupportColumnNamesAsIdentifiersDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ShowColumns_SupportColumnNamesAsIdentifiersDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ShowColumns_SupportColumnNamesAsIdentifiersDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ShowColumns_WithMutationSetup.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ShowColumns_WithMutationSetup.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/ShowColumns_WithMutationSetup.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/ShowColumns_WithMutationSetup.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Shuffle.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Shuffle.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Shuffle.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Shuffle.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/SingleColumnTableCoercion.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/SingleColumnTableCoercion.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/SingleColumnTableCoercion.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/SingleColumnTableCoercion.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/SingleColumnTableCoercion_ConsistentOneColumnTableResultDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/SingleColumnTableCoercion_ConsistentOneColumnTableResultDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/SingleColumnTableCoercion_ConsistentOneColumnTableResultDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/SingleColumnTableCoercion_ConsistentOneColumnTableResultDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Sort.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Sort.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Sort.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Sort.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/SortByColumns.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/SortByColumns.txt similarity index 99% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/SortByColumns.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/SortByColumns.txt index 1207b8a27..37ad4f40b 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/SortByColumns.txt +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/SortByColumns.txt @@ -1,652 +1,652 @@ ->> SortByColumns([1],"Value") -Table({Value:1}) - ->> SortByColumns([-2, -1, 0, 1, 2], "Value") -Table({Value:-2},{Value:-1},{Value:0},{Value:1},{Value:2}) - ->> SortByColumns(Table({Value:-2},{Value:-1},{Value:1},{Value:2},{Value:5}),"Value") -Table({Value:-2},{Value:-1},{Value:1},{Value:2},{Value:5}) - - -// ******** NUMBER PARAMETERS ******** - ->> SortByColumns([-2, -1, 5, 1, 2], "Value") -Table({Value:-2},{Value:-1},{Value:1},{Value:2},{Value:5}) - ->> SortByColumns([-2, -1, 5, 1, 2], "Value", SortOrder.Descending) -Table({Value:5},{Value:2},{Value:1},{Value:-1},{Value:-2}) - ->> SortByColumns(Table({a:8, b:-1}, {a:2}, {b:5}), "a") -Table({a:2,b:Blank()},{a:8,b:-1},{a:Blank(),b:5}) - ->> SortByColumns(Table({a:8, b:-1}, {a:2}, {b:5}), "b", SortOrder.Descending) -Table({a:Blank(),b:5},{a:8,b:-1},{a:2,b:Blank()}) - -// ******** STRING PARAMETERS ******** - ->> SortByColumns(["a", "b", "D", "x", "J", "C"], "Value") -Table({Value:"a"},{Value:"b"},{Value:"C"},{Value:"D"},{Value:"J"},{Value:"x"}) - ->> SortByColumns(["a", "b", "D", "x", "J", "C"], "Value", SortOrder.Descending) -Table({Value:"x"},{Value:"J"},{Value:"D"},{Value:"C"},{Value:"b"},{Value:"a"}) - ->> SortByColumns(["a", "b", "B", "x", "D", "C"], "Value") -Table({Value:"a"},{Value:"b"},{Value:"B"},{Value:"C"},{Value:"D"},{Value:"x"}) - ->> SortByColumns(["Hello", "hellO", "hello", "heLLo", "HELLO", "hElLo"], "Value") -Table({Value:"hello"},{Value:"hellO"},{Value:"heLLo"},{Value:"hElLo"},{Value:"Hello"},{Value:"HELLO"}) - ->> SortByColumns(["Hello", "hellO", "1", "%", "2", "hElLo"], "Value") -Table({Value:"%"},{Value:"1"},{Value:"2"},{Value:"hellO"},{Value:"hElLo"},{Value:"Hello"}) - ->> SortByColumns(["a", "b", Blank(), "x", "J", "C"], "Value") -Table({Value:"a"},{Value:"b"},{Value:"C"},{Value:"J"},{Value:"x"},{Value:Blank()}) - ->> SortByColumns(["a", "b", Blank(), "x", "J", "C"], "Value", SortOrder.Descending) -Table({Value:"x"},{Value:"J"},{Value:"C"},{Value:"b"},{Value:"a"},{Value:Blank()}) - -// ******** BOOLEAN PARAMETERS ******** - ->> SortByColumns([true,false,true,false,true], "Value") -Table({Value:false},{Value:false},{Value:true},{Value:true},{Value:true}) - ->> SortByColumns([true,false,true,false,true], "Value", SortOrder.Descending) -Table({Value:true},{Value:true},{Value:true},{Value:false},{Value:false}) -// ->> SortByColumns([true,false,Blank(),false,true], "Value") -Table({Value:false},{Value:false},{Value:true},{Value:true},{Value:Blank()}) - ->> SortByColumns([true,false,Blank(),false,true], "Value", SortOrder.Descending) -Table({Value:true},{Value:true},{Value:false},{Value:false},{Value:Blank()}) - -// ******** DATE PARAMETERS ******** - ->> SortByColumns([Date(2020, 01, 05), Date(2020, 01, 01), Date(1995, 01, 01)], "Value") -Table({Value:Date(1995,1,1)},{Value:Date(2020,1,1)},{Value:Date(2020,1,5)}) - ->> SortByColumns([Date(2020, 01, 05), Date(2012,12,14), Date(2022,1,1)], "Value") -Table({Value:Date(2012,12,14)},{Value:Date(2020,1,5)},{Value:Date(2022,1,1)}) - ->> SortByColumns([Date(2020, 01, 05), Date(2020, 01, 01), Date(1995, 01, 01)], "Value", SortOrder.Descending) -Table({Value:Date(2020,1,5)},{Value:Date(2020,1,1)},{Value:Date(1995,1,1)}) - ->> SortByColumns([Date(2020, 01, 05), Date(2012,12,14), Blank(), Date(2022,1,1)], "Value", SortOrder.Descending) -Table({Value:Date(2022,1,1)},{Value:Date(2020,1,5)},{Value:Date(2012,12,14)},{Value:Blank()}) - -// ******** TIME PARAMETERS ******** - ->> SortByColumns([Time(12,35,55), Time(23, 01, 01), Time(5,30,50)], "Value") -Table({Value:Time(5,30,50,0)},{Value:Time(12,35,55,0)},{Value:Time(23,1,1,0)}) - ->> SortByColumns([Time(12,35,55), Time(23, 01, 01), Time(5,30,50)], "Value", SortOrder.Descending) -Table({Value:Time(23,1,1,0)},{Value:Time(12,35,55,0)},{Value:Time(5,30,50,0)}) - ->> SortByColumns([Time(17,29,0), Blank(), Time(5,30,50)], "Value") -Table({Value:Time(5,30,50,0)},{Value:Time(17,29,0,0)},{Value:Blank()}) - -// ******** DATETIME PARAMETERS ******** - ->> SortByColumns([DateTime(2019,5,16,8,0,0), DateTime(2019,5,16,20,0,0), DateTime(2019,5,16,20,0,1)], "Value") -Table({Value:DateTime(2019,5,16,8,0,0,0)},{Value:DateTime(2019,5,16,20,0,0,0)},{Value:DateTime(2019,5,16,20,0,1,0)}) - ->> SortByColumns([DateTime(2019,5,16,8,0,0), DateTime(2019,5,16,20,0,0), DateTime(2019,5,16,20,0,1)], "Value", SortOrder.Descending) -Table({Value:DateTime(2019,5,16,20,0,1,0)},{Value:DateTime(2019,5,16,20,0,0,0)},{Value:DateTime(2019,5,16,8,0,0,0)}) - - -// ******** NULL and ERROR PARAMETERS ******** - ->> SortByColumns([-2, -1, 5, 1, 2], "Value", If(1/0<2,SortOrder.Ascending,SortOrder.Descending)) -Error({Kind:ErrorKind.Div0}) - -//Error passed as one of the table records ->> SortByColumns([-2, -1, 5, 1/0, 2], "Value") -Error({Kind:ErrorKind.Div0}) - ->> SortByColumns(["a", "b", "D", "x", "J", Error({Kind: 11})], "Value") -Error({Kind:ErrorKind.Validation}) - - ->> SortByColumns(Table({Value:Blank()}),"Value") -Table({Value:Blank()}) - ->> SortByColumns([{Value:Blank()},{Value:Blank()}], "Value", SortOrder.Descending) -Table({Value:Blank()},{Value:Blank()}) - ->> SortByColumns([-2, Blank(), 5, 1, 2], "Value") -Table({Value:-2},{Value:1},{Value:2},{Value:5},{Value:Blank()}) - ->> SortByColumns([-2, -Blank(), 5, 1, 2], "Value") -Table({Value:-2},{Value:0},{Value:1},{Value:2},{Value:5}) - ->> SortByColumns([-2, Blank(), 5, 1, 2, -Blank()], "Value", SortOrder.Descending) -Table({Value:5},{Value:2},{Value:1},{Value:0},{Value:-2},{Value:Blank()}) - -// Sort on error source ->> SortByColumns(If(Left("Hello", -1) = "",[1,2,3,4]), "Value", SortOrder.Ascending) -Error({Kind:ErrorKind.InvalidArgument}) - -// Sort null table with single valid column ->> SortByColumns(If(1<0,[1,2,3]), "Value",SortOrder.Ascending) -Blank() - -//Sort empty table with single valid column - ascending order ->> SortByColumns(Filter([1, 2, 3], Value > 100), "Value", SortOrder.Ascending) -Table() - -//Sort empty table with multiple valid column - ascending order ->> SortByColumns(Filter(Table({Value1:1,Value2:2}), Value1>100), "Value1", SortOrder.Ascending,"Value2", SortOrder.Ascending) -Table() - - -// ******** MULTI COLUMN SORT CASES ******** - -// SORT ON NUMERIC COLUMN "AGE" - ASCENDING ORDER - ->> SortByColumns(Table( - { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: Blank(), Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, - { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, - { First: "Helio", Last: Blank(), Age: 263, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, - { First: "Titan", Last: Blank(), Age: 792, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, - { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, - { First: Blank(), Last: "Rodriguez", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }),"Age") -Table({Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:263,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:792,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Clark",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Allen",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:Blank(),Last:"Brown",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Walker",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:Blank(),Last:"Rodriguez",Vaccinated:Blank()}) - - -// SORT ON NUMERIC COLUMN "AGE" - DESCENDING ORDER - ->> SortByColumns(Table( - { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: Blank(), Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, - { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, - { First: "Helio", Last: Blank(), Age: 263, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, - { First: "Titan", Last: Blank(), Age: 792, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, - { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, - { First: Blank(), Last: "Rodriguez", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }),"Age", SortOrder.Descending) -Table({Age:792,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:263,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Clark",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Allen",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:Blank(),Last:"Brown",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Walker",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:Blank(),Last:"Rodriguez",Vaccinated:Blank()}) - - -// SORT ON BOOL COLUMN "VACCINATED" - ASCENDING ORDER - ->> SortByColumns(Table( - { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: Blank(), Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, - { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, - { First: "Helio", Last: Blank(), Age: 263, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, - { First: "Titan", Last: Blank(), Age: 792, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, - { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, - { First: Blank(), Last: "Rodriguez", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }),"Vaccinated") -Table({Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Clark",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Allen",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:Blank(),Last:"Brown",Vaccinated:Blank()},{Age:263,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:792,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Walker",Vaccinated:Blank()},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:Blank(),Last:"Rodriguez",Vaccinated:Blank()}) - - -// SORT ON BOOL COLUMN "VACCINATED" - DESCENDING ORDER - ->> SortByColumns(Table( - { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: Blank(), Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, - { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, - { First: "Helio", Last: Blank(), Age: 263, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, - { First: "Titan", Last: Blank(), Age: 792, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, - { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, - { First: Blank(), Last: "Rodriguez", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }),"Vaccinated", SortOrder.Descending) -Table({Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true},{Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Clark",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Allen",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:Blank(),Last:"Brown",Vaccinated:Blank()},{Age:263,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:792,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Walker",Vaccinated:Blank()},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:Blank(),Last:"Rodriguez",Vaccinated:Blank()}) - - -// SORT ON STRING COLUMN "FIRST" - ASCENDING ORDER - ->> SortByColumns(Table( - { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: Blank(), Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, - { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, - { First: "Helio", Last: Blank(), Age: 263, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, - { First: "Titan", Last: Blank(), Age: 792, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, - { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, - { First: Blank(), Last: "Rodriguez", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }),"First") -Table({Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:263,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true},{Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:792,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Clark",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Allen",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:Blank(),Last:"Brown",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Walker",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:Blank(),Last:"Rodriguez",Vaccinated:Blank()}) - - -// SORT ON STRING COLUMN "FIRST" - DESCENDING ORDER - ->> SortByColumns(Table( - { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: Blank(), Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, - { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, - { First: "Helio", Last: Blank(), Age: 263, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, - { First: "Titan", Last: Blank(), Age: 792, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, - { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, - { First: Blank(), Last: "Rodriguez", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }),"First",SortOrder.Descending) -Table({Age:792,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true},{Age:263,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Clark",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Allen",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:Blank(),Last:"Brown",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Walker",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:Blank(),Last:"Rodriguez",Vaccinated:Blank()}) - - -// SORT HIERARCHICAL DATA ON BOOL COLUMN "HasID - ASCENDING ORDER - ->> SortByColumns(Table( - { First: "Bob", Age: 2, HasId: true, Properties: { Vaccinated: true, HasDog: true } }, - { First: "Alice", Age: 5, HasId: false, Properties: { Vaccinated: true, HasDog: false } }, - { First: Blank(), Age: 12, HasId: true, Properties: { Vaccinated: Blank(), HasDog: true } }, - { First: Blank(), Age: 12, HasId: false, Properties: Blank() }, - { First: "Mary", Age: 12, HasId: false, Properties: Blank() }, - { First: "Wonderland", Age: 5, HasId: Blank(), Properties: { Vaccinated: true, HasDog: Blank() } }, - { First: "Samantha", Age: 15, HasId: Blank(), Properties: { Vaccinated: Blank(), HasDog: Blank() } }), "HasId") -Table({Age:5,First:"Alice",HasId:false,Properties:{HasDog:false,Vaccinated:true}},{Age:12,First:Blank(),HasId:false,Properties:Blank()},{Age:12,First:"Mary",HasId:false,Properties:Blank()},{Age:2,First:"Bob",HasId:true,Properties:{HasDog:true,Vaccinated:true}},{Age:12,First:Blank(),HasId:true,Properties:{HasDog:true,Vaccinated:Blank()}},{Age:5,First:"Wonderland",HasId:Blank(),Properties:{HasDog:Blank(),Vaccinated:true}},{Age:15,First:"Samantha",HasId:Blank(),Properties:{HasDog:Blank(),Vaccinated:Blank()}}) - - -// SORT HIERARCHICAL DATA ON BOOL COLUMN "HasID - DESCENDING ORDER - ->> SortByColumns(Table( - { First: "Bob", Age: 2, HasId: true, Properties: { Vaccinated: true, HasDog: true } }, - { First: "Alice", Age: 5, HasId: false, Properties: { Vaccinated: true, HasDog: false } }, - { First: Blank(), Age: 12, HasId: true, Properties: { Vaccinated: Blank(), HasDog: true } }, - { First: Blank(), Age: 12, HasId: false, Properties: Blank() }, - { First: "Mary", Age: 12, HasId: false, Properties: Blank() }, - { First: "Wonderland", Age: 5, HasId: Blank(), Properties: { Vaccinated: true, HasDog: Blank() } }, - { First: "Samantha", Age: 15, HasId: Blank(), Properties: { Vaccinated: Blank(), HasDog: Blank() } }), "HasId",SortOrder.Descending) -Table({Age:2,First:"Bob",HasId:true,Properties:{HasDog:true,Vaccinated:true}},{Age:12,First:Blank(),HasId:true,Properties:{HasDog:true,Vaccinated:Blank()}},{Age:5,First:"Alice",HasId:false,Properties:{HasDog:false,Vaccinated:true}},{Age:12,First:Blank(),HasId:false,Properties:Blank()},{Age:12,First:"Mary",HasId:false,Properties:Blank()},{Age:5,First:"Wonderland",HasId:Blank(),Properties:{HasDog:Blank(),Vaccinated:true}},{Age:15,First:"Samantha",HasId:Blank(),Properties:{HasDog:Blank(),Vaccinated:Blank()}}) - - -// SORT A TABLE WITH ERROR ROWS - ASCENDING ORDER - ->> SortByColumns(Table( - { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, - { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, - If(Left("Hello", -1) = "", { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }), - { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false } - ),"First") -Error({Kind:ErrorKind.InvalidArgument}) - ->> SortByColumns(Table( - { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, - { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, - If(Left("Hello", -1) = "", { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }), - { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false } - ),"First", SortOrder.Descending) -Error({Kind:ErrorKind.InvalidArgument}) - - -// SORT A TABLE WITH BLANK ROWS - ASCENDING ORDER - ->> SortByColumns(Table( - { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, - Blank(), - Blank(), - Blank(), - { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, - { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, - { First: "Helio", Last: Blank(), Age: 263, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, - { First: "Titan", Last: Blank(), Age: 792, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - Blank(), - { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, - { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, - Blank(), - { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }),"First") -Table({Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:263,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true},{Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:792,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},Blank(),Blank(),Blank(),Blank(),Blank()) - - -// SORT A TABLE WITH BLANK ROWS - DESCENDING ORDER - ->> SortByColumns(Table( - { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, - Blank(), - Blank(), - Blank(), - { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, - { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, - { First: "Helio", Last: Blank(), Age: 263, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, - { First: "Titan", Last: Blank(), Age: 792, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - Blank(), - { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, - { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, - Blank(), - { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }),"First", SortOrder.Descending) -Table({Age:792,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true},{Age:263,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},Blank(),Blank(),Blank(),Blank(),Blank()) - - -// SORT A TABLE WITH NO VALUES FOR SOME COLUMNS - ->> SortByColumns(Table( - { First: "Bob", Last: "Smith", Age: 2 }, - { First: "Alice" }, - Blank(), - Blank(), - Blank(), - { First: "John", Last: "Batali", Age: 17 }, - { First: "Emily", Age: 29 }), "Last", SortOrder.Ascending, "Age", SortOrder.Descending) -Table({Age:17,First:"John",Last:"Batali"},{Age:2,First:"Bob",Last:"Smith"},{Age:29,First:"Emily",Last:Blank()},{Age:Blank(),First:"Alice",Last:Blank()},Blank(),Blank(),Blank()) - -// SORT BY MULTIPLE COLUMNS - TEST 1 - ->> SortByColumns(Table( - { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: Blank(), Last: "Walker", Age: 4, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: false, HasDog: true }, - { First: "Bob", Last: "Smith", Age: 2, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: Blank(), Last: "Walker", Age: 4, Gender: "Female", Vaccinated: false, HasDog: true }, - { First: "Bob", Last: "Smith", Age: 2, Gender: "Female", Vaccinated: false, HasDog: true }, - { First: "Bob", Last: "Walker", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Bob", Last: "Walker", Age: 2, Gender: "Male", Vaccinated: false, HasDog: true }, - { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Female", Vaccinated: true, HasDog: true }, - { First: "Bob", Last: "Walker", Age: 1, Gender: "Male", Vaccinated: false, HasDog: true }, - { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: false, HasDog: true }, - { First: "Bob", Last: "Walker", Age: 1, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: Blank(), Last: "James", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: true }, - { First: "Bob", Last: "Walker", Age: 1, Gender: "Female", Vaccinated: false, HasDog: true }, - { First: Blank(), Last: "James", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: true }, - { First: "Bob", Last: "Walker", Age: 2, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: "Bob", Last: "Walker", Age: 2, Gender: "Female", Vaccinated: false, HasDog: true }, - { First: "Bob", Last: "Walker", Age: 4, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "John", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Andrew", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "John", Last: "Walker", Age: 1, Gender: "Male", Vaccinated: false, HasDog: true }, - { First: "Bob", Last: "Walker", Age: 4, Gender: "Male", Vaccinated: false, HasDog: true }, - { First: "Andrew", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: false, HasDog: true }, - { First: "Bob", Last: "Walker", Age: 4, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: "Bob", Last: "Walker", Age: 4, Gender: "Female", Vaccinated: false, HasDog: true }, - { First: "Bob", Last: "Walker", Age: 1, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Andrew", Last: "Smith", Age: 2, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: "John", Last: "Bob", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: "Andrew", Last: "Smith", Age: 2, Gender: "Female", Vaccinated: false, HasDog: true }, - { First: "John", Last: "Newman", Age: 6, Gender: "Female", Vaccinated: false, HasDog: true }), "First", SortOrder.Ascending, "Last", SortOrder.Descending, "Age", SortOrder.Descending, "Gender", SortOrder.Descending, "Vaccinated", SortOrder.Ascending) -Table({Age:2,First:"Andrew",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:false},{Age:2,First:"Andrew",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:2,First:"Andrew",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:false},{Age:2,First:"Andrew",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:4,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:false},{Age:4,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:true},{Age:4,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:false},{Age:4,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:true},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:false},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:true},{Age:2,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:false},{Age:2,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:true},{Age:1,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:false},{Age:1,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:true},{Age:1,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:false},{Age:1,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:true},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:false},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:2,First:"Bob",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:false},{Age:2,First:"Bob",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:1,First:"John",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:false},{Age:2,First:"John",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:6,First:"John",Gender:"Female",HasDog:true,Last:"Newman",Vaccinated:false},{Age:5,First:"John",Gender:"Female",HasDog:true,Last:"Bob",Vaccinated:true},{Age:4,First:Blank(),Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:false},{Age:4,First:Blank(),Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:true},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:false},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:true},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:true,Last:"James",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:true,Last:"James",Vaccinated:Blank()}) - - -// SORT BY MULTIPLE COLUMNS - TEST 2 - ->> SortByColumns(Table( - { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: Blank(), Last: "Walker", Age: 4, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: false, HasDog: true }, - { First: "Bob", Last: "Smith", Age: 2, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: Blank(), Last: "Walker", Age: 4, Gender: "Female", Vaccinated: false, HasDog: true }, - { First: "Bob", Last: "Smith", Age: 2, Gender: "Female", Vaccinated: false, HasDog: true }, - { First: "Bob", Last: "Walker", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Bob", Last: "Walker", Age: 2, Gender: "Male", Vaccinated: false, HasDog: true }, - { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Female", Vaccinated: true, HasDog: true }, - { First: "Bob", Last: "Walker", Age: 1, Gender: "Male", Vaccinated: false, HasDog: true }, - { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: false, HasDog: true }, - { First: "Bob", Last: "Walker", Age: 1, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: Blank(), Last: "James", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: true }, - { First: "Bob", Last: "Walker", Age: 1, Gender: "Female", Vaccinated: false, HasDog: true }, - { First: Blank(), Last: "James", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: true }, - { First: "Bob", Last: "Walker", Age: 2, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: "Bob", Last: "Walker", Age: 2, Gender: "Female", Vaccinated: false, HasDog: true }, - { First: "Bob", Last: "Walker", Age: 4, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "John", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Andrew", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "John", Last: "Walker", Age: 1, Gender: "Male", Vaccinated: false, HasDog: true }, - { First: "Bob", Last: "Walker", Age: 4, Gender: "Male", Vaccinated: false, HasDog: true }, - { First: "Andrew", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: false, HasDog: true }, - { First: "Bob", Last: "Walker", Age: 4, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: "Bob", Last: "Walker", Age: 4, Gender: "Female", Vaccinated: false, HasDog: true }, - { First: "Bob", Last: "Walker", Age: 1, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Andrew", Last: "Smith", Age: 2, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: "John", Last: "Bob", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: "Andrew", Last: "Smith", Age: 2, Gender: "Female", Vaccinated: false, HasDog: true }, - { First: "John", Last: "Newman", Age: 6, Gender: "Female", Vaccinated: false, HasDog: true }), "First", SortOrder.Ascending, "Last", SortOrder.Ascending, "Age", SortOrder.Ascending, "Gender", SortOrder.Ascending, "Vaccinated", SortOrder.Descending) -Table({Age:2,First:"Andrew",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:2,First:"Andrew",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:false},{Age:2,First:"Andrew",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:2,First:"Andrew",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:false},{Age:2,First:"Bob",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:2,First:"Bob",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:false},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:false},{Age:1,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:true},{Age:1,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:false},{Age:1,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:true},{Age:1,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:false},{Age:2,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:true},{Age:2,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:false},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:true},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:false},{Age:4,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:true},{Age:4,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:false},{Age:4,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:true},{Age:4,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:false},{Age:5,First:"John",Gender:"Female",HasDog:true,Last:"Bob",Vaccinated:true},{Age:6,First:"John",Gender:"Female",HasDog:true,Last:"Newman",Vaccinated:false},{Age:2,First:"John",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:1,First:"John",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:false},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:true,Last:"James",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:true,Last:"James",Vaccinated:Blank()},{Age:4,First:Blank(),Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:true},{Age:4,First:Blank(),Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:false},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:true},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:false}) - - -// SORT BY MULTIPLE COLUMNS - TABLE WITH BLANK ROWS - - ->> SortByColumns(Table( - { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: false, HasDog: true }, - { First: "Bob", Last: "Smith", Age: 2, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: "Andrew", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Bob", Last: "Walker", Age: 1, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Andrew", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: false, HasDog: true }, - { First: "Bob", Last: "Walker", Age: 1, Gender: "Male", Vaccinated: false, HasDog: true }, - Blank(), - { First: "Bob", Last: "Walker", Age: 1, Gender: "Female", Vaccinated: true, HasDog: true }, - Blank(), - { First: "Bob", Last: "Walker", Age: 1, Gender: "Female", Vaccinated: false, HasDog: true }, - Blank(), - { First: "Andrew", Last: "Smith", Age: 2, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: "Bob", Last: "Smith", Age: 2, Gender: "Female", Vaccinated: false, HasDog: true }, - { First: "Bob", Last: "Walker", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Bob", Last: "Walker", Age: 2, Gender: "Male", Vaccinated: false, HasDog: true }, - { First: "Andrew", Last: "Smith", Age: 2, Gender: "Female", Vaccinated: false, HasDog: true }, - { First: "Bob", Last: "Walker", Age: 2, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: "Bob", Last: "Walker", Age: 2, Gender: "Female", Vaccinated: false, HasDog: true }, - { First: "Bob", Last: "Walker", Age: 4, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "John", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Bob", Last: "Walker", Age: 4, Gender: "Male", Vaccinated: false, HasDog: true }, - { First: Blank(), Last: "Walker", Age: 4, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: "Bob", Last: "Walker", Age: 4, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: "Bob", Last: "Walker", Age: 4, Gender: "Female", Vaccinated: false, HasDog: true }, - { First: Blank(), Last: "Walker", Age: 4, Gender: "Female", Vaccinated: false, HasDog: true }, - { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Female", Vaccinated: true, HasDog: true }, - { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: false, HasDog: true }, - { First: "John", Last: "Walker", Age: 1, Gender: "Male", Vaccinated: false, HasDog: true }, - { First: Blank(), Last: "James", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: true }, - { First: Blank(), Last: "James", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: true }, - { First: "John", Last: "Bob", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: "John", Last: "Newman", Age: 6, Gender: "Female", Vaccinated: false, HasDog: true }), "First", SortOrder.Ascending, "Last", SortOrder.Descending, "Age", SortOrder.Descending, "Gender", SortOrder.Descending, "Vaccinated", SortOrder.Ascending) -Table({Age:2,First:"Andrew",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:false},{Age:2,First:"Andrew",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:2,First:"Andrew",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:false},{Age:2,First:"Andrew",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:4,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:false},{Age:4,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:true},{Age:4,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:false},{Age:4,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:true},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:false},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:true},{Age:2,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:false},{Age:2,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:true},{Age:1,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:false},{Age:1,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:true},{Age:1,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:false},{Age:1,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:true},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:false},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:2,First:"Bob",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:false},{Age:2,First:"Bob",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:1,First:"John",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:false},{Age:2,First:"John",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:6,First:"John",Gender:"Female",HasDog:true,Last:"Newman",Vaccinated:false},{Age:5,First:"John",Gender:"Female",HasDog:true,Last:"Bob",Vaccinated:true},{Age:4,First:Blank(),Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:false},{Age:4,First:Blank(),Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:true},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:false},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:true},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:true,Last:"James",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:true,Last:"James",Vaccinated:Blank()},Blank(),Blank(),Blank()) - - -// SORT ON TABLE WITH COLUMNS HAVING SPECIAL CHARACTERS - DESCENDING ORDER - ->> SortByColumns(Table( - { 'A\"B': "Bob" }, { 'A\"B': "Alice" }, { 'A\"B': "Mary" }, { 'A\"B': "Wonderland" }, { 'A\"B': "Samantha" }), "A\""B", SortOrder.Descending) -Table({'A\"B':"Wonderland"},{'A\"B':"Samantha"},{'A\"B':"Mary"},{'A\"B':"Bob"},{'A\"B':"Alice"}) - -// SORT ON TABLE WITH SOME COLUMNS HAVING ERROR VALUES - ASCENDING ORDER - ->> SortByColumns(Table( - { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: "Helio", Last: Left("Hello", -1), Age: 263, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, - { First: "Titan", Last: Left("Hello", -1), Age: 792, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }),"Last", SortOrder.Ascending,"Age", SortOrder.Descending) -Error({Kind:ErrorKind.InvalidArgument}) - - -// SORT ON TABLE WITH SOME COLUMNS HAVING ERROR VALUES, BUT DOESN"T TOUCH ERROR - ASCENDING ORDER - ->> SortByColumns(Table( - { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: "Helio", Last: Left("Hello", -1), Age: 263, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, - { First: "Titan", Last: Left("Hello", -1), Age: 792, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }),"First", SortOrder.Ascending,"Age", SortOrder.Descending) -Table({Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:263,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Error({Kind:ErrorKind.InvalidArgument}),Vaccinated:Blank()},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:792,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Error({Kind:ErrorKind.InvalidArgument}),Vaccinated:Blank()}) - - -// SORT ON TABLE WITH ERROR PASSED AS SORT ORDER - ASCENDING ORDER - ->> SortByColumns(Table( - { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: Blank(), Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, - { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, - { First: "Helio", Last: Blank(), Age: 263, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, - { First: "Titan", Last: Blank(), Age: 792, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, - { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, - { First: Blank(), Last: "Rodriguez", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }),"First", If(Char(-1) <> "a", SortOrder.Ascending)) -Error({Kind:ErrorKind.InvalidArgument}) - - -// MULTIPLE COLUMNS USED FOR SORTING ORDER - ->> SortByColumns(Table( - {Flavor: "Chocolate", Quantity:100, Quantity2:100, Quantity3:100, Quantity4:100, Quantity5:100, Quantity6:100, Quantity7:100, Quantity8:100, Quantity9:100, Quantity10:100, Quantity11:100, Quantity12:100, Quantity13:100, Quantity14:100, Quantity15:100, Quantity16:100, Quantity17:100, Quantity18:100, Quantity19:100, Quantity20:100}),"Quantity", SortOrder.Ascending, "Quantity2", SortOrder.Ascending, "Quantity3", SortOrder.Ascending, "Quantity4", SortOrder.Ascending, "Quantity5", SortOrder.Ascending, "Quantity6", SortOrder.Ascending, "Quantity7", SortOrder.Ascending, "Quantity8", SortOrder.Ascending, "Quantity9", SortOrder.Ascending, "Quantity10", SortOrder.Ascending, "Quantity11", SortOrder.Ascending, "Quantity12", SortOrder.Ascending, "Quantity13", SortOrder.Ascending, "Quantity14", SortOrder.Ascending, "Quantity15", SortOrder.Ascending, "Quantity16", SortOrder.Ascending, "Quantity17", SortOrder.Ascending, "Quantity18", SortOrder.Ascending, "Quantity19", SortOrder.Ascending, "Quantity20", SortOrder.Ascending) -Table({Flavor:"Chocolate",Quantity:100,Quantity10:100,Quantity11:100,Quantity12:100,Quantity13:100,Quantity14:100,Quantity15:100,Quantity16:100,Quantity17:100,Quantity18:100,Quantity19:100,Quantity2:100,Quantity20:100,Quantity3:100,Quantity4:100,Quantity5:100,Quantity6:100,Quantity7:100,Quantity8:100,Quantity9:100}) - - -// SAME COLUMN USED TO SPECIFY CONFLICTING SORT ORDERS - ->> SortByColumns(Table( - {Flavor: "Chocolate", Quantity:100, OnOrder:150}, - {Flavor: "Vanilla", Quantity:200, OnOrder:20}, - {Flavor: "Strawberry", Quantity:300, OnOrder:20}, - {Flavor: "Mint", Quantity:60, OnOrder:100}, - {Flavor: "Pistachio", Quantity:200, OnOrder:10}),"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending) -Table({Flavor:"Pistachio",OnOrder:10,Quantity:200},{Flavor:"Vanilla",OnOrder:20,Quantity:200},{Flavor:"Strawberry",OnOrder:20,Quantity:300},{Flavor:"Mint",OnOrder:100,Quantity:60},{Flavor:"Chocolate",OnOrder:150,Quantity:100}) - - -//SAME COLUMN USED MULTIPLE TIMES FOR SORTING ORDER - ->> SortByColumns(Table( - {Flavor: "Chocolate", Quantity:100, OnOrder:150}, - {Flavor: "Vanilla", Quantity:200, OnOrder:20}, - {Flavor: "Strawberry", Quantity:300, OnOrder:20}, - {Flavor: "Mint", Quantity:60, OnOrder:100}, - {Flavor: "Pistachio", Quantity:200, OnOrder:10}),"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending) -Table({Flavor:"Pistachio",OnOrder:10,Quantity:200},{Flavor:"Vanilla",OnOrder:20,Quantity:200},{Flavor:"Strawberry",OnOrder:20,Quantity:300},{Flavor:"Mint",OnOrder:100,Quantity:60},{Flavor:"Chocolate",OnOrder:150,Quantity:100}) - -// ###### SortByColumns, column + ascending/descending overload ###### - -// Legacy behavior: we can pass non-string literals to the column name in SortByColumns ->> SortByColumns([1,3,2],Left("Value234", 5)) -Table({Value:1},{Value:2},{Value:3}) - ->> SortByColumns([1,3,2],Left("Value234", 5), SortOrder.Descending) -Table({Value:3},{Value:2},{Value:1}) - ->> SortByColumns(Table({a:1, b:"one"}, {a:2, b:"two"}, {a:3, b:"three"}, {a:4, b:"four"}), If(1>0,"a","b")) -Table({a:1,b:"one"},{a:2,b:"two"},{a:3,b:"three"},{a:4,b:"four"}) - ->> SortByColumns(Table({a:1, b:"one"}, {a:2, b:"two"}, {a:3, b:"three"}, {a:4, b:"four"}), If(1<0,"a","b")) -Table({a:4,b:"four"},{a:1,b:"one"},{a:3,b:"three"},{a:2,b:"two"}) - ->> SortByColumns(Table({a:1,b:"one"}, {a:2, b:"two"}, {a:1,b:"onebis"},{a:3, b:"three"}, {a:4, b:"four"}, {a:3, b:"threebis"}), "a", SortOrder.Ascending, Left("b1", 1), SortOrder.Descending) -Table({a:1,b:"onebis"},{a:1,b:"one"},{a:2,b:"two"},{a:3,b:"threebis"},{a:3,b:"three"},{a:4,b:"four"}) - ->> SortByColumns(Table({a:1, b:1111}, {a:2, b:222}, {a:3, b:33}), If(1>0,"columndoesnotexist")) -Error({Kind:ErrorKind.InvalidFunctionUsage}) - ->> SortByColumns(Table({a:1,b:"one"}, {a:2, b:"two"}, {a:1,b:"onebis"},{a:3, b:"three"}, {a:4, b:"four"}, {a:3, b:"threebis"}), "a", SortOrder.Ascending, Left("b1", 2), SortOrder.Descending) -Error({Kind:ErrorKind.InvalidFunctionUsage}) - ->> SortByColumns(Table( - { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, - { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, - { First: Blank(), Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, - { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, - { First: "Helio", Last: Blank(), Age: 263, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, - { First: "Titan", Last: Blank(), Age: 792, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, - { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, - { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, - { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, - { First: Blank(), Last: "Rodriguez", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, - { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }),Left("Hello", -1), SortOrder.Ascending,"Age", SortOrder.Descending) -Error({Kind:ErrorKind.InvalidArgument}) - -// ###### SortByColumns, column + order table overload ###### - ->> SortByColumns([1,3,2],Left("Value234", 5), [2,3,1]) -Table({Value:2},{Value:3},{Value:1}) - ->> SortByColumns(Table({a:1, b:"one"}, {a:2, b:"two"}, {a:3, b:"three"}, {a:4, b:"four"}), If(1<0,"a","b"), ["four", "three", "two", "one"]) -Table({a:4,b:"four"},{a:3,b:"three"},{a:2,b:"two"},{a:1,b:"one"}) - -// If order table does not match the column to be sorted, results are unchanged ->> SortByColumns(Table({a:1, b:"one"}, {a:2, b:"two"}, {a:3, b:"three"}, {a:4, b:"four"}), If(1>0,"a","b"), ["four", "three", "two", "one"]) -Table({a:1,b:"one"},{a:2,b:"two"},{a:3,b:"three"},{a:4,b:"four"}) - ->> SortByColumns(Table({a:1, b:1111}, {a:2, b:222}, {a:3, b:33}), If(1>0,"columndoesnotexist"), [1,2,3]) -Error({Kind:ErrorKind.InvalidFunctionUsage}) - -// ###### SortByColumns, column as (scope) variable ###### - ->> With({ col:"a" }, SortByColumns(Table({a:1,b:"one"},{a:2,b:"two"},{a:3,b:"three"}), col, SortOrder.Descending)) -Table({a:3,b:"three"},{a:2,b:"two"},{a:1,b:"one"}) - ->> With({ col:"b" }, SortByColumns(Table({a:1,b:"one"},{a:2,b:"two"},{a:3,b:"three"}), col, SortOrder.Descending)) -Table({a:2,b:"two"},{a:3,b:"three"},{a:1,b:"one"}) +>> SortByColumns([1],"Value") +Table({Value:1}) + +>> SortByColumns([-2, -1, 0, 1, 2], "Value") +Table({Value:-2},{Value:-1},{Value:0},{Value:1},{Value:2}) + +>> SortByColumns(Table({Value:-2},{Value:-1},{Value:1},{Value:2},{Value:5}),"Value") +Table({Value:-2},{Value:-1},{Value:1},{Value:2},{Value:5}) + + +// ******** NUMBER PARAMETERS ******** + +>> SortByColumns([-2, -1, 5, 1, 2], "Value") +Table({Value:-2},{Value:-1},{Value:1},{Value:2},{Value:5}) + +>> SortByColumns([-2, -1, 5, 1, 2], "Value", SortOrder.Descending) +Table({Value:5},{Value:2},{Value:1},{Value:-1},{Value:-2}) + +>> SortByColumns(Table({a:8, b:-1}, {a:2}, {b:5}), "a") +Table({a:2,b:Blank()},{a:8,b:-1},{a:Blank(),b:5}) + +>> SortByColumns(Table({a:8, b:-1}, {a:2}, {b:5}), "b", SortOrder.Descending) +Table({a:Blank(),b:5},{a:8,b:-1},{a:2,b:Blank()}) + +// ******** STRING PARAMETERS ******** + +>> SortByColumns(["a", "b", "D", "x", "J", "C"], "Value") +Table({Value:"a"},{Value:"b"},{Value:"C"},{Value:"D"},{Value:"J"},{Value:"x"}) + +>> SortByColumns(["a", "b", "D", "x", "J", "C"], "Value", SortOrder.Descending) +Table({Value:"x"},{Value:"J"},{Value:"D"},{Value:"C"},{Value:"b"},{Value:"a"}) + +>> SortByColumns(["a", "b", "B", "x", "D", "C"], "Value") +Table({Value:"a"},{Value:"b"},{Value:"B"},{Value:"C"},{Value:"D"},{Value:"x"}) + +>> SortByColumns(["Hello", "hellO", "hello", "heLLo", "HELLO", "hElLo"], "Value") +Table({Value:"hello"},{Value:"hellO"},{Value:"heLLo"},{Value:"hElLo"},{Value:"Hello"},{Value:"HELLO"}) + +>> SortByColumns(["Hello", "hellO", "1", "%", "2", "hElLo"], "Value") +Table({Value:"%"},{Value:"1"},{Value:"2"},{Value:"hellO"},{Value:"hElLo"},{Value:"Hello"}) + +>> SortByColumns(["a", "b", Blank(), "x", "J", "C"], "Value") +Table({Value:"a"},{Value:"b"},{Value:"C"},{Value:"J"},{Value:"x"},{Value:Blank()}) + +>> SortByColumns(["a", "b", Blank(), "x", "J", "C"], "Value", SortOrder.Descending) +Table({Value:"x"},{Value:"J"},{Value:"C"},{Value:"b"},{Value:"a"},{Value:Blank()}) + +// ******** BOOLEAN PARAMETERS ******** + +>> SortByColumns([true,false,true,false,true], "Value") +Table({Value:false},{Value:false},{Value:true},{Value:true},{Value:true}) + +>> SortByColumns([true,false,true,false,true], "Value", SortOrder.Descending) +Table({Value:true},{Value:true},{Value:true},{Value:false},{Value:false}) +// +>> SortByColumns([true,false,Blank(),false,true], "Value") +Table({Value:false},{Value:false},{Value:true},{Value:true},{Value:Blank()}) + +>> SortByColumns([true,false,Blank(),false,true], "Value", SortOrder.Descending) +Table({Value:true},{Value:true},{Value:false},{Value:false},{Value:Blank()}) + +// ******** DATE PARAMETERS ******** + +>> SortByColumns([Date(2020, 01, 05), Date(2020, 01, 01), Date(1995, 01, 01)], "Value") +Table({Value:Date(1995,1,1)},{Value:Date(2020,1,1)},{Value:Date(2020,1,5)}) + +>> SortByColumns([Date(2020, 01, 05), Date(2012,12,14), Date(2022,1,1)], "Value") +Table({Value:Date(2012,12,14)},{Value:Date(2020,1,5)},{Value:Date(2022,1,1)}) + +>> SortByColumns([Date(2020, 01, 05), Date(2020, 01, 01), Date(1995, 01, 01)], "Value", SortOrder.Descending) +Table({Value:Date(2020,1,5)},{Value:Date(2020,1,1)},{Value:Date(1995,1,1)}) + +>> SortByColumns([Date(2020, 01, 05), Date(2012,12,14), Blank(), Date(2022,1,1)], "Value", SortOrder.Descending) +Table({Value:Date(2022,1,1)},{Value:Date(2020,1,5)},{Value:Date(2012,12,14)},{Value:Blank()}) + +// ******** TIME PARAMETERS ******** + +>> SortByColumns([Time(12,35,55), Time(23, 01, 01), Time(5,30,50)], "Value") +Table({Value:Time(5,30,50,0)},{Value:Time(12,35,55,0)},{Value:Time(23,1,1,0)}) + +>> SortByColumns([Time(12,35,55), Time(23, 01, 01), Time(5,30,50)], "Value", SortOrder.Descending) +Table({Value:Time(23,1,1,0)},{Value:Time(12,35,55,0)},{Value:Time(5,30,50,0)}) + +>> SortByColumns([Time(17,29,0), Blank(), Time(5,30,50)], "Value") +Table({Value:Time(5,30,50,0)},{Value:Time(17,29,0,0)},{Value:Blank()}) + +// ******** DATETIME PARAMETERS ******** + +>> SortByColumns([DateTime(2019,5,16,8,0,0), DateTime(2019,5,16,20,0,0), DateTime(2019,5,16,20,0,1)], "Value") +Table({Value:DateTime(2019,5,16,8,0,0,0)},{Value:DateTime(2019,5,16,20,0,0,0)},{Value:DateTime(2019,5,16,20,0,1,0)}) + +>> SortByColumns([DateTime(2019,5,16,8,0,0), DateTime(2019,5,16,20,0,0), DateTime(2019,5,16,20,0,1)], "Value", SortOrder.Descending) +Table({Value:DateTime(2019,5,16,20,0,1,0)},{Value:DateTime(2019,5,16,20,0,0,0)},{Value:DateTime(2019,5,16,8,0,0,0)}) + + +// ******** NULL and ERROR PARAMETERS ******** + +>> SortByColumns([-2, -1, 5, 1, 2], "Value", If(1/0<2,SortOrder.Ascending,SortOrder.Descending)) +Error({Kind:ErrorKind.Div0}) + +//Error passed as one of the table records +>> SortByColumns([-2, -1, 5, 1/0, 2], "Value") +Error({Kind:ErrorKind.Div0}) + +>> SortByColumns(["a", "b", "D", "x", "J", Error({Kind: 11})], "Value") +Error({Kind:ErrorKind.Validation}) + + +>> SortByColumns(Table({Value:Blank()}),"Value") +Table({Value:Blank()}) + +>> SortByColumns([{Value:Blank()},{Value:Blank()}], "Value", SortOrder.Descending) +Table({Value:Blank()},{Value:Blank()}) + +>> SortByColumns([-2, Blank(), 5, 1, 2], "Value") +Table({Value:-2},{Value:1},{Value:2},{Value:5},{Value:Blank()}) + +>> SortByColumns([-2, -Blank(), 5, 1, 2], "Value") +Table({Value:-2},{Value:0},{Value:1},{Value:2},{Value:5}) + +>> SortByColumns([-2, Blank(), 5, 1, 2, -Blank()], "Value", SortOrder.Descending) +Table({Value:5},{Value:2},{Value:1},{Value:0},{Value:-2},{Value:Blank()}) + +// Sort on error source +>> SortByColumns(If(Left("Hello", -1) = "",[1,2,3,4]), "Value", SortOrder.Ascending) +Error({Kind:ErrorKind.InvalidArgument}) + +// Sort null table with single valid column +>> SortByColumns(If(1<0,[1,2,3]), "Value",SortOrder.Ascending) +Blank() + +//Sort empty table with single valid column - ascending order +>> SortByColumns(Filter([1, 2, 3], Value > 100), "Value", SortOrder.Ascending) +Table() + +//Sort empty table with multiple valid column - ascending order +>> SortByColumns(Filter(Table({Value1:1,Value2:2}), Value1>100), "Value1", SortOrder.Ascending,"Value2", SortOrder.Ascending) +Table() + + +// ******** MULTI COLUMN SORT CASES ******** + +// SORT ON NUMERIC COLUMN "AGE" - ASCENDING ORDER + +>> SortByColumns(Table( + { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: Blank(), Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, + { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, + { First: "Helio", Last: Blank(), Age: 263, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, + { First: "Titan", Last: Blank(), Age: 792, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, + { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, + { First: Blank(), Last: "Rodriguez", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }),"Age") +Table({Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:263,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:792,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Clark",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Allen",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:Blank(),Last:"Brown",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Walker",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:Blank(),Last:"Rodriguez",Vaccinated:Blank()}) + + +// SORT ON NUMERIC COLUMN "AGE" - DESCENDING ORDER + +>> SortByColumns(Table( + { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: Blank(), Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, + { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, + { First: "Helio", Last: Blank(), Age: 263, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, + { First: "Titan", Last: Blank(), Age: 792, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, + { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, + { First: Blank(), Last: "Rodriguez", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }),"Age", SortOrder.Descending) +Table({Age:792,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:263,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Clark",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Allen",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:Blank(),Last:"Brown",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Walker",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:Blank(),Last:"Rodriguez",Vaccinated:Blank()}) + + +// SORT ON BOOL COLUMN "VACCINATED" - ASCENDING ORDER + +>> SortByColumns(Table( + { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: Blank(), Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, + { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, + { First: "Helio", Last: Blank(), Age: 263, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, + { First: "Titan", Last: Blank(), Age: 792, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, + { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, + { First: Blank(), Last: "Rodriguez", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }),"Vaccinated") +Table({Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Clark",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Allen",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:Blank(),Last:"Brown",Vaccinated:Blank()},{Age:263,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:792,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Walker",Vaccinated:Blank()},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:Blank(),Last:"Rodriguez",Vaccinated:Blank()}) + + +// SORT ON BOOL COLUMN "VACCINATED" - DESCENDING ORDER + +>> SortByColumns(Table( + { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: Blank(), Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, + { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, + { First: "Helio", Last: Blank(), Age: 263, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, + { First: "Titan", Last: Blank(), Age: 792, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, + { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, + { First: Blank(), Last: "Rodriguez", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }),"Vaccinated", SortOrder.Descending) +Table({Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true},{Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Clark",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Allen",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:Blank(),Last:"Brown",Vaccinated:Blank()},{Age:263,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:792,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Walker",Vaccinated:Blank()},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:Blank(),Last:"Rodriguez",Vaccinated:Blank()}) + + +// SORT ON STRING COLUMN "FIRST" - ASCENDING ORDER + +>> SortByColumns(Table( + { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: Blank(), Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, + { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, + { First: "Helio", Last: Blank(), Age: 263, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, + { First: "Titan", Last: Blank(), Age: 792, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, + { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, + { First: Blank(), Last: "Rodriguez", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }),"First") +Table({Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:263,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true},{Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:792,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Clark",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Allen",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:Blank(),Last:"Brown",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Walker",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:Blank(),Last:"Rodriguez",Vaccinated:Blank()}) + + +// SORT ON STRING COLUMN "FIRST" - DESCENDING ORDER + +>> SortByColumns(Table( + { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: Blank(), Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, + { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, + { First: "Helio", Last: Blank(), Age: 263, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, + { First: "Titan", Last: Blank(), Age: 792, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, + { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, + { First: Blank(), Last: "Rodriguez", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }),"First",SortOrder.Descending) +Table({Age:792,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true},{Age:263,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Clark",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Allen",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:Blank(),Last:"Brown",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:Blank(),Last:"Walker",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:Blank(),Last:"Rodriguez",Vaccinated:Blank()}) + + +// SORT HIERARCHICAL DATA ON BOOL COLUMN "HasID - ASCENDING ORDER + +>> SortByColumns(Table( + { First: "Bob", Age: 2, HasId: true, Properties: { Vaccinated: true, HasDog: true } }, + { First: "Alice", Age: 5, HasId: false, Properties: { Vaccinated: true, HasDog: false } }, + { First: Blank(), Age: 12, HasId: true, Properties: { Vaccinated: Blank(), HasDog: true } }, + { First: Blank(), Age: 12, HasId: false, Properties: Blank() }, + { First: "Mary", Age: 12, HasId: false, Properties: Blank() }, + { First: "Wonderland", Age: 5, HasId: Blank(), Properties: { Vaccinated: true, HasDog: Blank() } }, + { First: "Samantha", Age: 15, HasId: Blank(), Properties: { Vaccinated: Blank(), HasDog: Blank() } }), "HasId") +Table({Age:5,First:"Alice",HasId:false,Properties:{HasDog:false,Vaccinated:true}},{Age:12,First:Blank(),HasId:false,Properties:Blank()},{Age:12,First:"Mary",HasId:false,Properties:Blank()},{Age:2,First:"Bob",HasId:true,Properties:{HasDog:true,Vaccinated:true}},{Age:12,First:Blank(),HasId:true,Properties:{HasDog:true,Vaccinated:Blank()}},{Age:5,First:"Wonderland",HasId:Blank(),Properties:{HasDog:Blank(),Vaccinated:true}},{Age:15,First:"Samantha",HasId:Blank(),Properties:{HasDog:Blank(),Vaccinated:Blank()}}) + + +// SORT HIERARCHICAL DATA ON BOOL COLUMN "HasID - DESCENDING ORDER + +>> SortByColumns(Table( + { First: "Bob", Age: 2, HasId: true, Properties: { Vaccinated: true, HasDog: true } }, + { First: "Alice", Age: 5, HasId: false, Properties: { Vaccinated: true, HasDog: false } }, + { First: Blank(), Age: 12, HasId: true, Properties: { Vaccinated: Blank(), HasDog: true } }, + { First: Blank(), Age: 12, HasId: false, Properties: Blank() }, + { First: "Mary", Age: 12, HasId: false, Properties: Blank() }, + { First: "Wonderland", Age: 5, HasId: Blank(), Properties: { Vaccinated: true, HasDog: Blank() } }, + { First: "Samantha", Age: 15, HasId: Blank(), Properties: { Vaccinated: Blank(), HasDog: Blank() } }), "HasId",SortOrder.Descending) +Table({Age:2,First:"Bob",HasId:true,Properties:{HasDog:true,Vaccinated:true}},{Age:12,First:Blank(),HasId:true,Properties:{HasDog:true,Vaccinated:Blank()}},{Age:5,First:"Alice",HasId:false,Properties:{HasDog:false,Vaccinated:true}},{Age:12,First:Blank(),HasId:false,Properties:Blank()},{Age:12,First:"Mary",HasId:false,Properties:Blank()},{Age:5,First:"Wonderland",HasId:Blank(),Properties:{HasDog:Blank(),Vaccinated:true}},{Age:15,First:"Samantha",HasId:Blank(),Properties:{HasDog:Blank(),Vaccinated:Blank()}}) + + +// SORT A TABLE WITH ERROR ROWS - ASCENDING ORDER + +>> SortByColumns(Table( + { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, + { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, + If(Left("Hello", -1) = "", { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }), + { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false } + ),"First") +Error({Kind:ErrorKind.InvalidArgument}) + +>> SortByColumns(Table( + { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, + { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, + If(Left("Hello", -1) = "", { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }), + { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false } + ),"First", SortOrder.Descending) +Error({Kind:ErrorKind.InvalidArgument}) + + +// SORT A TABLE WITH BLANK ROWS - ASCENDING ORDER + +>> SortByColumns(Table( + { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, + Blank(), + Blank(), + Blank(), + { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, + { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, + { First: "Helio", Last: Blank(), Age: 263, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, + { First: "Titan", Last: Blank(), Age: 792, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + Blank(), + { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, + { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, + Blank(), + { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }),"First") +Table({Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:263,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true},{Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:792,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},Blank(),Blank(),Blank(),Blank(),Blank()) + + +// SORT A TABLE WITH BLANK ROWS - DESCENDING ORDER + +>> SortByColumns(Table( + { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, + Blank(), + Blank(), + Blank(), + { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, + { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, + { First: "Helio", Last: Blank(), Age: 263, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, + { First: "Titan", Last: Blank(), Age: 792, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + Blank(), + { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, + { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, + Blank(), + { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }),"First", SortOrder.Descending) +Table({Age:792,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:32,First:"Nizam",Gender:"Male",HasDog:false,Last:"Lewis",Vaccinated:true},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:20,First:"Maria",Gender:"Female",HasDog:false,Last:"Martinez",Vaccinated:true},{Age:17,First:"John",Gender:"Male",HasDog:false,Last:"Batali",Vaccinated:false},{Age:29,First:"Jaideep",Gender:"Male",HasDog:false,Last:"Lopez",Vaccinated:true},{Age:263,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Blank(),Vaccinated:Blank()},{Age:29,First:"Emily",Gender:"Female",HasDog:Blank(),Last:"Jones",Vaccinated:true},{Age:4,First:"Dora",Gender:"Female",HasDog:true,Last:"TheExplorer",Vaccinated:Blank()},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:27,First:"Andrew",Gender:"Male",HasDog:false,Last:"Lee",Vaccinated:true},{Age:40,First:"Amelia",Gender:"Female",HasDog:Blank(),Last:"Bedelia",Vaccinated:Blank()},{Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},Blank(),Blank(),Blank(),Blank(),Blank()) + + +// SORT A TABLE WITH NO VALUES FOR SOME COLUMNS + +>> SortByColumns(Table( + { First: "Bob", Last: "Smith", Age: 2 }, + { First: "Alice" }, + Blank(), + Blank(), + Blank(), + { First: "John", Last: "Batali", Age: 17 }, + { First: "Emily", Age: 29 }), "Last", SortOrder.Ascending, "Age", SortOrder.Descending) +Table({Age:17,First:"John",Last:"Batali"},{Age:2,First:"Bob",Last:"Smith"},{Age:29,First:"Emily",Last:Blank()},{Age:Blank(),First:"Alice",Last:Blank()},Blank(),Blank(),Blank()) + +// SORT BY MULTIPLE COLUMNS - TEST 1 + +>> SortByColumns(Table( + { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: Blank(), Last: "Walker", Age: 4, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: false, HasDog: true }, + { First: "Bob", Last: "Smith", Age: 2, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: Blank(), Last: "Walker", Age: 4, Gender: "Female", Vaccinated: false, HasDog: true }, + { First: "Bob", Last: "Smith", Age: 2, Gender: "Female", Vaccinated: false, HasDog: true }, + { First: "Bob", Last: "Walker", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Bob", Last: "Walker", Age: 2, Gender: "Male", Vaccinated: false, HasDog: true }, + { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Female", Vaccinated: true, HasDog: true }, + { First: "Bob", Last: "Walker", Age: 1, Gender: "Male", Vaccinated: false, HasDog: true }, + { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: false, HasDog: true }, + { First: "Bob", Last: "Walker", Age: 1, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: Blank(), Last: "James", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: true }, + { First: "Bob", Last: "Walker", Age: 1, Gender: "Female", Vaccinated: false, HasDog: true }, + { First: Blank(), Last: "James", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: true }, + { First: "Bob", Last: "Walker", Age: 2, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: "Bob", Last: "Walker", Age: 2, Gender: "Female", Vaccinated: false, HasDog: true }, + { First: "Bob", Last: "Walker", Age: 4, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "John", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Andrew", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "John", Last: "Walker", Age: 1, Gender: "Male", Vaccinated: false, HasDog: true }, + { First: "Bob", Last: "Walker", Age: 4, Gender: "Male", Vaccinated: false, HasDog: true }, + { First: "Andrew", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: false, HasDog: true }, + { First: "Bob", Last: "Walker", Age: 4, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: "Bob", Last: "Walker", Age: 4, Gender: "Female", Vaccinated: false, HasDog: true }, + { First: "Bob", Last: "Walker", Age: 1, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Andrew", Last: "Smith", Age: 2, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: "John", Last: "Bob", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: "Andrew", Last: "Smith", Age: 2, Gender: "Female", Vaccinated: false, HasDog: true }, + { First: "John", Last: "Newman", Age: 6, Gender: "Female", Vaccinated: false, HasDog: true }), "First", SortOrder.Ascending, "Last", SortOrder.Descending, "Age", SortOrder.Descending, "Gender", SortOrder.Descending, "Vaccinated", SortOrder.Ascending) +Table({Age:2,First:"Andrew",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:false},{Age:2,First:"Andrew",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:2,First:"Andrew",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:false},{Age:2,First:"Andrew",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:4,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:false},{Age:4,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:true},{Age:4,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:false},{Age:4,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:true},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:false},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:true},{Age:2,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:false},{Age:2,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:true},{Age:1,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:false},{Age:1,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:true},{Age:1,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:false},{Age:1,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:true},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:false},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:2,First:"Bob",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:false},{Age:2,First:"Bob",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:1,First:"John",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:false},{Age:2,First:"John",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:6,First:"John",Gender:"Female",HasDog:true,Last:"Newman",Vaccinated:false},{Age:5,First:"John",Gender:"Female",HasDog:true,Last:"Bob",Vaccinated:true},{Age:4,First:Blank(),Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:false},{Age:4,First:Blank(),Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:true},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:false},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:true},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:true,Last:"James",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:true,Last:"James",Vaccinated:Blank()}) + + +// SORT BY MULTIPLE COLUMNS - TEST 2 + +>> SortByColumns(Table( + { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: Blank(), Last: "Walker", Age: 4, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: false, HasDog: true }, + { First: "Bob", Last: "Smith", Age: 2, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: Blank(), Last: "Walker", Age: 4, Gender: "Female", Vaccinated: false, HasDog: true }, + { First: "Bob", Last: "Smith", Age: 2, Gender: "Female", Vaccinated: false, HasDog: true }, + { First: "Bob", Last: "Walker", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Bob", Last: "Walker", Age: 2, Gender: "Male", Vaccinated: false, HasDog: true }, + { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Female", Vaccinated: true, HasDog: true }, + { First: "Bob", Last: "Walker", Age: 1, Gender: "Male", Vaccinated: false, HasDog: true }, + { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: false, HasDog: true }, + { First: "Bob", Last: "Walker", Age: 1, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: Blank(), Last: "James", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: true }, + { First: "Bob", Last: "Walker", Age: 1, Gender: "Female", Vaccinated: false, HasDog: true }, + { First: Blank(), Last: "James", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: true }, + { First: "Bob", Last: "Walker", Age: 2, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: "Bob", Last: "Walker", Age: 2, Gender: "Female", Vaccinated: false, HasDog: true }, + { First: "Bob", Last: "Walker", Age: 4, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "John", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Andrew", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "John", Last: "Walker", Age: 1, Gender: "Male", Vaccinated: false, HasDog: true }, + { First: "Bob", Last: "Walker", Age: 4, Gender: "Male", Vaccinated: false, HasDog: true }, + { First: "Andrew", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: false, HasDog: true }, + { First: "Bob", Last: "Walker", Age: 4, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: "Bob", Last: "Walker", Age: 4, Gender: "Female", Vaccinated: false, HasDog: true }, + { First: "Bob", Last: "Walker", Age: 1, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Andrew", Last: "Smith", Age: 2, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: "John", Last: "Bob", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: "Andrew", Last: "Smith", Age: 2, Gender: "Female", Vaccinated: false, HasDog: true }, + { First: "John", Last: "Newman", Age: 6, Gender: "Female", Vaccinated: false, HasDog: true }), "First", SortOrder.Ascending, "Last", SortOrder.Ascending, "Age", SortOrder.Ascending, "Gender", SortOrder.Ascending, "Vaccinated", SortOrder.Descending) +Table({Age:2,First:"Andrew",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:2,First:"Andrew",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:false},{Age:2,First:"Andrew",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:2,First:"Andrew",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:false},{Age:2,First:"Bob",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:2,First:"Bob",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:false},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:false},{Age:1,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:true},{Age:1,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:false},{Age:1,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:true},{Age:1,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:false},{Age:2,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:true},{Age:2,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:false},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:true},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:false},{Age:4,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:true},{Age:4,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:false},{Age:4,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:true},{Age:4,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:false},{Age:5,First:"John",Gender:"Female",HasDog:true,Last:"Bob",Vaccinated:true},{Age:6,First:"John",Gender:"Female",HasDog:true,Last:"Newman",Vaccinated:false},{Age:2,First:"John",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:1,First:"John",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:false},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:true,Last:"James",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:true,Last:"James",Vaccinated:Blank()},{Age:4,First:Blank(),Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:true},{Age:4,First:Blank(),Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:false},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:true},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:false}) + + +// SORT BY MULTIPLE COLUMNS - TABLE WITH BLANK ROWS + + +>> SortByColumns(Table( + { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: false, HasDog: true }, + { First: "Bob", Last: "Smith", Age: 2, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: "Andrew", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Bob", Last: "Walker", Age: 1, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Andrew", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: false, HasDog: true }, + { First: "Bob", Last: "Walker", Age: 1, Gender: "Male", Vaccinated: false, HasDog: true }, + Blank(), + { First: "Bob", Last: "Walker", Age: 1, Gender: "Female", Vaccinated: true, HasDog: true }, + Blank(), + { First: "Bob", Last: "Walker", Age: 1, Gender: "Female", Vaccinated: false, HasDog: true }, + Blank(), + { First: "Andrew", Last: "Smith", Age: 2, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: "Bob", Last: "Smith", Age: 2, Gender: "Female", Vaccinated: false, HasDog: true }, + { First: "Bob", Last: "Walker", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Bob", Last: "Walker", Age: 2, Gender: "Male", Vaccinated: false, HasDog: true }, + { First: "Andrew", Last: "Smith", Age: 2, Gender: "Female", Vaccinated: false, HasDog: true }, + { First: "Bob", Last: "Walker", Age: 2, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: "Bob", Last: "Walker", Age: 2, Gender: "Female", Vaccinated: false, HasDog: true }, + { First: "Bob", Last: "Walker", Age: 4, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "John", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Bob", Last: "Walker", Age: 4, Gender: "Male", Vaccinated: false, HasDog: true }, + { First: Blank(), Last: "Walker", Age: 4, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: "Bob", Last: "Walker", Age: 4, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: "Bob", Last: "Walker", Age: 4, Gender: "Female", Vaccinated: false, HasDog: true }, + { First: Blank(), Last: "Walker", Age: 4, Gender: "Female", Vaccinated: false, HasDog: true }, + { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Female", Vaccinated: true, HasDog: true }, + { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: false, HasDog: true }, + { First: "John", Last: "Walker", Age: 1, Gender: "Male", Vaccinated: false, HasDog: true }, + { First: Blank(), Last: "James", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: true }, + { First: Blank(), Last: "James", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: true }, + { First: "John", Last: "Bob", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: "John", Last: "Newman", Age: 6, Gender: "Female", Vaccinated: false, HasDog: true }), "First", SortOrder.Ascending, "Last", SortOrder.Descending, "Age", SortOrder.Descending, "Gender", SortOrder.Descending, "Vaccinated", SortOrder.Ascending) +Table({Age:2,First:"Andrew",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:false},{Age:2,First:"Andrew",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:2,First:"Andrew",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:false},{Age:2,First:"Andrew",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:4,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:false},{Age:4,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:true},{Age:4,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:false},{Age:4,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:true},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:false},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:true},{Age:2,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:false},{Age:2,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:true},{Age:1,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:false},{Age:1,First:"Bob",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:true},{Age:1,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:false},{Age:1,First:"Bob",Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:true},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:false},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:2,First:"Bob",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:false},{Age:2,First:"Bob",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:1,First:"John",Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:false},{Age:2,First:"John",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:6,First:"John",Gender:"Female",HasDog:true,Last:"Newman",Vaccinated:false},{Age:5,First:"John",Gender:"Female",HasDog:true,Last:"Bob",Vaccinated:true},{Age:4,First:Blank(),Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:false},{Age:4,First:Blank(),Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:true},{Age:Blank(),First:Blank(),Gender:"Male",HasDog:true,Last:"Walker",Vaccinated:false},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:true,Last:"Walker",Vaccinated:true},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:true,Last:"James",Vaccinated:Blank()},{Age:Blank(),First:Blank(),Gender:"Female",HasDog:true,Last:"James",Vaccinated:Blank()},Blank(),Blank(),Blank()) + + +// SORT ON TABLE WITH COLUMNS HAVING SPECIAL CHARACTERS - DESCENDING ORDER + +>> SortByColumns(Table( + { 'A\"B': "Bob" }, { 'A\"B': "Alice" }, { 'A\"B': "Mary" }, { 'A\"B': "Wonderland" }, { 'A\"B': "Samantha" }), "A\""B", SortOrder.Descending) +Table({'A\"B':"Wonderland"},{'A\"B':"Samantha"},{'A\"B':"Mary"},{'A\"B':"Bob"},{'A\"B':"Alice"}) + +// SORT ON TABLE WITH SOME COLUMNS HAVING ERROR VALUES - ASCENDING ORDER + +>> SortByColumns(Table( + { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: "Helio", Last: Left("Hello", -1), Age: 263, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, + { First: "Titan", Last: Left("Hello", -1), Age: 792, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }),"Last", SortOrder.Ascending,"Age", SortOrder.Descending) +Error({Kind:ErrorKind.InvalidArgument}) + + +// SORT ON TABLE WITH SOME COLUMNS HAVING ERROR VALUES, BUT DOESN"T TOUCH ERROR - ASCENDING ORDER + +>> SortByColumns(Table( + { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: "Helio", Last: Left("Hello", -1), Age: 263, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, + { First: "Titan", Last: Left("Hello", -1), Age: 792, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }),"First", SortOrder.Ascending,"Age", SortOrder.Descending) +Table({Age:5,First:"Alice",Gender:"Female",HasDog:true,Last:"Smith",Vaccinated:true},{Age:2,First:"Bob",Gender:"Male",HasDog:true,Last:"Smith",Vaccinated:true},{Age:263,First:"Helio",Gender:"Unknown",HasDog:Blank(),Last:Error({Kind:ErrorKind.InvalidArgument}),Vaccinated:Blank()},{Age:48,First:"Mary",Gender:"Female",HasDog:false,Last:"Harris",Vaccinated:false},{Age:792,First:"Titan",Gender:"Unknown",HasDog:Blank(),Last:Error({Kind:ErrorKind.InvalidArgument}),Vaccinated:Blank()}) + + +// SORT ON TABLE WITH ERROR PASSED AS SORT ORDER - ASCENDING ORDER + +>> SortByColumns(Table( + { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: Blank(), Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, + { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, + { First: "Helio", Last: Blank(), Age: 263, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, + { First: "Titan", Last: Blank(), Age: 792, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, + { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, + { First: Blank(), Last: "Rodriguez", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }),"First", If(Char(-1) <> "a", SortOrder.Ascending)) +Error({Kind:ErrorKind.InvalidArgument}) + + +// MULTIPLE COLUMNS USED FOR SORTING ORDER + +>> SortByColumns(Table( + {Flavor: "Chocolate", Quantity:100, Quantity2:100, Quantity3:100, Quantity4:100, Quantity5:100, Quantity6:100, Quantity7:100, Quantity8:100, Quantity9:100, Quantity10:100, Quantity11:100, Quantity12:100, Quantity13:100, Quantity14:100, Quantity15:100, Quantity16:100, Quantity17:100, Quantity18:100, Quantity19:100, Quantity20:100}),"Quantity", SortOrder.Ascending, "Quantity2", SortOrder.Ascending, "Quantity3", SortOrder.Ascending, "Quantity4", SortOrder.Ascending, "Quantity5", SortOrder.Ascending, "Quantity6", SortOrder.Ascending, "Quantity7", SortOrder.Ascending, "Quantity8", SortOrder.Ascending, "Quantity9", SortOrder.Ascending, "Quantity10", SortOrder.Ascending, "Quantity11", SortOrder.Ascending, "Quantity12", SortOrder.Ascending, "Quantity13", SortOrder.Ascending, "Quantity14", SortOrder.Ascending, "Quantity15", SortOrder.Ascending, "Quantity16", SortOrder.Ascending, "Quantity17", SortOrder.Ascending, "Quantity18", SortOrder.Ascending, "Quantity19", SortOrder.Ascending, "Quantity20", SortOrder.Ascending) +Table({Flavor:"Chocolate",Quantity:100,Quantity10:100,Quantity11:100,Quantity12:100,Quantity13:100,Quantity14:100,Quantity15:100,Quantity16:100,Quantity17:100,Quantity18:100,Quantity19:100,Quantity2:100,Quantity20:100,Quantity3:100,Quantity4:100,Quantity5:100,Quantity6:100,Quantity7:100,Quantity8:100,Quantity9:100}) + + +// SAME COLUMN USED TO SPECIFY CONFLICTING SORT ORDERS + +>> SortByColumns(Table( + {Flavor: "Chocolate", Quantity:100, OnOrder:150}, + {Flavor: "Vanilla", Quantity:200, OnOrder:20}, + {Flavor: "Strawberry", Quantity:300, OnOrder:20}, + {Flavor: "Mint", Quantity:60, OnOrder:100}, + {Flavor: "Pistachio", Quantity:200, OnOrder:10}),"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending) +Table({Flavor:"Pistachio",OnOrder:10,Quantity:200},{Flavor:"Vanilla",OnOrder:20,Quantity:200},{Flavor:"Strawberry",OnOrder:20,Quantity:300},{Flavor:"Mint",OnOrder:100,Quantity:60},{Flavor:"Chocolate",OnOrder:150,Quantity:100}) + + +//SAME COLUMN USED MULTIPLE TIMES FOR SORTING ORDER + +>> SortByColumns(Table( + {Flavor: "Chocolate", Quantity:100, OnOrder:150}, + {Flavor: "Vanilla", Quantity:200, OnOrder:20}, + {Flavor: "Strawberry", Quantity:300, OnOrder:20}, + {Flavor: "Mint", Quantity:60, OnOrder:100}, + {Flavor: "Pistachio", Quantity:200, OnOrder:10}),"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending, "OnOrder", SortOrder.Descending,"OnOrder", SortOrder.Ascending) +Table({Flavor:"Pistachio",OnOrder:10,Quantity:200},{Flavor:"Vanilla",OnOrder:20,Quantity:200},{Flavor:"Strawberry",OnOrder:20,Quantity:300},{Flavor:"Mint",OnOrder:100,Quantity:60},{Flavor:"Chocolate",OnOrder:150,Quantity:100}) + +// ###### SortByColumns, column + ascending/descending overload ###### + +// Legacy behavior: we can pass non-string literals to the column name in SortByColumns +>> SortByColumns([1,3,2],Left("Value234", 5)) +Table({Value:1},{Value:2},{Value:3}) + +>> SortByColumns([1,3,2],Left("Value234", 5), SortOrder.Descending) +Table({Value:3},{Value:2},{Value:1}) + +>> SortByColumns(Table({a:1, b:"one"}, {a:2, b:"two"}, {a:3, b:"three"}, {a:4, b:"four"}), If(1>0,"a","b")) +Table({a:1,b:"one"},{a:2,b:"two"},{a:3,b:"three"},{a:4,b:"four"}) + +>> SortByColumns(Table({a:1, b:"one"}, {a:2, b:"two"}, {a:3, b:"three"}, {a:4, b:"four"}), If(1<0,"a","b")) +Table({a:4,b:"four"},{a:1,b:"one"},{a:3,b:"three"},{a:2,b:"two"}) + +>> SortByColumns(Table({a:1,b:"one"}, {a:2, b:"two"}, {a:1,b:"onebis"},{a:3, b:"three"}, {a:4, b:"four"}, {a:3, b:"threebis"}), "a", SortOrder.Ascending, Left("b1", 1), SortOrder.Descending) +Table({a:1,b:"onebis"},{a:1,b:"one"},{a:2,b:"two"},{a:3,b:"threebis"},{a:3,b:"three"},{a:4,b:"four"}) + +>> SortByColumns(Table({a:1, b:1111}, {a:2, b:222}, {a:3, b:33}), If(1>0,"columndoesnotexist")) +Error({Kind:ErrorKind.InvalidFunctionUsage}) + +>> SortByColumns(Table({a:1,b:"one"}, {a:2, b:"two"}, {a:1,b:"onebis"},{a:3, b:"three"}, {a:4, b:"four"}, {a:3, b:"threebis"}), "a", SortOrder.Ascending, Left("b1", 2), SortOrder.Descending) +Error({Kind:ErrorKind.InvalidFunctionUsage}) + +>> SortByColumns(Table( + { First: "Bob", Last: "Smith", Age: 2, Gender: "Male", Vaccinated: true, HasDog: true }, + { First: "Alice", Last: "Smith", Age: 5, Gender: "Female", Vaccinated: true, HasDog: true }, + { First: Blank(), Last: "Clark", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Allen", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Brown", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "John", Last: "Batali", Age: 17, Gender: "Male", Vaccinated: false, HasDog: false }, + { First: "Emily", Last: "Jones", Age: 29, Gender: "Female", Vaccinated: true, HasDog: Blank() }, + { First: "Helio", Last: Blank(), Age: 263, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Mary", Last: "Harris", Age: 48, Gender: "Female", Vaccinated: false, HasDog: false }, + { First: "Titan", Last: Blank(), Age: 792, Gender: "Unknown", Vaccinated: Blank(), HasDog: Blank() }, + { First: Blank(), Last: "Walker", Age: Blank(), Gender: "Male", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Andrew", Last: "Lee", Age: 27, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Nizam", Last: "Lewis", Age: 32, Gender: "Male", Vaccinated: true, HasDog: false }, + { First: "Amelia", Last: "Bedelia", Age: 40, Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Dora", Last: "TheExplorer", Age: 4, Gender: "Female", Vaccinated: Blank(), HasDog: true }, + { First: "Maria", Last: "Martinez", Age: 20, Gender: "Female", Vaccinated: true, HasDog: false }, + { First: Blank(), Last: "Rodriguez", Age: Blank(), Gender: "Female", Vaccinated: Blank(), HasDog: Blank() }, + { First: "Jaideep", Last: "Lopez", Age: 29, Gender: "Male", Vaccinated: true, HasDog: false }),Left("Hello", -1), SortOrder.Ascending,"Age", SortOrder.Descending) +Error({Kind:ErrorKind.InvalidArgument}) + +// ###### SortByColumns, column + order table overload ###### + +>> SortByColumns([1,3,2],Left("Value234", 5), [2,3,1]) +Table({Value:2},{Value:3},{Value:1}) + +>> SortByColumns(Table({a:1, b:"one"}, {a:2, b:"two"}, {a:3, b:"three"}, {a:4, b:"four"}), If(1<0,"a","b"), ["four", "three", "two", "one"]) +Table({a:4,b:"four"},{a:3,b:"three"},{a:2,b:"two"},{a:1,b:"one"}) + +// If order table does not match the column to be sorted, results are unchanged +>> SortByColumns(Table({a:1, b:"one"}, {a:2, b:"two"}, {a:3, b:"three"}, {a:4, b:"four"}), If(1>0,"a","b"), ["four", "three", "two", "one"]) +Table({a:1,b:"one"},{a:2,b:"two"},{a:3,b:"three"},{a:4,b:"four"}) + +>> SortByColumns(Table({a:1, b:1111}, {a:2, b:222}, {a:3, b:33}), If(1>0,"columndoesnotexist"), [1,2,3]) +Error({Kind:ErrorKind.InvalidFunctionUsage}) + +// ###### SortByColumns, column as (scope) variable ###### + +>> With({ col:"a" }, SortByColumns(Table({a:1,b:"one"},{a:2,b:"two"},{a:3,b:"three"}), col, SortOrder.Descending)) +Table({a:3,b:"three"},{a:2,b:"two"},{a:1,b:"one"}) + +>> With({ col:"b" }, SortByColumns(Table({a:1,b:"one"},{a:2,b:"two"},{a:3,b:"three"}), col, SortOrder.Descending)) +Table({a:2,b:"two"},{a:3,b:"three"},{a:1,b:"one"}) diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/SortByColumnsOrderTable_ColumnNamesAsIdentifiersDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/SortByColumnsOrderTable_ColumnNamesAsIdentifiersDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/SortByColumnsOrderTable_ColumnNamesAsIdentifiersDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/SortByColumnsOrderTable_ColumnNamesAsIdentifiersDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/SortByColumnsOrderTable_ColumnNamesAsIdentifiersEnabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/SortByColumnsOrderTable_ColumnNamesAsIdentifiersEnabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/SortByColumnsOrderTable_ColumnNamesAsIdentifiersEnabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/SortByColumnsOrderTable_ColumnNamesAsIdentifiersEnabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/SortByColumnsOrderTable_ColumnNamesAsIdentifiersEnabled_V1CompatEnabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/SortByColumnsOrderTable_ColumnNamesAsIdentifiersEnabled_V1CompatEnabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/SortByColumnsOrderTable_ColumnNamesAsIdentifiersEnabled_V1CompatEnabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/SortByColumnsOrderTable_ColumnNamesAsIdentifiersEnabled_V1CompatEnabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/SortByColumns_ColumnNamesAsIdentifiersEnabled_V1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/SortByColumns_ColumnNamesAsIdentifiersEnabled_V1CompatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/SortByColumns_ColumnNamesAsIdentifiersEnabled_V1CompatDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/SortByColumns_ColumnNamesAsIdentifiersEnabled_V1CompatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/SortByColumns_ColumnNamesAsIdentifiersEnabled_V1CompatEnabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/SortByColumns_ColumnNamesAsIdentifiersEnabled_V1CompatEnabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/SortByColumns_ColumnNamesAsIdentifiersEnabled_V1CompatEnabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/SortByColumns_ColumnNamesAsIdentifiersEnabled_V1CompatEnabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Sort_OptionSets.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Sort_OptionSets.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Sort_OptionSets.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Sort_OptionSets.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Split.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Split.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Split.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Split.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Split_ConsistentOneColumnTableResultDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Split_ConsistentOneColumnTableResultDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Split_ConsistentOneColumnTableResultDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Split_ConsistentOneColumnTableResultDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Sqrt.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Sqrt.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Sqrt.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Sqrt.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/SqrtT.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/SqrtT.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/SqrtT.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/SqrtT.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/SqrtT_ConsistentOneColumnTableResultDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/SqrtT_ConsistentOneColumnTableResultDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/SqrtT_ConsistentOneColumnTableResultDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/SqrtT_ConsistentOneColumnTableResultDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/StartsWith.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/StartsWith.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/StartsWith.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/StartsWith.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/StdevP.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/StdevP.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/StdevP.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/StdevP.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/StringInterpolate.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/StringInterpolate.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/StringInterpolate.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/StringInterpolate.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/StronglyTypedEnum_BuiltInEnums.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/StronglyTypedEnum_BuiltInEnums.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/StronglyTypedEnum_BuiltInEnums.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/StronglyTypedEnum_BuiltInEnums.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/StronglyTypedEnum_BuiltInEnums_PreV1.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/StronglyTypedEnum_BuiltInEnums_PreV1.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/StronglyTypedEnum_BuiltInEnums_PreV1.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/StronglyTypedEnum_BuiltInEnums_PreV1.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/StronglyTypedEnum_TestEnums.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/StronglyTypedEnum_TestEnums.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/StronglyTypedEnum_TestEnums.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/StronglyTypedEnum_TestEnums.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/StronglyTypedEnum_TestEnums_PreV1.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/StronglyTypedEnum_TestEnums_PreV1.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/StronglyTypedEnum_TestEnums_PreV1.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/StronglyTypedEnum_TestEnums_PreV1.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/StronglyTypedEnums.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/StronglyTypedEnums.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/StronglyTypedEnums.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/StronglyTypedEnums.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/StronglyTypedEnums_PreV1.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/StronglyTypedEnums_PreV1.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/StronglyTypedEnums_PreV1.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/StronglyTypedEnums_PreV1.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Substitute.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Substitute.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Substitute.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Substitute.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/SubstituteT.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/SubstituteT.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/SubstituteT.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/SubstituteT.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/SubstituteT_ConsistentOneColumnTableResultDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/SubstituteT_ConsistentOneColumnTableResultDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/SubstituteT_ConsistentOneColumnTableResultDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/SubstituteT_ConsistentOneColumnTableResultDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Sum.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Sum.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Sum.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Sum.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Summarize.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Summarize.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Summarize.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Summarize.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Table.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Table.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Table.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Table.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/TableCoercion.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TableCoercion.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/TableCoercion.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TableCoercion.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/TableCoercion_StronglyTypedEnum.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TableCoercion_StronglyTypedEnum.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/TableCoercion_StronglyTypedEnum.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TableCoercion_StronglyTypedEnum.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/TableCoercion_StronglyTypedEnumDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TableCoercion_StronglyTypedEnumDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/TableCoercion_StronglyTypedEnumDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TableCoercion_StronglyTypedEnumDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/TableMathfuncs.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TableMathfuncs.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/TableMathfuncs.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TableMathfuncs.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/TableNodes.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TableNodes.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/TableNodes.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TableNodes.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/TableStringfuncs.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TableStringfuncs.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/TableStringfuncs.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TableStringfuncs.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/TableStringfuncsT_ConsistentOneColumnTableResultDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TableStringfuncsT_ConsistentOneColumnTableResultDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/TableStringfuncsT_ConsistentOneColumnTableResultDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TableStringfuncsT_ConsistentOneColumnTableResultDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Table_Mutation.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Table_Mutation.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Table_Mutation.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Table_Mutation.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Table_V1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Table_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Table_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Table_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/TabularOverloadsBlanksAndErrors.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TabularOverloadsBlanksAndErrors.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/TabularOverloadsBlanksAndErrors.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TabularOverloadsBlanksAndErrors.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/TabularOverloadsBlanksAndErrorsT_ConsistentOneColumnTableResultDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TabularOverloadsBlanksAndErrorsT_ConsistentOneColumnTableResultDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/TabularOverloadsBlanksAndErrorsT_ConsistentOneColumnTableResultDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TabularOverloadsBlanksAndErrorsT_ConsistentOneColumnTableResultDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Testing.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Testing.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Testing.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Testing.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Text.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Text.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Text.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Text.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/TextFirst.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TextFirst.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/TextFirst.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TextFirst.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Text_DefaultNumber_Decimal.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Text_DefaultNumber_Decimal.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Text_DefaultNumber_Decimal.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Text_DefaultNumber_Decimal.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Text_DefaultNumber_Float.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Text_DefaultNumber_Float.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Text_DefaultNumber_Float.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Text_DefaultNumber_Float.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Text_ExcelCompat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Text_ExcelCompat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Text_ExcelCompat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Text_ExcelCompat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Text_ExcelCompat_Decimal.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Text_ExcelCompat_Decimal.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Text_ExcelCompat_Decimal.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Text_ExcelCompat_Decimal.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Text_ExcelCompat_PowerFxV1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Text_ExcelCompat_PowerFxV1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Text_ExcelCompat_PowerFxV1Compat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Text_ExcelCompat_PowerFxV1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Text_ExcelCompat_PowerFxV1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Text_ExcelCompat_PowerFxV1CompatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Text_ExcelCompat_PowerFxV1CompatDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Text_ExcelCompat_PowerFxV1CompatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Text_Format.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Text_Format.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Text_Format.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Text_Format.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Text_Format_PowerFxV1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Text_Format_PowerFxV1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Text_Format_PowerFxV1Compat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Text_Format_PowerFxV1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Text_Format_PowerFxV1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Text_Format_PowerFxV1CompatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Text_Format_PowerFxV1CompatDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Text_Format_PowerFxV1CompatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Time.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Time.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Time.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Time.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Trace.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Trace.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Trace.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Trace.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Trace_StronglyTypedBuiltinEnums.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Trace_StronglyTypedBuiltinEnums.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Trace_StronglyTypedBuiltinEnums.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Trace_StronglyTypedBuiltinEnums.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Trace_StronglyTypedBuiltinEnumsDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Trace_StronglyTypedBuiltinEnumsDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Trace_StronglyTypedBuiltinEnumsDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Trace_StronglyTypedBuiltinEnumsDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/TrigFunctions.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TrigFunctions.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/TrigFunctions.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TrigFunctions.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/TrigTableFunctions.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TrigTableFunctions.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/TrigTableFunctions.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TrigTableFunctions.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/TrigTableFunctions_ConsistentOneColumnTableResultDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TrigTableFunctions_ConsistentOneColumnTableResultDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/TrigTableFunctions_ConsistentOneColumnTableResultDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TrigTableFunctions_ConsistentOneColumnTableResultDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Trim.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Trim.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Trim.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Trim.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/TrimEnds.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TrimEnds.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/TrimEnds.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TrimEnds.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Trunc.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Trunc.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Trunc.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Trunc.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/TruncT.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TruncT.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/TruncT.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TruncT.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/TruncT_ConsistentOneColumnTableResultDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TruncT_ConsistentOneColumnTableResultDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/TruncT_ConsistentOneColumnTableResultDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TruncT_ConsistentOneColumnTableResultDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/UniChar.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/UniChar.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/UniChar.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/UniChar.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/UniCharT.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/UniCharT.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/UniCharT.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/UniCharT.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/UntypedBlankAsTable.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/UntypedBlankAsTable.txt similarity index 95% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/UntypedBlankAsTable.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/UntypedBlankAsTable.txt index 47418d207..7c005b80e 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/UntypedBlankAsTable.txt +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/UntypedBlankAsTable.txt @@ -1,100 +1,100 @@ - -// Consider an untyped blank, such as the return type from Blank(), as an argument to a single column table enabled function. -// -// We favor treating these as a scalar blank, rather than a table blank. Scalar is the simpler form and more likely -// what the maker wanted. This treatment is also consistent with Canvas. -// -// A typed Blank, such as returned from a database or If(1<0,"foo") is fine, it specifically an untyped Blank that is odd. -// -// In general, a function that takes a blank table argument will return a blank table. - ->> Concatenate("a",Blank(),"b") -"ab" - ->> Concatenate(["a","b"],Blank()) -Table({Value:"a"},{Value:"b"}) - ->> Concatenate(Blank(),["a","b"]) -Table({Value:"a"},{Value:"b"}) - ->> Left(["abc","def"],2) -Table({Value:"ab"},{Value:"de"}) - ->> Left(["abc","def"],Blank()) -Table({Value:""},{Value:""}) - ->> Left("abcd",[2,3]) -Table({Value:"ab"},{Value:"abc"}) - ->> Left(Blank(),[2,3]) -Table({Value:""},{Value:""}) - ->> Right(["abc","def"],2) -Table({Value:"bc"},{Value:"ef"}) - ->> Right(["abc","def"],Blank()) -Table({Value:""},{Value:""}) - ->> Right("abc",[2,3]) -Table({Value:"bc"},{Value:"abc"}) - ->> Right(Blank(),[2,3]) -Table({Value:""},{Value:""}) - ->> Mid(["abc","def"],2) -Table({Value:"bc"},{Value:"ef"}) - ->> Mid(["abc","def"],Blank()) -Table({Value:Error({Kind:ErrorKind.InvalidArgument})},{Value:Error({Kind:ErrorKind.InvalidArgument})}) - ->> Mid(["abc","def"],3,Blank()) -Table({Value:""},{Value:""}) - ->> Mid(Blank(),[1,2]) -Table({Value:""},{Value:""}) - ->> Mod([10,22],[3,4]) -Table({Value:1},{Value:2}) - ->> Mod([1,2],Blank()) -Table({Value:Error({Kind:ErrorKind.Div0})},{Value:Error({Kind:ErrorKind.Div0})}) - ->> Mod(Blank(),[3,4]) -Table({Value:0},{Value:0}) - ->> Power(Blank(),3) -0 - ->> Power(Blank(),[3]) -Table({Value:0}) - ->> Power(If(1<0,[1]),3) -Blank() - ->> Log(Blank(),3) -Error({Kind:ErrorKind.Numeric}) - ->> Log(Blank(),[3]) -Table({Value:Error({Kind:ErrorKind.Numeric})}) - ->> Log(If(1<0,[1]),3) -Blank() - ->> Sum([4,5],Blank()) -Blank() - ->> Sum(If(1<0,[1,2,3]),4) -Blank() - ->> Sum(If(1<0,[1,2,3]),Blank()) -Blank() - -// one argument, single column table functions, will treat Blank() as a scalar ->> Sin(Blank()) -0 - ->> Len(Blank()) -0 - ->> Abs(Blank()) -0 + +// Consider an untyped blank, such as the return type from Blank(), as an argument to a single column table enabled function. +// +// We favor treating these as a scalar blank, rather than a table blank. Scalar is the simpler form and more likely +// what the maker wanted. This treatment is also consistent with Canvas. +// +// A typed Blank, such as returned from a database or If(1<0,"foo") is fine, it specifically an untyped Blank that is odd. +// +// In general, a function that takes a blank table argument will return a blank table. + +>> Concatenate("a",Blank(),"b") +"ab" + +>> Concatenate(["a","b"],Blank()) +Table({Value:"a"},{Value:"b"}) + +>> Concatenate(Blank(),["a","b"]) +Table({Value:"a"},{Value:"b"}) + +>> Left(["abc","def"],2) +Table({Value:"ab"},{Value:"de"}) + +>> Left(["abc","def"],Blank()) +Table({Value:""},{Value:""}) + +>> Left("abcd",[2,3]) +Table({Value:"ab"},{Value:"abc"}) + +>> Left(Blank(),[2,3]) +Table({Value:""},{Value:""}) + +>> Right(["abc","def"],2) +Table({Value:"bc"},{Value:"ef"}) + +>> Right(["abc","def"],Blank()) +Table({Value:""},{Value:""}) + +>> Right("abc",[2,3]) +Table({Value:"bc"},{Value:"abc"}) + +>> Right(Blank(),[2,3]) +Table({Value:""},{Value:""}) + +>> Mid(["abc","def"],2) +Table({Value:"bc"},{Value:"ef"}) + +>> Mid(["abc","def"],Blank()) +Table({Value:Error({Kind:ErrorKind.InvalidArgument})},{Value:Error({Kind:ErrorKind.InvalidArgument})}) + +>> Mid(["abc","def"],3,Blank()) +Table({Value:""},{Value:""}) + +>> Mid(Blank(),[1,2]) +Table({Value:""},{Value:""}) + +>> Mod([10,22],[3,4]) +Table({Value:1},{Value:2}) + +>> Mod([1,2],Blank()) +Table({Value:Error({Kind:ErrorKind.Div0})},{Value:Error({Kind:ErrorKind.Div0})}) + +>> Mod(Blank(),[3,4]) +Table({Value:0},{Value:0}) + +>> Power(Blank(),3) +0 + +>> Power(Blank(),[3]) +Table({Value:0}) + +>> Power(If(1<0,[1]),3) +Blank() + +>> Log(Blank(),3) +Error({Kind:ErrorKind.Numeric}) + +>> Log(Blank(),[3]) +Table({Value:Error({Kind:ErrorKind.Numeric})}) + +>> Log(If(1<0,[1]),3) +Blank() + +>> Sum([4,5],Blank()) +Blank() + +>> Sum(If(1<0,[1,2,3]),4) +Blank() + +>> Sum(If(1<0,[1,2,3]),Blank()) +Blank() + +// one argument, single column table functions, will treat Blank() as a scalar +>> Sin(Blank()) +0 + +>> Len(Blank()) +0 + +>> Abs(Blank()) +0 diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/UntypedBlankAsTable_V1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/UntypedBlankAsTable_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/UntypedBlankAsTable_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/UntypedBlankAsTable_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/UntypedBlankAsTable_V1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/UntypedBlankAsTable_V1CompatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/UntypedBlankAsTable_V1CompatDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/UntypedBlankAsTable_V1CompatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Upper.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Upper.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Upper.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Upper.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Value.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Value.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Value.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Value.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/VarP.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/VarP.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/VarP.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/VarP.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/WeekNum.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/WeekNum.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/WeekNum.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/WeekNum.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/WeekNum_StronglyTypedEnumDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/WeekNum_StronglyTypedEnumDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/WeekNum_StronglyTypedEnumDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/WeekNum_StronglyTypedEnumDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Weekday.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Weekday.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Weekday.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Weekday.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Weekday_StronglyTypedEnum.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Weekday_StronglyTypedEnum.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Weekday_StronglyTypedEnum.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Weekday_StronglyTypedEnum.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Weekday_StronglyTypedEnumDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Weekday_StronglyTypedEnumDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/Weekday_StronglyTypedEnumDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Weekday_StronglyTypedEnumDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/With.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/With.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/With.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/With.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/With_Decimal.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/With_Decimal.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/With_Decimal.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/With_Decimal.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/With_Float.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/With_Float.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/With_Float.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/With_Float.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/With_V1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/With_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/With_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/With_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/With_V1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/With_V1CompatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/With_V1CompatDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/With_V1CompatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/arithmetic.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/arithmetic.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/arithmetic.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/arithmetic.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/inScalar.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/inScalar.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/inScalar.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/inScalar.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/inScalar_V1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/inScalar_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/inScalar_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/inScalar_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/inScalar_V1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/inScalar_V1CompatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/inScalar_V1CompatDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/inScalar_V1CompatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/inTable.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/inTable.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/inTable.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/inTable.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/inTable_V1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/inTable_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/inTable_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/inTable_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/inTable_V1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/inTable_V1CompatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/inTable_V1CompatDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/inTable_V1CompatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/literals.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/literals.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/literals.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/literals.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/mathfuncs.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/mathfuncs.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/mathfuncs.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/mathfuncs.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/string.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/string.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/string.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/string.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/switch.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/switch.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/switch.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/switch.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/switch_V1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/switch_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/switch_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/switch_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/switch_V1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/switch_V1CompatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestCases/switch_V1CompatDisabled.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/switch_V1CompatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestHelpers/BaseRunner.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestHelpers/BaseRunner.cs similarity index 99% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestHelpers/BaseRunner.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestHelpers/BaseRunner.cs index 42cfcb4a1..00d7b40b7 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestHelpers/BaseRunner.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestHelpers/BaseRunner.cs @@ -4,10 +4,10 @@ using System; using System.Diagnostics; using System.Linq; -using System.Text; +using System.Text; using System.Text.RegularExpressions; using System.Threading; -using System.Threading.Tasks; +using System.Threading.Tasks; using Microsoft.PowerFx.Types; namespace Microsoft.PowerFx.Core.Tests @@ -82,21 +82,21 @@ namespace Microsoft.PowerFx.Core.Tests /// Maximum time to run test - this catches potential hangs in the engine. /// Any test should easily run in under 1s. /// - public static TimeSpan Timeout = TimeSpan.FromSeconds(20); - + public static TimeSpan Timeout = TimeSpan.FromSeconds(20); + /// /// Should the NumberIsFloat parser flag be in effect. - /// + /// public bool NumberIsFloat { get; set; } /// /// What base Features should be enabled, before adding file level #SETUP and #DISABLE. - /// - public Features Features = Features.None; - - /// - /// Logger action. - /// + /// + public Features Features = Features.None; + + /// + /// Logger action. + /// public Action Log = null; /// @@ -184,7 +184,7 @@ namespace Microsoft.PowerFx.Core.Tests FormulaValue originalResult = null; var expected = testCase.Expected; - + var expectedSkip = Regex.Match(expected, "^\\s*\\#skip", RegexOptions.IgnoreCase).Success; if (expectedSkip) { @@ -196,7 +196,7 @@ namespace Microsoft.PowerFx.Core.Tests { runResult = await RunAsyncInternal(testCase.Input, testCase.SetupHandlerName).ConfigureAwait(false); result = runResult.Value; - originalResult = runResult.OriginalValue; + originalResult = runResult.OriginalValue; // Unsupported is just for ignoring large groups of inherited tests. // If it's an override, then the override should specify the exact error. @@ -213,26 +213,26 @@ namespace Microsoft.PowerFx.Core.Tests // Errors: Error 15-16: Incompatible types for comparison. These types can't be compared: UntypedObject, UntypedObject. var expectedCompilerError = expected.StartsWith("Errors: Error") || expected.StartsWith("Errors: Warning"); // $$$ Match error message. if (expectedCompilerError) - { - string[] expectedStrArr = expected.Replace("Errors: ", string.Empty).Split("|"); - string[] actualStrArr = runResult.Errors.Select(err => err.ToString()).ToArray(); - bool isValid = true; + { + string[] expectedStrArr = expected.Replace("Errors: ", string.Empty).Split("|"); + string[] actualStrArr = runResult.Errors.Select(err => err.ToString()).ToArray(); + bool isValid = true; + + // Try both unaltered comparison and by replacing Decimal with Number for errors, + // for tests that are run with and without NumberIsFloat set. + foreach (var exp in expectedStrArr) + { + if (!actualStrArr.Contains(exp) && + !(NumberIsFloat && actualStrArr.Contains(Regex.Replace(exp, "(? = "NumberIsFloat" was specified - // = "disable:NumberIsFloat" was specified - // - // Use "Default" for all settings not explicilty called out. Without Default, if a setting is not - // specified, the test can be run with or without the setting. - // - // Setting strings are validated by here. Any of these are possible choices: - // * Engine.Features, determined through reflection - // * TexlParser.Flags, determined through reflection - // * Default, special case - // * PowerFxV1, special case, will expand to its constituent Features - // * Other handlers listed in this routine - public static Dictionary ParseSetupString(string setup) - { - var settings = new Dictionary(); - var possible = new HashSet(); - var powerFxV1 = new Dictionary(); - - // Features - foreach (var featureProperty in typeof(Features).GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) - { - if (featureProperty.PropertyType == typeof(bool) && featureProperty.CanWrite) - { - possible.Add(featureProperty.Name); - if ((bool)featureProperty.GetValue(Features.PowerFxV1)) - { - powerFxV1.Add(featureProperty.Name, true); - } - } - } - - // Parser Flags - foreach (var parserFlag in System.Enum.GetValues(typeof(TexlParser.Flags))) - { - possible.Add(parserFlag.ToString()); - } - - possible.Add("AllEnumsSetup"); - possible.Add("AllEnumsPlusTestEnumsSetup"); - possible.Add("AsyncTestSetup"); - possible.Add("Blob"); - possible.Add("DecimalSupport"); - possible.Add("Default"); - possible.Add("DisableMemChecks"); - possible.Add("EnableJsonFunctions"); - possible.Add("MutationFunctionsTestSetup"); - possible.Add("OptionSetSortTestSetup"); - possible.Add("OptionSetTestSetup"); + public string TestRoot { get; set; } = GetDefaultTestDir(); + + // Parses a comma delimited setup string, as found in TxtFileDataAttributes and the start of .txt files, + // into a dictionary for passing to AddDir, AddFile, etc. This routine is used both to determine what + // the current testing context supports (with TxtFileDataAtrributes) and what a given .txt file requires + // (with AddFile). These two dictionaries must be compatible and not contradict for a test to run. + // + // Dictionary contents, with NumberIsFloat as an example: + // = "NumberIsFloat" was specified + // = "disable:NumberIsFloat" was specified + // + // Use "Default" for all settings not explicilty called out. Without Default, if a setting is not + // specified, the test can be run with or without the setting. + // + // Setting strings are validated by here. Any of these are possible choices: + // * Engine.Features, determined through reflection + // * TexlParser.Flags, determined through reflection + // * Default, special case + // * PowerFxV1, special case, will expand to its constituent Features + // * Other handlers listed in this routine + public static Dictionary ParseSetupString(string setup) + { + var settings = new Dictionary(); + var possible = new HashSet(); + var powerFxV1 = new Dictionary(); + + // Features + foreach (var featureProperty in typeof(Features).GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) + { + if (featureProperty.PropertyType == typeof(bool) && featureProperty.CanWrite) + { + possible.Add(featureProperty.Name); + if ((bool)featureProperty.GetValue(Features.PowerFxV1)) + { + powerFxV1.Add(featureProperty.Name, true); + } + } + } + + // Parser Flags + foreach (var parserFlag in System.Enum.GetValues(typeof(TexlParser.Flags))) + { + possible.Add(parserFlag.ToString()); + } + + possible.Add("AllEnumsSetup"); + possible.Add("AllEnumsPlusTestEnumsSetup"); + possible.Add("AsyncTestSetup"); + possible.Add("Blob"); + possible.Add("DecimalSupport"); + possible.Add("Default"); + possible.Add("DisableMemChecks"); + possible.Add("EnableJsonFunctions"); + possible.Add("MutationFunctionsTestSetup"); + possible.Add("OptionSetSortTestSetup"); + possible.Add("OptionSetTestSetup"); possible.Add("PowerFxV1"); possible.Add("RegEx"); - possible.Add("TimeZoneInfo"); + possible.Add("TimeZoneInfo"); possible.Add("TraceSetup"); - - foreach (Match match in Regex.Matches(setup, @"(disable:)?(([\w]+|//)(\([^\)]*\))?)")) - { - bool enabled = !(match.Groups[1].Value == "disable:"); - var name = match.Groups[3].Value; - var complete = match.Groups[2].Value; - - // end of line comment on settings string - if (name == "//") - { - break; - } - - if (!possible.Contains(name)) - { - throw new ArgumentException($"Setup string not found: {name} from \"{setup}\""); - } - - settings.Add(complete, enabled); - - if (match.Groups[2].Value == "PowerFxV1") - { - foreach (var pfx1Feature in powerFxV1) - { - settings.Add(pfx1Feature.Key, true); - } - } - } - - return settings; + + foreach (Match match in Regex.Matches(setup, @"(disable:)?(([\w]+|//)(\([^\)]*\))?)")) + { + bool enabled = !(match.Groups[1].Value == "disable:"); + var name = match.Groups[3].Value; + var complete = match.Groups[2].Value; + + // end of line comment on settings string + if (name == "//") + { + break; + } + + if (!possible.Contains(name)) + { + throw new ArgumentException($"Setup string not found: {name} from \"{setup}\""); + } + + settings.Add(complete, enabled); + + if (match.Groups[2].Value == "PowerFxV1") + { + foreach (var pfx1Feature in powerFxV1) + { + settings.Add(pfx1Feature.Key, true); + } + } + } + + return settings; } public void AddDir(Dictionary setup, string directory = "") @@ -158,23 +158,23 @@ namespace Microsoft.PowerFx.Core.Tests // Directive should start with #, end in : like "#SETUP:" // Returns true if matched; false if not. Throws on error. private static bool TryParseDirective(string line, string directive, out string param) - { + { if (line.StartsWith(directive, StringComparison.OrdinalIgnoreCase)) - { - param = line.Substring(directive.Length).Trim(); - - // strip any end of line comment - if (param.Contains("//")) - { - param = param.Substring(0, param.IndexOf("//")); - } - + { + param = line.Substring(directive.Length).Trim(); + + // strip any end of line comment + if (param.Contains("//")) + { + param = param.Substring(0, param.IndexOf("//")); + } + return true; - } - else - { - param = null; - return false; + } + else + { + param = null; + return false; } } @@ -191,10 +191,10 @@ namespace Microsoft.PowerFx.Core.Tests Exception ParseError(int lineNumber, string message) => new InvalidOperationException( $"{Path.GetFileName(thisFile)} {lineNumber}: {message}"); - TestCase test = null; - - string fileSetup = null; - string fileOveride = null; + TestCase test = null; + + string fileSetup = null; + string fileOveride = null; Dictionary fileSetupDict = new Dictionary(); var i = -1; @@ -218,29 +218,29 @@ namespace Microsoft.PowerFx.Core.Tests // Can apply to multiple files. var countRemoved = Tests.RemoveAll(test => string.Equals(Path.GetFileName(test.SourceFile), fileDisable, StringComparison.OrdinalIgnoreCase)); } - else if (TryParseDirective(line, "#SETUP:", out var thisSetup)) - { - foreach (var flag in ParseSetupString(thisSetup)) - { - if (fileSetupDict.ContainsKey(flag.Key) && fileSetupDict[flag.Key] != flag.Value) - { - // Multiple setup lines are fine, but can't contradict. - // ParseSetupString may expand aggregate handlers, such as PowerFxV1, which may create unexpected contradictions. - throw new InvalidOperationException($"Duplicate and contradictory #SETUP directives: {line} {(flag.Value ? string.Empty : "disable:")}{flag.Key}"); - } - else - { - fileSetupDict.Add(flag.Key, flag.Value); - } - } + else if (TryParseDirective(line, "#SETUP:", out var thisSetup)) + { + foreach (var flag in ParseSetupString(thisSetup)) + { + if (fileSetupDict.ContainsKey(flag.Key) && fileSetupDict[flag.Key] != flag.Value) + { + // Multiple setup lines are fine, but can't contradict. + // ParseSetupString may expand aggregate handlers, such as PowerFxV1, which may create unexpected contradictions. + throw new InvalidOperationException($"Duplicate and contradictory #SETUP directives: {line} {(flag.Value ? string.Empty : "disable:")}{flag.Key}"); + } + else + { + fileSetupDict.Add(flag.Key, flag.Value); + } + } } else if (TryParseDirective(line, "#OVERRIDE:", out var thisOveride)) - { - if (fileOveride != null) - { - throw new InvalidOperationException($"Can't have multiple #OVERRIDE: directives"); - } - + { + if (fileOveride != null) + { + throw new InvalidOperationException($"Can't have multiple #OVERRIDE: directives"); + } + fileOveride = thisOveride; } else @@ -253,20 +253,20 @@ namespace Microsoft.PowerFx.Core.Tests } break; - } - - // If the test is incompatible with the base setup, skip it. - // It is OK for the test to turn on handlers and features that don't conflict. - foreach (var flag in fileSetupDict) - { - if ((setup.ContainsKey(flag.Key) && flag.Value != setup[flag.Key]) || - (!setup.ContainsKey(flag.Key) && setup.ContainsKey("Default") && flag.Value != setup["Default"])) - { - return; - } - } - - fileSetup = string.Join(",", fileSetupDict.Select(i => (i.Value ? string.Empty : "disable:") + i.Key)); + } + + // If the test is incompatible with the base setup, skip it. + // It is OK for the test to turn on handlers and features that don't conflict. + foreach (var flag in fileSetupDict) + { + if ((setup.ContainsKey(flag.Key) && flag.Value != setup[flag.Key]) || + (!setup.ContainsKey(flag.Key) && setup.ContainsKey("Default") && flag.Value != setup["Default"])) + { + return; + } + } + + fileSetup = string.Join(",", fileSetupDict.Select(i => (i.Value ? string.Empty : "disable:") + i.Key)); List duplicateTests = new List(); diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestHelpers/TestRunnerSummary.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestHelpers/TestRunnerSummary.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestHelpers/TestRunnerSummary.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestHelpers/TestRunnerSummary.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestHelpers/TxtFileDataAttribute.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestHelpers/TxtFileDataAttribute.cs similarity index 99% rename from src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestHelpers/TxtFileDataAttribute.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestHelpers/TxtFileDataAttribute.cs index 5799b9c5d..00b5516f7 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests/ExpressionTestHelpers/TxtFileDataAttribute.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestHelpers/TxtFileDataAttribute.cs @@ -6,10 +6,10 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; -using System.Text.RegularExpressions; -using Microsoft.PowerFx.Core.Parser; -using Microsoft.PowerFx.Core.Utils; -using Microsoft.PowerFx.Types; +using System.Text.RegularExpressions; +using Microsoft.PowerFx.Core.Parser; +using Microsoft.PowerFx.Core.Utils; +using Microsoft.PowerFx.Types; using Xunit.Sdk; namespace Microsoft.PowerFx.Core.Tests @@ -24,17 +24,17 @@ namespace Microsoft.PowerFx.Core.Tests { private readonly string _filePathCommon; private readonly string _filePathSpecific; - private readonly string _engineName; - private readonly Dictionary _setup; + private readonly string _engineName; + private readonly Dictionary _setup; public TxtFileDataAttribute(string filePathCommon, string filePathSpecific, string engineName, string setup) { _filePathCommon = filePathCommon; _filePathSpecific = filePathSpecific; - _engineName = engineName; + _engineName = engineName; _setup = TestRunner.ParseSetupString(setup); - } - + } + public override IEnumerable GetData(MethodInfo testMethod) { // This is run in a separate process. To debug, need to call Launch() and attach a debugger. @@ -58,12 +58,12 @@ namespace Microsoft.PowerFx.Core.Tests var allFiles = Directory.EnumerateFiles(GetDefaultTestDir(dir)); foreach (var file in allFiles) - { - // Skip .md files - - if (file.EndsWith(".txt", StringComparison.InvariantCultureIgnoreCase)) - { - parser.AddFile(_setup, file); + { + // Skip .md files + + if (file.EndsWith(".txt", StringComparison.InvariantCultureIgnoreCase)) + { + parser.AddFile(_setup, file); } } } @@ -97,13 +97,13 @@ namespace Microsoft.PowerFx.Core.Tests var testDir = Path.Combine(curDir, filePath); return testDir; } - } - - /// - /// Simpler version of TxtFileDataAttribute (above) for the mutation tests. - /// The mutation tests need to run tests, one after another, retaining state - they are not independent. - /// This attribute just gathers together the directory of test files, rather than pulling the individual tests out of those files. - /// + } + + /// + /// Simpler version of TxtFileDataAttribute (above) for the mutation tests. + /// The mutation tests need to run tests, one after another, retaining state - they are not independent. + /// This attribute just gathers together the directory of test files, rather than pulling the individual tests out of those files. + /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)] public class ReplFileSimpleListAttribute : DataAttribute { @@ -112,8 +112,8 @@ namespace Microsoft.PowerFx.Core.Tests public ReplFileSimpleListAttribute(string filePath) { _filePath = filePath; - } - + } + public override IEnumerable GetData(MethodInfo testMethod) { // This is run in a separate process. To debug, need to call Launch() and attach a debugger. @@ -127,21 +127,21 @@ namespace Microsoft.PowerFx.Core.Tests var list = new List(); try - { - var path = TxtFileDataAttribute.GetDefaultTestDir(_filePath); + { + var path = TxtFileDataAttribute.GetDefaultTestDir(_filePath); var dir = new DirectoryInfo(path); var allFiles = dir.EnumerateFiles("*.txt"); // skip .md files foreach (var file in allFiles) - { - list.Add(new object[] { file.Name }); + { + list.Add(new object[] { file.Name }); } } catch (Exception e) { // If this method crashes, then we just get 0 tests. // The only way to communicate a failure from here back to the developer - // is to pass a "fake" test object that always fails and contains the error. + // is to pass a "fake" test object that always fails and contains the error. // In this case, this will be a bogus file name that won't load. var item = $"ERROR: Test discovery failed with: {e}"; diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/Extensions.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/Extensions.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/Extensions.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/Extensions.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/FormatterTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/FormatterTests.cs similarity index 99% rename from src/tests/Microsoft.PowerFx.Core.Tests/FormatterTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/FormatterTests.cs index 935dde9ce..84d3331de 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests/FormatterTests.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/FormatterTests.cs @@ -15,7 +15,7 @@ namespace Microsoft.PowerFx.Tests [Theory] [InlineData( "Collect(Yep, { a: [1], b: \"Hello\" })", - "Collect(#$firstname$#, { #$fieldname$#:[ #$decimal$# ], #$fieldname$#:#$string$# })")] + "Collect(#$firstname$#, { #$fieldname$#:[ #$decimal$# ], #$fieldname$#:#$string$# })")] [InlineData( "Set(x, 10 + 3); Launch(\"example.com\", ThisItem.Text, Parent.Text)", "Set(#$firstname$#, #$decimal$# + #$decimal$#) ; Launch(#$string$#, #$firstname$#.#$righthandid$#, Parent.#$righthandid$#)")] @@ -24,7 +24,7 @@ namespace Microsoft.PowerFx.Tests "$\"#$string$##$string$#\"")] [InlineData( "$\"Hello {5}\"", - "$\"#$string$#{#$decimal$#}\"")] + "$\"#$string$#{#$decimal$#}\"")] public void TestStucturalPrint(string script, string expected) { var result = ParseScript( @@ -38,40 +38,40 @@ namespace Microsoft.PowerFx.Tests check.SetText(script, new ParserOptions { AllowsSideEffects = true }); var result2 = check.ApplyGetLogging(); Assert.Equal(expected, result2); - } - + } + [Theory] [InlineData( - "With({t:Table({a:1},{a:2})},t)", - "With({ #$fieldname$#:Table({ #$fieldname$#:#$decimal$# }, { #$fieldname$#:#$decimal$# }) }, #$firstname$#)", - "With({ #$fieldname$#:Table({ #$fieldname$#:#$decimal$# }, { #$fieldname$#:#$decimal$# }) }, #$LambdaField$#)")] + "With({t:Table({a:1},{a:2})},t)", + "With({ #$fieldname$#:Table({ #$fieldname$#:#$decimal$# }, { #$fieldname$#:#$decimal$# }) }, #$firstname$#)", + "With({ #$fieldname$#:Table({ #$fieldname$#:#$decimal$# }, { #$fieldname$#:#$decimal$# }) }, #$LambdaField$#)")] [InlineData( - "Set(x, 1); Set(y, 2); x + y", - "Set(#$firstname$#, #$decimal$#) ; Set(#$firstname$#, #$decimal$#) ; #$firstname$# + #$firstname$#", - "Set(#$firstname$#, #$decimal$#) ; Set(#$firstname$#, #$decimal$#) ; #$firstname$# + #$firstname$#")] + "Set(x, 1); Set(y, 2); x + y", + "Set(#$firstname$#, #$decimal$#) ; Set(#$firstname$#, #$decimal$#) ; #$firstname$# + #$firstname$#", + "Set(#$firstname$#, #$decimal$#) ; Set(#$firstname$#, #$decimal$#) ; #$firstname$# + #$firstname$#")] [InlineData( - "ForAll([1,2,3], Value * 2)", - "ForAll([ #$decimal$#, #$decimal$#, #$decimal$# ], #$firstname$# * #$decimal$#)", - "ForAll([ #$decimal$#, #$decimal$#, #$decimal$# ], #$LambdaField$# * #$decimal$#)")] + "ForAll([1,2,3], Value * 2)", + "ForAll([ #$decimal$#, #$decimal$#, #$decimal$# ], #$firstname$# * #$decimal$#)", + "ForAll([ #$decimal$#, #$decimal$#, #$decimal$# ], #$LambdaField$# * #$decimal$#)")] [InlineData( - "ForAll([1,2,3], ThisRecord.Value * 2)", - "ForAll([ #$decimal$#, #$decimal$#, #$decimal$# ], #$firstname$#.#$righthandid$# * #$decimal$#)", - "ForAll([ #$decimal$#, #$decimal$#, #$decimal$# ], #$LambdaFullRecord$#.#$righthandid$# * #$decimal$#)")] - + "ForAll([1,2,3], ThisRecord.Value * 2)", + "ForAll([ #$decimal$#, #$decimal$#, #$decimal$# ], #$firstname$#.#$righthandid$# * #$decimal$#)", + "ForAll([ #$decimal$#, #$decimal$#, #$decimal$# ], #$LambdaFullRecord$#.#$righthandid$# * #$decimal$#)")] + public void TestStucturalPrintWithBinding(string script, string beforebinding, string afterbinding) { var result = ParseScript( script, flags: Flags.EnableExpressionChaining); - Assert.Equal(beforebinding, StructuralPrint.Print(result.Root)); - - // Test same cases via CheckResult - var check = new CheckResult(new Engine()); + Assert.Equal(beforebinding, StructuralPrint.Print(result.Root)); - check.SetText(script, new ParserOptions { AllowsSideEffects = true }) - .SetBindingInfo() - .ApplyBinding(); + // Test same cases via CheckResult + var check = new CheckResult(new Engine()); + + check.SetText(script, new ParserOptions { AllowsSideEffects = true }) + .SetBindingInfo() + .ApplyBinding(); var result2 = check.ApplyGetLogging(); Assert.Equal(afterbinding, result2); @@ -132,13 +132,13 @@ namespace Microsoft.PowerFx.Tests [InlineData("RGBA(\n 255,\n 255,\n 255,\n 1\n)", "RGBA(255,255,255,1)")] [InlineData("RGBA(\n 255,\n /*r */255,\n 255,\n 1\n)//com ", "RGBA(255,\n /*r */255,255,1)//com ")] [InlineData("If(\n Text(\n Coalesce(\n Sum(\n Filter(\n Expenses,\n BudgetTitle = Gallery1.Selected.BudgetTitle && BudgetId=Gallery1.Selected.BudgetId\n ),\n Value(Expense)\n ),\n 0\n ),\n \"$#,##\"\n )=\"$\",\n \"$0\",\n Text(\n Coalesce(\n Sum(\n Filter(\n Expenses,\n BudgetId = Gallery1.Selected.BudgetId\n ),\n Value(Expense)\n ),\n 0\n ),\n \"$#,##\"\n )\n)", "If(Text(Coalesce(Sum(Filter(Expenses,BudgetTitle=Gallery1.Selected.BudgetTitle&&BudgetId=Gallery1.Selected.BudgetId),Value(Expense)),0),\"$#,##\")=\"$\",\"$0\",Text(Coalesce(Sum(Filter(Expenses,BudgetId=Gallery1.Selected.BudgetId),Value(Expense)),0),\"$#,##\"))")] - [InlineData("If(\n Text(\n Value(ThisItem.Expense)\n )= \"0\",\n \"$\",\n Text(\n Value(ThisItem.Expense),\n \"$#,##\"\n )\n)", "If(Text(Value(ThisItem.Expense))=\"0\",\"$\",Text(Value(ThisItem.Expense),\"$#,##\"))")] - [InlineData("$\"1 + 2 is\n\n{6} not 3\"", "$\"1 + 2 is\n\n{6} not 3\"")] - [InlineData("$\"\n\n1 + 2 is\n\n{6} not 3\n\n\t\"", "$\"\n\n1 + 2 is\n\n{6} not 3\n\n\t\"")] - [InlineData("$\" Foo\n\n\n\n\n Bar\"\"{{1}}\"", "$\" Foo\n\n\n\n\n Bar\"\"{{1}}\"")] - [InlineData("$\"{\"ddddd\"} Foo is bar\"", "$\"{\"ddddd\"} Foo is bar\"")] - [InlineData("$\" Foo is bar \n\"", "$\" Foo is bar \n\"")] - [InlineData("$\" String n{$\" Foo\"\"\n bar {4+4} rr\"} between {223} trail\"", "$\" String n{$\" Foo\"\"\n bar {4+4} rr\"} between {223} trail\"")] + [InlineData("If(\n Text(\n Value(ThisItem.Expense)\n )= \"0\",\n \"$\",\n Text(\n Value(ThisItem.Expense),\n \"$#,##\"\n )\n)", "If(Text(Value(ThisItem.Expense))=\"0\",\"$\",Text(Value(ThisItem.Expense),\"$#,##\"))")] + [InlineData("$\"1 + 2 is\n\n{6} not 3\"", "$\"1 + 2 is\n\n{6} not 3\"")] + [InlineData("$\"\n\n1 + 2 is\n\n{6} not 3\n\n\t\"", "$\"\n\n1 + 2 is\n\n{6} not 3\n\n\t\"")] + [InlineData("$\" Foo\n\n\n\n\n Bar\"\"{{1}}\"", "$\" Foo\n\n\n\n\n Bar\"\"{{1}}\"")] + [InlineData("$\"{\"ddddd\"} Foo is bar\"", "$\"{\"ddddd\"} Foo is bar\"")] + [InlineData("$\" Foo is bar \n\"", "$\" Foo is bar \n\"")] + [InlineData("$\" String n{$\" Foo\"\"\n bar {4+4} rr\"} between {223} trail\"", "$\" String n{$\" Foo\"\"\n bar {4+4} rr\"} between {223} trail\"")] [InlineData("\" Foo bar space\" ; 34", "\" Foo bar space\";34")] public void TestRemoveWhiteSpace(string script, string expected) { @@ -203,9 +203,9 @@ namespace Microsoft.PowerFx.Tests [InlineData("$\"{{{{1+1}}}}\"", "$\"{{{{1+1}}}}\"")] [InlineData("Set(str, $\"{{}}\")", "Set(\n str,\n $\"{{}}\"\n)")] [InlineData("Set(additionText, $\"The sum of 1 and 3 is {{{1 + 3}}})\")", "Set(\n additionText,\n $\"The sum of 1 and 3 is {{{1 + 3}}})\"\n)")] - [InlineData("$\"This is {{\"Another\"}} interpolated {{string}}\"", "$\"This is {{\"Another\"}} interpolated {{string}}\"")] + [InlineData("$\"This is {{\"Another\"}} interpolated {{string}}\"", "$\"This is {{\"Another\"}} interpolated {{string}}\"")] public void TestPrettyPrint(string script, string expected) - { + { // Act & Assert var result = Format(script); Assert.NotNull(result); @@ -215,68 +215,68 @@ namespace Microsoft.PowerFx.Tests result = Format(result); Assert.NotNull(result); Assert.Equal(expected, result); - } - - [Theory] - [InlineData(TexlLexer.ReservedBlank)] - [InlineData(TexlLexer.ReservedChild)] - [InlineData(TexlLexer.ReservedChildren)] - [InlineData(TexlLexer.ReservedEmpty)] - [InlineData(TexlLexer.ReservedIs)] - [InlineData(TexlLexer.ReservedNone)] - [InlineData(TexlLexer.ReservedNothing)] - [InlineData(TexlLexer.ReservedNull)] - [InlineData(TexlLexer.ReservedSiblings)] - [InlineData(TexlLexer.ReservedThis)] - [InlineData(TexlLexer.ReservedUndefined)] + } + + [Theory] + [InlineData(TexlLexer.ReservedBlank)] + [InlineData(TexlLexer.ReservedChild)] + [InlineData(TexlLexer.ReservedChildren)] + [InlineData(TexlLexer.ReservedEmpty)] + [InlineData(TexlLexer.ReservedIs)] + [InlineData(TexlLexer.ReservedNone)] + [InlineData(TexlLexer.ReservedNothing)] + [InlineData(TexlLexer.ReservedNull)] + [InlineData(TexlLexer.ReservedSiblings)] + [InlineData(TexlLexer.ReservedThis)] + [InlineData(TexlLexer.ReservedUndefined)] public void TestPrettyPrintWithDisabledReservedKeywordsFlag(string keyword) - { - // Arrange - var expression = $"Set({keyword}; true)"; - var expectedFormattedExpr = $"Set(\n {keyword};\n true\n)"; - var flags = Flags.DisableReservedKeywords | Flags.EnableExpressionChaining; - - // Act - var result = Format(expression, flags); - + { + // Arrange + var expression = $"Set({keyword}; true)"; + var expectedFormattedExpr = $"Set(\n {keyword};\n true\n)"; + var flags = Flags.DisableReservedKeywords | Flags.EnableExpressionChaining; + + // Act + var result = Format(expression, flags); + // Asssert Assert.NotNull(result); - Assert.Equal(expectedFormattedExpr, result); + Assert.Equal(expectedFormattedExpr, result); // Act: Ensure idempotence - result = Format(result, flags); - + result = Format(result, flags); + // Assert: Ensure idempotence Assert.NotNull(result); Assert.Equal(expectedFormattedExpr, result); - } - - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - [InlineData(4)] - [InlineData(5)] - [InlineData(10)] - public void TestPrettyPrintAndRemoveWhitespaceRoundtripWithDisabledReservedKeywordsFlag(int trips) - { - // Arrange - var unformattedExpr = $"Set({TexlLexer.ReservedChildren}; true )"; - var formatedExpr = $"Set(\n {TexlLexer.ReservedChildren};\n true\n)"; - var expectedOutcome = trips % 2 == 0 ? unformattedExpr : formatedExpr; - - // Act - var outcome = unformattedExpr; - for (var i = 1; i <= trips; ++i) - { - outcome = i % 2 == 0 ? - TexlLexer.InvariantLexer.RemoveWhiteSpace(outcome) : - Format(outcome, Flags.DisableReservedKeywords | Flags.EnableExpressionChaining); - } - - // Assert - Assert.Equal(expectedOutcome, outcome); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + [InlineData(4)] + [InlineData(5)] + [InlineData(10)] + public void TestPrettyPrintAndRemoveWhitespaceRoundtripWithDisabledReservedKeywordsFlag(int trips) + { + // Arrange + var unformattedExpr = $"Set({TexlLexer.ReservedChildren}; true )"; + var formatedExpr = $"Set(\n {TexlLexer.ReservedChildren};\n true\n)"; + var expectedOutcome = trips % 2 == 0 ? unformattedExpr : formatedExpr; + + // Act + var outcome = unformattedExpr; + for (var i = 1; i <= trips; ++i) + { + outcome = i % 2 == 0 ? + TexlLexer.InvariantLexer.RemoveWhiteSpace(outcome) : + Format(outcome, Flags.DisableReservedKeywords | Flags.EnableExpressionChaining); + } + + // Assert + Assert.Equal(expectedOutcome, outcome); } } } diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/FormulaSetTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/FormulaSetTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/FormulaSetTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/FormulaSetTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/FunctionInfoTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/FunctionInfoTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/FunctionInfoTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/FunctionInfoTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/FunctionTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/FunctionTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/FunctionTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/FunctionTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/GuardSingleThreadedTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/GuardSingleThreadedTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/GuardSingleThreadedTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/GuardSingleThreadedTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/Helpers/DoNotUseCulture.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/Helpers/DoNotUseCulture.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/Helpers/DoNotUseCulture.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/Helpers/DoNotUseCulture.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/Helpers/TestTabularDataSource.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/Helpers/TestTabularDataSource.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/Helpers/TestTabularDataSource.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/Helpers/TestTabularDataSource.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/Helpers/TestUtils.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/Helpers/TestUtils.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/Helpers/TestUtils.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/Helpers/TestUtils.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IRPrintTest.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IRPrintTest.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/IRPrintTest.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IRPrintTest.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IRTests/IRPrettyPrinter.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IRTests/IRPrettyPrinter.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/IRTests/IRPrettyPrinter.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IRTests/IRPrettyPrinter.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IRTests/IRTranslatorTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IRTests/IRTranslatorTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/IRTests/IRTranslatorTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IRTests/IRTranslatorTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IdentTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IdentTests.cs similarity index 96% rename from src/tests/Microsoft.PowerFx.Core.Tests/IdentTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IdentTests.cs index 62a120e19..4f312c2ef 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests/IdentTests.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IdentTests.cs @@ -1,45 +1,45 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -using System; -using Microsoft.PowerFx.Syntax; -using Xunit; - -namespace Microsoft.PowerFx.Core.Tests -{ - public class IdentTests : PowerFxTest - { - [Theory] - [InlineData("x")] - [InlineData("ident with space")] - [InlineData("ident with multiple spaces")] - [InlineData(" name ")] - [InlineData("123\u00ae")] - [InlineData(" ", "_ ")] - [InlineData(" ", "_ ")] - [InlineData("'")] - [InlineData("''")] - [InlineData("", "_")] - [InlineData("***", null)] - public void MakeValidIdentifier(string input, string expected = null) - { - if (expected == null) - { - expected = input; - } - - var validIdentifier = IdentToken.MakeValidIdentifier(input); - var token = TexlLexer.InvariantLexer.GetTokens(validIdentifier)[0] as IdentToken; - Assert.Equal(expected, token.Name.Value); - - token = TexlLexer.CommaDecimalSeparatorLexer.GetTokens(validIdentifier)[0] as IdentToken; - Assert.Equal(expected, token.Name.Value); - } - - [Fact] - public void MakeValidIdentifierNull() - { - Assert.Throws(() => IdentToken.MakeValidIdentifier(null)); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using Microsoft.PowerFx.Syntax; +using Xunit; + +namespace Microsoft.PowerFx.Core.Tests +{ + public class IdentTests : PowerFxTest + { + [Theory] + [InlineData("x")] + [InlineData("ident with space")] + [InlineData("ident with multiple spaces")] + [InlineData(" name ")] + [InlineData("123\u00ae")] + [InlineData(" ", "_ ")] + [InlineData(" ", "_ ")] + [InlineData("'")] + [InlineData("''")] + [InlineData("", "_")] + [InlineData("***", null)] + public void MakeValidIdentifier(string input, string expected = null) + { + if (expected == null) + { + expected = input; + } + + var validIdentifier = IdentToken.MakeValidIdentifier(input); + var token = TexlLexer.InvariantLexer.GetTokens(validIdentifier)[0] as IdentToken; + Assert.Equal(expected, token.Name.Value); + + token = TexlLexer.CommaDecimalSeparatorLexer.GetTokens(validIdentifier)[0] as IdentToken; + Assert.Equal(expected, token.Name.Value); + } + + [Fact] + public void MakeValidIdentifierNull() + { + Assert.Throws(() => IdentToken.MakeValidIdentifier(null)); + } + } +} diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ImmutabilityTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ImmutabilityTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ImmutabilityTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ImmutabilityTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseOperationsTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseOperationsTests.cs similarity index 97% rename from src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseOperationsTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseOperationsTests.cs index 8ed3aace6..2d69159d2 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseOperationsTests.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseOperationsTests.cs @@ -1,235 +1,235 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using Microsoft.PowerFx.Intellisense; -using Microsoft.PowerFx.Syntax; -using Microsoft.PowerFx.Types; -using Xunit; - -namespace Microsoft.PowerFx.Core.Tests -{ - public class IntellisenseOperationsTests : PowerFxTest - { - [Fact] - public void CheckIf() - { - var formula = "If(!IsBlank(X), \"Hello \" & X & \"!\", \"Hello world!\")"; - - var config = new PowerFxConfig(); - var engine = new Engine(config); - - var formulaParams = RecordType.Empty(); - formulaParams = formulaParams.Add("X", FormulaType.String); - - var parseResult = engine.Parse(formula); - Assert.True(parseResult.IsSuccess); - - var checkResult = engine.Check(parseResult, formulaParams); - Assert.True(checkResult.IsSuccess); - - // Check existing args - var args = parseResult.Root.AsCall().Args.ChildNodes; - var result = checkResult.ValidateInvocation("If", args, out var returnType); - Assert.True(result); - Assert.Equal(FormulaType.String, returnType); - - // Swap args - var args2 = new[] { args[0], args[2], args[1] }; - var result2 = checkResult.ValidateInvocation("If", args2, out var returnType2); - Assert.True(result2); - Assert.Equal(FormulaType.String, returnType2); - - // Take sub-tree - var args3 = new[] { args[0], args[1].AsBinaryOp().Right, args[2] }; - var result3 = checkResult.ValidateInvocation("If", args3, out var returnType3); - Assert.True(result3); - Assert.Equal(FormulaType.String, returnType3); - - // Remove one arg - var args4 = new[] { args[0], args[2] }; - var result4 = checkResult.ValidateInvocation("If", args4, out var returnType4); - Assert.True(result4); - Assert.Equal(FormulaType.String, returnType4); - - // Remove two args - var args5 = new[] { args[1] }; - var result5 = checkResult.ValidateInvocation("If", args5, out var returnType5); - Assert.False(result5); - Assert.Null(returnType5); // Not part of contract, but we expect this to be null - - // Use different (invalid) function - var args6 = args; - var result6 = checkResult.ValidateInvocation("CountIf", args6, out var returnType6); - Assert.False(result6); - Assert.Null(returnType6); - - // Use different (valid) function - var args7 = args; - var result7 = checkResult.ValidateInvocation("IfError", args7, out var _); - Assert.True(result7); - } - - [Theory] - [InlineData("SUM(X)")] - [InlineData("SUM(1)")] - [InlineData("SUM(X, 1)")] - [InlineData("SUM(Y, X)")] - [InlineData("SUM(Y, X, Y)")] - [InlineData("SUM(T)")] - [InlineData("SUM(T, A + B)")] - [InlineData("SUM(T, A + B, 1)")] - public void CheckOverloads(string formula) - { - var config = new PowerFxConfig(); - var engine = new Engine(config); - - var tableType = - TableType.Empty().Add(new NamedFormulaType("A", FormulaType.Number)) - .Add(new NamedFormulaType("B", FormulaType.Number)); - var formulaParams = - RecordType.Empty().Add("X", FormulaType.Number).Add("Y", FormulaType.Number).Add("Table", tableType); - - var parseResult = engine.Parse(formula); - var args = parseResult.Root.AsCall().Args.ChildNodes; - var fnc = parseResult.Root.AsCall().Head; - var fncName = fnc.Namespace.IsRoot ? fnc.Name.Value : $"{fnc.Namespace.ToDottedSyntax()}.{fnc.Name.Value}"; - - Assert.True(parseResult.IsSuccess); - - var checkResult = engine.Check(parseResult, formulaParams); - var expectedType = checkResult.IsSuccess ? checkResult.ReturnType : null; - var validateResult = checkResult.ValidateInvocation(fncName, args, out var retType); - - Assert.Equal(checkResult.IsSuccess, validateResult); - Assert.Equal(expectedType, retType); - } - - [Fact] - public void CheckNamespace() - { - var config = new PowerFxConfig(); - var engine = new Engine(config); - var formula = "true"; // does not matter what the formula is (not using arguments from it) - var checkResult = engine.Check(formula); - Assert.True(checkResult.IsSuccess); - - var result = checkResult.ValidateInvocation("Clock.AmPm", new TexlNode[0], out var _); - Assert.True(result); - } - - [Fact] - public void InvalidFncNames() - { - var config = new PowerFxConfig(); - var engine = new Engine(config); - var formula = "true"; // does not matter what the formula is (not using arguments from it) - var checkResult = engine.Check(formula); - Assert.True(checkResult.IsSuccess); - - var result = checkResult.ValidateInvocation("invalid fnc name", new TexlNode[0], out var _); - Assert.False(result); - } - - [Fact] - public void InvalidNodes() - { - var config = new PowerFxConfig(); - var engine = new Engine(config); - - var formula1 = "If(true, false, true)"; - var parseResult1 = engine.Parse(formula1); - Assert.True(parseResult1.IsSuccess); - var args1 = parseResult1.Root.AsCall().Args.ChildNodes; - - var formula2 = "If(false, true, false)"; - var parseResult2 = engine.Parse(formula2); - Assert.True(parseResult2.IsSuccess); - var checkResult2 = engine.Check(parseResult2); - var args2 = parseResult2.Root.AsCall().Args.ChildNodes; - - var mixedNodes = new[] { args2[0], args1[0], args2[1] }; - Assert.Throws(() => checkResult2.ValidateInvocation("If", mixedNodes, out _)); - } - - [Theory] - [InlineData("normalFnc", "normalFnc")] - [InlineData("ns1.normalFnc", "normalFnc", "ns1")] - [InlineData("ns1.ns2.someFnc", "someFnc", "ns1.ns2")] - [InlineData("'escaped fnc name'", "escaped fnc name")] - [InlineData("ns1.'escaped fnc name'", "escaped fnc name", "ns1")] - [InlineData("ns1.'escaped namespace'.'escaped fnc name'", "escaped fnc name", "ns1.'escaped namespace'")] - [InlineData("invalid fnc", null)] - [InlineData("ns1.", null)] - [InlineData(".fnc", null)] - [InlineData("abc(", null)] - public void FunctionNameParse(string fncName, string expectedName, string expectedNs = "") - { - var expectedResult = expectedName != null; - - var result = IntellisenseOperations.TryParseFunctionNameWithNamespace(fncName, out var ident); - Assert.Equal(expectedResult, result); - - if (result) - { - Assert.Equal(expectedName, ident.Name.Value); - Assert.Equal(expectedNs, ident.Namespace.ToDottedSyntax()); - } - else - { - Assert.Null(ident); - } - } - - // TODO: What are some further interesting cases? - [Theory] - [InlineData("Filter", 0, false)] - [InlineData("Filter", 1, true)] - [InlineData("Filter", 2, true)] - [InlineData("Filter", 42, true)] - [InlineData("If", 0, false)] - [InlineData("If", 1, false)] - [InlineData("If", 2, false)] - [InlineData("If", 42, false)] - [InlineData("Sum", 0, false)] - [InlineData("Sum", 1, true)] - [InlineData("Sum", 2, true)] - [InlineData("Sum", 42, true)] - [InlineData("NonExistentFnc", 0, false)] - [InlineData("NonExistentFnc", 1, false)] - [InlineData("NonExistentFnc", 2, false)] - [InlineData("NonExistentFnc", 42, false)] - [InlineData("Clock.AmPm", 0, false)] - [InlineData("Clock.AmPm", 1, false)] - [InlineData("Clock.AmPm", 2, false)] - [InlineData("Clock.AmPm", 42, false)] - [InlineData("And", 0, false)] - [InlineData("And", 1, false)] - public void CheckRowScope(string fncName, int arg, bool expectedResult) - { - var engine = new Engine(new PowerFxConfig()); - var checkResult = engine.Check("0"); // Check on a dummy formula to obtain the result - - var intellisense = new IntellisenseOperations(checkResult); - var resultString = intellisense.MaybeRowScopeArg(fncName, arg); - Assert.Equal(expectedResult, resultString); - - var fncParseOk = IntellisenseOperations.TryParseFunctionNameWithNamespace(fncName, out Identifier ident); - Assert.True(fncParseOk); - - var resultIdent = intellisense.MaybeRowScopeArg(ident, arg); - Assert.Equal(expectedResult, resultIdent); - } - } - - internal static class ValidateUtils - { - public static bool ValidateInvocation( - this CheckResult result, - string fncName, - IReadOnlyList args, - out FormulaType retType) => new IntellisenseOperations(result).ValidateInvocation(fncName, args, out retType); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Microsoft.PowerFx.Intellisense; +using Microsoft.PowerFx.Syntax; +using Microsoft.PowerFx.Types; +using Xunit; + +namespace Microsoft.PowerFx.Core.Tests +{ + public class IntellisenseOperationsTests : PowerFxTest + { + [Fact] + public void CheckIf() + { + var formula = "If(!IsBlank(X), \"Hello \" & X & \"!\", \"Hello world!\")"; + + var config = new PowerFxConfig(); + var engine = new Engine(config); + + var formulaParams = RecordType.Empty(); + formulaParams = formulaParams.Add("X", FormulaType.String); + + var parseResult = engine.Parse(formula); + Assert.True(parseResult.IsSuccess); + + var checkResult = engine.Check(parseResult, formulaParams); + Assert.True(checkResult.IsSuccess); + + // Check existing args + var args = parseResult.Root.AsCall().Args.ChildNodes; + var result = checkResult.ValidateInvocation("If", args, out var returnType); + Assert.True(result); + Assert.Equal(FormulaType.String, returnType); + + // Swap args + var args2 = new[] { args[0], args[2], args[1] }; + var result2 = checkResult.ValidateInvocation("If", args2, out var returnType2); + Assert.True(result2); + Assert.Equal(FormulaType.String, returnType2); + + // Take sub-tree + var args3 = new[] { args[0], args[1].AsBinaryOp().Right, args[2] }; + var result3 = checkResult.ValidateInvocation("If", args3, out var returnType3); + Assert.True(result3); + Assert.Equal(FormulaType.String, returnType3); + + // Remove one arg + var args4 = new[] { args[0], args[2] }; + var result4 = checkResult.ValidateInvocation("If", args4, out var returnType4); + Assert.True(result4); + Assert.Equal(FormulaType.String, returnType4); + + // Remove two args + var args5 = new[] { args[1] }; + var result5 = checkResult.ValidateInvocation("If", args5, out var returnType5); + Assert.False(result5); + Assert.Null(returnType5); // Not part of contract, but we expect this to be null + + // Use different (invalid) function + var args6 = args; + var result6 = checkResult.ValidateInvocation("CountIf", args6, out var returnType6); + Assert.False(result6); + Assert.Null(returnType6); + + // Use different (valid) function + var args7 = args; + var result7 = checkResult.ValidateInvocation("IfError", args7, out var _); + Assert.True(result7); + } + + [Theory] + [InlineData("SUM(X)")] + [InlineData("SUM(1)")] + [InlineData("SUM(X, 1)")] + [InlineData("SUM(Y, X)")] + [InlineData("SUM(Y, X, Y)")] + [InlineData("SUM(T)")] + [InlineData("SUM(T, A + B)")] + [InlineData("SUM(T, A + B, 1)")] + public void CheckOverloads(string formula) + { + var config = new PowerFxConfig(); + var engine = new Engine(config); + + var tableType = + TableType.Empty().Add(new NamedFormulaType("A", FormulaType.Number)) + .Add(new NamedFormulaType("B", FormulaType.Number)); + var formulaParams = + RecordType.Empty().Add("X", FormulaType.Number).Add("Y", FormulaType.Number).Add("Table", tableType); + + var parseResult = engine.Parse(formula); + var args = parseResult.Root.AsCall().Args.ChildNodes; + var fnc = parseResult.Root.AsCall().Head; + var fncName = fnc.Namespace.IsRoot ? fnc.Name.Value : $"{fnc.Namespace.ToDottedSyntax()}.{fnc.Name.Value}"; + + Assert.True(parseResult.IsSuccess); + + var checkResult = engine.Check(parseResult, formulaParams); + var expectedType = checkResult.IsSuccess ? checkResult.ReturnType : null; + var validateResult = checkResult.ValidateInvocation(fncName, args, out var retType); + + Assert.Equal(checkResult.IsSuccess, validateResult); + Assert.Equal(expectedType, retType); + } + + [Fact] + public void CheckNamespace() + { + var config = new PowerFxConfig(); + var engine = new Engine(config); + var formula = "true"; // does not matter what the formula is (not using arguments from it) + var checkResult = engine.Check(formula); + Assert.True(checkResult.IsSuccess); + + var result = checkResult.ValidateInvocation("Clock.AmPm", new TexlNode[0], out var _); + Assert.True(result); + } + + [Fact] + public void InvalidFncNames() + { + var config = new PowerFxConfig(); + var engine = new Engine(config); + var formula = "true"; // does not matter what the formula is (not using arguments from it) + var checkResult = engine.Check(formula); + Assert.True(checkResult.IsSuccess); + + var result = checkResult.ValidateInvocation("invalid fnc name", new TexlNode[0], out var _); + Assert.False(result); + } + + [Fact] + public void InvalidNodes() + { + var config = new PowerFxConfig(); + var engine = new Engine(config); + + var formula1 = "If(true, false, true)"; + var parseResult1 = engine.Parse(formula1); + Assert.True(parseResult1.IsSuccess); + var args1 = parseResult1.Root.AsCall().Args.ChildNodes; + + var formula2 = "If(false, true, false)"; + var parseResult2 = engine.Parse(formula2); + Assert.True(parseResult2.IsSuccess); + var checkResult2 = engine.Check(parseResult2); + var args2 = parseResult2.Root.AsCall().Args.ChildNodes; + + var mixedNodes = new[] { args2[0], args1[0], args2[1] }; + Assert.Throws(() => checkResult2.ValidateInvocation("If", mixedNodes, out _)); + } + + [Theory] + [InlineData("normalFnc", "normalFnc")] + [InlineData("ns1.normalFnc", "normalFnc", "ns1")] + [InlineData("ns1.ns2.someFnc", "someFnc", "ns1.ns2")] + [InlineData("'escaped fnc name'", "escaped fnc name")] + [InlineData("ns1.'escaped fnc name'", "escaped fnc name", "ns1")] + [InlineData("ns1.'escaped namespace'.'escaped fnc name'", "escaped fnc name", "ns1.'escaped namespace'")] + [InlineData("invalid fnc", null)] + [InlineData("ns1.", null)] + [InlineData(".fnc", null)] + [InlineData("abc(", null)] + public void FunctionNameParse(string fncName, string expectedName, string expectedNs = "") + { + var expectedResult = expectedName != null; + + var result = IntellisenseOperations.TryParseFunctionNameWithNamespace(fncName, out var ident); + Assert.Equal(expectedResult, result); + + if (result) + { + Assert.Equal(expectedName, ident.Name.Value); + Assert.Equal(expectedNs, ident.Namespace.ToDottedSyntax()); + } + else + { + Assert.Null(ident); + } + } + + // TODO: What are some further interesting cases? + [Theory] + [InlineData("Filter", 0, false)] + [InlineData("Filter", 1, true)] + [InlineData("Filter", 2, true)] + [InlineData("Filter", 42, true)] + [InlineData("If", 0, false)] + [InlineData("If", 1, false)] + [InlineData("If", 2, false)] + [InlineData("If", 42, false)] + [InlineData("Sum", 0, false)] + [InlineData("Sum", 1, true)] + [InlineData("Sum", 2, true)] + [InlineData("Sum", 42, true)] + [InlineData("NonExistentFnc", 0, false)] + [InlineData("NonExistentFnc", 1, false)] + [InlineData("NonExistentFnc", 2, false)] + [InlineData("NonExistentFnc", 42, false)] + [InlineData("Clock.AmPm", 0, false)] + [InlineData("Clock.AmPm", 1, false)] + [InlineData("Clock.AmPm", 2, false)] + [InlineData("Clock.AmPm", 42, false)] + [InlineData("And", 0, false)] + [InlineData("And", 1, false)] + public void CheckRowScope(string fncName, int arg, bool expectedResult) + { + var engine = new Engine(new PowerFxConfig()); + var checkResult = engine.Check("0"); // Check on a dummy formula to obtain the result + + var intellisense = new IntellisenseOperations(checkResult); + var resultString = intellisense.MaybeRowScopeArg(fncName, arg); + Assert.Equal(expectedResult, resultString); + + var fncParseOk = IntellisenseOperations.TryParseFunctionNameWithNamespace(fncName, out Identifier ident); + Assert.True(fncParseOk); + + var resultIdent = intellisense.MaybeRowScopeArg(ident, arg); + Assert.Equal(expectedResult, resultIdent); + } + } + + internal static class ValidateUtils + { + public static bool ValidateInvocation( + this CheckResult result, + string fncName, + IReadOnlyList args, + out FormulaType retType) => new IntellisenseOperations(result).ValidateInvocation(fncName, args, out retType); + } +} diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/DisclaimerProviderTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/DisclaimerProviderTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/DisclaimerProviderTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/DisclaimerProviderTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/IntellisenseTestBase.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/IntellisenseTestBase.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/IntellisenseTestBase.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/IntellisenseTestBase.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/MarkdownStringTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/MarkdownStringTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/MarkdownStringTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/MarkdownStringTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/SignatureHelpTest.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/SignatureHelpTest.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/SignatureHelpTest.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/SignatureHelpTest.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/SuggestTest.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/SuggestTest.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/SuggestTest.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/SuggestTest.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/0.json b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/0.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/0.json rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/0.json diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/1.json b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/1.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/1.json rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/1.json diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/10.json b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/10.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/10.json rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/10.json diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/11.json b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/11.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/11.json rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/11.json diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/12.json b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/12.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/12.json rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/12.json diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/13.json b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/13.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/13.json rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/13.json diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/14.json b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/14.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/14.json rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/14.json diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/15.json b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/15.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/15.json rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/15.json diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/16.json b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/16.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/16.json rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/16.json diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/17.json b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/17.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/17.json rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/17.json diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/18.json b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/18.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/18.json rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/18.json diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/19.json b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/19.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/19.json rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/19.json diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/2.json b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/2.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/2.json rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/2.json diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/3.json b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/3.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/3.json rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/3.json diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/4.json b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/4.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/4.json rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/4.json diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/5.json b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/5.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/5.json rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/5.json diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/6.json b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/6.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/6.json rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/6.json diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/7.json b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/7.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/7.json rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/7.json diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/8.json b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/8.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/8.json rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/8.json diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/9.json b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/9.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TestSignatures/9.json rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TestSignatures/9.json diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TokenizationTests/ExpectedToken.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TokenizationTests/ExpectedToken.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TokenizationTests/ExpectedToken.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TokenizationTests/ExpectedToken.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TokenizationTests/NumDecLitTokenTestCases.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TokenizationTests/NumDecLitTokenTestCases.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TokenizationTests/NumDecLitTokenTestCases.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TokenizationTests/NumDecLitTokenTestCases.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TokenizationTests/SkippingTokensTestCases.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TokenizationTests/SkippingTokensTestCases.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TokenizationTests/SkippingTokensTestCases.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TokenizationTests/SkippingTokensTestCases.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TokenizationTests/StringInterpolationTestCases.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TokenizationTests/StringInterpolationTestCases.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TokenizationTests/StringInterpolationTestCases.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TokenizationTests/StringInterpolationTestCases.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TokenizationTests/TokenizationTestCase.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TokenizationTests/TokenizationTestCase.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TokenizationTests/TokenizationTestCase.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TokenizationTests/TokenizationTestCase.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TokenizationTests/TokenizationTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TokenizationTests/TokenizationTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/IntellisenseTests/TokenizationTests/TokenizationTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/IntellisenseTests/TokenizationTests/TokenizationTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/JsonNormalizer.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/JsonNormalizer.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/JsonNormalizer.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/JsonNormalizer.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/LexerTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/LexerTests.cs similarity index 99% rename from src/tests/Microsoft.PowerFx.Core.Tests/LexerTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/LexerTests.cs index afb02cd63..7c43a4f8f 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests/LexerTests.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/LexerTests.cs @@ -14,27 +14,27 @@ using Xunit; namespace Microsoft.PowerFx.Core.Tests { public sealed class LexerTests : PowerFxTest - { - private void AssertTokens(string value, params TokKind[] tokKinds) - { - AssertTokens(TexlLexer.Flags.None, value, tokKinds); - } + { + private void AssertTokens(string value, params TokKind[] tokKinds) + { + AssertTokens(TexlLexer.Flags.None, value, tokKinds); + } private void AssertTokens(TexlLexer.Flags flags, string value, params TokKind[] tokKinds) { var tokens = TexlLexer.InvariantLexer.LexSource(value, flags); Assert.NotNull(tokens); Assert.Equal(tokKinds.Length, tokens.Count); - Assert.True(tokens.Zip(tokKinds, (t, k) => t.Kind == k).All(b => b)); - } - + Assert.True(tokens.Zip(tokKinds, (t, k) => t.Kind == k).All(b => b)); + } + private Token AssertTokensAndReturnOne(TexlLexer.Flags flags, string value, int one, params TokKind[] tokKinds) { var tokens = TexlLexer.InvariantLexer.LexSource(value, flags); Assert.NotNull(tokens); Assert.Equal(tokKinds.Length, tokens.Count); - Assert.True(tokens.Zip(tokKinds, (t, k) => t.Kind == k).All(b => b)); - return tokens[one]; + Assert.True(tokens.Zip(tokKinds, (t, k) => t.Kind == k).All(b => b)); + return tokens[one]; } [Fact] @@ -160,8 +160,8 @@ namespace Microsoft.PowerFx.Core.Tests Assert.Equal(TokKind.Error, tokens[0].Kind); Assert.Equal(2, (tokens[0] as ErrorToken).ResourceKeyFormatStringArgs.Length); Assert.Equal((tokens[0] as ErrorToken).DetailErrorKey.Value, TexlStrings.UnexpectedCharacterToken); - } - + } + [Fact] public void TestZeroWidthSpaceCharactersPFxV1Flag() { @@ -206,19 +206,19 @@ namespace Microsoft.PowerFx.Core.Tests Assert.Equal(TokKind.Error, tokens[0].Kind); Assert.Equal(2, (tokens[0] as ErrorToken).ResourceKeyFormatStringArgs.Length); Assert.Equal((tokens[0] as ErrorToken).DetailErrorKey.Value, TexlStrings.UnexpectedCharacterToken); - } - + } + [Fact] public void TestZeroWidthSpaceCharactersStringLength() - { - var expression = "\"AA" + char.ConvertFromUtf32(8203) + "BB\""; - var tokens = TexlLexer.InvariantLexer.LexSource(expression, TexlLexer.Flags.PFxV1); - var stringToken = tokens[0] as StrLitToken; - Assert.Equal(stringToken.Value.Length, expression.Length - 2); // Flags.PFxV1 will keep the zero width space char. - - expression = "\"AA" + char.ConvertFromUtf32(8203) + "BB\""; - tokens = TexlLexer.InvariantLexer.LexSource(expression, TexlLexer.Flags.None); - stringToken = tokens[0] as StrLitToken; + { + var expression = "\"AA" + char.ConvertFromUtf32(8203) + "BB\""; + var tokens = TexlLexer.InvariantLexer.LexSource(expression, TexlLexer.Flags.PFxV1); + var stringToken = tokens[0] as StrLitToken; + Assert.Equal(stringToken.Value.Length, expression.Length - 2); // Flags.PFxV1 will keep the zero width space char. + + expression = "\"AA" + char.ConvertFromUtf32(8203) + "BB\""; + tokens = TexlLexer.InvariantLexer.LexSource(expression, TexlLexer.Flags.None); + stringToken = tokens[0] as StrLitToken; Assert.Equal(stringToken.Value.Length, expression.Length - 3); // Flags.None will remove the zero width space char. } @@ -259,18 +259,18 @@ namespace Microsoft.PowerFx.Core.Tests [Fact] public void TestLexNumbersWithLanguageSettings() - { - var tokensN = TexlLexer.GetLocalizedInstance(GetFrenchSettings()).LexSource("123456,78", TexlLexer.Flags.NumberIsFloat); + { + var tokensN = TexlLexer.GetLocalizedInstance(GetFrenchSettings()).LexSource("123456,78", TexlLexer.Flags.NumberIsFloat); Assert.NotNull(tokensN); - Assert.Equal(2, tokensN.Count); - Assert.True(tokensN[0].Kind == TokKind.NumLit); - Assert.Equal(123456.78, tokensN[0].As().Value); - - var tokensW = TexlLexer.GetLocalizedInstance(GetFrenchSettings()).LexSource("123456,78"); + Assert.Equal(2, tokensN.Count); + Assert.True(tokensN[0].Kind == TokKind.NumLit); + Assert.Equal(123456.78, tokensN[0].As().Value); + + var tokensW = TexlLexer.GetLocalizedInstance(GetFrenchSettings()).LexSource("123456,78"); Assert.NotNull(tokensW); - Assert.Equal(2, tokensW.Count); - Assert.True(tokensW[0].Kind == TokKind.DecLit); - Assert.Equal(123456.78m, tokensW[0].As().Value); + Assert.Equal(2, tokensW.Count); + Assert.True(tokensW[0].Kind == TokKind.DecLit); + Assert.Equal(123456.78m, tokensW[0].As().Value); } [Fact] @@ -279,29 +279,29 @@ namespace Microsoft.PowerFx.Core.Tests var tokensN = TexlLexer.GetLocalizedInstance(GetFrenchSettings()).LexSource("[1,2;2,3;4]", TexlLexer.Flags.NumberIsFloat); Assert.NotNull(tokensN); Assert.Equal(8, tokensN.Count); - Assert.Equal(TokKind.BracketOpen, tokensN[0].Kind); - Assert.Equal(TokKind.NumLit, tokensN[1].Kind); - Assert.Equal(1.2, tokensN[1].As().Value); - Assert.Equal(TokKind.Comma, tokensN[2].Kind); - Assert.Equal(TokKind.NumLit, tokensN[3].Kind); - Assert.Equal(2.3, tokensN[3].As().Value); - Assert.Equal(TokKind.Comma, tokensN[4].Kind); - Assert.Equal(TokKind.NumLit, tokensN[5].Kind); - Assert.Equal(4, tokensN[5].As().Value); + Assert.Equal(TokKind.BracketOpen, tokensN[0].Kind); + Assert.Equal(TokKind.NumLit, tokensN[1].Kind); + Assert.Equal(1.2, tokensN[1].As().Value); + Assert.Equal(TokKind.Comma, tokensN[2].Kind); + Assert.Equal(TokKind.NumLit, tokensN[3].Kind); + Assert.Equal(2.3, tokensN[3].As().Value); + Assert.Equal(TokKind.Comma, tokensN[4].Kind); + Assert.Equal(TokKind.NumLit, tokensN[5].Kind); + Assert.Equal(4, tokensN[5].As().Value); Assert.Equal(TokKind.BracketClose, tokensN[6].Kind); Assert.Equal(TokKind.Eof, tokensN[7].Kind); - - var tokensW = TexlLexer.GetLocalizedInstance(GetFrenchSettings()).LexSource("[1,2;2,3;4]"); + + var tokensW = TexlLexer.GetLocalizedInstance(GetFrenchSettings()).LexSource("[1,2;2,3;4]"); Assert.NotNull(tokensW); Assert.Equal(8, tokensW.Count); - Assert.Equal(TokKind.BracketOpen, tokensW[0].Kind); - Assert.Equal(TokKind.DecLit, tokensW[1].Kind); - Assert.Equal(1.2m, tokensW[1].As().Value); - Assert.Equal(TokKind.Comma, tokensW[2].Kind); - Assert.Equal(TokKind.DecLit, tokensW[3].Kind); - Assert.Equal(2.3m, tokensW[3].As().Value); - Assert.Equal(TokKind.Comma, tokensW[4].Kind); - Assert.Equal(TokKind.DecLit, tokensW[5].Kind); + Assert.Equal(TokKind.BracketOpen, tokensW[0].Kind); + Assert.Equal(TokKind.DecLit, tokensW[1].Kind); + Assert.Equal(1.2m, tokensW[1].As().Value); + Assert.Equal(TokKind.Comma, tokensW[2].Kind); + Assert.Equal(TokKind.DecLit, tokensW[3].Kind); + Assert.Equal(2.3m, tokensW[3].As().Value); + Assert.Equal(TokKind.Comma, tokensW[4].Kind); + Assert.Equal(TokKind.DecLit, tokensW[5].Kind); Assert.Equal(4, tokensW[5].As().Value); Assert.Equal(TokKind.BracketClose, tokensW[6].Kind); Assert.Equal(TokKind.Eof, tokensW[7].Kind); @@ -458,7 +458,7 @@ namespace Microsoft.PowerFx.Core.Tests [Fact] public void TestUnsupportedDecimalSeparatorCausesFallback() { - // Simulate an override of the decimal separator to something that AXL does not support. + // Simulate an override of the decimal separator to something that AXL does not support. // $$$ can't use current culture var oldCulture = CultureInfo.CurrentCulture; var newCulture = new CultureInfo(CultureInfo.CurrentCulture.Name); @@ -468,19 +468,19 @@ namespace Microsoft.PowerFx.Core.Tests // The lexer should fall back to the invariant separator. var lexer = TexlLexer.GetLocalizedInstance(null); Assert.Equal(lexer.LocalizedPunctuatorDecimalSeparator, TexlLexer.PunctuatorDecimalSeparatorInvariant); - - var tokens = lexer.LexSource("123456.78", TexlLexer.Flags.NumberIsFloat); - Assert.NotNull(tokens); - Assert.Equal(2, tokens.Count); - Assert.Equal(TokKind.NumLit, tokens[0].Kind); - Assert.Equal(123456.78, tokens[0].As().Value); - - tokens = lexer.LexSource("123456.78"); - Assert.NotNull(tokens); - Assert.Equal(2, tokens.Count); - Assert.Equal(TokKind.DecLit, tokens[0].Kind); - Assert.Equal(123456.78m, tokens[0].As().Value); - + + var tokens = lexer.LexSource("123456.78", TexlLexer.Flags.NumberIsFloat); + Assert.NotNull(tokens); + Assert.Equal(2, tokens.Count); + Assert.Equal(TokKind.NumLit, tokens[0].Kind); + Assert.Equal(123456.78, tokens[0].As().Value); + + tokens = lexer.LexSource("123456.78"); + Assert.NotNull(tokens); + Assert.Equal(2, tokens.Count); + Assert.Equal(TokKind.DecLit, tokens[0].Kind); + Assert.Equal(123456.78m, tokens[0].As().Value); + // $$$ can't use current culture CultureInfo.CurrentCulture = oldCulture; } @@ -527,9 +527,9 @@ namespace Microsoft.PowerFx.Core.Tests [Fact] public void TestStringInterpolationWithTable() { - AssertTokens( + AssertTokens( TexlLexer.Flags.NumberIsFloat, - "$\"Hello {Table({a: 5})} World!\"", + "$\"Hello {Table({a: 5})} World!\"", TokKind.StrInterpStart, TokKind.StrLit, TokKind.IslandStart, @@ -545,8 +545,8 @@ namespace Microsoft.PowerFx.Core.Tests TokKind.IslandEnd, TokKind.StrLit, TokKind.StrInterpEnd, - TokKind.Eof); - + TokKind.Eof); + AssertTokens( "$\"Hello {Table({a: 5})} World!\"", TokKind.StrInterpStart, @@ -593,130 +593,130 @@ namespace Microsoft.PowerFx.Core.Tests Assert.True(object.ReferenceEquals(TexlLexer.InvariantLexer, TexlLexer.InvariantLexer)); Assert.True(object.ReferenceEquals(TexlLexer.CommaDecimalSeparatorLexer, TexlLexer.CommaDecimalSeparatorLexer)); Assert.False(object.ReferenceEquals(TexlLexer.InvariantLexer, TexlLexer.CommaDecimalSeparatorLexer)); - } - + } + [Fact] public void TextFirstWhitespace() - { - string str = " Initial Whitespace"; - - var lit = AssertTokensAndReturnOne( - TexlLexer.Flags.TextFirst, - str, + { + string str = " Initial Whitespace"; + + var lit = AssertTokensAndReturnOne( + TexlLexer.Flags.TextFirst, + str, 1, TokKind.StrInterpStart, TokKind.StrLit, TokKind.StrInterpEnd, - TokKind.Eof); - - Assert.True(lit.Span.Min == 0); - Assert.True(lit.Span.Lim == str.Length); - } - + TokKind.Eof); + + Assert.True(lit.Span.Min == 0); + Assert.True(lit.Span.Lim == str.Length); + } + [Fact] public void TextFirstBasic() - { - string str = "Hello, World"; - - var lit = AssertTokensAndReturnOne( - TexlLexer.Flags.TextFirst, - str, + { + string str = "Hello, World"; + + var lit = AssertTokensAndReturnOne( + TexlLexer.Flags.TextFirst, + str, 1, TokKind.StrInterpStart, TokKind.StrLit, TokKind.StrInterpEnd, - TokKind.Eof); - - Assert.True(lit.Span.Min == 0); - Assert.True(lit.Span.Lim == str.Length); - } - + TokKind.Eof); + + Assert.True(lit.Span.Min == 0); + Assert.True(lit.Span.Lim == str.Length); + } + [Fact] public void TextFirstBasicFormulaInterpolation() - { - string str = "=$\"Hello, World\""; - - var lit = AssertTokensAndReturnOne( - TexlLexer.Flags.TextFirst, - str, + { + string str = "=$\"Hello, World\""; + + var lit = AssertTokensAndReturnOne( + TexlLexer.Flags.TextFirst, + str, 1, TokKind.StrInterpStart, TokKind.StrLit, TokKind.StrInterpEnd, - TokKind.Eof); - - Assert.True(lit.Span.Min == 3); - Assert.True(lit.Span.Lim == str.Length - 1); - } - + TokKind.Eof); + + Assert.True(lit.Span.Min == 3); + Assert.True(lit.Span.Lim == str.Length - 1); + } + [Fact] public void TextFirstBasicFormulaStrLit() - { - string str = "=\"Hello, World\""; - - var lit = AssertTokensAndReturnOne( - TexlLexer.Flags.TextFirst, - str, + { + string str = "=\"Hello, World\""; + + var lit = AssertTokensAndReturnOne( + TexlLexer.Flags.TextFirst, + str, 0, TokKind.StrLit, - TokKind.Eof); - - Assert.True(lit.Span.Min == 1); - Assert.True(lit.Span.Lim == str.Length); - } - + TokKind.Eof); + + Assert.True(lit.Span.Min == 1); + Assert.True(lit.Span.Lim == str.Length); + } + [Fact] public void TextFirstEmptyStringLit() - { - string str = string.Empty; - - var lit = AssertTokensAndReturnOne( - TexlLexer.Flags.TextFirst, - str, + { + string str = string.Empty; + + var lit = AssertTokensAndReturnOne( + TexlLexer.Flags.TextFirst, + str, 2, - TokKind.StrInterpStart, + TokKind.StrInterpStart, TokKind.StrInterpEnd, - TokKind.Eof); - - Assert.True(lit.Span.Min == 0); - Assert.True(lit.Span.Lim == 0); - } - + TokKind.Eof); + + Assert.True(lit.Span.Min == 0); + Assert.True(lit.Span.Lim == 0); + } + [Theory] - [InlineData("Hello ${\"World\"}", 3, "$\":True", "Hello :True", "World:False", "\":True")] - [InlineData("Hello ${$\"World\"}", 3, "$\":True", "Hello :True", "$\":False", "World:False", "\":False", "\":True")] + [InlineData("Hello ${\"World\"}", 3, "$\":True", "Hello :True", "World:False", "\":True")] + [InlineData("Hello ${$\"World\"}", 3, "$\":True", "Hello :True", "$\":False", "World:False", "\":False", "\":True")] [InlineData("=$\"Hello {\"World\"}\"", 0, "$\":False", "Hello :False", "World:False", "\":False")] public void TextFirstTokenFlagTest(string expression, int expectedCount, params string[] textFirstToken) - { - var tokens = TexlLexer.InvariantLexer.LexSource(expression, TexlLexer.Flags.TextFirst); - var tokensWithTextFirstFlag = tokens.Where(t => t is ITextFirstFlag).ToArray(); - - Assert.Equal(textFirstToken.Length, tokensWithTextFirstFlag.Count()); - - for (int i = 0; i < tokensWithTextFirstFlag.Count(); i++) - { - var token = tokensWithTextFirstFlag[i]; - var textFirstFlag = token as ITextFirstFlag; - var textFirstTokenString = token.ToString() + ":" + textFirstFlag.IsTextFirst; - - Assert.Equal(textFirstToken[i], textFirstTokenString); - } - - var engine = new Engine(); - var check = engine.Check(expression, new ParserOptions() { TextFirst = true }); - var actualCount = check.GetTextTokens().Where(t => t is ITextFirstFlag flag ? flag.IsTextFirst : false).Count(); - - Assert.Equal(expectedCount, actualCount); - } - - [Fact] + { + var tokens = TexlLexer.InvariantLexer.LexSource(expression, TexlLexer.Flags.TextFirst); + var tokensWithTextFirstFlag = tokens.Where(t => t is ITextFirstFlag).ToArray(); + + Assert.Equal(textFirstToken.Length, tokensWithTextFirstFlag.Count()); + + for (int i = 0; i < tokensWithTextFirstFlag.Count(); i++) + { + var token = tokensWithTextFirstFlag[i]; + var textFirstFlag = token as ITextFirstFlag; + var textFirstTokenString = token.ToString() + ":" + textFirstFlag.IsTextFirst; + + Assert.Equal(textFirstToken[i], textFirstTokenString); + } + + var engine = new Engine(); + var check = engine.Check(expression, new ParserOptions() { TextFirst = true }); + var actualCount = check.GetTextTokens().Where(t => t is ITextFirstFlag flag ? flag.IsTextFirst : false).Count(); + + Assert.Equal(expectedCount, actualCount); + } + + [Fact] public void ITextFirstTokenTest() - { - TokenTextSpan token = new TokenTextSpan("Name", 0, 100, TokenType.BinaryOp, true); - Assert.True(token.CanBeHidden); - - ITokenTextSpan tokenTextSpan = token; - Assert.True(tokenTextSpan.CanBeHidden); - } + { + TokenTextSpan token = new TokenTextSpan("Name", 0, 100, TokenType.BinaryOp, true); + Assert.True(token.CanBeHidden); + + ITokenTextSpan tokenTextSpan = token; + Assert.True(tokenTextSpan.CanBeHidden); + } } } diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/Microsoft.PowerFx.Core.Tests.Shared.projitems b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/Microsoft.PowerFx.Core.Tests.Shared.projitems new file mode 100644 index 000000000..c932ec541 --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/Microsoft.PowerFx.Core.Tests.Shared.projitems @@ -0,0 +1,49 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + d274e7b2-0fed-4f9e-b39a-9fa4a8accf39 + + + Microsoft.PowerFx.Core.Tests.Shared + + + + + + + + + + + + + PreserveNewest + + + IntellisenseTests\TestSignatures\%(FileName)%(Extension) + PreserveNewest + + + + + Always + + + + + True + True + Resources.resx + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + \ No newline at end of file diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/Microsoft.PowerFx.Core.Tests.Shared.shproj b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/Microsoft.PowerFx.Core.Tests.Shared.shproj new file mode 100644 index 000000000..f82a78087 --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/Microsoft.PowerFx.Core.Tests.Shared.shproj @@ -0,0 +1,13 @@ + + + + d274e7b2-0fed-4f9e-b39a-9fa4a8accf39 + 14.0 + + + + + + + + diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/NamedFormulasTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/NamedFormulasTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/NamedFormulasTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/NamedFormulasTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ParseTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ParseTests.cs similarity index 99% rename from src/tests/Microsoft.PowerFx.Core.Tests/ParseTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ParseTests.cs index 6db7d6c3a..9a5fe036e 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests/ParseTests.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ParseTests.cs @@ -5,21 +5,21 @@ using System; using System.Globalization; using System.Linq; using System.Runtime.InteropServices; -using Microsoft.PowerFx.Core.Binding; +using Microsoft.PowerFx.Core.Binding; using Microsoft.PowerFx.Core.Errors; using Microsoft.PowerFx.Core.Functions; -using Microsoft.PowerFx.Core.Glue; +using Microsoft.PowerFx.Core.Glue; using Microsoft.PowerFx.Core.Localization; using Microsoft.PowerFx.Core.Parser; -using Microsoft.PowerFx.Core.Texl; +using Microsoft.PowerFx.Core.Texl; using Microsoft.PowerFx.Syntax; using Xunit; namespace Microsoft.PowerFx.Core.Tests { public class ParseTests : PowerFxTest - { - private static readonly ReadOnlySymbolTable _primitiveTypes = ReadOnlySymbolTable.PrimitiveTypesTableInstance; + { + private static readonly ReadOnlySymbolTable _primitiveTypes = ReadOnlySymbolTable.PrimitiveTypesTableInstance; [Theory] [InlineData("0")] @@ -52,14 +52,14 @@ namespace Microsoft.PowerFx.Core.Tests [InlineData("123456789")] [InlineData("-123456789")] [InlineData("123456789.987654321", "123456789.98765433")] - [InlineData("-123456789.987654321", "-123456789.98765433")] + [InlineData("-123456789.987654321", "-123456789.98765433")] [InlineData("1.00000000000000000000001", "1")] [InlineData("2.E5", "200000")] public void TexlParseNumericLiterals(string script, string expected = null) { TestRoundtrip(script, expected, NodeKind.Error, null, TexlParser.Flags.NumberIsFloat); - } - + } + [Theory] [InlineData("0")] [InlineData("-0")] @@ -91,7 +91,7 @@ namespace Microsoft.PowerFx.Core.Tests [InlineData("123456789")] [InlineData("-123456789")] [InlineData("123456789.987654321", "123456789.987654321")] - [InlineData("-123456789.987654321", "-123456789.987654321")] + [InlineData("-123456789.987654321", "-123456789.987654321")] [InlineData("1.00000000000000000000001", "1.00000000000000000000001")] [InlineData("2.E5", "200000")] public void TexlParseDecimalLiterals(string script, string expected = null) @@ -118,8 +118,8 @@ namespace Microsoft.PowerFx.Core.Tests public void TexlParseLargeNumerics_Negative(string script) { TestParseErrors(script, 1, StringResources.Get(TexlStrings.ErrNumberTooLarge)); - } - + } + [Theory] [InlineData("true")] [InlineData("false")] @@ -694,20 +694,20 @@ namespace Microsoft.PowerFx.Core.Tests public void TestParseRecordsNegative(string script) { TestParseErrors(script); - } - - [Theory] - [InlineData("[{A]", 0, 4)] - [InlineData("[{A:2}]", 0, 7)] - [InlineData("With({A:2", 0, 9)] - [InlineData("Filter(CDS, {A:2", 0, 16)] - [InlineData("{", 0, 1)] - [InlineData("Filter(CDS, {", 0, 13)] + } + + [Theory] + [InlineData("[{A]", 0, 4)] + [InlineData("[{A:2}]", 0, 7)] + [InlineData("With({A:2", 0, 9)] + [InlineData("Filter(CDS, {A:2", 0, 16)] + [InlineData("{", 0, 1)] + [InlineData("Filter(CDS, {", 0, 13)] public void TestParseRecordNodesSpan(string script, int min, int lim) - { - var result = TexlParser.ParseScript(script); - var span = result.Root.GetCompleteSpan(); - Assert.Equal(min, span.Min); + { + var result = TexlParser.ParseScript(script); + var span = result.Root.GetCompleteSpan(); + Assert.Equal(min, span.Min); Assert.Equal(lim, span.Lim); } @@ -823,23 +823,23 @@ namespace Microsoft.PowerFx.Core.Tests internal void TestFormulasParseRoundtrip(string script) { - var parserOptions = new ParserOptions() - { - AllowsSideEffects = false - }; - var parseResult = UserDefinitions.Parse(script, parserOptions); + var parserOptions = new ParserOptions() + { + AllowsSideEffects = false + }; + var parseResult = UserDefinitions.Parse(script, parserOptions); Assert.False(parseResult.HasErrors); } private ParseUserDefinitionResult TestFormulasParseError(string script) { - var parserOptions = new ParserOptions() - { - AllowsSideEffects = false - }; - var parseResult = UserDefinitions.Parse(script, parserOptions); - Assert.True(parseResult.HasErrors); - + var parserOptions = new ParserOptions() + { + AllowsSideEffects = false + }; + var parseResult = UserDefinitions.Parse(script, parserOptions); + Assert.True(parseResult.HasErrors); + return parseResult; } @@ -880,80 +880,80 @@ namespace Microsoft.PowerFx.Core.Tests // Parser restarted, and found 'c' correctly Assert.Contains(formulasResult.NamedFormulas, kvp => kvp.Key.Name.Value == key); - } - - [Theory] + } + + [Theory] [InlineData("a = 10; b = in'valid ; c = 20;", 0, 0, 3, true)] [InlineData("a = 10; b = in(valid ; c = 20;", 0, 0, 3, true)] [InlineData("a = 10; b = in)valid ; c = 20;", 0, 0, 3, true)] [InlineData("a = 10; b = in{valid ; c = 20;", 0, 0, 3, true)] - [InlineData("a = 10; b = in}valid ; c = 20;", 0, 0, 3, true)] - [InlineData("Foo(x: Number): Number = Abs(x);", 1, 1, 0, false)] - [InlineData("x = 1; Foo(x: Number): Number = Abs(x); y = 2;", 1, 1, 2, false)] - [InlineData("Add(x: Number, y:Number): Number = x + y; Foo(x: Number): Number = Abs(x); y = 2;", 2, 2, 1, false)] - [InlineData("Add(x: Number, y:Number): Number = x + y;;; Foo(x: Number): Number = Abs(x); y = 2;", 2, 2, 1, true)] - [InlineData(@"F2(b: Text): Text  = ""Test"";", 1, 1, 0, false)] - [InlineData(@"F2(b: Text): Text  = ""Test;", 0, 0, 0, true)] - [InlineData("Add(x: Number, y:Number): Number = (x + y;;; Foo(x: Number): Number = Abs(x); y = 2;", 2, 1, 1, true)] - public void TestUDFNamedFormulaCountsRestart(string script, int udfCount, int validUdfCount, int namedFormulaCount, bool expectErrors) - { - var parserOptions = new ParserOptions() - { - AllowsSideEffects = false - }; - - var parseResult = UserDefinitions.Parse(script, parserOptions); - var udfs = UserDefinedFunction.CreateFunctions(parseResult.UDFs.Where(udf => udf.IsParseValid), _primitiveTypes, out var errors); - errors.AddRange(parseResult.Errors ?? Enumerable.Empty()); - - Assert.Equal(udfCount, parseResult.UDFs.Count()); - Assert.Equal(validUdfCount, udfs.Count()); - Assert.Equal(namedFormulaCount, parseResult.NamedFormulas.Count()); - Assert.Equal(expectErrors, errors.Any()); - } - - [Theory] - + [InlineData("a = 10; b = in}valid ; c = 20;", 0, 0, 3, true)] + [InlineData("Foo(x: Number): Number = Abs(x);", 1, 1, 0, false)] + [InlineData("x = 1; Foo(x: Number): Number = Abs(x); y = 2;", 1, 1, 2, false)] + [InlineData("Add(x: Number, y:Number): Number = x + y; Foo(x: Number): Number = Abs(x); y = 2;", 2, 2, 1, false)] + [InlineData("Add(x: Number, y:Number): Number = x + y;;; Foo(x: Number): Number = Abs(x); y = 2;", 2, 2, 1, true)] + [InlineData(@"F2(b: Text): Text  = ""Test"";", 1, 1, 0, false)] + [InlineData(@"F2(b: Text): Text  = ""Test;", 0, 0, 0, true)] + [InlineData("Add(x: Number, y:Number): Number = (x + y;;; Foo(x: Number): Number = Abs(x); y = 2;", 2, 1, 1, true)] + public void TestUDFNamedFormulaCountsRestart(string script, int udfCount, int validUdfCount, int namedFormulaCount, bool expectErrors) + { + var parserOptions = new ParserOptions() + { + AllowsSideEffects = false + }; + + var parseResult = UserDefinitions.Parse(script, parserOptions); + var udfs = UserDefinedFunction.CreateFunctions(parseResult.UDFs.Where(udf => udf.IsParseValid), _primitiveTypes, out var errors); + errors.AddRange(parseResult.Errors ?? Enumerable.Empty()); + + Assert.Equal(udfCount, parseResult.UDFs.Count()); + Assert.Equal(validUdfCount, udfs.Count()); + Assert.Equal(namedFormulaCount, parseResult.NamedFormulas.Count()); + Assert.Equal(expectErrors, errors.Any()); + } + + [Theory] + [InlineData("a = 10; b = a + c ; c = 20;", 3, false, new int[] { 0, 8, 20 })] - [InlineData("a = 10; b = in(valid ; c = 20;", 3, true, new int[] { 0, 8, 23 })] - public void TestNamedFormulaStarIndex(string script, int namedFormulaCount, bool expectErrors, int[] expectedStartingIndex) - { - var parserOptions = new ParserOptions() - { - AllowsSideEffects = false - }; - - var parseResult = UserDefinitions.Parse(script, parserOptions); - - var nfs = parseResult.NamedFormulas; - Assert.Equal(namedFormulaCount, nfs.Count()); - Assert.Equal(expectErrors, parseResult.Errors?.Any() ?? false); - - int i = 0; - foreach (var nf in nfs) - { - Assert.Equal(expectedStartingIndex[i], nf.StartingIndex); - i++; - } - } - - [Theory] - [InlineData("SomeFunc(): Number = ({x:5, y5);", 1, 0, true)] - [InlineData("Add(x: Number, y:Number): Number = ;", 1, 0, true)] - [InlineData("Valid(x: Number): Number = x; Invalid(x: Text): Text = {;};", 2, 1, true)] - [InlineData("Invalid(x: Text): Text = ({); A(): Text = \"Hello\";", 2, 1, true)] - public void TestUDFInvalidBody(string script, int udfCount, int validUdfCount, bool expectErrors) - { - var parserOptions = new ParserOptions() - { - AllowsSideEffects = true - }; - - var parseResult = UserDefinitions.Parse(script, parserOptions); - - Assert.Equal(udfCount, parseResult.UDFs.Count()); - Assert.Equal(validUdfCount, parseResult.UDFs.Where(udf => udf.IsParseValid).Count()); - Assert.Equal(expectErrors, parseResult.HasErrors); + [InlineData("a = 10; b = in(valid ; c = 20;", 3, true, new int[] { 0, 8, 23 })] + public void TestNamedFormulaStarIndex(string script, int namedFormulaCount, bool expectErrors, int[] expectedStartingIndex) + { + var parserOptions = new ParserOptions() + { + AllowsSideEffects = false + }; + + var parseResult = UserDefinitions.Parse(script, parserOptions); + + var nfs = parseResult.NamedFormulas; + Assert.Equal(namedFormulaCount, nfs.Count()); + Assert.Equal(expectErrors, parseResult.Errors?.Any() ?? false); + + int i = 0; + foreach (var nf in nfs) + { + Assert.Equal(expectedStartingIndex[i], nf.StartingIndex); + i++; + } + } + + [Theory] + [InlineData("SomeFunc(): Number = ({x:5, y5);", 1, 0, true)] + [InlineData("Add(x: Number, y:Number): Number = ;", 1, 0, true)] + [InlineData("Valid(x: Number): Number = x; Invalid(x: Text): Text = {;};", 2, 1, true)] + [InlineData("Invalid(x: Text): Text = ({); A(): Text = \"Hello\";", 2, 1, true)] + public void TestUDFInvalidBody(string script, int udfCount, int validUdfCount, bool expectErrors) + { + var parserOptions = new ParserOptions() + { + AllowsSideEffects = true + }; + + var parseResult = UserDefinitions.Parse(script, parserOptions); + + Assert.Equal(udfCount, parseResult.UDFs.Count()); + Assert.Equal(validUdfCount, parseResult.UDFs.Where(udf => udf.IsParseValid).Count()); + Assert.Equal(expectErrors, parseResult.HasErrors); } } } diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/PowerFxTest.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/PowerFxTest.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/PowerFxTest.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/PowerFxTest.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/PrimitiveValueConversionsTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/PrimitiveValueConversionsTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/PrimitiveValueConversionsTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/PrimitiveValueConversionsTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/Properties/Resources.Designer.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/Properties/Resources.Designer.cs similarity index 98% rename from src/tests/Microsoft.PowerFx.Core.Tests/Properties/Resources.Designer.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/Properties/Resources.Designer.cs index 0e1bd00bd..9311ca3dc 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests/Properties/Resources.Designer.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/Properties/Resources.Designer.cs @@ -8,7 +8,8 @@ // //------------------------------------------------------------------------------ -namespace Microsoft.PowerFx.Core.Tests.Properties { +namespace Microsoft.PowerFx.Core.Tests.Properties +{ using System; diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/Properties/Resources.resx b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/Properties/Resources.resx similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/Properties/Resources.resx rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/Properties/Resources.resx diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/PublicSurfaceTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/PublicSurfaceTests.cs similarity index 99% rename from src/tests/Microsoft.PowerFx.Core.Tests/PublicSurfaceTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/PublicSurfaceTests.cs index 6f17f7e7e..1a24664db 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests/PublicSurfaceTests.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/PublicSurfaceTests.cs @@ -28,9 +28,9 @@ namespace Microsoft.PowerFx.Core.Tests var allowed = new HashSet() { // Core namespace. - "Microsoft.PowerFx.CheckResult", - "Microsoft.PowerFx.DefinitionsCheckResult", - "Microsoft.PowerFx.CheckContextSummary", + "Microsoft.PowerFx.CheckResult", + "Microsoft.PowerFx.DefinitionsCheckResult", + "Microsoft.PowerFx.CheckContextSummary", "Microsoft.PowerFx.Core.Parser.ParseFormulasResult", "Microsoft.PowerFx.Engine", "Microsoft.PowerFx.ErrorKind", @@ -38,15 +38,15 @@ namespace Microsoft.PowerFx.Core.Tests "Microsoft.PowerFx.ExpressionError", "Microsoft.PowerFx.Features", "Microsoft.PowerFx.FormulaWithParameters", - "Microsoft.PowerFx.FunctionInfo", + "Microsoft.PowerFx.FunctionInfo", "Microsoft.PowerFx.FunctionInfoSignature", "Microsoft.PowerFx.ParameterInfoSignature", "Microsoft.PowerFx.NameCollisionException", "Microsoft.PowerFx.OptionSet", "Microsoft.PowerFx.ParseResult", - "Microsoft.PowerFx.ParserOptions", - "Microsoft.PowerFx.IPostCheckErrorHandler", - + "Microsoft.PowerFx.ParserOptions", + "Microsoft.PowerFx.IPostCheckErrorHandler", + "Microsoft.PowerFx.EngineDocumentation", // Config & Symbols @@ -54,14 +54,14 @@ namespace Microsoft.PowerFx.Core.Tests "Microsoft.PowerFx.ISymbolSlot", "Microsoft.PowerFx.PowerFxConfig", "Microsoft.PowerFx.ReadOnlySymbolTable", - "Microsoft.PowerFx.SymbolTable", - "Microsoft.PowerFx.SymbolProperties", + "Microsoft.PowerFx.SymbolTable", + "Microsoft.PowerFx.SymbolProperties", "Microsoft.PowerFx.SymbolEntry", - "Microsoft.PowerFx.DeferredSymbolPlaceholder", + "Microsoft.PowerFx.DeferredSymbolPlaceholder", // Lexer "Microsoft.PowerFx.Syntax.BinaryOp", - "Microsoft.PowerFx.Syntax.CommentToken", + "Microsoft.PowerFx.Syntax.CommentToken", "Microsoft.PowerFx.Syntax.DecLitToken", "Microsoft.PowerFx.Syntax.ErrorToken", "Microsoft.PowerFx.Syntax.IdentToken", @@ -71,7 +71,7 @@ namespace Microsoft.PowerFx.Core.Tests "Microsoft.PowerFx.Syntax.Token", "Microsoft.PowerFx.Syntax.TokKind", "Microsoft.PowerFx.Syntax.UnaryOp", - "Microsoft.PowerFx.Syntax.VariadicOp", + "Microsoft.PowerFx.Syntax.VariadicOp", "Microsoft.PowerFx.Syntax.ITextFirstFlag", // Parse nodes @@ -79,7 +79,7 @@ namespace Microsoft.PowerFx.Core.Tests "Microsoft.PowerFx.Syntax.BinaryOpNode", "Microsoft.PowerFx.Syntax.BlankNode", "Microsoft.PowerFx.Syntax.BoolLitNode", - "Microsoft.PowerFx.Syntax.CallNode", + "Microsoft.PowerFx.Syntax.CallNode", "Microsoft.PowerFx.Syntax.DecLitNode", "Microsoft.PowerFx.Syntax.DottedNameNode", "Microsoft.PowerFx.Syntax.ErrorNode", @@ -98,7 +98,7 @@ namespace Microsoft.PowerFx.Core.Tests "Microsoft.PowerFx.Syntax.TexlNode", "Microsoft.PowerFx.Syntax.UnaryOpNode", "Microsoft.PowerFx.Syntax.VariadicBase", - "Microsoft.PowerFx.Syntax.VariadicOpNode", + "Microsoft.PowerFx.Syntax.VariadicOpNode", "Microsoft.PowerFx.Syntax.TypeLiteralNode", // Visitors @@ -110,8 +110,8 @@ namespace Microsoft.PowerFx.Core.Tests "Microsoft.PowerFx.Types.AggregateType", "Microsoft.PowerFx.Types.BindingErrorType", "Microsoft.PowerFx.Types.BlankType", - "Microsoft.PowerFx.Types.BlankValue", - "Microsoft.PowerFx.Types.BlobType", + "Microsoft.PowerFx.Types.BlankValue", + "Microsoft.PowerFx.Types.BlobType", "Microsoft.PowerFx.Types.BlobValue", "Microsoft.PowerFx.Types.BlobContent", "Microsoft.PowerFx.Types.BooleanType", @@ -123,11 +123,11 @@ namespace Microsoft.PowerFx.Core.Tests "Microsoft.PowerFx.Types.DateTimeType", "Microsoft.PowerFx.Types.DateTimeValue", "Microsoft.PowerFx.Types.DateType", - "Microsoft.PowerFx.Types.DateValue", + "Microsoft.PowerFx.Types.DateValue", "Microsoft.PowerFx.Types.DecimalType", "Microsoft.PowerFx.Types.DecimalValue", - "Microsoft.PowerFx.Types.DeferredType", - "Microsoft.PowerFx.Types.DelegationParameters", + "Microsoft.PowerFx.Types.DeferredType", + "Microsoft.PowerFx.Types.DelegationParameters", "Microsoft.PowerFx.Types.DelegationParameterFeatures", "Microsoft.PowerFx.Types.DValue`1", "Microsoft.PowerFx.Types.ErrorValue", @@ -137,7 +137,7 @@ namespace Microsoft.PowerFx.Core.Tests "Microsoft.PowerFx.Types.FormulaValue", "Microsoft.PowerFx.Types.GuidType", "Microsoft.PowerFx.Types.GuidValue", - "Microsoft.PowerFx.Types.HyperlinkType", + "Microsoft.PowerFx.Types.HyperlinkType", "Microsoft.PowerFx.Types.IDelegatableTableValue", "Microsoft.PowerFx.Types.ITypeVisitor", "Microsoft.PowerFx.Types.IUntypedObject", @@ -151,7 +151,7 @@ namespace Microsoft.PowerFx.Core.Tests "Microsoft.PowerFx.Types.PrimitiveValue`1", "Microsoft.PowerFx.Types.PrimitiveValueConversions", "Microsoft.PowerFx.Types.RecordType", - "Microsoft.PowerFx.Types.RecordValue", + "Microsoft.PowerFx.Types.RecordValue", "Microsoft.PowerFx.Types.SpecialFieldKind", "Microsoft.PowerFx.Types.StringType", "Microsoft.PowerFx.Types.StringValue", @@ -171,7 +171,7 @@ namespace Microsoft.PowerFx.Core.Tests // Most evaluators should never need these. "Microsoft.PowerFx.Intellisense.CodeFixHandler", "Microsoft.PowerFx.Intellisense.CodeFixSuggestion", - "Microsoft.PowerFx.Intellisense.ConnectorSuggestion", + "Microsoft.PowerFx.Intellisense.ConnectorSuggestion", "Microsoft.PowerFx.Intellisense.ConnectorSuggestions", "Microsoft.PowerFx.Intellisense.IIntellisenseResult", "Microsoft.PowerFx.Intellisense.IIntellisenseSuggestion", @@ -181,25 +181,25 @@ namespace Microsoft.PowerFx.Core.Tests "Microsoft.PowerFx.Intellisense.SignatureHelp.SignatureHelp", "Microsoft.PowerFx.Intellisense.SignatureHelp.SignatureInformation", "Microsoft.PowerFx.Intellisense.SuggestionIconKind", - "Microsoft.PowerFx.Intellisense.SuggestionKind", + "Microsoft.PowerFx.Intellisense.SuggestionKind", "Microsoft.PowerFx.Intellisense.TokenResultType", - "Microsoft.PowerFx.Intellisense.UIString", - "Microsoft.PowerFx.Intellisense.MarkdownString", - "Microsoft.PowerFx.Core.Texl.Intellisense.TokenTextSpan", + "Microsoft.PowerFx.Intellisense.UIString", + "Microsoft.PowerFx.Intellisense.MarkdownString", + "Microsoft.PowerFx.Core.Texl.Intellisense.TokenTextSpan", "Microsoft.PowerFx.Core.Texl.Intellisense.TokenType", // TBD ... "Microsoft.PowerFx.BasicUserInfo", "Microsoft.PowerFx.Core.DisplayNameProvider", "Microsoft.PowerFx.Core.DisplayNameUtility", - "Microsoft.PowerFx.Core.Entities.IRefreshable", + "Microsoft.PowerFx.Core.Entities.IRefreshable", "Microsoft.PowerFx.Core.Localization.ErrorResourceKey", "Microsoft.PowerFx.Core.RenameDriver", "Microsoft.PowerFx.Core.Utils.DName", "Microsoft.PowerFx.Core.Utils.DPath", - "Microsoft.PowerFx.Core.Utils.ICheckable", - "Microsoft.PowerFx.UserInfo", - "Microsoft.PowerFx.Logging.ITracer", + "Microsoft.PowerFx.Core.Utils.ICheckable", + "Microsoft.PowerFx.UserInfo", + "Microsoft.PowerFx.Logging.ITracer", "Microsoft.PowerFx.Logging.TraceSeverity" }; diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ResourceValidationTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ResourceValidationTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ResourceValidationTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ResourceValidationTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/SlotMapTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/SlotMapTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/SlotMapTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/SlotMapTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/SymbolTableTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/SymbolTableTests.cs similarity index 99% rename from src/tests/Microsoft.PowerFx.Core.Tests/SymbolTableTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/SymbolTableTests.cs index 802fa759c..300e847e0 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests/SymbolTableTests.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/SymbolTableTests.cs @@ -193,7 +193,7 @@ namespace Microsoft.PowerFx.Core.Tests var symbolTableOriginal = new Engine(new PowerFxConfig()).SupportedFunctions; var symbolTableCopy1 = symbolTableOriginal.GetMutableCopyOfFunctions(); var symbolTableCopy2 = symbolTableOriginal.GetMutableCopyOfFunctions(); - var symbolTableCopy3 = symbolTableOriginal.GetMutableCopyOfFunctions(); + var symbolTableCopy3 = symbolTableOriginal.GetMutableCopyOfFunctions(); var originalCount = symbolTableOriginal.Functions.Count(); var copyCount1 = symbolTableCopy1.Functions.Count(); @@ -215,12 +215,12 @@ namespace Microsoft.PowerFx.Core.Tests Assert.NotEqual(copyCount1, symbolTableCopy1.Functions.Count()); Assert.NotEqual(copyCount2, symbolTableCopy2.Functions.Count()); - Assert.NotEqual(copyCount3, symbolTableCopy3.Functions.Count()); - + Assert.NotEqual(copyCount3, symbolTableCopy3.Functions.Count()); + Assert.True(symbolTableOriginal.Functions.AnyWithName("Abs")); Assert.True(symbolTableOriginal.Functions.AnyWithName("Day")); Assert.True(symbolTableOriginal.Functions.AnyWithName("Text")); - Assert.True(symbolTableOriginal.Functions.AnyWithName("Cos")); + Assert.True(symbolTableOriginal.Functions.AnyWithName("Cos")); Assert.True(symbolTableCopy1.Functions.AnyWithName("Day")); Assert.True(symbolTableCopy1.Functions.AnyWithName("Text")); @@ -228,14 +228,14 @@ namespace Microsoft.PowerFx.Core.Tests Assert.True(symbolTableCopy2.Functions.AnyWithName("Abs")); Assert.True(symbolTableCopy2.Functions.AnyWithName("Text")); - Assert.True(symbolTableCopy2.Functions.AnyWithName("Cos")); - - Assert.True(symbolTableCopy3.Functions.AnyWithName("Abs")); + Assert.True(symbolTableCopy2.Functions.AnyWithName("Cos")); + + Assert.True(symbolTableCopy3.Functions.AnyWithName("Abs")); Assert.True(symbolTableCopy3.Functions.AnyWithName("Day")); Assert.True(symbolTableCopy3.Functions.AnyWithName("Text")); Assert.False(symbolTableCopy1.Functions.AnyWithName("Abs")); - Assert.False(symbolTableCopy2.Functions.AnyWithName("Day")); + Assert.False(symbolTableCopy2.Functions.AnyWithName("Day")); Assert.False(symbolTableCopy3.Functions.AnyWithName("Cos")); // Check if nothing else has been copied @@ -311,107 +311,107 @@ namespace Microsoft.PowerFx.Core.Tests { var symbol = new SymbolTable(); Assert.Throws(() => symbol.AddVariable("x", FormulaType.Void, mutable: true)); - } - - // $$$ Consistent with SymbolNames - - [Fact] - public void TryGetType() - { - var os = new OptionSet( - "os1", - DisplayNameUtility.MakeUnique(new Dictionary { { "Yes", "Yes1" }, { "No", "No1" } })); - - PowerFxConfig config = new PowerFxConfig(); - - bool fOk = config.SymbolTable.TryGetSymbolType("os1", out var type); - Assert.False(fOk); - - config.AddOptionSet(os); - - fOk = config.SymbolTable.TryGetSymbolType("os1", out type); - Assert.True(fOk); - - AssertOptionSetType(type, os); - - // Case sensitivity - fOk = config.SymbolTable.TryGetSymbolType("OS1", out type); - Assert.False(fOk); // case sensitive - - // Consistent with SymbolNames - var names = config.SymbolTable.SymbolNames.ToArray(); - Assert.Single(names); - var name0 = names[0]; - Assert.Equal("os1", name0.Name); - Assert.Equal(string.Empty, name0.DisplayName); - AssertOptionSetType(name0.Type, os); - - // Composed tables. - var st1 = new SymbolTable(); - var st2 = ReadOnlySymbolTable.Compose(st1, config.SymbolTable); - - fOk = st1.TryGetSymbolType("os1", out type); - Assert.False(fOk); - - fOk = st2.TryGetSymbolType("os1", out type); - Assert.True(fOk); - } - - // Reads should be thread safe. - [Fact] - public void Threading() - { - int nLoops = 100; - - for (int i = 0; i < nLoops; i++) - { - // Create complex tree of composed tables. - var composed1a = new ComposedReadOnlySymbolTable(new SymbolTable(), new SymbolTable(), new SymbolTable(), new SymbolTable()); - var composed1b = new ComposedReadOnlySymbolTable(new SymbolTable(), new SymbolTable(), new SymbolTable(), new SymbolTable()); - - var composed1 = new ComposedReadOnlySymbolTable(composed1a, composed1b); - var composed2 = new ComposedReadOnlySymbolTable(new SymbolTable(), new SymbolTable(), new SymbolTable(), new SymbolTable()); - - var composed = new ComposedReadOnlySymbolTable(composed1, composed2, new SymbolTable(), new SymbolTable()); - - var engine = new Engine(); - - // Reads should be thread-safe. - Parallel.For( - 0, - 2, - (i) => - { - Assert.True(engine.Check("Sum(1)", symbolTable: composed).IsSuccess); - }); - } - } - - // Type is wrong: https://github.com/microsoft/Power-Fx/issues/2342 - // Option Set returns as a record. - private void AssertOptionSetType(FormulaType actualType, OptionSet expected) - { - RecordType recordType = (RecordType)actualType; - var actualNames = recordType.FieldNames.OrderBy(x => x).ToArray(); - - var expectedNames = expected.Options.Select(kv => kv.Key.Value).OrderBy(x => x).ToArray(); - - Assert.True(actualNames.SequenceEqual(expectedNames)); - } - - // IsDefined also maps display names. - [Fact] - public void TryGetTypeFindsDisplayNames() - { - var st = new SymbolTable(); - st.AddVariable("var1", FormulaType.Number, displayName: "Display1"); - - var ok = st.TryGetSymbolType("var1", out var type); - Assert.True(ok); - - // not display names - ok = st.TryGetSymbolType("Display1", out type); - Assert.True(ok); - } + } + + // $$$ Consistent with SymbolNames + + [Fact] + public void TryGetType() + { + var os = new OptionSet( + "os1", + DisplayNameUtility.MakeUnique(new Dictionary { { "Yes", "Yes1" }, { "No", "No1" } })); + + PowerFxConfig config = new PowerFxConfig(); + + bool fOk = config.SymbolTable.TryGetSymbolType("os1", out var type); + Assert.False(fOk); + + config.AddOptionSet(os); + + fOk = config.SymbolTable.TryGetSymbolType("os1", out type); + Assert.True(fOk); + + AssertOptionSetType(type, os); + + // Case sensitivity + fOk = config.SymbolTable.TryGetSymbolType("OS1", out type); + Assert.False(fOk); // case sensitive + + // Consistent with SymbolNames + var names = config.SymbolTable.SymbolNames.ToArray(); + Assert.Single(names); + var name0 = names[0]; + Assert.Equal("os1", name0.Name); + Assert.Equal(string.Empty, name0.DisplayName); + AssertOptionSetType(name0.Type, os); + + // Composed tables. + var st1 = new SymbolTable(); + var st2 = ReadOnlySymbolTable.Compose(st1, config.SymbolTable); + + fOk = st1.TryGetSymbolType("os1", out type); + Assert.False(fOk); + + fOk = st2.TryGetSymbolType("os1", out type); + Assert.True(fOk); + } + + // Reads should be thread safe. + [Fact] + public void Threading() + { + int nLoops = 100; + + for (int i = 0; i < nLoops; i++) + { + // Create complex tree of composed tables. + var composed1a = new ComposedReadOnlySymbolTable(new SymbolTable(), new SymbolTable(), new SymbolTable(), new SymbolTable()); + var composed1b = new ComposedReadOnlySymbolTable(new SymbolTable(), new SymbolTable(), new SymbolTable(), new SymbolTable()); + + var composed1 = new ComposedReadOnlySymbolTable(composed1a, composed1b); + var composed2 = new ComposedReadOnlySymbolTable(new SymbolTable(), new SymbolTable(), new SymbolTable(), new SymbolTable()); + + var composed = new ComposedReadOnlySymbolTable(composed1, composed2, new SymbolTable(), new SymbolTable()); + + var engine = new Engine(); + + // Reads should be thread-safe. + Parallel.For( + 0, + 2, + (i) => + { + Assert.True(engine.Check("Sum(1)", symbolTable: composed).IsSuccess); + }); + } + } + + // Type is wrong: https://github.com/microsoft/Power-Fx/issues/2342 + // Option Set returns as a record. + private void AssertOptionSetType(FormulaType actualType, OptionSet expected) + { + RecordType recordType = (RecordType)actualType; + var actualNames = recordType.FieldNames.OrderBy(x => x).ToArray(); + + var expectedNames = expected.Options.Select(kv => kv.Key.Value).OrderBy(x => x).ToArray(); + + Assert.True(actualNames.SequenceEqual(expectedNames)); + } + + // IsDefined also maps display names. + [Fact] + public void TryGetTypeFindsDisplayNames() + { + var st = new SymbolTable(); + st.AddVariable("var1", FormulaType.Number, displayName: "Display1"); + + var ok = st.TryGetSymbolType("var1", out var type); + Assert.True(ok); + + // not display names + ok = st.TryGetSymbolType("Display1", out type); + Assert.True(ok); + } } } diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/TableValueMutationTest.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/TableValueMutationTest.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/TableValueMutationTest.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/TableValueMutationTest.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/TestRunnerTests/Bad1.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/TestRunnerTests/Bad1.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/TestRunnerTests/Bad1.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/TestRunnerTests/Bad1.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/TestRunnerTests/Bad2.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/TestRunnerTests/Bad2.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/TestRunnerTests/Bad2.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/TestRunnerTests/Bad2.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/TestRunnerTests/Bad3.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/TestRunnerTests/Bad3.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/TestRunnerTests/Bad3.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/TestRunnerTests/Bad3.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/TestRunnerTests/File1.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/TestRunnerTests/File1.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/TestRunnerTests/File1.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/TestRunnerTests/File1.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/TestRunnerTests/File2.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/TestRunnerTests/File2.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/TestRunnerTests/File2.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/TestRunnerTests/File2.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/TestRunnerTests/FileDisable.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/TestRunnerTests/FileDisable.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/TestRunnerTests/FileDisable.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/TestRunnerTests/FileDisable.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/TestRunnerTests/FileOverride.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/TestRunnerTests/FileOverride.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/TestRunnerTests/FileOverride.txt rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/TestRunnerTests/FileOverride.txt diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/TestRunnerTests/InternalSetup.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/TestRunnerTests/InternalSetup.cs similarity index 97% rename from src/tests/Microsoft.PowerFx.Core.Tests/TestRunnerTests/InternalSetup.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/TestRunnerTests/InternalSetup.cs index aa1e10072..e93c93872 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests/TestRunnerTests/InternalSetup.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/TestRunnerTests/InternalSetup.cs @@ -1,120 +1,120 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text.RegularExpressions; -using Microsoft.PowerFx.Core.Parser; - -namespace Microsoft.PowerFx.Core.Tests -{ - internal class InternalSetup - { - internal List HandlerNames { get; set; } - - internal TexlParser.Flags Flags { get; set; } - - internal Features Features { get; set; } - - internal TimeZoneInfo TimeZoneInfo { get; set; } - - /// - /// By default, we run expressions with a memory governor to enforce a limited amount of memory. - /// When true, disable memory checks and allow expression to use as much memory as it needs. - /// - internal bool DisableMemoryChecks { get; set; } - - private static bool TryGetFeaturesProperty(string featureName, out PropertyInfo propertyInfo) - { - propertyInfo = typeof(Features).GetProperty(featureName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); - return propertyInfo?.CanWrite == true; - } - - internal static InternalSetup Parse(string setupHandlerName, bool numberIsFloat = false) - { - return Parse(setupHandlerName, new Features(), numberIsFloat); - } - - internal static InternalSetup Parse(string setupHandlerName, Features features, bool numberIsFloat = false) - { - var iSetup = new InternalSetup { Features = features }; - - if (numberIsFloat) - { - iSetup.Flags |= TexlParser.Flags.NumberIsFloat; - } - - if (string.IsNullOrWhiteSpace(setupHandlerName)) - { - return iSetup; - } - - var parts = setupHandlerName.Split(",").Select(x => x.Trim()).Where(x => !string.IsNullOrEmpty(x)).ToList(); - - foreach (var part in parts.ToArray()) - { - var isDisable = false; - var partName = part; - if (part.StartsWith("disable:", StringComparison.OrdinalIgnoreCase)) - { - isDisable = true; - partName = part.Substring("disable:".Length); - } - - if (string.Equals(part, "DisableMemChecks", StringComparison.OrdinalIgnoreCase)) - { - iSetup.DisableMemoryChecks = true; - parts.Remove(part); - } - else if (Enum.TryParse(partName, out var flag)) - { - if (isDisable) - { - iSetup.Flags &= ~flag; - } - else - { - iSetup.Flags |= flag; - } - - parts.Remove(part); - } - else if (TryGetFeaturesProperty(partName, out var prop)) - { - if (isDisable) - { - prop.SetValue(iSetup.Features, false); - } - else - { - prop.SetValue(iSetup.Features, true); - } - - parts.Remove(part); - } - else if (part.StartsWith("TimeZoneInfo", StringComparison.OrdinalIgnoreCase)) - { - var m = new Regex(@"TimeZoneInfo\(""(?[^)]+)""\)", RegexOptions.IgnoreCase).Match(part); - - if (m.Success) - { - var tz = m.Groups["tz"].Value; - - // This call will throw if the Id in invalid - iSetup.TimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(tz); - parts.Remove(part); - } - else - { - throw new ArgumentException("Invalid TimeZoneInfo setup!"); - } - } - } - - iSetup.HandlerNames = parts; - return iSetup; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; +using Microsoft.PowerFx.Core.Parser; + +namespace Microsoft.PowerFx.Core.Tests +{ + internal class InternalSetup + { + internal List HandlerNames { get; set; } + + internal TexlParser.Flags Flags { get; set; } + + internal Features Features { get; set; } + + internal TimeZoneInfo TimeZoneInfo { get; set; } + + /// + /// By default, we run expressions with a memory governor to enforce a limited amount of memory. + /// When true, disable memory checks and allow expression to use as much memory as it needs. + /// + internal bool DisableMemoryChecks { get; set; } + + private static bool TryGetFeaturesProperty(string featureName, out PropertyInfo propertyInfo) + { + propertyInfo = typeof(Features).GetProperty(featureName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + return propertyInfo?.CanWrite == true; + } + + internal static InternalSetup Parse(string setupHandlerName, bool numberIsFloat = false) + { + return Parse(setupHandlerName, new Features(), numberIsFloat); + } + + internal static InternalSetup Parse(string setupHandlerName, Features features, bool numberIsFloat = false) + { + var iSetup = new InternalSetup { Features = features }; + + if (numberIsFloat) + { + iSetup.Flags |= TexlParser.Flags.NumberIsFloat; + } + + if (string.IsNullOrWhiteSpace(setupHandlerName)) + { + return iSetup; + } + + var parts = setupHandlerName.Split(",").Select(x => x.Trim()).Where(x => !string.IsNullOrEmpty(x)).ToList(); + + foreach (var part in parts.ToArray()) + { + var isDisable = false; + var partName = part; + if (part.StartsWith("disable:", StringComparison.OrdinalIgnoreCase)) + { + isDisable = true; + partName = part.Substring("disable:".Length); + } + + if (string.Equals(part, "DisableMemChecks", StringComparison.OrdinalIgnoreCase)) + { + iSetup.DisableMemoryChecks = true; + parts.Remove(part); + } + else if (Enum.TryParse(partName, out var flag)) + { + if (isDisable) + { + iSetup.Flags &= ~flag; + } + else + { + iSetup.Flags |= flag; + } + + parts.Remove(part); + } + else if (TryGetFeaturesProperty(partName, out var prop)) + { + if (isDisable) + { + prop.SetValue(iSetup.Features, false); + } + else + { + prop.SetValue(iSetup.Features, true); + } + + parts.Remove(part); + } + else if (part.StartsWith("TimeZoneInfo", StringComparison.OrdinalIgnoreCase)) + { + var m = new Regex(@"TimeZoneInfo\(""(?[^)]+)""\)", RegexOptions.IgnoreCase).Match(part); + + if (m.Success) + { + var tz = m.Groups["tz"].Value; + + // This call will throw if the Id in invalid + iSetup.TimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(tz); + parts.Remove(part); + } + else + { + throw new ArgumentException("Invalid TimeZoneInfo setup!"); + } + } + } + + iSetup.HandlerNames = parts; + return iSetup; + } + } +} diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/TestRunnerTests/InternalSetupTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/TestRunnerTests/InternalSetupTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/TestRunnerTests/InternalSetupTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/TestRunnerTests/InternalSetupTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/TestRunnerTests/TestRunnerTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/TestRunnerTests/TestRunnerTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/TestRunnerTests/TestRunnerTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/TestRunnerTests/TestRunnerTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/TexlTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/TexlTests.cs similarity index 99% rename from src/tests/Microsoft.PowerFx.Core.Tests/TexlTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/TexlTests.cs index e5859bf25..4f58a509a 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests/TexlTests.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/TexlTests.cs @@ -14,7 +14,7 @@ using Microsoft.PowerFx.Core.Entities; using Microsoft.PowerFx.Core.Functions; using Microsoft.PowerFx.Core.Functions.Delegation; using Microsoft.PowerFx.Core.Functions.Delegation.DelegationMetadata; -using Microsoft.PowerFx.Core.Glue; +using Microsoft.PowerFx.Core.Glue; using Microsoft.PowerFx.Core.Localization; using Microsoft.PowerFx.Core.Parser; using Microsoft.PowerFx.Core.Tests.Helpers; @@ -24,7 +24,7 @@ using Microsoft.PowerFx.Core.Types; using Microsoft.PowerFx.Core.Utils; using Microsoft.PowerFx.Syntax; using Microsoft.PowerFx.Tests; -using Microsoft.PowerFx.Types; +using Microsoft.PowerFx.Types; using Xunit; namespace Microsoft.PowerFx.Core.Tests @@ -125,29 +125,29 @@ namespace Microsoft.PowerFx.Core.Tests [InlineData("DateDiff([Date(2000,1,1)],Date(2001,1,1),\"years\")", "*[Result:n]")] public void TexlDateTableFunctions_Float(string expression, string expectedType) { - var engine = new Engine(new PowerFxConfig(Features.None)); - var options = new ParserOptions() { NumberIsFloat = true }; + var engine = new Engine(new PowerFxConfig(Features.None)); + var options = new ParserOptions() { NumberIsFloat = true }; var result = engine.Check(expression, options); Assert.True(DType.TryParse(expectedType, out var expectedDType)); Assert.Equal(expectedDType, result.Binding.ResultType); Assert.True(result.IsSuccess); - } - + } + [Theory] [InlineData("DateDiff([Date(2000,1,1)],[Date(2001,1,1)],\"years\")", "*[Result:w]")] [InlineData("DateDiff(Date(2000,1,1),[Date(2001,1,1)],\"years\")", "*[Result:w]")] [InlineData("DateDiff([Date(2000,1,1)],Date(2001,1,1),\"years\")", "*[Result:w]")] public void TexlDateTableFunctions(string expression, string expectedType) { - var engine = new Engine(new PowerFxConfig(Features.None)); + var engine = new Engine(new PowerFxConfig(Features.None)); var result = engine.Check(expression); Assert.True(DType.TryParse(expectedType, out var expectedDType)); Assert.Equal(expectedDType, result.Binding.ResultType); Assert.True(result.IsSuccess); - } - + } + [Theory] [InlineData("DateAdd([Date(2000,1,1)],1)", "*[Value:D]")] [InlineData("DateAdd([Date(2000,1,1)],[3])", "*[Value:D]")] @@ -161,7 +161,7 @@ namespace Microsoft.PowerFx.Core.Tests [InlineData("DateDiff([Date(2000,1,1)],Date(2001,1,1),\"years\")", "*[Value:w]")] public void TexlDateTableFunctions_ConsistentOneColumnTableResult(string expression, string expectedType) { - var engine = new Engine(new PowerFxConfig(new Features { ConsistentOneColumnTableResult = true })); + var engine = new Engine(new PowerFxConfig(new Features { ConsistentOneColumnTableResult = true })); var options = new ParserOptions() { NumberIsFloat = false }; var result = engine.Check(expression, options); @@ -203,322 +203,322 @@ namespace Microsoft.PowerFx.Core.Tests var result = engine.Check(expression); Assert.False(result.IsSuccess); - } - - [Theory] - [InlineData("DropColumns([{A:\"hello\",B:1}], A)", "*[B:n]")] - [InlineData("DropColumns([{A:\"hello\",B:1}], B)", "*[A:s]")] - [InlineData("DropColumns([{A:\"hello\",B:1}], A, B)", "*[]")] - [InlineData("DropColumns({A:\"hello\",B:1}, A)", "![B:n]")] - [InlineData("DropColumns({A:\"hello\",B:1}, B)", "![A:s]")] - [InlineData("DropColumns({A:\"hello\",B:1}, A, B)", "![]")] - public void TexlFunctionTypeSemanticsDropColumns(string expression, string expectedType) - { + } + + [Theory] + [InlineData("DropColumns([{A:\"hello\",B:1}], A)", "*[B:n]")] + [InlineData("DropColumns([{A:\"hello\",B:1}], B)", "*[A:s]")] + [InlineData("DropColumns([{A:\"hello\",B:1}], A, B)", "*[]")] + [InlineData("DropColumns({A:\"hello\",B:1}, A)", "![B:n]")] + [InlineData("DropColumns({A:\"hello\",B:1}, B)", "![A:s]")] + [InlineData("DropColumns({A:\"hello\",B:1}, A, B)", "![]")] + public void TexlFunctionTypeSemanticsDropColumns(string expression, string expectedType) + { var engine = new Engine(new PowerFxConfig(Features.PowerFxV1)); var options = new ParserOptions() { NumberIsFloat = true }; var result = engine.Check(expression, options); Assert.True(DType.TryParse(expectedType, out var expectedDType)); Assert.Equal(expectedDType, result.Binding.ResultType); - Assert.True(result.IsSuccess); + Assert.True(result.IsSuccess); } - - [Theory] - [InlineData("DropColumns([{A:\"hello\",B:1}], \"A\")", "*[B:n]")] - [InlineData("DropColumns([{A:\"hello\",B:1}], \"B\")", "*[A:s]")] - [InlineData("DropColumns([{A:\"hello\",B:1}], \"A\", \"B\")", "*[]")] - [InlineData("DropColumns({A:\"hello\",B:1}, \"A\")", "![B:n]")] - [InlineData("DropColumns({A:\"hello\",B:1}, \"B\")", "![A:s]")] - [InlineData("DropColumns({A:\"hello\",B:1}, \"A\", \"B\")", "![]")] - public void TexlFunctionTypeSemanticsDropColumns_ColumnNamesAsIdentifiersDisabled(string expression, string expectedType) - { + + [Theory] + [InlineData("DropColumns([{A:\"hello\",B:1}], \"A\")", "*[B:n]")] + [InlineData("DropColumns([{A:\"hello\",B:1}], \"B\")", "*[A:s]")] + [InlineData("DropColumns([{A:\"hello\",B:1}], \"A\", \"B\")", "*[]")] + [InlineData("DropColumns({A:\"hello\",B:1}, \"A\")", "![B:n]")] + [InlineData("DropColumns({A:\"hello\",B:1}, \"B\")", "![A:s]")] + [InlineData("DropColumns({A:\"hello\",B:1}, \"A\", \"B\")", "![]")] + public void TexlFunctionTypeSemanticsDropColumns_ColumnNamesAsIdentifiersDisabled(string expression, string expectedType) + { var engine = new Engine(new PowerFxConfig(new Features { SupportColumnNamesAsIdentifiers = false, TableSyntaxDoesntWrapRecords = true })); var options = new ParserOptions() { NumberIsFloat = true }; var result = engine.Check(expression, options); Assert.True(DType.TryParse(expectedType, out var expectedDType)); Assert.Equal(expectedDType, result.Binding.ResultType); - Assert.True(result.IsSuccess); + Assert.True(result.IsSuccess); } - - [Theory] - [InlineData("DropColumns([{A:\"hello\",B:1}], C)")] - [InlineData("DropColumns([{A:\"hello\",B:1}], AB)")] - [InlineData("DropColumns([{A:\"hello\",B:1}], A1)")] - [InlineData("DropColumns({A:\"hello\",B:1}, C)")] - [InlineData("DropColumns({A:\"hello\",B:1}, AB)")] - [InlineData("DropColumns({A:\"hello\",B:1}, A1)")] - [InlineData("DropColumns([{A:\"hello\",B:1}], \"A\")")] - public void TexlFunctionTypeSemanticsDropColumns_Negative(string expression) - { + + [Theory] + [InlineData("DropColumns([{A:\"hello\",B:1}], C)")] + [InlineData("DropColumns([{A:\"hello\",B:1}], AB)")] + [InlineData("DropColumns([{A:\"hello\",B:1}], A1)")] + [InlineData("DropColumns({A:\"hello\",B:1}, C)")] + [InlineData("DropColumns({A:\"hello\",B:1}, AB)")] + [InlineData("DropColumns({A:\"hello\",B:1}, A1)")] + [InlineData("DropColumns([{A:\"hello\",B:1}], \"A\")")] + public void TexlFunctionTypeSemanticsDropColumns_Negative(string expression) + { var engine = new Engine(new PowerFxConfig()); var result = engine.Check(expression); Assert.False(result.IsSuccess); - } - - [Theory] - [InlineData("ShowColumns([{A:\"hello\",B:1}], A)", "*[A:s]")] - [InlineData("ShowColumns([{A:\"hello\",B:1}], B)", "*[B:n]")] - [InlineData("ShowColumns([{A:\"hello\",B:1}], A, B)", "*[A:s,B:n]")] - [InlineData("ShowColumns({A:\"hello\",B:1}, A)", "![A:s]")] - [InlineData("ShowColumns({A:\"hello\",B:1}, B)", "![B:n]")] - [InlineData("ShowColumns({A:\"hello\",B:1}, A, B)", "![A:s,B:n]")] - public void TexlFunctionTypeSemanticsShowColumns(string expression, string expectedType) - { - var engine = new Engine(new PowerFxConfig(Features.PowerFxV1)); - var options = new ParserOptions() { NumberIsFloat = true }; - var result = engine.Check(expression, options); - - Assert.True(DType.TryParse(expectedType, out var expectedDType)); - Assert.Equal(expectedDType, result.Binding.ResultType); - Assert.True(result.IsSuccess); - } - - private static TableType CreateTableTypeWithDisplayNames() - { - var myTableType = DType.EmptyTable - .Add(new DName("LogicalName1"), DType.String) - .Add(new DName("LogicalName2"), DType.Number); - - var myDisplayNameProvider = DisplayNameProvider.New(new Dictionary - { - { new DName("LogicalName1"), new DName("Display Name 1") }, - { new DName("LogicalName2"), new DName("Display Name 2") }, - }); - - var myTableTypeWithDisplayNames = DType.AttachOrDisableDisplayNameProvider(myTableType, myDisplayNameProvider); - return new TableType(myTableTypeWithDisplayNames); - } - - [Theory] - [InlineData("ForAll( ShowColumns( MyTable, 'Display Name 2' ), { a: 'Display Name 2' } )", "*[a:n]")] - [InlineData("ForAll( ShowColumns( MyTable, 'Display Name 1', 'Display Name 2' ), { a: 'Display Name 1', b: 'Display Name 2' } )", "*[a:s,b:n]")] - [InlineData("ForAll( ShowColumns( MyTable, LogicalName2 ), { a: 'Display Name 2' } )", "*[a:n]")] - [InlineData("ForAll( ShowColumns( MyTable, LogicalName2 ), { a: 'LogicalName2' } )", "*[a:n]")] - [InlineData("ForAll( ShowColumns( MyTable, LogicalName1, 'Display Name 2' ), { a: 'Display Name 1', b: LogicalName2 } )", "*[a:s,b:n]")] - public void TexlFunctionTypeSemanticsShowColumns_DisplayNames(string expression, string expectedType) - { - var symbol = new SymbolTable(); - symbol.AddVariable("MyTable", CreateTableTypeWithDisplayNames()); - - var engine = new Engine(new PowerFxConfig(Features.PowerFxV1) { SymbolTable = symbol }); - var result = engine.Check(expression); - - Assert.True(DType.TryParse(expectedType, out var expectedDType)); - Assert.Equal(expectedDType, result.Binding.ResultType); - Assert.True(result.IsSuccess); - } - - [Theory] - [InlineData( - "With({'Display Name 2':false}, ForAll(ShowColumns(MyTable, 'Display Name 1', 'Display Name 2'), { a:LogicalName1, b:'Display Name 2' }))", - "*[a:s,b:b]")] - public void TexlFunctionTypeSemanticsShowColumns_DisplayNamesNotPropagatedPrePFxV1(string expression, string expectedType) - { - var symbol = new SymbolTable(); - symbol.AddVariable("MyTable", CreateTableTypeWithDisplayNames()); - - var features = new Features(Features.PowerFxV1) { PowerFxV1CompatibilityRules = false }; - var engine = new Engine(new PowerFxConfig(features) { SymbolTable = symbol }); - var result = engine.Check(expression); - - Assert.True(DType.TryParse(expectedType, out var expectedDType)); - Assert.Equal(expectedDType, result.Binding.ResultType); - Assert.True(result.IsSuccess); - } - - [Theory] - [InlineData("ShowColumns([{A:\"hello\",B:1}], \"A\")", "*[A:s]")] - [InlineData("ShowColumns([{A:\"hello\",B:1}], \"B\")", "*[B:n]")] - [InlineData("ShowColumns([{A:\"hello\",B:1}], \"A\", \"B\")", "*[A:s,B:n]")] - [InlineData("ShowColumns({A:\"hello\",B:1}, \"A\")", "![A:s]")] - [InlineData("ShowColumns({A:\"hello\",B:1}, \"B\")", "![B:n]")] - [InlineData("ShowColumns({A:\"hello\",B:1}, \"A\", \"B\")", "![A:s,B:n]")] - public void TexlFunctionTypeSemanticsShowColumns_ColumnNamesAsIdentifiersDisabled(string expression, string expectedType) - { - var engine = new Engine(new PowerFxConfig(new Features { SupportColumnNamesAsIdentifiers = false, TableSyntaxDoesntWrapRecords = true })); - var options = new ParserOptions() { NumberIsFloat = true }; - var result = engine.Check(expression, options); - - Assert.True(DType.TryParse(expectedType, out var expectedDType)); - Assert.Equal(expectedDType, result.Binding.ResultType); - Assert.True(result.IsSuccess); - } - - [Theory] - [InlineData("ShowColumns([{A:\"hello\",B:1}], C)")] - [InlineData("ShowColumns([{A:\"hello\",B:1}], AB)")] - [InlineData("ShowColumns([{A:\"hello\",B:1}], A1)")] - [InlineData("ShowColumns({A:\"hello\",B:1}, C)")] - [InlineData("ShowColumns({A:\"hello\",B:1}, AB)")] - [InlineData("ShowColumns({A:\"hello\",B:1}, A1)")] - [InlineData("ShowColumns([{A:\"hello\",B:1}], \"B\")")] - public void TexlFunctionTypeSemanticsShowColumns_Negative(string expression) - { - var engine = new Engine(new PowerFxConfig()); - var result = engine.Check(expression); - - Assert.False(result.IsSuccess); - } - - [Theory] - [InlineData("RenameColumns([{A:\"hello\",B:1}], A, C)", "*[B:n,C:s]")] - [InlineData("RenameColumns([{A:\"hello\",B:1}], B, C)", "*[A:s,C:n]")] - [InlineData("RenameColumns([{A:\"hello\",B:1}], A, C, B, D)", "*[C:s,D:n]")] - [InlineData("RenameColumns({A:\"hello\",B:1}, A, C)", "![B:n,C:s]")] - [InlineData("RenameColumns({A:\"hello\",B:1}, B, C)", "![A:s,C:n]")] - [InlineData("RenameColumns({A:\"hello\",B:1}, A, C, B, D)", "![C:s,D:n]")] - public void TexlFunctionTypeSemanticsRenameColumns(string expression, string expectedType) - { - var engine = new Engine(new PowerFxConfig(Features.PowerFxV1)); - var options = new ParserOptions() { NumberIsFloat = true }; - var result = engine.Check(expression, options); - - Assert.True(DType.TryParse(expectedType, out var expectedDType)); - Assert.Equal(expectedDType, result.Binding.ResultType); - Assert.True(result.IsSuccess); - } - - [Theory] - [InlineData("RenameColumns([{A:\"hello\",B:1}], \"A\", \"C\")", "*[B:n,C:s]")] - [InlineData("RenameColumns([{A:\"hello\",B:1}], \"B\", \"C\")", "*[A:s,C:n]")] - [InlineData("RenameColumns([{A:\"hello\",B:1}], \"A\", \"C\", \"B\", \"D\")", "*[C:s,D:n]")] - [InlineData("RenameColumns({A:\"hello\",B:1}, \"A\", \"C\")", "![B:n,C:s]")] - [InlineData("RenameColumns({A:\"hello\",B:1}, \"B\", \"C\")", "![A:s,C:n]")] - [InlineData("RenameColumns({A:\"hello\",B:1}, \"A\", \"C\", \"B\", \"D\")", "![C:s,D:n]")] - public void TexlFunctionTypeSemanticsRenameColumns_ColumnNamesAsIdentifiersDisabled(string expression, string expectedType) - { - var engine = new Engine(new PowerFxConfig(new Features { SupportColumnNamesAsIdentifiers = false, TableSyntaxDoesntWrapRecords = true })); - var options = new ParserOptions() { NumberIsFloat = true }; - var result = engine.Check(expression, options); - - Assert.True(DType.TryParse(expectedType, out var expectedDType)); - Assert.Equal(expectedDType, result.Binding.ResultType); - Assert.True(result.IsSuccess); - } - - [Theory] - [InlineData("RenameColumns([{A:\"hello\",B:1}], C, B)")] - [InlineData("RenameColumns([{A:\"hello\",B:1}], AB, B)")] - [InlineData("RenameColumns([{A:\"hello\",B:1}], A1, B)")] - [InlineData("RenameColumns({A:\"hello\",B:1}, C, B)")] - [InlineData("RenameColumns({A:\"hello\",B:1}, AB, B)")] - [InlineData("RenameColumns({A:\"hello\",B:1}, A1, B)")] - [InlineData("RenameColumns([{A:\"hello\",B:1}], \"A\", \"B\")")] - [InlineData("RenameColumns([{A:\"hello\",B:1}], A, A1, A, A2)")] - [InlineData("RenameColumns(true, A, A1)")] - public void TexlFunctionTypeSemanticsRenameColumns_Negative(string expression) - { - var engine = new Engine(new PowerFxConfig()); - var result = engine.Check(expression); - - Assert.False(result.IsSuccess); - } - - [Theory] - [InlineData("IfError(\"Hello\", \"one\")", "s")] - [InlineData("IfError(\"Hello\", 1)", "s")] - [InlineData("IfError(\"Hello\", 1, 3)", "n")] - [InlineData("IfError(\"Hello\", \"one\", \"world\", 2, 1)", "n")] - [InlineData("IfError({a:1}, \"one\", [1,2,3], 2, 1)", "n")] - public void TexlFunctionTypeSemanticsIfError(string expression, string expectedType) - { - var engine = new Engine(new PowerFxConfig()); - var options = new ParserOptions() { NumberIsFloat = true }; - var result = engine.Check(expression, options); - - Assert.True(DType.TryParse(expectedType, out var expectedDType)); - Assert.Equal(expectedDType, result.Binding.ResultType); - Assert.True(result.IsSuccess); - } - - [Theory] - [InlineData("IfError(\"Hello\", {a:\"one\"})")] - [InlineData("IfError(\"Hello\", 1, {a:2})", false, true)] - [InlineData("IfError(\"Hello\", 1, 3, [true])")] - [InlineData("IfError({a:1}, true)")] - [InlineData("IfError(1, [1], true, {a:1}, \"hello\")", false, true)] - [InlineData("IfError(IfError({a:1}, true), true)")] - [InlineData("false; IfError({a:1}, true); true", true)] - [InlineData("IsError(false; IfError({a:1}, true); true)", true)] - public void TexlFunctionTypeSemanticsIfError_MismatchedTypes(string expression, bool usesChain = false, bool preV1Bug = false) - { - foreach (var usePowerFxV1Rules in new[] { false, true }) - { - if (!usePowerFxV1Rules && preV1Bug) - { - // Bug on pre-V1 logic; will not fix for legacy reasons - continue; - } - - foreach (var isBehavior in new[] { false, true }) - { - var features = new Features(Features.PowerFxV1) - { - PowerFxV1CompatibilityRules = usePowerFxV1Rules - }; - - var engine = new Engine(new PowerFxConfig(features)); - var parserOptions = new ParserOptions() { NumberIsFloat = true, AllowsSideEffects = isBehavior }; - var result = engine.Check(expression, parserOptions); - - if (usePowerFxV1Rules && !usesChain) - { - Assert.True(result.IsSuccess); - Assert.Equal(DType.Void, result.Binding.ResultType); - } - else - { - Assert.True(isBehavior == result.IsSuccess, $"For PFxV1={usePowerFxV1Rules} and isBehavior={isBehavior}, expression {expression} should {(isBehavior ? "succeed" : "fail")}"); - } - } - } - } - - [Theory] - [InlineData("Switch(1, 2, true, 3, {}, 4, [])", "-")] // Mismatched return types: Void result - [InlineData("Switch(11;If(true, 123, {}); 1, 2, true, 3, false)", "b")] // Void inside a chain - ok - [InlineData("Switch(1, 2, If(1<2, true, 1<3, []), 3, Color.Blue)", "-")] // Void as one of return types: Void result - public void TexlFunctionTypeSemanticsSwitch_VoidExpressions(string expression, string expectedType) - { - foreach (var usePowerFxV1Rules in new[] { false, true }) - { - var features = new Features(Features.PowerFxV1) - { - PowerFxV1CompatibilityRules = usePowerFxV1Rules - }; - - var engine = new Engine(new PowerFxConfig(features)); - var parserOptions = new ParserOptions() { NumberIsFloat = true, AllowsSideEffects = true }; - var result = engine.Check(expression, parserOptions); - - Assert.True(result.IsSuccess, $"For PFxV1={usePowerFxV1Rules}, expression {expression} should succeed"); - Assert.Equal(TestUtils.DT(expectedType), result.Binding.ResultType); - } } - - [Theory] - [InlineData("Switch(If(true, true, {}), true, 1, 2)")] - [InlineData("Switch(11;22;If(true, 10;20;If(true, 33, 123;{}), false), true, 1, false, 2)")] - [InlineData("Switch(If(1<2, true, 1<3, [], 1<4, {},1<5, Color.Blue), true, true, 1, false, 2)")] - public void TexlFunctionTypeSemanticsSwitch_MismatchedTypes_Negative(string expression) - { - foreach (var usePowerFxV1Rules in new[] { false, true }) - { - var features = new Features(Features.PowerFxV1) - { - PowerFxV1CompatibilityRules = usePowerFxV1Rules - }; - - var engine = new Engine(new PowerFxConfig(features)); - var parserOptions = new ParserOptions() { NumberIsFloat = true, AllowsSideEffects = true }; - var result = engine.Check(expression, parserOptions); - - Assert.False(result.IsSuccess, $"For PFxV1={usePowerFxV1Rules}, expression {expression} should fail"); - } + + [Theory] + [InlineData("ShowColumns([{A:\"hello\",B:1}], A)", "*[A:s]")] + [InlineData("ShowColumns([{A:\"hello\",B:1}], B)", "*[B:n]")] + [InlineData("ShowColumns([{A:\"hello\",B:1}], A, B)", "*[A:s,B:n]")] + [InlineData("ShowColumns({A:\"hello\",B:1}, A)", "![A:s]")] + [InlineData("ShowColumns({A:\"hello\",B:1}, B)", "![B:n]")] + [InlineData("ShowColumns({A:\"hello\",B:1}, A, B)", "![A:s,B:n]")] + public void TexlFunctionTypeSemanticsShowColumns(string expression, string expectedType) + { + var engine = new Engine(new PowerFxConfig(Features.PowerFxV1)); + var options = new ParserOptions() { NumberIsFloat = true }; + var result = engine.Check(expression, options); + + Assert.True(DType.TryParse(expectedType, out var expectedDType)); + Assert.Equal(expectedDType, result.Binding.ResultType); + Assert.True(result.IsSuccess); } - + + private static TableType CreateTableTypeWithDisplayNames() + { + var myTableType = DType.EmptyTable + .Add(new DName("LogicalName1"), DType.String) + .Add(new DName("LogicalName2"), DType.Number); + + var myDisplayNameProvider = DisplayNameProvider.New(new Dictionary + { + { new DName("LogicalName1"), new DName("Display Name 1") }, + { new DName("LogicalName2"), new DName("Display Name 2") }, + }); + + var myTableTypeWithDisplayNames = DType.AttachOrDisableDisplayNameProvider(myTableType, myDisplayNameProvider); + return new TableType(myTableTypeWithDisplayNames); + } + + [Theory] + [InlineData("ForAll( ShowColumns( MyTable, 'Display Name 2' ), { a: 'Display Name 2' } )", "*[a:n]")] + [InlineData("ForAll( ShowColumns( MyTable, 'Display Name 1', 'Display Name 2' ), { a: 'Display Name 1', b: 'Display Name 2' } )", "*[a:s,b:n]")] + [InlineData("ForAll( ShowColumns( MyTable, LogicalName2 ), { a: 'Display Name 2' } )", "*[a:n]")] + [InlineData("ForAll( ShowColumns( MyTable, LogicalName2 ), { a: 'LogicalName2' } )", "*[a:n]")] + [InlineData("ForAll( ShowColumns( MyTable, LogicalName1, 'Display Name 2' ), { a: 'Display Name 1', b: LogicalName2 } )", "*[a:s,b:n]")] + public void TexlFunctionTypeSemanticsShowColumns_DisplayNames(string expression, string expectedType) + { + var symbol = new SymbolTable(); + symbol.AddVariable("MyTable", CreateTableTypeWithDisplayNames()); + + var engine = new Engine(new PowerFxConfig(Features.PowerFxV1) { SymbolTable = symbol }); + var result = engine.Check(expression); + + Assert.True(DType.TryParse(expectedType, out var expectedDType)); + Assert.Equal(expectedDType, result.Binding.ResultType); + Assert.True(result.IsSuccess); + } + + [Theory] + [InlineData( + "With({'Display Name 2':false}, ForAll(ShowColumns(MyTable, 'Display Name 1', 'Display Name 2'), { a:LogicalName1, b:'Display Name 2' }))", + "*[a:s,b:b]")] + public void TexlFunctionTypeSemanticsShowColumns_DisplayNamesNotPropagatedPrePFxV1(string expression, string expectedType) + { + var symbol = new SymbolTable(); + symbol.AddVariable("MyTable", CreateTableTypeWithDisplayNames()); + + var features = new Features(Features.PowerFxV1) { PowerFxV1CompatibilityRules = false }; + var engine = new Engine(new PowerFxConfig(features) { SymbolTable = symbol }); + var result = engine.Check(expression); + + Assert.True(DType.TryParse(expectedType, out var expectedDType)); + Assert.Equal(expectedDType, result.Binding.ResultType); + Assert.True(result.IsSuccess); + } + + [Theory] + [InlineData("ShowColumns([{A:\"hello\",B:1}], \"A\")", "*[A:s]")] + [InlineData("ShowColumns([{A:\"hello\",B:1}], \"B\")", "*[B:n]")] + [InlineData("ShowColumns([{A:\"hello\",B:1}], \"A\", \"B\")", "*[A:s,B:n]")] + [InlineData("ShowColumns({A:\"hello\",B:1}, \"A\")", "![A:s]")] + [InlineData("ShowColumns({A:\"hello\",B:1}, \"B\")", "![B:n]")] + [InlineData("ShowColumns({A:\"hello\",B:1}, \"A\", \"B\")", "![A:s,B:n]")] + public void TexlFunctionTypeSemanticsShowColumns_ColumnNamesAsIdentifiersDisabled(string expression, string expectedType) + { + var engine = new Engine(new PowerFxConfig(new Features { SupportColumnNamesAsIdentifiers = false, TableSyntaxDoesntWrapRecords = true })); + var options = new ParserOptions() { NumberIsFloat = true }; + var result = engine.Check(expression, options); + + Assert.True(DType.TryParse(expectedType, out var expectedDType)); + Assert.Equal(expectedDType, result.Binding.ResultType); + Assert.True(result.IsSuccess); + } + + [Theory] + [InlineData("ShowColumns([{A:\"hello\",B:1}], C)")] + [InlineData("ShowColumns([{A:\"hello\",B:1}], AB)")] + [InlineData("ShowColumns([{A:\"hello\",B:1}], A1)")] + [InlineData("ShowColumns({A:\"hello\",B:1}, C)")] + [InlineData("ShowColumns({A:\"hello\",B:1}, AB)")] + [InlineData("ShowColumns({A:\"hello\",B:1}, A1)")] + [InlineData("ShowColumns([{A:\"hello\",B:1}], \"B\")")] + public void TexlFunctionTypeSemanticsShowColumns_Negative(string expression) + { + var engine = new Engine(new PowerFxConfig()); + var result = engine.Check(expression); + + Assert.False(result.IsSuccess); + } + + [Theory] + [InlineData("RenameColumns([{A:\"hello\",B:1}], A, C)", "*[B:n,C:s]")] + [InlineData("RenameColumns([{A:\"hello\",B:1}], B, C)", "*[A:s,C:n]")] + [InlineData("RenameColumns([{A:\"hello\",B:1}], A, C, B, D)", "*[C:s,D:n]")] + [InlineData("RenameColumns({A:\"hello\",B:1}, A, C)", "![B:n,C:s]")] + [InlineData("RenameColumns({A:\"hello\",B:1}, B, C)", "![A:s,C:n]")] + [InlineData("RenameColumns({A:\"hello\",B:1}, A, C, B, D)", "![C:s,D:n]")] + public void TexlFunctionTypeSemanticsRenameColumns(string expression, string expectedType) + { + var engine = new Engine(new PowerFxConfig(Features.PowerFxV1)); + var options = new ParserOptions() { NumberIsFloat = true }; + var result = engine.Check(expression, options); + + Assert.True(DType.TryParse(expectedType, out var expectedDType)); + Assert.Equal(expectedDType, result.Binding.ResultType); + Assert.True(result.IsSuccess); + } + + [Theory] + [InlineData("RenameColumns([{A:\"hello\",B:1}], \"A\", \"C\")", "*[B:n,C:s]")] + [InlineData("RenameColumns([{A:\"hello\",B:1}], \"B\", \"C\")", "*[A:s,C:n]")] + [InlineData("RenameColumns([{A:\"hello\",B:1}], \"A\", \"C\", \"B\", \"D\")", "*[C:s,D:n]")] + [InlineData("RenameColumns({A:\"hello\",B:1}, \"A\", \"C\")", "![B:n,C:s]")] + [InlineData("RenameColumns({A:\"hello\",B:1}, \"B\", \"C\")", "![A:s,C:n]")] + [InlineData("RenameColumns({A:\"hello\",B:1}, \"A\", \"C\", \"B\", \"D\")", "![C:s,D:n]")] + public void TexlFunctionTypeSemanticsRenameColumns_ColumnNamesAsIdentifiersDisabled(string expression, string expectedType) + { + var engine = new Engine(new PowerFxConfig(new Features { SupportColumnNamesAsIdentifiers = false, TableSyntaxDoesntWrapRecords = true })); + var options = new ParserOptions() { NumberIsFloat = true }; + var result = engine.Check(expression, options); + + Assert.True(DType.TryParse(expectedType, out var expectedDType)); + Assert.Equal(expectedDType, result.Binding.ResultType); + Assert.True(result.IsSuccess); + } + + [Theory] + [InlineData("RenameColumns([{A:\"hello\",B:1}], C, B)")] + [InlineData("RenameColumns([{A:\"hello\",B:1}], AB, B)")] + [InlineData("RenameColumns([{A:\"hello\",B:1}], A1, B)")] + [InlineData("RenameColumns({A:\"hello\",B:1}, C, B)")] + [InlineData("RenameColumns({A:\"hello\",B:1}, AB, B)")] + [InlineData("RenameColumns({A:\"hello\",B:1}, A1, B)")] + [InlineData("RenameColumns([{A:\"hello\",B:1}], \"A\", \"B\")")] + [InlineData("RenameColumns([{A:\"hello\",B:1}], A, A1, A, A2)")] + [InlineData("RenameColumns(true, A, A1)")] + public void TexlFunctionTypeSemanticsRenameColumns_Negative(string expression) + { + var engine = new Engine(new PowerFxConfig()); + var result = engine.Check(expression); + + Assert.False(result.IsSuccess); + } + + [Theory] + [InlineData("IfError(\"Hello\", \"one\")", "s")] + [InlineData("IfError(\"Hello\", 1)", "s")] + [InlineData("IfError(\"Hello\", 1, 3)", "n")] + [InlineData("IfError(\"Hello\", \"one\", \"world\", 2, 1)", "n")] + [InlineData("IfError({a:1}, \"one\", [1,2,3], 2, 1)", "n")] + public void TexlFunctionTypeSemanticsIfError(string expression, string expectedType) + { + var engine = new Engine(new PowerFxConfig()); + var options = new ParserOptions() { NumberIsFloat = true }; + var result = engine.Check(expression, options); + + Assert.True(DType.TryParse(expectedType, out var expectedDType)); + Assert.Equal(expectedDType, result.Binding.ResultType); + Assert.True(result.IsSuccess); + } + + [Theory] + [InlineData("IfError(\"Hello\", {a:\"one\"})")] + [InlineData("IfError(\"Hello\", 1, {a:2})", false, true)] + [InlineData("IfError(\"Hello\", 1, 3, [true])")] + [InlineData("IfError({a:1}, true)")] + [InlineData("IfError(1, [1], true, {a:1}, \"hello\")", false, true)] + [InlineData("IfError(IfError({a:1}, true), true)")] + [InlineData("false; IfError({a:1}, true); true", true)] + [InlineData("IsError(false; IfError({a:1}, true); true)", true)] + public void TexlFunctionTypeSemanticsIfError_MismatchedTypes(string expression, bool usesChain = false, bool preV1Bug = false) + { + foreach (var usePowerFxV1Rules in new[] { false, true }) + { + if (!usePowerFxV1Rules && preV1Bug) + { + // Bug on pre-V1 logic; will not fix for legacy reasons + continue; + } + + foreach (var isBehavior in new[] { false, true }) + { + var features = new Features(Features.PowerFxV1) + { + PowerFxV1CompatibilityRules = usePowerFxV1Rules + }; + + var engine = new Engine(new PowerFxConfig(features)); + var parserOptions = new ParserOptions() { NumberIsFloat = true, AllowsSideEffects = isBehavior }; + var result = engine.Check(expression, parserOptions); + + if (usePowerFxV1Rules && !usesChain) + { + Assert.True(result.IsSuccess); + Assert.Equal(DType.Void, result.Binding.ResultType); + } + else + { + Assert.True(isBehavior == result.IsSuccess, $"For PFxV1={usePowerFxV1Rules} and isBehavior={isBehavior}, expression {expression} should {(isBehavior ? "succeed" : "fail")}"); + } + } + } + } + + [Theory] + [InlineData("Switch(1, 2, true, 3, {}, 4, [])", "-")] // Mismatched return types: Void result + [InlineData("Switch(11;If(true, 123, {}); 1, 2, true, 3, false)", "b")] // Void inside a chain - ok + [InlineData("Switch(1, 2, If(1<2, true, 1<3, []), 3, Color.Blue)", "-")] // Void as one of return types: Void result + public void TexlFunctionTypeSemanticsSwitch_VoidExpressions(string expression, string expectedType) + { + foreach (var usePowerFxV1Rules in new[] { false, true }) + { + var features = new Features(Features.PowerFxV1) + { + PowerFxV1CompatibilityRules = usePowerFxV1Rules + }; + + var engine = new Engine(new PowerFxConfig(features)); + var parserOptions = new ParserOptions() { NumberIsFloat = true, AllowsSideEffects = true }; + var result = engine.Check(expression, parserOptions); + + Assert.True(result.IsSuccess, $"For PFxV1={usePowerFxV1Rules}, expression {expression} should succeed"); + Assert.Equal(TestUtils.DT(expectedType), result.Binding.ResultType); + } + } + + [Theory] + [InlineData("Switch(If(true, true, {}), true, 1, 2)")] + [InlineData("Switch(11;22;If(true, 10;20;If(true, 33, 123;{}), false), true, 1, false, 2)")] + [InlineData("Switch(If(1<2, true, 1<3, [], 1<4, {},1<5, Color.Blue), true, true, 1, false, 2)")] + public void TexlFunctionTypeSemanticsSwitch_MismatchedTypes_Negative(string expression) + { + foreach (var usePowerFxV1Rules in new[] { false, true }) + { + var features = new Features(Features.PowerFxV1) + { + PowerFxV1CompatibilityRules = usePowerFxV1Rules + }; + + var engine = new Engine(new PowerFxConfig(features)); + var parserOptions = new ParserOptions() { NumberIsFloat = true, AllowsSideEffects = true }; + var result = engine.Check(expression, parserOptions); + + Assert.False(result.IsSuccess, $"For PFxV1={usePowerFxV1Rules}, expression {expression} should fail"); + } + } + [Theory] [InlineData("Average(\"3\")")] [InlineData("Average(\"3\", 4)")] @@ -657,14 +657,14 @@ namespace Microsoft.PowerFx.Core.Tests public IEnumerable OptionNames => new[] { new DName("No"), new DName("Yes") }; - public DKind BackingKind => DKind.Boolean; - - public bool CanCoerceFromBackingKind => false; - - public bool CanCoerceToBackingKind => true; - - public bool CanConcatenateStronglyTyped => false; - + public DKind BackingKind => DKind.Boolean; + + public bool CanCoerceFromBackingKind => false; + + public bool CanCoerceToBackingKind => true; + + public bool CanConcatenateStronglyTyped => false; + public bool CanCompareNumeric => false; public bool IsConvertingDisplayNameMapping => false; @@ -713,7 +713,7 @@ namespace Microsoft.PowerFx.Core.Tests [InlineData("CountIf(Table, Today() + A)")] [InlineData("CountIf(Table, A > 0, Today() + A)")] [InlineData("CountIf(Table, {Result:A})")] - [InlineData("CountIf([1,3,4], NonBoolOptionSet.First")] + [InlineData("CountIf([1,3,4], NonBoolOptionSet.First")] [InlineData("CountIf(First(Table), true)")] public void TexlFunctionTypeSemanticsCountIf_Negative(string expression) { @@ -732,7 +732,7 @@ namespace Microsoft.PowerFx.Core.Tests DType.Number, symbol, optionSets: new[] { new OptionSet("NonBoolOptionSet", nonBoolOptionSetDisplayNameProvider) }); - } + } [Fact] public void TexlFunctionTypeSemanticsCountRows() @@ -1291,16 +1291,16 @@ namespace Microsoft.PowerFx.Core.Tests [Theory] [InlineData("RandBetween(Number1,Number2)", "n")] - [InlineData("RandBetween(Number1,Decimal1)", "n")] - [InlineData("RandBetween(Decimal1,Number1)", "n")] + [InlineData("RandBetween(Number1,Decimal1)", "n")] + [InlineData("RandBetween(Decimal1,Number1)", "n")] [InlineData("RandBetween(Decimal1,Decimal2)", "w")] public void TexlFunctionTypeSemanticsRandBetween(string script, string expectedType) { var symbol = new SymbolTable(); - symbol.AddVariable("Number1", new NumberType()); + symbol.AddVariable("Number1", new NumberType()); symbol.AddVariable("Number2", new NumberType()); symbol.AddVariable("Decimal1", new DecimalType()); - symbol.AddVariable("Decimal2", new DecimalType()); + symbol.AddVariable("Decimal2", new DecimalType()); TestSimpleBindingSuccess( script, @@ -1620,253 +1620,253 @@ namespace Microsoft.PowerFx.Core.Tests } [Theory] - [InlineData("SortByColumns([1,2,3,4,5], \"Value\", \"Ascending\")", "*[Value:n]")] - [InlineData("SortByColumns([1,2,3,4,5], \"Value\")", "*[Value:n]")] - [InlineData("SortByColumns(Table({a:\"hello\"}), \"a\", \"Ascending\")", "*[a:s]")] - [InlineData("SortByColumns(Table({a:\"hello\"}), \"a\")", "*[a:s]")] - [InlineData("SortByColumns(Table({a:1, b:\"hello\"}), \"a\", \"Ascending\", \"b\")", "*[a:n,b:s]")] - [InlineData("SortByColumns(Table({a:1, b:\"hello\"}), \"a\", \"Ascending\", \"b\", \"Descending\")", "*[a:n,b:s]")] - [InlineData("SortByColumns(Table({a:1, b:\"hello\", c:3}), \"a\", \"Ascending\", \"b\", \"Descending\", \"c\")", "*[a:n,b:s,c:n]")] - [InlineData("SortByColumns(Table({a:1, b:\"hello\", c:3}), \"a\", \"Ascending\", \"b\", \"Descending\", \"c\", \"Ascending\")", "*[a:n,b:s,c:n]")] - [InlineData("SortByColumns([1,2,3,4,5], First([\"Value\"]).Value, \"Ascending\")", "*[Value:n]")] - [InlineData("SortByColumns([1,2,3,4,5], First([\"Value\"]).Value)", "*[Value:n]")] - [InlineData("SortByColumns(Table({a:\"hello\"}), If(true,\"a\"), \"Ascending\")", "*[a:s]")] - [InlineData("SortByColumns(Table({a:\"hello\"}), If(true,\"a\"))", "*[a:s]")] - [InlineData("SortByColumns(Table({a:1, b:\"hello\"}), If(true,\"a\"), \"Ascending\", \"b\")", "*[a:n,b:s]")] - [InlineData("SortByColumns(Table({a:1, b:\"hello\"}), \"a\", \"Ascending\", If(true,\"b\"), \"Descending\")", "*[a:n,b:s]")] - [InlineData("SortByColumns(Table({a:1, b:\"hello\", c:3}), If(true,\"a\"), \"Ascending\", If(true,\"b\"), \"Descending\", \"c\")", "*[a:n,b:s,c:n]")] - [InlineData("SortByColumns(Table({a:1, b:\"hello\", c:3}), If(true,\"c\"), \"Ascending\", If(true,\"a\"), \"Descending\", If(true,\"b\"), \"Ascending\")", "*[a:n,b:s,c:n]")] - [InlineData("SortByColumns([1,2,3,4,5], \"Value\", [3,4,5])", "*[Value:n]")] - [InlineData("SortByColumns(Table({a:\"hello\"}), \"a\", Table({q:\"hello\"}))", "*[a:s]")] - [InlineData("SortByColumns(Table({a:1}), \"a\", Table({c:1}))", "*[a:n]")] - [InlineData("SortByColumns(Table({a:true}), \"a\", Table({d:true}))", "*[a:b]")] - [InlineData("SortByColumns(Table({a:DateTimeValue(\"21 Jan 2014\")}), \"a\", Table({e:DateTimeValue(\"21 Jan 2014\")}))", "*[a:d]")] - [InlineData("SortByColumns(Table({a:TimeValue(\"11:15\")}), \"a\", Table({b:TimeValue(\"12:15\")}))", "*[a:T]")] - [InlineData("SortByColumns(Table({a:DateValue(\"21 Jan 2014\")}), \"a\", Table({b:DateValue(\"21 Jan 2014\")}))", "*[a:D]")] - [InlineData("SortByColumns(Table({a:2}), If(true,\"a\"),[3,4,5])", "*[a:n]")] - [InlineData("SortByColumns(Table({a:2}), stringVar, SortOrder.Ascending)", "*[a:n]")] - [InlineData("SortByColumns(T, \"A\", SortOrder.Ascending, \"B\", SortOrder.Ascending)", "*[A:n, B:b, C:s]", "*[A:n, B:b, C:s]")] - [InlineData("SortByColumns(T, \"A\", SortOrder.Ascending, \"B\", SortOrder.Ascending)", "*[A:n, B:b, C:s, H:h, I:i]", "*[A:n, B:b, C:s, H:h, I:i]")] + [InlineData("SortByColumns([1,2,3,4,5], \"Value\", \"Ascending\")", "*[Value:n]")] + [InlineData("SortByColumns([1,2,3,4,5], \"Value\")", "*[Value:n]")] + [InlineData("SortByColumns(Table({a:\"hello\"}), \"a\", \"Ascending\")", "*[a:s]")] + [InlineData("SortByColumns(Table({a:\"hello\"}), \"a\")", "*[a:s]")] + [InlineData("SortByColumns(Table({a:1, b:\"hello\"}), \"a\", \"Ascending\", \"b\")", "*[a:n,b:s]")] + [InlineData("SortByColumns(Table({a:1, b:\"hello\"}), \"a\", \"Ascending\", \"b\", \"Descending\")", "*[a:n,b:s]")] + [InlineData("SortByColumns(Table({a:1, b:\"hello\", c:3}), \"a\", \"Ascending\", \"b\", \"Descending\", \"c\")", "*[a:n,b:s,c:n]")] + [InlineData("SortByColumns(Table({a:1, b:\"hello\", c:3}), \"a\", \"Ascending\", \"b\", \"Descending\", \"c\", \"Ascending\")", "*[a:n,b:s,c:n]")] + [InlineData("SortByColumns([1,2,3,4,5], First([\"Value\"]).Value, \"Ascending\")", "*[Value:n]")] + [InlineData("SortByColumns([1,2,3,4,5], First([\"Value\"]).Value)", "*[Value:n]")] + [InlineData("SortByColumns(Table({a:\"hello\"}), If(true,\"a\"), \"Ascending\")", "*[a:s]")] + [InlineData("SortByColumns(Table({a:\"hello\"}), If(true,\"a\"))", "*[a:s]")] + [InlineData("SortByColumns(Table({a:1, b:\"hello\"}), If(true,\"a\"), \"Ascending\", \"b\")", "*[a:n,b:s]")] + [InlineData("SortByColumns(Table({a:1, b:\"hello\"}), \"a\", \"Ascending\", If(true,\"b\"), \"Descending\")", "*[a:n,b:s]")] + [InlineData("SortByColumns(Table({a:1, b:\"hello\", c:3}), If(true,\"a\"), \"Ascending\", If(true,\"b\"), \"Descending\", \"c\")", "*[a:n,b:s,c:n]")] + [InlineData("SortByColumns(Table({a:1, b:\"hello\", c:3}), If(true,\"c\"), \"Ascending\", If(true,\"a\"), \"Descending\", If(true,\"b\"), \"Ascending\")", "*[a:n,b:s,c:n]")] + [InlineData("SortByColumns([1,2,3,4,5], \"Value\", [3,4,5])", "*[Value:n]")] + [InlineData("SortByColumns(Table({a:\"hello\"}), \"a\", Table({q:\"hello\"}))", "*[a:s]")] + [InlineData("SortByColumns(Table({a:1}), \"a\", Table({c:1}))", "*[a:n]")] + [InlineData("SortByColumns(Table({a:true}), \"a\", Table({d:true}))", "*[a:b]")] + [InlineData("SortByColumns(Table({a:DateTimeValue(\"21 Jan 2014\")}), \"a\", Table({e:DateTimeValue(\"21 Jan 2014\")}))", "*[a:d]")] + [InlineData("SortByColumns(Table({a:TimeValue(\"11:15\")}), \"a\", Table({b:TimeValue(\"12:15\")}))", "*[a:T]")] + [InlineData("SortByColumns(Table({a:DateValue(\"21 Jan 2014\")}), \"a\", Table({b:DateValue(\"21 Jan 2014\")}))", "*[a:D]")] + [InlineData("SortByColumns(Table({a:2}), If(true,\"a\"),[3,4,5])", "*[a:n]")] + [InlineData("SortByColumns(Table({a:2}), stringVar, SortOrder.Ascending)", "*[a:n]")] + [InlineData("SortByColumns(T, \"A\", SortOrder.Ascending, \"B\", SortOrder.Ascending)", "*[A:n, B:b, C:s]", "*[A:n, B:b, C:s]")] + [InlineData("SortByColumns(T, \"A\", SortOrder.Ascending, \"B\", SortOrder.Ascending)", "*[A:n, B:b, C:s, H:h, I:i]", "*[A:n, B:b, C:s, H:h, I:i]")] public void TexlFunctionTypeSemanticsSortByColumns_PowerFxV1Disabled(string script, string expectedType, string tVariableType = null) - { - var symbolTable = new SymbolTable(); - symbolTable.AddVariable("stringVar", FormulaType.String); - if (tVariableType != null) - { - symbolTable.AddVariable("T", new TableType(TestUtils.DT(tVariableType))); - } - - foreach (var columnNamesAsIdentifiers in new[] { false, true }) - { - var features = new Features - { - SupportColumnNamesAsIdentifiers = columnNamesAsIdentifiers, - PowerFxV1CompatibilityRules = false - }; - - var expectedDType = TestUtils.DT(expectedType); - TestSimpleBindingSuccess(script, expectedDType, symbolTable: symbolTable, features: features); - } + { + var symbolTable = new SymbolTable(); + symbolTable.AddVariable("stringVar", FormulaType.String); + if (tVariableType != null) + { + symbolTable.AddVariable("T", new TableType(TestUtils.DT(tVariableType))); + } + + foreach (var columnNamesAsIdentifiers in new[] { false, true }) + { + var features = new Features + { + SupportColumnNamesAsIdentifiers = columnNamesAsIdentifiers, + PowerFxV1CompatibilityRules = false + }; + + var expectedDType = TestUtils.DT(expectedType); + TestSimpleBindingSuccess(script, expectedDType, symbolTable: symbolTable, features: features); + } } [Theory] - [InlineData("SortByColumns([1,2,3,4,5], Value, SortOrder.Ascending)", "*[Value:n]")] - [InlineData("SortByColumns([1,2,3,4,5], Value)", "*[Value:n]")] - [InlineData("SortByColumns(Table({a:\"hello\"}), a, SortOrder.Ascending)", "*[a:s]")] - [InlineData("SortByColumns(Table({a:\"hello\"}), a)", "*[a:s]")] - [InlineData("SortByColumns(Table({a:1, b:\"hello\"}), a, SortOrder.Ascending, b)", "*[a:n,b:s]")] - [InlineData("SortByColumns(Table({a:1, b:\"hello\"}), a, SortOrder.Ascending, b, SortOrder.Descending)", "*[a:n,b:s]")] - [InlineData("SortByColumns(Table({a:1, b:\"hello\", c:3}), a, SortOrder.Ascending, b, SortOrder.Descending, c)", "*[a:n,b:s,c:n]")] - [InlineData("SortByColumns(Table({a:1, b:\"hello\", c:3}), a, SortOrder.Ascending, b, SortOrder.Descending, c, SortOrder.Ascending)", "*[a:n,b:s,c:n]")] - [InlineData("SortByColumns(Table({a:1, b:\"hello\", c:3}), \"a\", SortOrder.Ascending, b, SortOrder.Descending, c)", "*[a:n,b:s,c:n]")] - [InlineData("SortByColumns(Table({a:1, b:\"hello\"}), If(true,\"a\"), SortOrder.Ascending, b)", "*[a:n,b:s]")] - [InlineData("SortByColumns(Table({a:1, b:\"hello\"}), a, SortOrder.Ascending, If(true,\"b\"), SortOrder.Descending)", "*[a:n,b:s]")] - [InlineData("SortByColumns([1,2,3,4,5], Value, [3,4,5])", "*[Value:n]")] - [InlineData("SortByColumns(Table({a:\"hello\"}), a, Table({q:\"hello\"}))", "*[a:s]")] - [InlineData("SortByColumns(Table({a:1}), a, Table({c:1}))", "*[a:n]")] - [InlineData("SortByColumns(Table({a:true}), a, Table({d:true}))", "*[a:b]")] - [InlineData("SortByColumns(Table({a:DateTimeValue(\"21 Jan 2014\")}), a, Table({e:DateTimeValue(\"21 Jan 2014\")}))", "*[a:d]")] - [InlineData("SortByColumns(Table({a:TimeValue(\"11:15\")}), a, Table({b:TimeValue(\"12:15\")}))", "*[a:T]")] - [InlineData("SortByColumns(Table({a:DateValue(\"21 Jan 2014\")}), a, Table({b:DateValue(\"21 Jan 2014\")}))", "*[a:D]")] + [InlineData("SortByColumns([1,2,3,4,5], Value, SortOrder.Ascending)", "*[Value:n]")] + [InlineData("SortByColumns([1,2,3,4,5], Value)", "*[Value:n]")] + [InlineData("SortByColumns(Table({a:\"hello\"}), a, SortOrder.Ascending)", "*[a:s]")] + [InlineData("SortByColumns(Table({a:\"hello\"}), a)", "*[a:s]")] + [InlineData("SortByColumns(Table({a:1, b:\"hello\"}), a, SortOrder.Ascending, b)", "*[a:n,b:s]")] + [InlineData("SortByColumns(Table({a:1, b:\"hello\"}), a, SortOrder.Ascending, b, SortOrder.Descending)", "*[a:n,b:s]")] + [InlineData("SortByColumns(Table({a:1, b:\"hello\", c:3}), a, SortOrder.Ascending, b, SortOrder.Descending, c)", "*[a:n,b:s,c:n]")] + [InlineData("SortByColumns(Table({a:1, b:\"hello\", c:3}), a, SortOrder.Ascending, b, SortOrder.Descending, c, SortOrder.Ascending)", "*[a:n,b:s,c:n]")] + [InlineData("SortByColumns(Table({a:1, b:\"hello\", c:3}), \"a\", SortOrder.Ascending, b, SortOrder.Descending, c)", "*[a:n,b:s,c:n]")] + [InlineData("SortByColumns(Table({a:1, b:\"hello\"}), If(true,\"a\"), SortOrder.Ascending, b)", "*[a:n,b:s]")] + [InlineData("SortByColumns(Table({a:1, b:\"hello\"}), a, SortOrder.Ascending, If(true,\"b\"), SortOrder.Descending)", "*[a:n,b:s]")] + [InlineData("SortByColumns([1,2,3,4,5], Value, [3,4,5])", "*[Value:n]")] + [InlineData("SortByColumns(Table({a:\"hello\"}), a, Table({q:\"hello\"}))", "*[a:s]")] + [InlineData("SortByColumns(Table({a:1}), a, Table({c:1}))", "*[a:n]")] + [InlineData("SortByColumns(Table({a:true}), a, Table({d:true}))", "*[a:b]")] + [InlineData("SortByColumns(Table({a:DateTimeValue(\"21 Jan 2014\")}), a, Table({e:DateTimeValue(\"21 Jan 2014\")}))", "*[a:d]")] + [InlineData("SortByColumns(Table({a:TimeValue(\"11:15\")}), a, Table({b:TimeValue(\"12:15\")}))", "*[a:T]")] + [InlineData("SortByColumns(Table({a:DateValue(\"21 Jan 2014\")}), a, Table({b:DateValue(\"21 Jan 2014\")}))", "*[a:D]")] public void TexlFunctionTypeSemanticsSortByColumns_ColumnsAsIdentifiersOn_PFxV1Off_Negative(string script, string expectedType) - { - var symbolTable = new SymbolTable(); - - var features = new Features - { - SupportColumnNamesAsIdentifiers = true, - PowerFxV1CompatibilityRules = false - }; - - var expectedDType = TestUtils.DT(expectedType); - TestBindingErrors(script, expectedDType, symbolTable: symbolTable, features: features); + { + var symbolTable = new SymbolTable(); + + var features = new Features + { + SupportColumnNamesAsIdentifiers = true, + PowerFxV1CompatibilityRules = false + }; + + var expectedDType = TestUtils.DT(expectedType); + TestBindingErrors(script, expectedDType, symbolTable: symbolTable, features: features); } [Theory] - [InlineData("SortByColumns([1,2,3,4,5], Value, SortOrder.Ascending)", "*[Value:n]")] - [InlineData("SortByColumns([1,2,3,4,5], Value)", "*[Value:n]")] - [InlineData("SortByColumns([1,2,3,4,5], \"Value\")", "*[Value:n]")] - [InlineData("SortByColumns(Table({a:\"hello\"}), a, SortOrder.Ascending)", "*[a:s]")] - [InlineData("SortByColumns(Table({a:\"hello\"}), a)", "*[a:s]")] - [InlineData("SortByColumns(Table({a:1, b:\"hello\"}), a, SortOrder.Ascending, b)", "*[a:n,b:s]")] - [InlineData("SortByColumns(Table({a:1, b:\"hello\"}), a, SortOrder.Ascending, b, SortOrder.Descending)", "*[a:n,b:s]")] - [InlineData("SortByColumns(Table({a:1, b:\"hello\", c:3}), a, SortOrder.Ascending, b, SortOrder.Descending, c)", "*[a:n,b:s,c:n]")] - [InlineData("SortByColumns(Table({a:1, b:\"hello\", c:3}), a, SortOrder.Ascending, b, SortOrder.Descending, c, SortOrder.Ascending)", "*[a:n,b:s,c:n]")] - [InlineData("SortByColumns(Table({a:1, b:\"hello\", c:3}), \"a\", SortOrder.Ascending, b, SortOrder.Descending, c)", "*[a:n,b:s,c:n]")] - [InlineData("SortByColumns([1,2,3,4,5], First([\"Value\"]).Value, SortOrder.Ascending)", "*[Value:n]")] - [InlineData("SortByColumns([1,2,3,4,5], First([\"Value\"]).Value)", "*[Value:n]")] - [InlineData("SortByColumns(Table({a:\"hello\"}), If(true,\"a\"), SortOrder.Ascending)", "*[a:s]")] - [InlineData("SortByColumns(Table({a:\"hello\"}), If(true,\"a\"))", "*[a:s]")] - [InlineData("SortByColumns(Table({a:1, b:\"hello\"}), If(true,\"a\"), SortOrder.Ascending, b)", "*[a:n,b:s]")] - [InlineData("SortByColumns(Table({a:1, b:\"hello\"}), a, SortOrder.Ascending, If(true,\"b\"), SortOrder.Descending)", "*[a:n,b:s]")] - [InlineData("SortByColumns(Table({a:1, b:\"hello\", c:3}), If(true,\"a\"), SortOrder.Ascending, If(true,\"b\"), SortOrder.Descending, \"c\")", "*[a:n,b:s,c:n]")] - [InlineData("SortByColumns(Table({a:1, b:\"hello\", c:3}), If(true,\"c\"), SortOrder.Ascending, If(true,\"a\"), SortOrder.Descending, If(true,\"b\"), SortOrder.Ascending)", "*[a:n,b:s,c:n]")] - [InlineData("SortByColumns([1,2,3,4,5], \"Value\", [3,4,5])", "*[Value:n]")] - [InlineData("SortByColumns([1,2,3,4,5], Value, [3,4,5])", "*[Value:n]")] - [InlineData("SortByColumns(Table({a:\"hello\"}), a, Table({q:\"hello\"}))", "*[a:s]")] - [InlineData("SortByColumns(Table({a:1}), a, Table({c:1}))", "*[a:n]")] - [InlineData("SortByColumns(Table({a:true}), a, Table({d:true}))", "*[a:b]")] - [InlineData("SortByColumns(Table({a:DateTimeValue(\"21 Jan 2014\")}), a, Table({e:DateTimeValue(\"21 Jan 2014\")}))", "*[a:d]")] - [InlineData("SortByColumns(Table({a:TimeValue(\"11:15\")}), a, Table({b:TimeValue(\"12:15\")}))", "*[a:T]")] - [InlineData("SortByColumns(Table({a:DateValue(\"21 Jan 2014\")}), a, Table({b:DateValue(\"21 Jan 2014\")}))", "*[a:D]")] - [InlineData("SortByColumns(Table({a:2}), If(true,\"a\"),[3,4,5])", "*[a:n]")] - [InlineData("SortByColumns(Table({a:2}), stringVar, SortOrder.Ascending)", "*[a:n]")] - [InlineData("SortByColumns(T, \"A\", SortOrder.Ascending, \"B\", SortOrder.Ascending)", "*[A:n, B:b, C:s]", "*[A:n, B:b, C:s]")] - [InlineData("SortByColumns(T, \"A\", SortOrder.Ascending, \"B\", SortOrder.Ascending)", "*[A:n, B:b, C:s, H:h, I:i]", "*[A:n, B:b, C:s, H:h, I:i]")] - [InlineData("SortByColumns(T, A, SortOrder.Ascending, B, SortOrder.Ascending)", "*[A:n, B:b, C:s]", "*[A:n, B:b, C:s]")] - [InlineData("SortByColumns(T, A, SortOrder.Ascending, B, SortOrder.Ascending)", "*[A:n, B:b, C:s, H:h, I:i]", "*[A:n, B:b, C:s, H:h, I:i]")] + [InlineData("SortByColumns([1,2,3,4,5], Value, SortOrder.Ascending)", "*[Value:n]")] + [InlineData("SortByColumns([1,2,3,4,5], Value)", "*[Value:n]")] + [InlineData("SortByColumns([1,2,3,4,5], \"Value\")", "*[Value:n]")] + [InlineData("SortByColumns(Table({a:\"hello\"}), a, SortOrder.Ascending)", "*[a:s]")] + [InlineData("SortByColumns(Table({a:\"hello\"}), a)", "*[a:s]")] + [InlineData("SortByColumns(Table({a:1, b:\"hello\"}), a, SortOrder.Ascending, b)", "*[a:n,b:s]")] + [InlineData("SortByColumns(Table({a:1, b:\"hello\"}), a, SortOrder.Ascending, b, SortOrder.Descending)", "*[a:n,b:s]")] + [InlineData("SortByColumns(Table({a:1, b:\"hello\", c:3}), a, SortOrder.Ascending, b, SortOrder.Descending, c)", "*[a:n,b:s,c:n]")] + [InlineData("SortByColumns(Table({a:1, b:\"hello\", c:3}), a, SortOrder.Ascending, b, SortOrder.Descending, c, SortOrder.Ascending)", "*[a:n,b:s,c:n]")] + [InlineData("SortByColumns(Table({a:1, b:\"hello\", c:3}), \"a\", SortOrder.Ascending, b, SortOrder.Descending, c)", "*[a:n,b:s,c:n]")] + [InlineData("SortByColumns([1,2,3,4,5], First([\"Value\"]).Value, SortOrder.Ascending)", "*[Value:n]")] + [InlineData("SortByColumns([1,2,3,4,5], First([\"Value\"]).Value)", "*[Value:n]")] + [InlineData("SortByColumns(Table({a:\"hello\"}), If(true,\"a\"), SortOrder.Ascending)", "*[a:s]")] + [InlineData("SortByColumns(Table({a:\"hello\"}), If(true,\"a\"))", "*[a:s]")] + [InlineData("SortByColumns(Table({a:1, b:\"hello\"}), If(true,\"a\"), SortOrder.Ascending, b)", "*[a:n,b:s]")] + [InlineData("SortByColumns(Table({a:1, b:\"hello\"}), a, SortOrder.Ascending, If(true,\"b\"), SortOrder.Descending)", "*[a:n,b:s]")] + [InlineData("SortByColumns(Table({a:1, b:\"hello\", c:3}), If(true,\"a\"), SortOrder.Ascending, If(true,\"b\"), SortOrder.Descending, \"c\")", "*[a:n,b:s,c:n]")] + [InlineData("SortByColumns(Table({a:1, b:\"hello\", c:3}), If(true,\"c\"), SortOrder.Ascending, If(true,\"a\"), SortOrder.Descending, If(true,\"b\"), SortOrder.Ascending)", "*[a:n,b:s,c:n]")] + [InlineData("SortByColumns([1,2,3,4,5], \"Value\", [3,4,5])", "*[Value:n]")] + [InlineData("SortByColumns([1,2,3,4,5], Value, [3,4,5])", "*[Value:n]")] + [InlineData("SortByColumns(Table({a:\"hello\"}), a, Table({q:\"hello\"}))", "*[a:s]")] + [InlineData("SortByColumns(Table({a:1}), a, Table({c:1}))", "*[a:n]")] + [InlineData("SortByColumns(Table({a:true}), a, Table({d:true}))", "*[a:b]")] + [InlineData("SortByColumns(Table({a:DateTimeValue(\"21 Jan 2014\")}), a, Table({e:DateTimeValue(\"21 Jan 2014\")}))", "*[a:d]")] + [InlineData("SortByColumns(Table({a:TimeValue(\"11:15\")}), a, Table({b:TimeValue(\"12:15\")}))", "*[a:T]")] + [InlineData("SortByColumns(Table({a:DateValue(\"21 Jan 2014\")}), a, Table({b:DateValue(\"21 Jan 2014\")}))", "*[a:D]")] + [InlineData("SortByColumns(Table({a:2}), If(true,\"a\"),[3,4,5])", "*[a:n]")] + [InlineData("SortByColumns(Table({a:2}), stringVar, SortOrder.Ascending)", "*[a:n]")] + [InlineData("SortByColumns(T, \"A\", SortOrder.Ascending, \"B\", SortOrder.Ascending)", "*[A:n, B:b, C:s]", "*[A:n, B:b, C:s]")] + [InlineData("SortByColumns(T, \"A\", SortOrder.Ascending, \"B\", SortOrder.Ascending)", "*[A:n, B:b, C:s, H:h, I:i]", "*[A:n, B:b, C:s, H:h, I:i]")] + [InlineData("SortByColumns(T, A, SortOrder.Ascending, B, SortOrder.Ascending)", "*[A:n, B:b, C:s]", "*[A:n, B:b, C:s]")] + [InlineData("SortByColumns(T, A, SortOrder.Ascending, B, SortOrder.Ascending)", "*[A:n, B:b, C:s, H:h, I:i]", "*[A:n, B:b, C:s, H:h, I:i]")] public void TexlFunctionTypeSemanticsSortByColumns_ColumnsAsIdentifiersOn_PFxV1On(string script, string expectedType, string tVariableType = null) - { - var symbolTable = new SymbolTable(); - symbolTable.AddVariable("stringVar", FormulaType.String); - if (tVariableType != null) - { - symbolTable.AddVariable("T", new TableType(TestUtils.DT(tVariableType))); - } - - var features = new Features - { - SupportColumnNamesAsIdentifiers = true, - PowerFxV1CompatibilityRules = true - }; - - var expectedDType = TestUtils.DT(expectedType); - TestSimpleBindingSuccess(script, expectedDType, symbolTable: symbolTable, features: features); - } - - [Theory] - [InlineData("SortByColumns([Now()], Value, [false, true])", "*[Value:d]")] - [InlineData("SortByColumns([{a:[1,2,3]}], a)", "*[a:*[Value:n]]")] - [InlineData("SortByColumns([1,2,3], Value2)", "*[Value:n]")] - public void TexlFunctionTypeSemanticsSortByColumns_Negative_ColumnsAsIdentifiersEnabled(string script, string expectedType) - { - foreach (var pfxV1 in new[] { false, true }) - { - var features = new Features - { - TableSyntaxDoesntWrapRecords = true, - SupportColumnNamesAsIdentifiers = true, - PowerFxV1CompatibilityRules = pfxV1 - }; - - var expectedDType = TestUtils.DT(expectedType); - TestBindingErrors(script, expectedDType, features: features); - } - } - - [Theory] - [InlineData("SortByColumns(T, \"a\")", "*[a:![b:n]]")] - [InlineData("SortByColumns(T, \"a\")", "*[a:*[b:n]]")] - [InlineData("SortByColumns(T, \"a\")", "*[a:O]")] - public void TexlFunctionTypeSemanticsSortByColumns_Negative(string script, string tVariableType) - { - foreach (var pfxV1 in new[] { false, true }) - { - var symbolTable = new SymbolTable(); - var tType = TestUtils.DT(tVariableType); - symbolTable.AddVariable("T", new TableType(tType)); - - var features = new Features - { - SupportColumnNamesAsIdentifiers = true, - PowerFxV1CompatibilityRules = pfxV1 - }; - - TestBindingErrors(script, tType, symbolTable: symbolTable, features: features); - } + { + var symbolTable = new SymbolTable(); + symbolTable.AddVariable("stringVar", FormulaType.String); + if (tVariableType != null) + { + symbolTable.AddVariable("T", new TableType(TestUtils.DT(tVariableType))); + } + + var features = new Features + { + SupportColumnNamesAsIdentifiers = true, + PowerFxV1CompatibilityRules = true + }; + + var expectedDType = TestUtils.DT(expectedType); + TestSimpleBindingSuccess(script, expectedDType, symbolTable: symbolTable, features: features); } - [Theory] - [InlineData("SortByColumns(T, \"a\")", "*[a:i]")] - [InlineData("SortByColumns(T, \"a\")", "*[a:h]")] - [InlineData("SortByColumns(T, \"a\")", "*[a:o]")] - [InlineData("SortByColumns(T, \"a\")", "*[a:m]")] - [InlineData("SortByColumns(T, \"a\")", "*[a:g]")] + [Theory] + [InlineData("SortByColumns([Now()], Value, [false, true])", "*[Value:d]")] + [InlineData("SortByColumns([{a:[1,2,3]}], a)", "*[a:*[Value:n]]")] + [InlineData("SortByColumns([1,2,3], Value2)", "*[Value:n]")] + public void TexlFunctionTypeSemanticsSortByColumns_Negative_ColumnsAsIdentifiersEnabled(string script, string expectedType) + { + foreach (var pfxV1 in new[] { false, true }) + { + var features = new Features + { + TableSyntaxDoesntWrapRecords = true, + SupportColumnNamesAsIdentifiers = true, + PowerFxV1CompatibilityRules = pfxV1 + }; + + var expectedDType = TestUtils.DT(expectedType); + TestBindingErrors(script, expectedDType, features: features); + } + } + + [Theory] + [InlineData("SortByColumns(T, \"a\")", "*[a:![b:n]]")] + [InlineData("SortByColumns(T, \"a\")", "*[a:*[b:n]]")] + [InlineData("SortByColumns(T, \"a\")", "*[a:O]")] + public void TexlFunctionTypeSemanticsSortByColumns_Negative(string script, string tVariableType) + { + foreach (var pfxV1 in new[] { false, true }) + { + var symbolTable = new SymbolTable(); + var tType = TestUtils.DT(tVariableType); + symbolTable.AddVariable("T", new TableType(tType)); + + var features = new Features + { + SupportColumnNamesAsIdentifiers = true, + PowerFxV1CompatibilityRules = pfxV1 + }; + + TestBindingErrors(script, tType, symbolTable: symbolTable, features: features); + } + } + + [Theory] + [InlineData("SortByColumns(T, \"a\")", "*[a:i]")] + [InlineData("SortByColumns(T, \"a\")", "*[a:h]")] + [InlineData("SortByColumns(T, \"a\")", "*[a:o]")] + [InlineData("SortByColumns(T, \"a\")", "*[a:m]")] + [InlineData("SortByColumns(T, \"a\")", "*[a:g]")] public void TexlFunctionTypeSemanticsSortByColumns_Negative_PowerFxV1Enabled(string script, string tVariableType) - { - var features = Features.PowerFxV1; - var symbolTable = new SymbolTable(); - var tType = TestUtils.DT(tVariableType); - symbolTable.AddVariable("stringVar", FormulaType.String); - if (tVariableType != null) - { - symbolTable.AddVariable("T", new TableType(tType)); - } - - TestBindingErrors(script, tType, symbolTable: symbolTable, features: features); - } - + { + var features = Features.PowerFxV1; + var symbolTable = new SymbolTable(); + var tType = TestUtils.DT(tVariableType); + symbolTable.AddVariable("stringVar", FormulaType.String); + if (tVariableType != null) + { + symbolTable.AddVariable("T", new TableType(tType)); + } + + TestBindingErrors(script, tType, symbolTable: symbolTable, features: features); + } + [Theory] [InlineData("Mod(Number1,Number2)", "n")] - [InlineData("Mod(Number1,Decimal1)", "n")] - [InlineData("Mod(Decimal1,Number1)", "n")] + [InlineData("Mod(Number1,Decimal1)", "n")] + [InlineData("Mod(Decimal1,Number1)", "n")] [InlineData("Mod(Decimal1,Decimal2)", "w")] public void TexlFunctionTypeSemanticsMod(string script, string expectedType) { var symbol = new SymbolTable(); - symbol.AddVariable("Number1", new NumberType()); + symbol.AddVariable("Number1", new NumberType()); symbol.AddVariable("Number2", new NumberType()); symbol.AddVariable("Decimal1", new DecimalType()); - symbol.AddVariable("Decimal2", new DecimalType()); + symbol.AddVariable("Decimal2", new DecimalType()); TestSimpleBindingSuccess( script, TestUtils.DT(expectedType), symbol); - } - + } + [Theory] [InlineData("Mod(NumberT1,NumberT2)", "*[Value:n]")] - [InlineData("Mod(NumberT1,DecimalT1)", "*[Value:n]")] - [InlineData("Mod(DecimalT1,NumberT1)", "*[Value:n]")] - [InlineData("Mod(DecimalT1,DecimalT2)", "*[Value:w]")] + [InlineData("Mod(NumberT1,DecimalT1)", "*[Value:n]")] + [InlineData("Mod(DecimalT1,NumberT1)", "*[Value:n]")] + [InlineData("Mod(DecimalT1,DecimalT2)", "*[Value:w]")] [InlineData("Mod(NumberT1,Number2)", "*[Value:n]")] - [InlineData("Mod(NumberT1,Decimal1)", "*[Value:n]")] - [InlineData("Mod(DecimalT1,Number1)", "*[Value:n]")] - [InlineData("Mod(DecimalT1,Decimal2)", "*[Value:w]")] + [InlineData("Mod(NumberT1,Decimal1)", "*[Value:n]")] + [InlineData("Mod(DecimalT1,Number1)", "*[Value:n]")] + [InlineData("Mod(DecimalT1,Decimal2)", "*[Value:w]")] [InlineData("Mod(Number1,NumberT2)", "*[Value:n]")] - [InlineData("Mod(Number1,DecimalT1)", "*[Value:n]")] - [InlineData("Mod(Decimal1,NumberT1)", "*[Value:n]")] + [InlineData("Mod(Number1,DecimalT1)", "*[Value:n]")] + [InlineData("Mod(Decimal1,NumberT1)", "*[Value:n]")] [InlineData("Mod(Decimal1,DecimalT2)", "*[Value:w]")] public void TexlFunctionTypeSemanticsModT(string script, string expectedType) { var symbol = new SymbolTable(); symbol.AddVariable("NumberT1", new TableType(TestUtils.DT("*[Value:n]"))); - symbol.AddVariable("NumberT2", new TableType(TestUtils.DT("*[Value:n]"))); + symbol.AddVariable("NumberT2", new TableType(TestUtils.DT("*[Value:n]"))); symbol.AddVariable("DecimalT1", new TableType(TestUtils.DT("*[Value:w]"))); - symbol.AddVariable("DecimalT2", new TableType(TestUtils.DT("*[Value:w]"))); - symbol.AddVariable("Number1", new NumberType()); + symbol.AddVariable("DecimalT2", new TableType(TestUtils.DT("*[Value:w]"))); + symbol.AddVariable("Number1", new NumberType()); symbol.AddVariable("Number2", new NumberType()); symbol.AddVariable("Decimal1", new DecimalType()); - symbol.AddVariable("Decimal2", new DecimalType()); + symbol.AddVariable("Decimal2", new DecimalType()); TestSimpleBindingSuccess( script, - TestUtils.DT(expectedType), - symbol, + TestUtils.DT(expectedType), + symbol, features: Features.PowerFxV1); // for consistent single column result name - } - + } + [Theory] [InlineData("Sqrt(1234.567)", "n")] [InlineData("Sqrt(T)", "*[A:n]")] @@ -2105,52 +2105,52 @@ namespace Microsoft.PowerFx.Core.Tests { var symbol = new SymbolTable(); symbol.AddVariable("S", FormulaType.Date); - symbol.AddVariable("R", FormulaType.Number); + symbol.AddVariable("R", FormulaType.Number); symbol.AddVariable("W", FormulaType.Decimal); TestSimpleBindingSuccess( "WeekNum(S)", DType.Decimal, - symbol, + symbol, numberIsFloat: false); TestSimpleBindingSuccess( "WeekNum(S, R)", DType.Decimal, - symbol, - numberIsFloat: false); - + symbol, + numberIsFloat: false); + TestSimpleBindingSuccess( "WeekNum(S, W)", DType.Decimal, - symbol, + symbol, numberIsFloat: false); - } - + } + [Fact] public void TexlFunctionTypeSemanticsWeekNum_Float() { var symbol = new SymbolTable(); symbol.AddVariable("S", FormulaType.Date); - symbol.AddVariable("R", FormulaType.Number); + symbol.AddVariable("R", FormulaType.Number); symbol.AddVariable("W", FormulaType.Decimal); TestSimpleBindingSuccess( "WeekNum(S)", DType.Number, - symbol, + symbol, numberIsFloat: true); TestSimpleBindingSuccess( "WeekNum(S, R)", DType.Number, - symbol, - numberIsFloat: true); - + symbol, + numberIsFloat: true); + TestSimpleBindingSuccess( "WeekNum(S, W)", DType.Number, - symbol, + symbol, numberIsFloat: true); } @@ -2163,10 +2163,10 @@ namespace Microsoft.PowerFx.Core.Tests TestSimpleBindingSuccess( "ISOWeekNum(S)", DType.Decimal, - symbol, + symbol, numberIsFloat: false); - } - + } + [Fact] public void TexlFunctionTypeSemanticsISOWeekNum_Float() { @@ -2176,10 +2176,10 @@ namespace Microsoft.PowerFx.Core.Tests TestSimpleBindingSuccess( "ISOWeekNum(S)", DType.Number, - symbol, - numberIsFloat: true); - } - + symbol, + numberIsFloat: true); + } + [Theory] [InlineData("IsToday(true)")] [InlineData("IsToday(Time(1,2,3))")] @@ -2573,39 +2573,39 @@ namespace Microsoft.PowerFx.Core.Tests [InlineData("Table({X:false, Y:123}, {Y:7, X:true}, {Y:99.9}, {X:true}, {})", "*[X:b, Y:n]")] [InlineData("Table({Y:123}, {Y:7}, {Y:99.9}, {X:true})", "*[X:b, Y:n]")] [InlineData("Table({Y:123}, {Z:true}, {X:\"hello\"}, {W:{A:1, B:true, C:\"hello\"}})", "*[X:s, Y:n, Z:b, W:![A:n, B:b, C:s]]")] - [InlineData("Table({Nested:Table({Nested:Table({X:1})})})", "*[Nested:*[Nested:*[X:n]]]")] + [InlineData("Table({Nested:Table({Nested:Table({X:1})})})", "*[Nested:*[Nested:*[X:n]]]")] [InlineData("Table([])", "*[]")] [InlineData("Table(Table())", "*[]")] - [InlineData("Table(Table(Table()))", "*[]")] - [InlineData("Table(Blank(), Blank())", "*[]")] - [InlineData("Table([1, 2, 3, 4], Blank())", "*[Value:n]")] - [InlineData("Table([{a:0, b:false, c:\"Hello\"}], [{a:1, b:true, c:\"World\"}])", "*[a:n, b:b, c:s]")] - [InlineData("Table([{a:0}], [{b:true}], [{c:\"Hello\"}])", "*[a:n, b:b, c:s]")] - [InlineData("Table(Blank(), T2, Blank(), T1)", "*[a:n, b:b, c:s]")] - [InlineData("Table([{a:0}], [{b:true}], [{c:\"Hello\", d: {x: \"World\"}}])", "*[a:n, b:b, c:s, d:![x:s]]")] - [InlineData("Table(T1, T2)", "*[a:n, b:n, c:n]")] - [InlineData("Table(T2, T1)", "*[a:n, b:b, c:s]")] - [InlineData("Table(T2, Table(T1, T2))", "*[a:n, b:b, c:s]")] - [InlineData("Table(T1, Table(T2, T3))", "*[a:n, b:n, c:n, d:n]")] - [InlineData("Table(Table(T1, T2), T3)", "*[a:n, b:n, c:n, d:n]")] - [InlineData("Table(T1, Table(T2, T4))", "*[a:n, b:n, c:n]")] - [InlineData("Table(Table(T1, T2), T4)", "*[a:n, b:n, c:n]")] - [InlineData("Table([1,2], If(1/0<2,[3,4]), [5,6])", "*[Value: n]")] - [InlineData("Table(Sequence(20000))", "*[Value: n]")] - [InlineData("Table(Filter(T1, b = 5))", "*[a:n, b:n, c:n]")] - [InlineData("Table({X:1}, [1, 2, 3])", "*[X:n, Value:n]")] - [InlineData("Table(First(DS))", "*[Id:n, Name:s, Age:n]")] - [InlineData("Table(First(DS), Last(DS))", "*[Id:n, Name:s, Age:n]")] - [InlineData("Table(Last(DS), {count: CountRows(DS)})", "*[Id:n, Name:s, Age:n, count:n]")] + [InlineData("Table(Table(Table()))", "*[]")] + [InlineData("Table(Blank(), Blank())", "*[]")] + [InlineData("Table([1, 2, 3, 4], Blank())", "*[Value:n]")] + [InlineData("Table([{a:0, b:false, c:\"Hello\"}], [{a:1, b:true, c:\"World\"}])", "*[a:n, b:b, c:s]")] + [InlineData("Table([{a:0}], [{b:true}], [{c:\"Hello\"}])", "*[a:n, b:b, c:s]")] + [InlineData("Table(Blank(), T2, Blank(), T1)", "*[a:n, b:b, c:s]")] + [InlineData("Table([{a:0}], [{b:true}], [{c:\"Hello\", d: {x: \"World\"}}])", "*[a:n, b:b, c:s, d:![x:s]]")] + [InlineData("Table(T1, T2)", "*[a:n, b:n, c:n]")] + [InlineData("Table(T2, T1)", "*[a:n, b:b, c:s]")] + [InlineData("Table(T2, Table(T1, T2))", "*[a:n, b:b, c:s]")] + [InlineData("Table(T1, Table(T2, T3))", "*[a:n, b:n, c:n, d:n]")] + [InlineData("Table(Table(T1, T2), T3)", "*[a:n, b:n, c:n, d:n]")] + [InlineData("Table(T1, Table(T2, T4))", "*[a:n, b:n, c:n]")] + [InlineData("Table(Table(T1, T2), T4)", "*[a:n, b:n, c:n]")] + [InlineData("Table([1,2], If(1/0<2,[3,4]), [5,6])", "*[Value: n]")] + [InlineData("Table(Sequence(20000))", "*[Value: n]")] + [InlineData("Table(Filter(T1, b = 5))", "*[a:n, b:n, c:n]")] + [InlineData("Table({X:1}, [1, 2, 3])", "*[X:n, Value:n]")] + [InlineData("Table(First(DS))", "*[Id:n, Name:s, Age:n]")] + [InlineData("Table(First(DS), Last(DS))", "*[Id:n, Name:s, Age:n]")] + [InlineData("Table(Last(DS), {count: CountRows(DS)})", "*[Id:n, Name:s, Age:n, count:n]")] [InlineData("With({majors: Filter(DS, Age >= 18)},Table(majors,{Id:0, Name: \"Other\", Age:100}))", "*[Id:n, Name:s, Age:n]")] public void TexlFunctionTypeSemanticsTable(string script, string expectedType) - { + { var symbol = new DelegatableSymbolTable(); symbol.AddVariable("T1", new TableType(TestUtils.DT("*[a:n, b:n, c:n]"))); - symbol.AddVariable("T2", new TableType(TestUtils.DT("*[a:n, b:b, c:s]"))); - symbol.AddVariable("T3", new TableType(TestUtils.DT("*[d:n]"))); - symbol.AddVariable("T4", new TableType(TestUtils.DT("*[a:s]"))); - + symbol.AddVariable("T2", new TableType(TestUtils.DT("*[a:n, b:b, c:s]"))); + symbol.AddVariable("T3", new TableType(TestUtils.DT("*[d:n]"))); + symbol.AddVariable("T4", new TableType(TestUtils.DT("*[a:s]"))); + var dataSourceSchema = TestUtils.DT("*[Id:n, Name:s, Age:n]"); symbol.AddEntity(new TestDelegableDataSource( "DS", @@ -2618,15 +2618,15 @@ namespace Microsoft.PowerFx.Core.Tests new Dictionary(), new Dictionary(), new DelegationCapability(DelegationCapability.Equal | DelegationCapability.GreaterThanOrEqual), - null)), - true)); + null)), + true)); Assert.True(DType.TryParse(expectedType, out DType type)); Assert.True(type.IsValid); TestSimpleBindingSuccess( script, type, - symbol, + symbol, Features.PowerFxV1); } @@ -2634,22 +2634,22 @@ namespace Microsoft.PowerFx.Core.Tests [InlineData("Table(1, 2, 3)", "*[]")] [InlineData("Table(true, false)", "*[]")] [InlineData("Table(true, 2, \"hello\")", "*[]")] - [InlineData("Table(\"hello\", \"world\")", "*[]")] - [InlineData("Table(T1, T2)", "*[V: n]")] - [InlineData("Table([{a:Date(2024,1,1)}], [{a:GUID(\"some-guid-value-1234\")}])", "*[a: D]")] + [InlineData("Table(\"hello\", \"world\")", "*[]")] + [InlineData("Table(T1, T2)", "*[V: n]")] + [InlineData("Table([{a:Date(2024,1,1)}], [{a:GUID(\"some-guid-value-1234\")}])", "*[a: D]")] [InlineData("Table([{a:1}], 1/0, {a:3}, [{a:4}])", "*[a: n]")] public void TexlFunctionTypeSemanticsTable_Negative(string script, string expectedType) - { + { var symbol = new SymbolTable(); symbol.AddVariable("T1", new TableType(TestUtils.DT("*[V:n]"))); - symbol.AddVariable("T2", new TableType(TestUtils.DT("*[V:![a:n]]"))); + symbol.AddVariable("T2", new TableType(TestUtils.DT("*[V:![a:n]]"))); Assert.True(DType.TryParse(expectedType, out DType type)); Assert.True(type.IsValid); TestBindingErrors( script, type, - symbol, + symbol, features: Features.PowerFxV1); } @@ -2855,7 +2855,7 @@ namespace Microsoft.PowerFx.Core.Tests var node = result.Root; Assert.NotNull(node); Assert.Equal(expectedDepth, node.Depth); - } + } [Fact] public void TexlFunctionThatSupportsCoercedParams() @@ -2970,98 +2970,98 @@ namespace Microsoft.PowerFx.Core.Tests public void TestBlankFunction_Positive() { TestSimpleBindingSuccess("Blank()", TestUtils.DT("N")); - } + } [Fact] public void TestBlankFunction_Negative() { TestBindingErrors("Blank(\"null\")", TestUtils.DT("N")); - } - - [Theory] - [InlineData(@"{%%:""hi""}", "![%%:s]", "![_:e]")] - [InlineData(@"With( { %%: 3 }, %% )", "w", "?")] - [InlineData(@"With( { %%: 3 }, { Value: %% } )", "![Value:w]", "?")] - [InlineData(@"AddColumns( [1,2,3], %%, Value*3 )", "*[%%:w, Value:w]", "*[Value:w]")] + } + + [Theory] + [InlineData(@"{%%:""hi""}", "![%%:s]", "![_:e]")] + [InlineData(@"With( { %%: 3 }, %% )", "w", "?")] + [InlineData(@"With( { %%: 3 }, { Value: %% } )", "![Value:w]", "?")] + [InlineData(@"AddColumns( [1,2,3], %%, Value*3 )", "*[%%:w, Value:w]", "*[Value:w]")] public void TestReservedWords_Disallowed(string script, string successType, string failType) - { - // OkToUse should work, all others include As (an existing keyword) should fail - string[] words = new string[] { "OkToUse", "As", "This", "blank", "null", "nothing", "undefined", "none", "empty", "Is", "Child", "Children", "Siblings" }; - - var config = new PowerFxConfig(Features.PowerFxV1); - var engine = new Engine(config); - var opts = new ParserOptions(); - - foreach (var word in words) - { - var scriptWord = script.Replace("%%", word); - var successTypeWord = TestUtils.DT(successType.Replace("%%", word)); - var failTypeWord = TestUtils.DT(failType.Replace("%%", word)); - var result = engine.Check(scriptWord, opts); - - if (word == "OkToUse") - { - Assert.True(result.IsSuccess, $"should succeed: {word}"); - Assert.Equal(successTypeWord, result.Binding.ResultType); - } - else - { - Assert.False(result.IsSuccess, $"should fail: {word}"); - Assert.Equal(failTypeWord, result.Binding.ResultType); - } - - // should always work if enclosed in single quotes - scriptWord = script.Replace("%%", $"'{word}'"); - successTypeWord = TestUtils.DT(successType.Replace("%%", word)); - failTypeWord = TestUtils.DT(failType.Replace("%%", word)); - result = engine.Check(scriptWord, opts); - - Assert.True(result.IsSuccess, $"should succeed with single quotes: {word}"); - Assert.Equal(successTypeWord, result.Binding.ResultType); - } - } - - [Theory] - [InlineData(@"{%%:""hi""}", "![%%:s]", "![_:e]")] - [InlineData(@"With( { %%: 3 }, %% )", "w", "?")] - [InlineData(@"With( { %%: 3 }, { Value: %% } )", "![Value:w]", "?")] + { + // OkToUse should work, all others include As (an existing keyword) should fail + string[] words = new string[] { "OkToUse", "As", "This", "blank", "null", "nothing", "undefined", "none", "empty", "Is", "Child", "Children", "Siblings" }; + + var config = new PowerFxConfig(Features.PowerFxV1); + var engine = new Engine(config); + var opts = new ParserOptions(); + + foreach (var word in words) + { + var scriptWord = script.Replace("%%", word); + var successTypeWord = TestUtils.DT(successType.Replace("%%", word)); + var failTypeWord = TestUtils.DT(failType.Replace("%%", word)); + var result = engine.Check(scriptWord, opts); + + if (word == "OkToUse") + { + Assert.True(result.IsSuccess, $"should succeed: {word}"); + Assert.Equal(successTypeWord, result.Binding.ResultType); + } + else + { + Assert.False(result.IsSuccess, $"should fail: {word}"); + Assert.Equal(failTypeWord, result.Binding.ResultType); + } + + // should always work if enclosed in single quotes + scriptWord = script.Replace("%%", $"'{word}'"); + successTypeWord = TestUtils.DT(successType.Replace("%%", word)); + failTypeWord = TestUtils.DT(failType.Replace("%%", word)); + result = engine.Check(scriptWord, opts); + + Assert.True(result.IsSuccess, $"should succeed with single quotes: {word}"); + Assert.Equal(successTypeWord, result.Binding.ResultType); + } + } + + [Theory] + [InlineData(@"{%%:""hi""}", "![%%:s]", "![_:e]")] + [InlineData(@"With( { %%: 3 }, %% )", "w", "?")] + [InlineData(@"With( { %%: 3 }, { Value: %% } )", "![Value:w]", "?")] public void TestReservedWords_Allowed(string script, string successType, string failType) - { - // As should fail (an existing keyword), all others should work - string[] words = new string[] { "OkToUse", "As", "This", "blank", "null", "nothing", "undefined", "none", "empty", "Is", "Child", "Children", "Siblings" }; - - var config = new PowerFxConfig(); - var engine = new Engine(config); - var opts = new ParserOptions() { DisableReservedKeywords = true }; - - foreach (var word in words) - { - var scriptWord = script.Replace("%%", word); - var successTypeWord = TestUtils.DT(successType.Replace("%%", word)); - var failTypeWord = TestUtils.DT(failType.Replace("%%", word)); - var result = engine.Check(scriptWord, opts); - - if (word != "As") - { - Assert.True(result.IsSuccess, $"should succeed: {word}"); - Assert.Equal(successTypeWord, result.Binding.ResultType); - } - else - { - Assert.False(result.IsSuccess, $"should fail: {word}"); - Assert.Equal(failTypeWord, result.Binding.ResultType); - } - - // should always work if enclosed in single quotes - scriptWord = script.Replace("%%", $"'{word}'"); - successTypeWord = TestUtils.DT(successType.Replace("%%", word)); - failTypeWord = TestUtils.DT(failType.Replace("%%", word)); - result = engine.Check(scriptWord, opts); - - Assert.True(result.IsSuccess, $"should succeed with single quotes: {word}"); - Assert.Equal(successTypeWord, result.Binding.ResultType); - } - } + { + // As should fail (an existing keyword), all others should work + string[] words = new string[] { "OkToUse", "As", "This", "blank", "null", "nothing", "undefined", "none", "empty", "Is", "Child", "Children", "Siblings" }; + + var config = new PowerFxConfig(); + var engine = new Engine(config); + var opts = new ParserOptions() { DisableReservedKeywords = true }; + + foreach (var word in words) + { + var scriptWord = script.Replace("%%", word); + var successTypeWord = TestUtils.DT(successType.Replace("%%", word)); + var failTypeWord = TestUtils.DT(failType.Replace("%%", word)); + var result = engine.Check(scriptWord, opts); + + if (word != "As") + { + Assert.True(result.IsSuccess, $"should succeed: {word}"); + Assert.Equal(successTypeWord, result.Binding.ResultType); + } + else + { + Assert.False(result.IsSuccess, $"should fail: {word}"); + Assert.Equal(failTypeWord, result.Binding.ResultType); + } + + // should always work if enclosed in single quotes + scriptWord = script.Replace("%%", $"'{word}'"); + successTypeWord = TestUtils.DT(successType.Replace("%%", word)); + failTypeWord = TestUtils.DT(failType.Replace("%%", word)); + result = engine.Check(scriptWord, opts); + + Assert.True(result.IsSuccess, $"should succeed with single quotes: {word}"); + Assert.Equal(successTypeWord, result.Binding.ResultType); + } + } [Theory] [InlineData("With({A: 1, B: \"test\"}, B & \" \" & A)", "![A:n, B:s]", "s")] @@ -3143,40 +3143,40 @@ namespace Microsoft.PowerFx.Core.Tests [Theory] [InlineData("Sum(T, 1)", "w")] - [InlineData("Average(T, \"Item\")", "w")] - [InlineData("Filter(T, true)", "*[Item:n]")] + [InlineData("Average(T, \"Item\")", "w")] + [InlineData("Filter(T, true)", "*[Item:n]")] [InlineData("Sum(TW, 1)", "w")] - [InlineData("Average(TW, \"Item\")", "w")] - [InlineData("Filter(TW, true)", "*[Item:w]")] + [InlineData("Average(TW, \"Item\")", "w")] + [InlineData("Filter(TW, true)", "*[Item:w]")] public void TestWarningOnLiteralPredicate(string script, string expectedType) { var symbol = new SymbolTable(); - symbol.AddVariable("T", new TableType(TestUtils.DT("*[Item:n]"))); + symbol.AddVariable("T", new TableType(TestUtils.DT("*[Item:n]"))); symbol.AddVariable("TW", new TableType(TestUtils.DT("*[Item:w]"))); TestBindingWarning( script, TestUtils.DT(expectedType), expectedErrorCount: null, symbolTable: symbol); - } - + } + [Theory] [InlineData("Sum(T, 1)", "n")] - [InlineData("Average(T, \"Item\")", "n")] - [InlineData("Filter(T, true)", "*[Item:n]")] + [InlineData("Average(T, \"Item\")", "n")] + [InlineData("Filter(T, true)", "*[Item:n]")] [InlineData("Sum(TW, 1)", "n")] - [InlineData("Average(TW, \"Item\")", "n")] - [InlineData("Filter(TW, true)", "*[Item:w]")] + [InlineData("Average(TW, \"Item\")", "n")] + [InlineData("Filter(TW, true)", "*[Item:w]")] public void TestWarningOnLiteralPredicate_NumberIsFloat(string script, string expectedType) { var symbol = new SymbolTable(); - symbol.AddVariable("T", new TableType(TestUtils.DT("*[Item:n]"))); + symbol.AddVariable("T", new TableType(TestUtils.DT("*[Item:n]"))); symbol.AddVariable("TW", new TableType(TestUtils.DT("*[Item:w]"))); TestBindingWarning( script, TestUtils.DT(expectedType), expectedErrorCount: null, - symbolTable: symbol, + symbolTable: symbol, numberIsFloat: true); } @@ -3985,10 +3985,10 @@ namespace Microsoft.PowerFx.Core.Tests symbol.AddEntity(new TestDataSource("DS", schema)); TestSimpleBindingSuccess(script, expectedType, symbol); - } - + } + [Theory] - [InlineData("Search(T, \"a\", \"Name\")", "*[Name:s]")] + [InlineData("Search(T, \"a\", \"Name\")", "*[Name:s]")] [InlineData("Search(T, If(1<0,\"a\"), \"Name\")", "*[Name:s]")] [InlineData("Search(T2,\"a\", \"Name\", \"Address\")", "*[Name:s,Age:n,Address:s]")] public void TexlFunctionTypeSemanticsSearch(string script, string expectedType) @@ -4001,10 +4001,10 @@ namespace Microsoft.PowerFx.Core.Tests script, TestUtils.DT(expectedType), symbol); - } - + } + [Theory] - [InlineData("Search(T, \"a\", Name)", "*[Name:s]")] + [InlineData("Search(T, \"a\", Name)", "*[Name:s]")] [InlineData("Search(T, If(1<0,\"a\"), Name)", "*[Name:s]")] [InlineData("Search(T2,\"a\", Name, Address)", "*[Name:s,Age:n,Address:s]")] public void TexlFunctionTypeSemanticsSearch_SupportColumnNamesAsIdentifiers(string script, string expectedType) @@ -4018,27 +4018,27 @@ namespace Microsoft.PowerFx.Core.Tests TestUtils.DT(expectedType), symbol, new Features { SupportColumnNamesAsIdentifiers = true }); - } - - [Theory] - - // 1st arg needs to be a table. - [InlineData("Search({Name: \"test\"}, \"a\", \"Name\")", "*[]")] - + } + + [Theory] + + // 1st arg needs to be a table. + [InlineData("Search({Name: \"test\"}, \"a\", \"Name\")", "*[]")] + // missing 3rd arg - [InlineData("Search(T, \"a\")", "*[]")] - + [InlineData("Search(T, \"a\")", "*[]")] + // Age is number, only string supported. - [InlineData("Search(T2, \"a\", \"Age\")", "*[Address:s, Age:n, Name:s]")] - [InlineData("Search(T3, \"a\", \"Age\")", "*[Age:n]")] - - // 3rd onwards Arg should always be constant string. - [InlineData("Search(T2, \"a\", If(1<0, \"Name\"))", "*[Address:s, Age:n, Name:s]")] - - // 2nd Arg should only be a string. - [InlineData("Search(T2, 1, \"Name\")", "*[]")] - - // missing field. + [InlineData("Search(T2, \"a\", \"Age\")", "*[Address:s, Age:n, Name:s]")] + [InlineData("Search(T3, \"a\", \"Age\")", "*[Age:n]")] + + // 3rd onwards Arg should always be constant string. + [InlineData("Search(T2, \"a\", If(1<0, \"Name\"))", "*[Address:s, Age:n, Name:s]")] + + // 2nd Arg should only be a string. + [InlineData("Search(T2, 1, \"Name\")", "*[]")] + + // missing field. [InlineData("Search(T2, \"a\", \"Name\", \"Address\", \"Does not exist\")", "*[Address:s, Age:n, Name:s]")] public void TexlFunctionTypeSemanticsSearch_Negative(string script, string expectedSchema) { @@ -4051,27 +4051,27 @@ namespace Microsoft.PowerFx.Core.Tests script, TestUtils.DT(expectedSchema), symbolTable: symbol); - } - - [Theory] - - // 1st arg needs to be a table. - [InlineData("Search({Name: \"test\"}, \"a\", Name)", "*[]")] - + } + + [Theory] + + // 1st arg needs to be a table. + [InlineData("Search({Name: \"test\"}, \"a\", Name)", "*[]")] + // missing 3rd arg - [InlineData("Search(T, \"a\")", "*[]")] - + [InlineData("Search(T, \"a\")", "*[]")] + // Age is number, only string supported. - [InlineData("Search(T2, \"a\", Age)", "*[Address:s, Age:n, Name:s]")] - [InlineData("Search(T3, \"a\", Age)", "*[Age:n]")] - - // 3rd onwards Arg should always be identifier. - [InlineData("Search(T2, \"a\", If(1<0, Name))", "*[Address:s, Age:n, Name:s]")] - - // 2nd Arg should only be a string. - [InlineData("Search(T2, 1, Name)", "*[]")] - - // missing field. + [InlineData("Search(T2, \"a\", Age)", "*[Address:s, Age:n, Name:s]")] + [InlineData("Search(T3, \"a\", Age)", "*[Age:n]")] + + // 3rd onwards Arg should always be identifier. + [InlineData("Search(T2, \"a\", If(1<0, Name))", "*[Address:s, Age:n, Name:s]")] + + // 2nd Arg should only be a string. + [InlineData("Search(T2, 1, Name)", "*[]")] + + // missing field. [InlineData("Search(T2, \"a\", Name, Address, 'Does not exist')", "*[Address:s, Age:n, Name:s]")] public void TexlFunctionTypeSemanticsSearch_SupportColumnNamesAsIdentifiers_Negative(string script, string expectedSchema) { @@ -4162,139 +4162,139 @@ namespace Microsoft.PowerFx.Core.Tests { symbol.RemoveFunction(silentFilterFunction); } - } - - [Theory] - - // Single column access syntax is supported pre PowerFxV1 rules. - [InlineData("T.A", "*[A:n]", false)] - [InlineData("T.B", "*[B:![C:s]]", false)] - - // Single column access syntax is not supported PowerFxV1 rules onwards. - [InlineData("T.A", "e", true)] - [InlineData("T.B", "e", true)] - public void TestSingleColumnAccessDependentOnTableType(string script, string expectedSchema, bool usePowerFxV1Rules) - { - var symbol = new SymbolTable(); - symbol.AddVariable("T", new TableType(TestUtils.DT("*[A:n, B:![C:s]]"))); - - if (usePowerFxV1Rules) - { - var features = new Features() { PowerFxV1CompatibilityRules = true }; - TestBindingErrors( - script, - TestUtils.DT(expectedSchema), - symbol, - features: features); - } - else - { - TestSimpleBindingSuccess( - script, - TestUtils.DT(expectedSchema), - symbol, - features: Features.None); - } } - [Theory] - [InlineData("ColumnNames(ParseJSON('Hello'))")] - [InlineData("ColumnNames(ParseJSON('[]'))")] - [InlineData("ColumnNames(ParseJSON('Hello').a)")] - [InlineData("ColumnNames(ParseJSON('{''a'':1}').a)")] - [InlineData("ColumnNames(Blank())")] - [InlineData("ColumnNames(ParseJSON('{''a'':{''b'':1}}').a)")] - public void TexlFunctionTypeSemanticsColumnNames(string expression) - { - var engine = new Engine(new PowerFxConfig()); - var result = engine.Check(expression.Replace('\'', '\"')); - - Assert.True(DType.TryParse("*[Value:s]", out var expectedDType)); - Assert.Equal(expectedDType, result.Binding.ResultType); - Assert.True(result.IsSuccess); - } - - [Theory] - [InlineData("ColumnNames({a:1,b:true})")] // Does not work with records - [InlineData("ColumnNames([1,2,3])")] // Does not work with arrays - [InlineData("ColumnNames(1)")] - public void TexlFunctionTypeSemanticsColumnNames_Negative(string expression) - { - var engine = new Engine(new PowerFxConfig()); - var result = engine.Check(expression); - - Assert.False(result.IsSuccess); - } - - [Theory] - [InlineData("Column(ParseJSON('Hello'), 'a')")] // Compiles, would fail at runtime - [InlineData("Column(ParseJSON('[]'), 'Value')")] // Compiles, would fail at runtime - [InlineData("Column(ParseJSON('Hello').a, 'a')")] // Compiles, would fail at runtime - [InlineData("Column(ParseJSON('{''a'':1}'), 'a')")] - [InlineData("Column(ParseJSON('{''a'':1}').a, 'a')")] // Compiles, would fail at runtime - [InlineData("Column(Blank(), 'a')")] - [InlineData("Column(ParseJSON('{''a'':{''b'':1}}'), 'a')")] - public void TexlFunctionTypeSemanticsColumn(string expression) - { - var engine = new Engine(new PowerFxConfig()); - var result = engine.Check(expression.Replace('\'', '\"')); - - Assert.Equal(DType.UntypedObject, result.Binding.ResultType); - Assert.True(result.IsSuccess); - } - - [Theory] - [InlineData("Column({a:1,b:true}, 'a')")] // Does not work with records - [InlineData("Column([1,2,3], 'Value')")] // Does not work with arrays - [InlineData("Column(1)")] - [InlineData("Column(1, 'Value')")] - public void TexlFunctionTypeSemanticColumn_Negative(string expression) - { - var engine = new Engine(new PowerFxConfig()); - var result = engine.Check(expression.Replace('\'', '\"')); - - Assert.False(result.IsSuccess); - } - - [Theory] - [InlineData("in")] - [InlineData("exactin")] - public void PrettyPrintTest(string op) - { - string expr = $"1 {op} [0]"; - - CheckResult check = new CheckResult(new Engine()).SetText(expr); - ParseResult parse = check.ApplyParse(); - - string str = parse.Root.ToString(); - - Assert.Equal($"1 {op} [ 0 ]", str); - } - - internal class BlobFunc : TexlFunction - { - public override bool IsSelfContained => false; - - public BlobFunc() - : base(DPath.Root, "BlobFunc", "BlobFunc", (_) => "Returns a blob", FunctionCategories.Behavior, DType.Blob, 0, 0, 0) - { - } - - public override IEnumerable GetSignatures() - { - throw new NotImplementedException(); - } - } - - [Theory] - [InlineData("BlobFunc()", "o")] - [InlineData("blobVar", "o")] - public void TestBlobFunction(string expression, string expectedType) - { - var symbolTable = new SymbolTable(); - symbolTable.AddFunction(new BlobFunc()); - symbolTable.AddVariable("blobVar", FormulaType.Blob); - + [Theory] + + // Single column access syntax is supported pre PowerFxV1 rules. + [InlineData("T.A", "*[A:n]", false)] + [InlineData("T.B", "*[B:![C:s]]", false)] + + // Single column access syntax is not supported PowerFxV1 rules onwards. + [InlineData("T.A", "e", true)] + [InlineData("T.B", "e", true)] + public void TestSingleColumnAccessDependentOnTableType(string script, string expectedSchema, bool usePowerFxV1Rules) + { + var symbol = new SymbolTable(); + symbol.AddVariable("T", new TableType(TestUtils.DT("*[A:n, B:![C:s]]"))); + + if (usePowerFxV1Rules) + { + var features = new Features() { PowerFxV1CompatibilityRules = true }; + TestBindingErrors( + script, + TestUtils.DT(expectedSchema), + symbol, + features: features); + } + else + { + TestSimpleBindingSuccess( + script, + TestUtils.DT(expectedSchema), + symbol, + features: Features.None); + } + } + + [Theory] + [InlineData("ColumnNames(ParseJSON('Hello'))")] + [InlineData("ColumnNames(ParseJSON('[]'))")] + [InlineData("ColumnNames(ParseJSON('Hello').a)")] + [InlineData("ColumnNames(ParseJSON('{''a'':1}').a)")] + [InlineData("ColumnNames(Blank())")] + [InlineData("ColumnNames(ParseJSON('{''a'':{''b'':1}}').a)")] + public void TexlFunctionTypeSemanticsColumnNames(string expression) + { + var engine = new Engine(new PowerFxConfig()); + var result = engine.Check(expression.Replace('\'', '\"')); + + Assert.True(DType.TryParse("*[Value:s]", out var expectedDType)); + Assert.Equal(expectedDType, result.Binding.ResultType); + Assert.True(result.IsSuccess); + } + + [Theory] + [InlineData("ColumnNames({a:1,b:true})")] // Does not work with records + [InlineData("ColumnNames([1,2,3])")] // Does not work with arrays + [InlineData("ColumnNames(1)")] + public void TexlFunctionTypeSemanticsColumnNames_Negative(string expression) + { + var engine = new Engine(new PowerFxConfig()); + var result = engine.Check(expression); + + Assert.False(result.IsSuccess); + } + + [Theory] + [InlineData("Column(ParseJSON('Hello'), 'a')")] // Compiles, would fail at runtime + [InlineData("Column(ParseJSON('[]'), 'Value')")] // Compiles, would fail at runtime + [InlineData("Column(ParseJSON('Hello').a, 'a')")] // Compiles, would fail at runtime + [InlineData("Column(ParseJSON('{''a'':1}'), 'a')")] + [InlineData("Column(ParseJSON('{''a'':1}').a, 'a')")] // Compiles, would fail at runtime + [InlineData("Column(Blank(), 'a')")] + [InlineData("Column(ParseJSON('{''a'':{''b'':1}}'), 'a')")] + public void TexlFunctionTypeSemanticsColumn(string expression) + { + var engine = new Engine(new PowerFxConfig()); + var result = engine.Check(expression.Replace('\'', '\"')); + + Assert.Equal(DType.UntypedObject, result.Binding.ResultType); + Assert.True(result.IsSuccess); + } + + [Theory] + [InlineData("Column({a:1,b:true}, 'a')")] // Does not work with records + [InlineData("Column([1,2,3], 'Value')")] // Does not work with arrays + [InlineData("Column(1)")] + [InlineData("Column(1, 'Value')")] + public void TexlFunctionTypeSemanticColumn_Negative(string expression) + { + var engine = new Engine(new PowerFxConfig()); + var result = engine.Check(expression.Replace('\'', '\"')); + + Assert.False(result.IsSuccess); + } + + [Theory] + [InlineData("in")] + [InlineData("exactin")] + public void PrettyPrintTest(string op) + { + string expr = $"1 {op} [0]"; + + CheckResult check = new CheckResult(new Engine()).SetText(expr); + ParseResult parse = check.ApplyParse(); + + string str = parse.Root.ToString(); + + Assert.Equal($"1 {op} [ 0 ]", str); + } + + internal class BlobFunc : TexlFunction + { + public override bool IsSelfContained => false; + + public BlobFunc() + : base(DPath.Root, "BlobFunc", "BlobFunc", (_) => "Returns a blob", FunctionCategories.Behavior, DType.Blob, 0, 0, 0) + { + } + + public override IEnumerable GetSignatures() + { + throw new NotImplementedException(); + } + } + + [Theory] + [InlineData("BlobFunc()", "o")] + [InlineData("blobVar", "o")] + public void TestBlobFunction(string expression, string expectedType) + { + var symbolTable = new SymbolTable(); + symbolTable.AddFunction(new BlobFunc()); + symbolTable.AddVariable("blobVar", FormulaType.Blob); + var config = new PowerFxConfig() { SymbolTable = symbolTable @@ -4302,21 +4302,21 @@ namespace Microsoft.PowerFx.Core.Tests var engine = new Engine(config); var opts = new ParserOptions() { AllowsSideEffects = true }; - var result = engine.Check(expression, opts); + var result = engine.Check(expression, opts); var expectedDType = TestUtils.DT(expectedType); Assert.Equal(expectedDType, result.Binding.ResultType); Assert.True(result.IsSuccess); - } - - [Theory] + } + + [Theory] [InlineData("Table(DS, Blank())", "*[Id:n, Name:s, Age:n]", 1)] - [InlineData("Table(DS, T1)", "*[Id:n, Name:s, Age:n, a:n, b:s]", 1)] - [InlineData("Table(DS, Filter(DS, Name = \"Foo\"))", "*[Id:n, Name:s, Age:n]", 2)] - [InlineData("Table([], Table(DS, []))", "*[Id:n, Name:s, Age:n]", 1)] - [InlineData("Table([], Table([], Search(DS, \"Foo\", Name)))", "*[Id:n, Name:s, Age:n]", 1)] - [InlineData("Table(Search(DS, \"Foo\", Name), FirstN(LastN(DS, 10), 5))", "*[Id:n, Name:s, Age:n]", 1)] - - // existing warning due to sqrt should propagate, no new warnigns on table + [InlineData("Table(DS, T1)", "*[Id:n, Name:s, Age:n, a:n, b:s]", 1)] + [InlineData("Table(DS, Filter(DS, Name = \"Foo\"))", "*[Id:n, Name:s, Age:n]", 2)] + [InlineData("Table([], Table(DS, []))", "*[Id:n, Name:s, Age:n]", 1)] + [InlineData("Table([], Table([], Search(DS, \"Foo\", Name)))", "*[Id:n, Name:s, Age:n]", 1)] + [InlineData("Table(Search(DS, \"Foo\", Name), FirstN(LastN(DS, 10), 5))", "*[Id:n, Name:s, Age:n]", 1)] + + // existing warning due to sqrt should propagate, no new warnigns on table [InlineData("Table(Filter(DS, Sqrt(Age) > 5), FirstN(LastN(DS, 10), 5))", "*[Id:n, Name:s, Age:n]", 1)] public void TexlFunctionTypeSemanticsTable_PageableInputs(string script, string expectedSchema, int errorCount) { @@ -4332,24 +4332,24 @@ namespace Microsoft.PowerFx.Core.Tests new FilterOpMetadata( dataSourceSchema, new Dictionary(), - new Dictionary - { - { DPath.Root.Append(new DName("Name")), new DelegationCapability(DelegationCapability.Filter | DelegationCapability.Contains) } + new Dictionary + { + { DPath.Root.Append(new DName("Name")), new DelegationCapability(DelegationCapability.Filter | DelegationCapability.Contains) } }, new DelegationCapability(DelegationCapability.Equal), - null)), - true)); - + null)), + true)); + symbol.AddVariable("T1", new TableType(TestUtils.DT("*[a:n, b:s]"))); - TestBindingWarning( - script, - TestUtils.DT(expectedSchema), - errorCount, - symbol, + TestBindingWarning( + script, + TestUtils.DT(expectedSchema), + errorCount, + symbol, features: Features.PowerFxV1); - } - + } + private void TestBindingPurity(string script, bool isPure, SymbolTable symbolTable = null) { var config = new PowerFxConfig @@ -4366,10 +4366,10 @@ namespace Microsoft.PowerFx.Core.Tests private void TestBindingWarning(string script, DType expectedType, int? expectedErrorCount, SymbolTable symbolTable = null, bool numberIsFloat = false, Features features = null) { - var config = features != null ? new PowerFxConfig(features) : new PowerFxConfig(Features.None); - var parserOptions = new ParserOptions() - { - NumberIsFloat = numberIsFloat + var config = features != null ? new PowerFxConfig(features) : new PowerFxConfig(Features.None); + var parserOptions = new ParserOptions() + { + NumberIsFloat = numberIsFloat }; var engine = new Engine(config); @@ -4446,7 +4446,7 @@ namespace Microsoft.PowerFx.Core.Tests var engine = new Engine(config); var opts = new ParserOptions() { NumberIsFloat = numberIsFloat }; var result = engine.Check(script, opts); - Assert.Equal(expectedType, result.Binding.ResultType); + Assert.Equal(expectedType, result.Binding.ResultType); Assert.False(result.Binding.ErrorContainer.HasErrors()); Assert.True(result.IsSuccess); } diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/ThreadingTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ThreadingTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/ThreadingTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/ThreadingTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/TopologicalSortTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/TopologicalSortTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/TopologicalSortTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/TopologicalSortTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/TypeSystemTests/DTypeTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/TypeSystemTests/DTypeTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/TypeSystemTests/DTypeTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/TypeSystemTests/DTypeTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/TypeSystemTests/LazyTypeTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/TypeSystemTests/LazyTypeTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/TypeSystemTests/LazyTypeTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/TypeSystemTests/LazyTypeTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/TypeSystemTests/TypeTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/TypeSystemTests/TypeTests.cs similarity index 99% rename from src/tests/Microsoft.PowerFx.Core.Tests/TypeSystemTests/TypeTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/TypeSystemTests/TypeTests.cs index fb1f91c0c..1e63d8a43 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests/TypeSystemTests/TypeTests.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/TypeSystemTests/TypeTests.cs @@ -81,22 +81,22 @@ namespace Microsoft.PowerFx.Tests Assert.Throws( () => r1.TryGetFieldType(string.Empty, out var actualLogical, out var formulaType)); - } - - [Fact] - public void DefaultOptionSetValueTypeTest() - { - var opt = new OptionSetValueType(); - Assert.Null(opt.LogicalNames); - Assert.False(opt.TryGetValue("random", out _)); - } - - [Fact] - public void PolymorphicRecordTypeTest() - { - var recordType = RecordType.Polymorphic(); - Assert.True(recordType is RecordType); - Assert.True(recordType._type.IsPolymorphic); + } + + [Fact] + public void DefaultOptionSetValueTypeTest() + { + var opt = new OptionSetValueType(); + Assert.Null(opt.LogicalNames); + Assert.False(opt.TryGetValue("random", out _)); + } + + [Fact] + public void PolymorphicRecordTypeTest() + { + var recordType = RecordType.Polymorphic(); + Assert.True(recordType is RecordType); + Assert.True(recordType._type.IsPolymorphic); } } } diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/UserDefinedFunctionTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/UserDefinedFunctionTests.cs similarity index 98% rename from src/tests/Microsoft.PowerFx.Core.Tests/UserDefinedFunctionTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/UserDefinedFunctionTests.cs index 6c241c336..5d80fc24c 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests/UserDefinedFunctionTests.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/UserDefinedFunctionTests.cs @@ -1,580 +1,580 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -using System; -using System.Collections.Generic; -using System.Diagnostics.Contracts; -using System.Linq; -using System.Text; -using Microsoft.PowerFx.Core.Binding; -using Microsoft.PowerFx.Core.Errors; -using Microsoft.PowerFx.Core.Functions; -using Microsoft.PowerFx.Core.Glue; -using Microsoft.PowerFx.Core.IR; -using Microsoft.PowerFx.Core.Syntax; -using Microsoft.PowerFx.Core.Texl; -using Microsoft.PowerFx.Syntax; -using Microsoft.PowerFx.Types; -using Xunit; - -namespace Microsoft.PowerFx.Core.Tests -{ - public class UserDefinedFunctionTests : PowerFxTest - { - private static readonly ReadOnlySymbolTable _primitiveTypes = ReadOnlySymbolTable.PrimitiveTypesTableInstance; - - [Theory] - [InlineData("Foo(x: Number): Number = Abs(x);", 1, 0, false)] - [InlineData("IsType(x: Number): Number = Abs(x);", 0, 0, true)] - [InlineData("AsType(x: Number): Number = Abs(x);", 0, 0, true)] - [InlineData("Type(x: Number): Number = Abs(x);", 0, 0, true)] - [InlineData("Foo(x: Number): Number = Abs(x); x = 1;", 1, 1, false)] - [InlineData("x = 1; Foo(x: Number): Number = Abs(x);", 1, 1, false)] - [InlineData("/*this is a test*/ x = 1; Foo(x: Number): Number = Abs(x);", 1, 1, false)] - [InlineData("x = 1; Foo(x: Number): Number = Abs(x); y = 2;", 1, 2, false)] - [InlineData("Add(x: Number, y:Number): Number = x + y; Foo(x: Number): Number = Abs(x); y = 2;", 2, 1, false)] - [InlineData("Foo(x: Number): Number = /*this is a test*/ Abs(x); y = 2;", 1, 1, false)] - [InlineData("Add(x: Number, y:Number): Number = b + b; Foo(x: Number): Number = Abs(x); y = 2;", 2, 1, true)] - [InlineData("Add(x: Number, y:Number): Boolean = x + y;", 1, 0, false)] - [InlineData("Add(x: Number, y:Number): SomeType = x + y;", 0, 0, true)] - [InlineData("Add(x: SomeType, y:Number): Number = x + y;", 0, 0, true)] - [InlineData("Add(x: Number, y:Number): Number = x + y", 0, 0, true)] - [InlineData("x = 1; Add(x: Number, y:Number): Number = x + y", 0, 1, true)] - [InlineData("Add(x: Number, y:Number) = x + y;", 0, 0, true)] - [InlineData("Add(x): Number = x + 2;", 0, 0, true)] - [InlineData("Add(a:Number, b:Number): Number { a + b + 1; \n a + b; };", 0, 0, true)] - [InlineData("Add(a:Number, b:Number): Number { a + b; };", 0, 0, true)] - [InlineData("Add(a:Number, b:Number): Number { /*this is a test*/ a + b; };", 0, 0, true)] - [InlineData("Add(a:Number, b:Number): Number { /*this is a test*/ a + b; ;", 0, 0, true)] - [InlineData("Add(a:Number, a:Number): Number { a; };", 0, 0, true)] - [InlineData(@"F2(b: Number): Number = F1(b*3); F1(a:Number): Number = a*2;", 2, 0, false)] - [InlineData(@"F2(b: Text): Text = ""Test"";", 1, 0, false)] - [InlineData(@"F2(b: String): String = ""Test"";", 0, 0, true)] - public void TestUDFNamedFormulaCounts(string script, int udfCount, int namedFormulaCount, bool expectErrors) - { - var parserOptions = new ParserOptions() - { - AllowsSideEffects = false - }; - - var parseResult = UserDefinitions.Parse(script, parserOptions); - - var nameResolver = ReadOnlySymbolTable.NewDefault(BuiltinFunctionsCore._library, FormulaType.PrimitiveTypes); - - var udfs = UserDefinedFunction.CreateFunctions(parseResult.UDFs.Where(udf => udf.IsParseValid), nameResolver, out var errors); - errors.AddRange(parseResult.Errors ?? Enumerable.Empty()); - - var glue = new Glue2DocumentBinderGlue(); - var hasBinderErrors = false; - - foreach (var udf in udfs) - { - var binding = udf.BindBody(ReadOnlySymbolTable.Compose(nameResolver, ReadOnlySymbolTable.NewDefault(udfs)), glue, BindingConfig.Default); - hasBinderErrors |= binding.ErrorContainer.HasErrors(); - } - - Assert.Equal(udfCount, udfs.Count()); - Assert.Equal(namedFormulaCount, parseResult.NamedFormulas.Count()); - Assert.Equal(expectErrors, (errors?.Any() ?? false) || hasBinderErrors); - } - - [Theory] - [InlineData("Mul(x:Number, y:DateTime): DateTime = x * y;", "Mul(\"4a\", Date(1900, 1, 3))", "Mul:d(Float:n(\"4a\":s), DateToDateTime:d(Date:D(Coalesce:n(Float:n(1900:w), 0:n), Coalesce:n(Float:n(1:w), 0:n), Coalesce:n(Float:n(3:w), 0:n))))")] - public void TestCoercionWithUDFParams(string udfScript, string invocationScript, string expectedIR) - { - var parserOptions = new ParserOptions() - { - AllowsSideEffects = false - }; - - var nameResolver = ReadOnlySymbolTable.NewDefault(BuiltinFunctionsCore._library); - var glue = new Glue2DocumentBinderGlue(); - - var parseResult = UserDefinitions.Parse(udfScript, parserOptions); - var udfs = UserDefinedFunction.CreateFunctions(parseResult.UDFs.Where(udf => udf.IsParseValid), _primitiveTypes, out var errors); - errors.AddRange(parseResult.Errors ?? Enumerable.Empty()); - - var texlFunctionSet = new TexlFunctionSet(udfs); - - var engine = new Engine(); - var result = engine.Check(invocationScript, symbolTable: ReadOnlySymbolTable.Compose(ReadOnlySymbolTable.NewDefault(BuiltinFunctionsCore._library), ReadOnlySymbolTable.NewDefault(texlFunctionSet))); - - var actualIR = result.PrintIR(); - Assert.Equal(expectedIR, actualIR); - } - - [Theory] - [InlineData("Mul(x:Number, y:DateTime): DateTime = x * y;", "(NumberToDateTime:d(MulNumbers:n(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo), DateTimeToNumber:n(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)))), Scope 0)")] - [InlineData("sTon(x:Text): Number = x;", "(Float:n(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] - [InlineData("bTon(x:Boolean): Number = x;", "(BooleanToNumber:n(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] - [InlineData("dateTon(x:Date): Number = x;", "(DateToNumber:n(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] - [InlineData("timeTon(x:Time): Number = x;", "(TimeToNumber:n(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] - [InlineData("dateTimeTon(x:DateTime): Number = x;", "(DateTimeToNumber:n(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] - [InlineData("textToHyperlink(x:Text): Hyperlink = x;", "(TextToHyperlink:h(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] - [InlineData("nTos(x:Number): Text = x;", "(NumberToText:s(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] - [InlineData("bTos(x:Boolean): Text = x;", "(BooleanToText:s(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] - [InlineData("dateTos(x:Date): Text = x;", "(Text:s(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] - [InlineData("dateTimeTos(x:DateTime): Text = x;", "(Text:s(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] - [InlineData("timeTos(x:Time): Text = x;", "(Text:s(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] - [InlineData("nToBool(x:Number): Boolean = x;", "(NumberToBoolean:b(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] - [InlineData("sToBool(x:Text): Boolean = x;", "(TextToBoolean:b(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] - [InlineData("nToDateTime(x:Number): DateTime = x;", "(NumberToDateTime:d(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] - [InlineData("nToDate(x:Number): Date = x;", "(NumberToDate:D(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] - [InlineData("nToTime(x:Number): Time = x;", "(NumberToTime:T(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] - [InlineData("sToDateTime(x:Text): DateTime = x;", "(DateTimeValue:d(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] - [InlineData("sToDate(x:Text): Date = x;", "(DateValue:D(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] - [InlineData("sToTime(x:Text): Time = x;", "(TimeValue:T(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] - [InlineData("dateTimeToTime(x:DateTime): Time = x;", "(DateTimeToTime:T(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] - [InlineData("dateToTime(x:Date): Time = x;", "(DateToTime:T(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] - [InlineData("timeToDate(x:Time): Date = x;", "(TimeToDate:D(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] - [InlineData("dateTimeToDate(x:DateTime): Date = x;", "(DateTimeToDate:D(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] - [InlineData("timeToDateTime(x:Time): DateTime = x;", "(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo), Scope 0)")] - [InlineData("dateToDateTime(x:Date): DateTime = x;", "(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo), Scope 0)")] - [InlineData("textToGUID(x:Text): GUID = x;", "(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo), Scope 0)")] - [InlineData("GUIDToText(x:GUID): Text = x;", "(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo), Scope 0)")] - public void TestCoercionWithUDFBody(string udfScript, string expectedIR) - { - var parserOptions = new ParserOptions() - { - AllowsSideEffects = false - }; - - var nameResolver = ReadOnlySymbolTable.NewDefault(BuiltinFunctionsCore._library, FormulaType.PrimitiveTypes); - var glue = new Glue2DocumentBinderGlue(); - - var parseResult = UserDefinitions.Parse(udfScript, parserOptions); - var udfs = UserDefinedFunction.CreateFunctions(parseResult.UDFs.Where(udf => udf.IsParseValid), nameResolver, out var errors); - errors.AddRange(parseResult.Errors ?? Enumerable.Empty()); - - var texlFunctionSet = new TexlFunctionSet(udfs); - - Assert.Single(udfs); - - var udf = udfs.First(); - var binding = udf.BindBody(ReadOnlySymbolTable.Compose(nameResolver, ReadOnlySymbolTable.NewDefault(texlFunctionSet)), glue, BindingConfig.Default); - var actualIR = IRTranslator.Translate(binding).ToString(); - - Assert.Equal(expectedIR, actualIR); - } - - [Theory] - [InlineData("/* Comment1 */ Foo(x: Number): Number = /* Comment2 */ Abs(x) /* Comment3 */;/* Comment4 */", 4)] - [InlineData("Foo(x: Number): Number /* Comment1 */ = Abs(x);// Comment2", 2)] - public void TestCommentsFromUserDefinitionsScript(string script, int commentCount) - { - var parserOptions = new ParserOptions() - { - AllowsSideEffects = false - }; - - var parseResult = UserDefinitions.Parse(script, parserOptions); - - Assert.Equal(commentCount, parseResult.Comments.Count()); - } - - [Theory] - [InlineData("/* Comment1 */ Foo(x: Number): Number = Abs(x);", 0, 15)] - [InlineData("Foo(x: Number): Number /* Comment1 */ = Abs(x);", 23, 38)] - [InlineData("Foo(x: Number): Number = Abs(x) /* Comment1 */;", 32, 46)] - [InlineData("Foo(x: Number): Number = Abs(x); /* Comment1 */", 33, 47)] - public void TestCommentSpansFromUserDefinitionsScript(string script, int begin, int end) - { - var parserOptions = new ParserOptions() - { - AllowsSideEffects = false - }; - - var parseResult = UserDefinitions.Parse(script, parserOptions); - - Assert.Single(parseResult.Comments); - - var commentSpan = parseResult.Comments.First().Span; - - Assert.Equal(commentSpan.Min, begin); - Assert.Equal(commentSpan.Lim, end); - } - - [Theory] - [InlineData("a = Abs(1.2);\nAdd(a: Number, b: Number):Number = a + b;\nb = Add(1, 2);", 2, 1, 0)] - [InlineData("a = Abs(1.2);\nAdd(a: Number, b:):Number = a + b;\nb = Add(1, 2); ", 2, 0, 1)] - [InlineData("a = Abs(1.2);\nAdd(a: Number, b:/*comment*/):Number = a + b;\nb = Add(1, 2); ", 2, 0, 1)] - [InlineData("a = Abs(1.2);\nAdd(a: Number, b:/*comment*/Number):Number = a + b;\nb = Add(1, 2); ", 2, 1, 0)] - [InlineData("a = Abs(1.2);\nF1(a: Number):Number = a;\nb = F1(1, 2); F2(", 2, 1, 1)] - [InlineData("a = Abs(1.2);\nF1(a: Number):Number = a;\nb = F1(1, 2); F2(a):Number = 1;", 2, 1, 1)] - [InlineData("a = Abs(1.2);\nF1(a: Number):Number = a;\nb = F1(1, 2); F2(a):Number = ;", 2, 1, 1)] - [InlineData("a = Abs(1.2);\nF1(a: Number):Number = a;\nb = F1(1, 2); F2(a:Number): = 1;", 2, 1, 1)] - [InlineData(@"A(a:Number, b:/*comment*/Number):Number = 12;b(a:Number): = 1;c(a:Number):Number = 100;x = 10;", 1, 2, 1)] - [InlineData(@"A(a:Number, b:/*comment*/Number):Number = 12;b(a:):Number = 1;c(a:Number):Number = 100;", 0, 2, 1)] - [InlineData("a = Abs(1.2);\nAdd(a: Number, b: Number):/* Number */Number", 1, 0, 1)] - [InlineData("a = Abs(1.2);\nAdd(a: Number, b: Number):/* Number */Number = a + b;", 1, 1, 0)] - - public void TestParseUserDefinitionsCountswithIncompleteUDFs(string script, int nfCount, int validUDFCount, int inValidUDFCount) - { - var parserOptions = new ParserOptions() - { - AllowsSideEffects = false - }; - - var parseResult = UserDefinitions.Parse(script, parserOptions); - - Assert.Equal(nfCount, parseResult.NamedFormulas.Count()); - Assert.Equal(validUDFCount, parseResult.UDFs.Count(udf => udf.IsParseValid)); - Assert.Equal(inValidUDFCount, parseResult.UDFs.Count(udf => !udf.IsParseValid)); - } - - [Theory] - - [InlineData("a = Abs(1.2);\n F1(a:Number):Number = a;", 0)] - [InlineData("a = Abs(1.2);\n F1(a):Number = a;", 1)] - [InlineData("a = Abs(1.2);\n F1(a:):Number = a;", 1)] - [InlineData("a = Abs(1.2);\n F1(a:Number): = a;", 1)] - [InlineData("a = -;\n F1(a:):Number = 1;F2(a:Number): = 1;", 3)] - public void TestErrorCountsWithUDFs(string script, int errorCount) - { - var parserOptions = new ParserOptions() - { - AllowsSideEffects = false - }; - - var parseResult = UserDefinitions.Parse(script, parserOptions); - - Assert.Equal(errorCount, parseResult.Errors?.Count() ?? 0); - } - - /// - /// Verifies that UDFs are marked as valid/invalid approriately. - /// - [Fact] - public void TestUserDefinedFunctionValidity() - { - var parserOptions = new ParserOptions() - { - AllowsSideEffects = false - }; - - var script = @" a = Abs(1.2); - F1(a:Number, b: Number):Number = a + b /*comment*/; - F2(a:Number, b):Number = a + b; - F3(a:Number, b:):Number = a + b; - b = ""test""; - F4("; - var parseResult = UserDefinitions.Parse(script, parserOptions); - Assert.Equal(2, parseResult.NamedFormulas.Count()); - Assert.Equal(1, parseResult.UDFs.Count(udf => udf.IsParseValid)); - Assert.Equal(3, parseResult.UDFs.Count(udf => !udf.IsParseValid)); - - foreach (var udf in parseResult.UDFs) - { - if (udf.Ident.Name == "F1") - { - // Verify return type colon token span - Assert.Equal(67, udf.ReturnTypeColonToken.Span.Min); - Assert.Equal(68, udf.ReturnTypeColonToken.Span.Lim); - } - else if (udf.Ident.Name == "F2") - { - Assert.Equal(2, udf.Args.Count()); - var firstArg = udf.Args.ElementAt(0); - var secondArg = udf.Args.ElementAt(1); - Assert.NotNull(firstArg.ColonToken); - - // Verify first arg colon token span - Assert.Equal(129, firstArg.ColonToken.Span.Min); - Assert.Equal(130, firstArg.ColonToken.Span.Lim); - - Assert.Null(secondArg.ColonToken); - Assert.Null(secondArg.TypeIdent); - } - else if (udf.Ident.Name == "F3") - { - Assert.Equal(2, udf.Args.Count()); - Assert.Null(udf.Args.ElementAt(1).TypeIdent); - } - else if (udf.Ident.Name == "F4") - { - Assert.Empty(udf.Args); - Assert.Null(udf.ReturnTypeColonToken); - Assert.Null(udf.ReturnType); - Assert.Null(udf.Body); - } - } - } - - /// - /// Verifies that UDFs are marked as valid/invalid approriately. - /// - [Fact] - public void TestUserDefinedFunctionValidity2() - { - var parserOptions = new ParserOptions() - { - AllowsSideEffects = false - }; - - var script = @"func(a"; - var parseResult = UserDefinitions.Parse(script, parserOptions); - var func = parseResult.UDFs.FirstOrDefault(udf => udf.Ident.Name == "func"); - - Assert.False(func.IsParseValid); - Assert.NotNull(func); - Assert.Single(func.Args); - var firstArg = func.Args.Single(); - Assert.NotNull(firstArg.NameIdent); - Assert.Null(firstArg.TypeIdent); - Assert.Null(firstArg.ColonToken); - Assert.Null(func.ReturnTypeColonToken); - Assert.Null(func.ReturnType); - Assert.Null(func.Body); - - script = @"func(a:"; - parseResult = UserDefinitions.Parse(script, parserOptions); - func = parseResult.UDFs.FirstOrDefault(udf => udf.Ident.Name == "func"); - - Assert.False(func.IsParseValid); - Assert.NotNull(func); - Assert.Single(func.Args); - firstArg = func.Args.Single(); - Assert.NotNull(firstArg.NameIdent); - Assert.Null(firstArg.TypeIdent); - Assert.NotNull(firstArg.ColonToken); - - script = @"func(a:Number"; - parseResult = UserDefinitions.Parse(script, parserOptions); - func = parseResult.UDFs.FirstOrDefault(udf => udf.Ident.Name == "func"); - - Assert.False(func.IsParseValid); - Assert.NotNull(func); - Assert.Single(func.Args); - firstArg = func.Args.Single(); - Assert.NotNull(firstArg.NameIdent); - Assert.NotNull(firstArg.TypeIdent); - Assert.NotNull(firstArg.ColonToken); - - script = @"func(a:Number, b"; - parseResult = UserDefinitions.Parse(script, parserOptions); - func = parseResult.UDFs.FirstOrDefault(udf => udf.Ident.Name == "func"); - - Assert.False(func.IsParseValid); - Assert.NotNull(func); - Assert.Equal(2, func.Args.Count()); - firstArg = func.Args.ElementAt(0); - Assert.NotNull(firstArg.NameIdent); - Assert.NotNull(firstArg.TypeIdent); - Assert.NotNull(firstArg.ColonToken); - firstArg = func.Args.ElementAt(1); - Assert.NotNull(firstArg.NameIdent); - - script = @"func(a:Number):"; - parseResult = UserDefinitions.Parse(script, parserOptions); - func = parseResult.UDFs.FirstOrDefault(udf => udf.Ident.Name == "func"); - - Assert.False(func.IsParseValid); - Assert.NotNull(func); - Assert.Single(func.Args); - Assert.NotNull(func.ReturnTypeColonToken); - Assert.Null(func.ReturnType); - Assert.Null(func.Body); - - script = @"func(a:Number):Number"; - parseResult = UserDefinitions.Parse(script, parserOptions); - func = parseResult.UDFs.FirstOrDefault(udf => udf.Ident.Name == "func"); - - Assert.False(func.IsParseValid); - Assert.NotNull(func); - Assert.Single(func.Args); - Assert.NotNull(func.ReturnTypeColonToken); - Assert.NotNull(func.ReturnType); - Assert.Null(func.Body); - - script = @"func(a:Number):Number = 1;"; - parseResult = UserDefinitions.Parse(script, parserOptions); - func = parseResult.UDFs.FirstOrDefault(udf => udf.Ident.Name == "func"); - - Assert.NotNull(func); - Assert.Single(func.Args); - Assert.NotNull(func.ReturnTypeColonToken); - Assert.NotNull(func.ReturnType); - Assert.NotNull(func.Body); - Assert.True(func.IsParseValid); - } - - // Show definitions directly on symbol tables - [Fact] - public void Basic() - { - var st1 = SymbolTable.WithPrimitiveTypes(); - st1.AddUserDefinedFunction("Foo1(x: Number): Number = x*2;"); - st1.AddUserDefinedFunction("Foo2(x: Number): Number = Foo1(x)+1;"); - - var engine = new Engine(); - var check = engine.Check("Foo2(3)", symbolTable: st1); - Assert.True(check.IsSuccess); - Assert.Equal(FormulaType.Number, check.ReturnType); - - // A different symbol table can have same function name with different type. - var st2 = SymbolTable.WithPrimitiveTypes(); - st2.AddUserDefinedFunction("Foo2(x: Number): Text = x;"); - check = engine.Check("Foo2(3)", symbolTable: st2); - Assert.True(check.IsSuccess); - Assert.Equal(FormulaType.String, check.ReturnType); - } - - [Fact] - public void DefineEmpty() - { - // Empty symbol table doesn't get builtins. - var st = SymbolTable.WithPrimitiveTypes(); - st.AddUserDefinedFunction("Foo1(x: Number): Number = x;"); // ok - Assert.Throws(() => st.AddUserDefinedFunction("Foo2(x: Number): Number = Abs(x);")); - } - - // Show definitions on public symbol tables - [Fact] - public void BasicEngine() - { - var extra = new SymbolTable(); - extra.AddVariable("K1", FormulaType.Number); - - var engine = new Engine(); - engine.AddUserDefinedFunction("Foo1(x: Number): Number = Abs(K1);", symbolTable: extra); - - var check = engine.Check("Foo1(3)"); - Assert.True(check.IsSuccess); - Assert.Equal(FormulaType.Number, check.ReturnType); - } - - [Fact] - public void TestUserDefinedFunctionCloning() - { - var parserOptions = new ParserOptions() - { - AllowsSideEffects = false - }; - - var script = "Add(a: Number, b: Number):Number = a + b;"; - - var parseResult = UserDefinitions.Parse(script, parserOptions); - - var nameResolver = ReadOnlySymbolTable.NewDefault(BuiltinFunctionsCore._library, FormulaType.PrimitiveTypes); - - var udfs = UserDefinedFunction.CreateFunctions(parseResult.UDFs.Where(udf => udf.IsParseValid), nameResolver, out var errors); - errors.AddRange(parseResult.Errors ?? Enumerable.Empty()); - - var func = udfs.FirstOrDefault(); - Assert.NotNull(func); - - var glue = new Glue2DocumentBinderGlue(); - var texlFunctionSet = new TexlFunctionSet(udfs); - - Assert.Single(udfs); - - var udf = udfs.First(); - var binding = udf.BindBody(ReadOnlySymbolTable.Compose(nameResolver, ReadOnlySymbolTable.NewDefault(texlFunctionSet)), glue, BindingConfig.Default); - var clonedFunc = func.WithBinding(nameResolver, glue, out binding); - Assert.NotNull(clonedFunc); - Assert.NotNull(binding); - - Assert.NotEqual(func, clonedFunc); - } - - [Theory] - [InlineData("x = $\"{\"}\";", 1, 0, 0)] - [InlineData("x = First([$\"{ {a:1,b:2,c:3}.a }]).Value;", 1, 0, 0)] - [InlineData("x = $\"{\"1$\"}.{\"}\";\r\nudf():Text = $\"{\"}\";\r\ny = 2;", 2, 1, 0)] - [InlineData("x = $\"{$\"{$\"{$\"{.12e4}\"}}\"}\";\r\nudf():Text = $\"{\"}\";\r\ny = 2;", 2, 1, 0)] - [InlineData("x = $\"{$\"{$\"{$\"{.12e4}\"}\"}\"}{$\"Another nested}\";\r\nudf():Text = $\"{\"}\";\r\ny = 2;", 2, 1, 0)] - public void TestUDF(string formula, int nfCount, int udfCount, int validUdfCount) - { - var parserOptions = new ParserOptions() - { - AllowsSideEffects = false - }; - - var parseResult = UserDefinitions.Parse(formula, parserOptions); - var udfs = UserDefinedFunction.CreateFunctions(parseResult.UDFs.Where(udf => udf.IsParseValid), _primitiveTypes, out var errors); - errors.AddRange(parseResult.Errors ?? Enumerable.Empty()); - - Assert.Equal(nfCount, parseResult.NamedFormulas.Count()); - Assert.Equal(udfCount, parseResult.UDFs.Count()); - Assert.Equal(validUdfCount, udfs.Count()); - Assert.Contains(errors, e => e.MessageKey == "ErrBadToken"); - } - - [Theory] - [InlineData("Foo(x: Number): None = Abs(x);")] - [InlineData("Foo(x: None): Number = Abs(x);")] - [InlineData("Foo(x: Decimal): Number = Abs(x);")] - [InlineData("Foo(x: Number): Decimal = Abs(x);")] - [InlineData("Foo(x: DateTimeTZInd): Decimal = Abs(x);")] - [InlineData("Foo(x: Number): DateTimeTZInd = Abs(x);")] - public void TestUDFsWithRestrictedTypes(string script) - { - var parserOptions = new ParserOptions() - { - AllowsSideEffects = false - }; - - var parseResult = UserDefinitions.Parse(script, parserOptions); - var udfs = UserDefinedFunction.CreateFunctions(parseResult.UDFs.Where(udf => udf.IsParseValid), _primitiveTypes, out var errors); - errors.AddRange(parseResult.Errors ?? Enumerable.Empty()); - - Assert.Contains(errors, x => x.MessageKey == "ErrUDF_InvalidReturnType" || x.MessageKey == "ErrUDF_InvalidParamType"); - } - - [Theory] - [InlineData("Set(x: Number):Number = x + 1;", true)] - [InlineData("Count():Number = 5;", false)] - public void TestUDFsReservedNames(string script, bool expectedError) - { - var parserOptions = new ParserOptions() - { - AllowsSideEffects = false - }; - - var parseResult = UserDefinitions.Parse(script, parserOptions); - var udfs = UserDefinedFunction.CreateFunctions(parseResult.UDFs.Where(udf => udf.IsParseValid), _primitiveTypes, out var errors); - errors.AddRange(parseResult.Errors ?? Enumerable.Empty()); - - if (expectedError) - { - Assert.Contains(errors, x => x.MessageKey == "ErrUDF_FunctionNameRestricted"); - } - else - { - Assert.True(errors.Count() == 0); - } - } - - [Fact] - public void TestUDFsReservedNamesTracking() - { - var parserOptions = new ParserOptions() - { - AllowsSideEffects = false - }; - - // Adding a restricted UDF name is a breaking change, this test will need to be updated and a conversion will be needed for existing scenarios - var restrictedUDFNames = new HashSet - { - "Type", "IsType", "AsType", "Set", "Collect", "ClearCollect", - "UpdateContext", "Navigate", - }; - - foreach (var func in BuiltinFunctionsCore._library.FunctionNames.Union(BuiltinFunctionsCore.OtherKnownFunctions)) - { - var script = $"{func}():Boolean = true;"; - var parseResult = UserDefinitions.Parse(script, parserOptions); - var udfs = UserDefinedFunction.CreateFunctions(parseResult.UDFs.Where(udf => udf.IsParseValid), _primitiveTypes, out var errors); - errors.AddRange(parseResult.Errors ?? Enumerable.Empty()); - if (!restrictedUDFNames.Contains(func)) - { - Assert.True(errors.Count() == 0); - } - else - { - Assert.Contains(errors, x => x.MessageKey == "ErrUDF_FunctionNameRestricted"); - } - } - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Text; +using Microsoft.PowerFx.Core.Binding; +using Microsoft.PowerFx.Core.Errors; +using Microsoft.PowerFx.Core.Functions; +using Microsoft.PowerFx.Core.Glue; +using Microsoft.PowerFx.Core.IR; +using Microsoft.PowerFx.Core.Syntax; +using Microsoft.PowerFx.Core.Texl; +using Microsoft.PowerFx.Syntax; +using Microsoft.PowerFx.Types; +using Xunit; + +namespace Microsoft.PowerFx.Core.Tests +{ + public class UserDefinedFunctionTests : PowerFxTest + { + private static readonly ReadOnlySymbolTable _primitiveTypes = ReadOnlySymbolTable.PrimitiveTypesTableInstance; + + [Theory] + [InlineData("Foo(x: Number): Number = Abs(x);", 1, 0, false)] + [InlineData("IsType(x: Number): Number = Abs(x);", 0, 0, true)] + [InlineData("AsType(x: Number): Number = Abs(x);", 0, 0, true)] + [InlineData("Type(x: Number): Number = Abs(x);", 0, 0, true)] + [InlineData("Foo(x: Number): Number = Abs(x); x = 1;", 1, 1, false)] + [InlineData("x = 1; Foo(x: Number): Number = Abs(x);", 1, 1, false)] + [InlineData("/*this is a test*/ x = 1; Foo(x: Number): Number = Abs(x);", 1, 1, false)] + [InlineData("x = 1; Foo(x: Number): Number = Abs(x); y = 2;", 1, 2, false)] + [InlineData("Add(x: Number, y:Number): Number = x + y; Foo(x: Number): Number = Abs(x); y = 2;", 2, 1, false)] + [InlineData("Foo(x: Number): Number = /*this is a test*/ Abs(x); y = 2;", 1, 1, false)] + [InlineData("Add(x: Number, y:Number): Number = b + b; Foo(x: Number): Number = Abs(x); y = 2;", 2, 1, true)] + [InlineData("Add(x: Number, y:Number): Boolean = x + y;", 1, 0, false)] + [InlineData("Add(x: Number, y:Number): SomeType = x + y;", 0, 0, true)] + [InlineData("Add(x: SomeType, y:Number): Number = x + y;", 0, 0, true)] + [InlineData("Add(x: Number, y:Number): Number = x + y", 0, 0, true)] + [InlineData("x = 1; Add(x: Number, y:Number): Number = x + y", 0, 1, true)] + [InlineData("Add(x: Number, y:Number) = x + y;", 0, 0, true)] + [InlineData("Add(x): Number = x + 2;", 0, 0, true)] + [InlineData("Add(a:Number, b:Number): Number { a + b + 1; \n a + b; };", 0, 0, true)] + [InlineData("Add(a:Number, b:Number): Number { a + b; };", 0, 0, true)] + [InlineData("Add(a:Number, b:Number): Number { /*this is a test*/ a + b; };", 0, 0, true)] + [InlineData("Add(a:Number, b:Number): Number { /*this is a test*/ a + b; ;", 0, 0, true)] + [InlineData("Add(a:Number, a:Number): Number { a; };", 0, 0, true)] + [InlineData(@"F2(b: Number): Number = F1(b*3); F1(a:Number): Number = a*2;", 2, 0, false)] + [InlineData(@"F2(b: Text): Text = ""Test"";", 1, 0, false)] + [InlineData(@"F2(b: String): String = ""Test"";", 0, 0, true)] + public void TestUDFNamedFormulaCounts(string script, int udfCount, int namedFormulaCount, bool expectErrors) + { + var parserOptions = new ParserOptions() + { + AllowsSideEffects = false + }; + + var parseResult = UserDefinitions.Parse(script, parserOptions); + + var nameResolver = ReadOnlySymbolTable.NewDefault(BuiltinFunctionsCore._library, FormulaType.PrimitiveTypes); + + var udfs = UserDefinedFunction.CreateFunctions(parseResult.UDFs.Where(udf => udf.IsParseValid), nameResolver, out var errors); + errors.AddRange(parseResult.Errors ?? Enumerable.Empty()); + + var glue = new Glue2DocumentBinderGlue(); + var hasBinderErrors = false; + + foreach (var udf in udfs) + { + var binding = udf.BindBody(ReadOnlySymbolTable.Compose(nameResolver, ReadOnlySymbolTable.NewDefault(udfs)), glue, BindingConfig.Default); + hasBinderErrors |= binding.ErrorContainer.HasErrors(); + } + + Assert.Equal(udfCount, udfs.Count()); + Assert.Equal(namedFormulaCount, parseResult.NamedFormulas.Count()); + Assert.Equal(expectErrors, (errors?.Any() ?? false) || hasBinderErrors); + } + + [Theory] + [InlineData("Mul(x:Number, y:DateTime): DateTime = x * y;", "Mul(\"4a\", Date(1900, 1, 3))", "Mul:d(Float:n(\"4a\":s), DateToDateTime:d(Date:D(Coalesce:n(Float:n(1900:w), 0:n), Coalesce:n(Float:n(1:w), 0:n), Coalesce:n(Float:n(3:w), 0:n))))")] + public void TestCoercionWithUDFParams(string udfScript, string invocationScript, string expectedIR) + { + var parserOptions = new ParserOptions() + { + AllowsSideEffects = false + }; + + var nameResolver = ReadOnlySymbolTable.NewDefault(BuiltinFunctionsCore._library); + var glue = new Glue2DocumentBinderGlue(); + + var parseResult = UserDefinitions.Parse(udfScript, parserOptions); + var udfs = UserDefinedFunction.CreateFunctions(parseResult.UDFs.Where(udf => udf.IsParseValid), _primitiveTypes, out var errors); + errors.AddRange(parseResult.Errors ?? Enumerable.Empty()); + + var texlFunctionSet = new TexlFunctionSet(udfs); + + var engine = new Engine(); + var result = engine.Check(invocationScript, symbolTable: ReadOnlySymbolTable.Compose(ReadOnlySymbolTable.NewDefault(BuiltinFunctionsCore._library), ReadOnlySymbolTable.NewDefault(texlFunctionSet))); + + var actualIR = result.PrintIR(); + Assert.Equal(expectedIR, actualIR); + } + + [Theory] + [InlineData("Mul(x:Number, y:DateTime): DateTime = x * y;", "(NumberToDateTime:d(MulNumbers:n(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo), DateTimeToNumber:n(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)))), Scope 0)")] + [InlineData("sTon(x:Text): Number = x;", "(Float:n(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] + [InlineData("bTon(x:Boolean): Number = x;", "(BooleanToNumber:n(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] + [InlineData("dateTon(x:Date): Number = x;", "(DateToNumber:n(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] + [InlineData("timeTon(x:Time): Number = x;", "(TimeToNumber:n(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] + [InlineData("dateTimeTon(x:DateTime): Number = x;", "(DateTimeToNumber:n(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] + [InlineData("textToHyperlink(x:Text): Hyperlink = x;", "(TextToHyperlink:h(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] + [InlineData("nTos(x:Number): Text = x;", "(NumberToText:s(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] + [InlineData("bTos(x:Boolean): Text = x;", "(BooleanToText:s(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] + [InlineData("dateTos(x:Date): Text = x;", "(Text:s(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] + [InlineData("dateTimeTos(x:DateTime): Text = x;", "(Text:s(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] + [InlineData("timeTos(x:Time): Text = x;", "(Text:s(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] + [InlineData("nToBool(x:Number): Boolean = x;", "(NumberToBoolean:b(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] + [InlineData("sToBool(x:Text): Boolean = x;", "(TextToBoolean:b(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] + [InlineData("nToDateTime(x:Number): DateTime = x;", "(NumberToDateTime:d(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] + [InlineData("nToDate(x:Number): Date = x;", "(NumberToDate:D(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] + [InlineData("nToTime(x:Number): Time = x;", "(NumberToTime:T(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] + [InlineData("sToDateTime(x:Text): DateTime = x;", "(DateTimeValue:d(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] + [InlineData("sToDate(x:Text): Date = x;", "(DateValue:D(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] + [InlineData("sToTime(x:Text): Time = x;", "(TimeValue:T(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] + [InlineData("dateTimeToTime(x:DateTime): Time = x;", "(DateTimeToTime:T(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] + [InlineData("dateToTime(x:Date): Time = x;", "(DateToTime:T(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] + [InlineData("timeToDate(x:Time): Date = x;", "(TimeToDate:D(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] + [InlineData("dateTimeToDate(x:DateTime): Date = x;", "(DateTimeToDate:D(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo)), Scope 0)")] + [InlineData("timeToDateTime(x:Time): DateTime = x;", "(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo), Scope 0)")] + [InlineData("dateToDateTime(x:Date): DateTime = x;", "(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo), Scope 0)")] + [InlineData("textToGUID(x:Text): GUID = x;", "(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo), Scope 0)")] + [InlineData("GUIDToText(x:GUID): Text = x;", "(ResolvedObject(Microsoft.PowerFx.Core.Binding.BindInfo.UDFParameterInfo), Scope 0)")] + public void TestCoercionWithUDFBody(string udfScript, string expectedIR) + { + var parserOptions = new ParserOptions() + { + AllowsSideEffects = false + }; + + var nameResolver = ReadOnlySymbolTable.NewDefault(BuiltinFunctionsCore._library, FormulaType.PrimitiveTypes); + var glue = new Glue2DocumentBinderGlue(); + + var parseResult = UserDefinitions.Parse(udfScript, parserOptions); + var udfs = UserDefinedFunction.CreateFunctions(parseResult.UDFs.Where(udf => udf.IsParseValid), nameResolver, out var errors); + errors.AddRange(parseResult.Errors ?? Enumerable.Empty()); + + var texlFunctionSet = new TexlFunctionSet(udfs); + + Assert.Single(udfs); + + var udf = udfs.First(); + var binding = udf.BindBody(ReadOnlySymbolTable.Compose(nameResolver, ReadOnlySymbolTable.NewDefault(texlFunctionSet)), glue, BindingConfig.Default); + var actualIR = IRTranslator.Translate(binding).ToString(); + + Assert.Equal(expectedIR, actualIR); + } + + [Theory] + [InlineData("/* Comment1 */ Foo(x: Number): Number = /* Comment2 */ Abs(x) /* Comment3 */;/* Comment4 */", 4)] + [InlineData("Foo(x: Number): Number /* Comment1 */ = Abs(x);// Comment2", 2)] + public void TestCommentsFromUserDefinitionsScript(string script, int commentCount) + { + var parserOptions = new ParserOptions() + { + AllowsSideEffects = false + }; + + var parseResult = UserDefinitions.Parse(script, parserOptions); + + Assert.Equal(commentCount, parseResult.Comments.Count()); + } + + [Theory] + [InlineData("/* Comment1 */ Foo(x: Number): Number = Abs(x);", 0, 15)] + [InlineData("Foo(x: Number): Number /* Comment1 */ = Abs(x);", 23, 38)] + [InlineData("Foo(x: Number): Number = Abs(x) /* Comment1 */;", 32, 46)] + [InlineData("Foo(x: Number): Number = Abs(x); /* Comment1 */", 33, 47)] + public void TestCommentSpansFromUserDefinitionsScript(string script, int begin, int end) + { + var parserOptions = new ParserOptions() + { + AllowsSideEffects = false + }; + + var parseResult = UserDefinitions.Parse(script, parserOptions); + + Assert.Single(parseResult.Comments); + + var commentSpan = parseResult.Comments.First().Span; + + Assert.Equal(commentSpan.Min, begin); + Assert.Equal(commentSpan.Lim, end); + } + + [Theory] + [InlineData("a = Abs(1.2);\nAdd(a: Number, b: Number):Number = a + b;\nb = Add(1, 2);", 2, 1, 0)] + [InlineData("a = Abs(1.2);\nAdd(a: Number, b:):Number = a + b;\nb = Add(1, 2); ", 2, 0, 1)] + [InlineData("a = Abs(1.2);\nAdd(a: Number, b:/*comment*/):Number = a + b;\nb = Add(1, 2); ", 2, 0, 1)] + [InlineData("a = Abs(1.2);\nAdd(a: Number, b:/*comment*/Number):Number = a + b;\nb = Add(1, 2); ", 2, 1, 0)] + [InlineData("a = Abs(1.2);\nF1(a: Number):Number = a;\nb = F1(1, 2); F2(", 2, 1, 1)] + [InlineData("a = Abs(1.2);\nF1(a: Number):Number = a;\nb = F1(1, 2); F2(a):Number = 1;", 2, 1, 1)] + [InlineData("a = Abs(1.2);\nF1(a: Number):Number = a;\nb = F1(1, 2); F2(a):Number = ;", 2, 1, 1)] + [InlineData("a = Abs(1.2);\nF1(a: Number):Number = a;\nb = F1(1, 2); F2(a:Number): = 1;", 2, 1, 1)] + [InlineData(@"A(a:Number, b:/*comment*/Number):Number = 12;b(a:Number): = 1;c(a:Number):Number = 100;x = 10;", 1, 2, 1)] + [InlineData(@"A(a:Number, b:/*comment*/Number):Number = 12;b(a:):Number = 1;c(a:Number):Number = 100;", 0, 2, 1)] + [InlineData("a = Abs(1.2);\nAdd(a: Number, b: Number):/* Number */Number", 1, 0, 1)] + [InlineData("a = Abs(1.2);\nAdd(a: Number, b: Number):/* Number */Number = a + b;", 1, 1, 0)] + + public void TestParseUserDefinitionsCountswithIncompleteUDFs(string script, int nfCount, int validUDFCount, int inValidUDFCount) + { + var parserOptions = new ParserOptions() + { + AllowsSideEffects = false + }; + + var parseResult = UserDefinitions.Parse(script, parserOptions); + + Assert.Equal(nfCount, parseResult.NamedFormulas.Count()); + Assert.Equal(validUDFCount, parseResult.UDFs.Count(udf => udf.IsParseValid)); + Assert.Equal(inValidUDFCount, parseResult.UDFs.Count(udf => !udf.IsParseValid)); + } + + [Theory] + + [InlineData("a = Abs(1.2);\n F1(a:Number):Number = a;", 0)] + [InlineData("a = Abs(1.2);\n F1(a):Number = a;", 1)] + [InlineData("a = Abs(1.2);\n F1(a:):Number = a;", 1)] + [InlineData("a = Abs(1.2);\n F1(a:Number): = a;", 1)] + [InlineData("a = -;\n F1(a:):Number = 1;F2(a:Number): = 1;", 3)] + public void TestErrorCountsWithUDFs(string script, int errorCount) + { + var parserOptions = new ParserOptions() + { + AllowsSideEffects = false + }; + + var parseResult = UserDefinitions.Parse(script, parserOptions); + + Assert.Equal(errorCount, parseResult.Errors?.Count() ?? 0); + } + + /// + /// Verifies that UDFs are marked as valid/invalid approriately. + /// + [Fact] + public void TestUserDefinedFunctionValidity() + { + var parserOptions = new ParserOptions() + { + AllowsSideEffects = false + }; + + var script = @" a = Abs(1.2); + F1(a:Number, b: Number):Number = a + b /*comment*/; + F2(a:Number, b):Number = a + b; + F3(a:Number, b:):Number = a + b; + b = ""test""; + F4("; + var parseResult = UserDefinitions.Parse(script, parserOptions); + Assert.Equal(2, parseResult.NamedFormulas.Count()); + Assert.Equal(1, parseResult.UDFs.Count(udf => udf.IsParseValid)); + Assert.Equal(3, parseResult.UDFs.Count(udf => !udf.IsParseValid)); + + foreach (var udf in parseResult.UDFs) + { + if (udf.Ident.Name == "F1") + { + // Verify return type colon token span + Assert.Equal(67, udf.ReturnTypeColonToken.Span.Min); + Assert.Equal(68, udf.ReturnTypeColonToken.Span.Lim); + } + else if (udf.Ident.Name == "F2") + { + Assert.Equal(2, udf.Args.Count()); + var firstArg = udf.Args.ElementAt(0); + var secondArg = udf.Args.ElementAt(1); + Assert.NotNull(firstArg.ColonToken); + + // Verify first arg colon token span + Assert.Equal(129, firstArg.ColonToken.Span.Min); + Assert.Equal(130, firstArg.ColonToken.Span.Lim); + + Assert.Null(secondArg.ColonToken); + Assert.Null(secondArg.TypeIdent); + } + else if (udf.Ident.Name == "F3") + { + Assert.Equal(2, udf.Args.Count()); + Assert.Null(udf.Args.ElementAt(1).TypeIdent); + } + else if (udf.Ident.Name == "F4") + { + Assert.Empty(udf.Args); + Assert.Null(udf.ReturnTypeColonToken); + Assert.Null(udf.ReturnType); + Assert.Null(udf.Body); + } + } + } + + /// + /// Verifies that UDFs are marked as valid/invalid approriately. + /// + [Fact] + public void TestUserDefinedFunctionValidity2() + { + var parserOptions = new ParserOptions() + { + AllowsSideEffects = false + }; + + var script = @"func(a"; + var parseResult = UserDefinitions.Parse(script, parserOptions); + var func = parseResult.UDFs.FirstOrDefault(udf => udf.Ident.Name == "func"); + + Assert.False(func.IsParseValid); + Assert.NotNull(func); + Assert.Single(func.Args); + var firstArg = func.Args.Single(); + Assert.NotNull(firstArg.NameIdent); + Assert.Null(firstArg.TypeIdent); + Assert.Null(firstArg.ColonToken); + Assert.Null(func.ReturnTypeColonToken); + Assert.Null(func.ReturnType); + Assert.Null(func.Body); + + script = @"func(a:"; + parseResult = UserDefinitions.Parse(script, parserOptions); + func = parseResult.UDFs.FirstOrDefault(udf => udf.Ident.Name == "func"); + + Assert.False(func.IsParseValid); + Assert.NotNull(func); + Assert.Single(func.Args); + firstArg = func.Args.Single(); + Assert.NotNull(firstArg.NameIdent); + Assert.Null(firstArg.TypeIdent); + Assert.NotNull(firstArg.ColonToken); + + script = @"func(a:Number"; + parseResult = UserDefinitions.Parse(script, parserOptions); + func = parseResult.UDFs.FirstOrDefault(udf => udf.Ident.Name == "func"); + + Assert.False(func.IsParseValid); + Assert.NotNull(func); + Assert.Single(func.Args); + firstArg = func.Args.Single(); + Assert.NotNull(firstArg.NameIdent); + Assert.NotNull(firstArg.TypeIdent); + Assert.NotNull(firstArg.ColonToken); + + script = @"func(a:Number, b"; + parseResult = UserDefinitions.Parse(script, parserOptions); + func = parseResult.UDFs.FirstOrDefault(udf => udf.Ident.Name == "func"); + + Assert.False(func.IsParseValid); + Assert.NotNull(func); + Assert.Equal(2, func.Args.Count()); + firstArg = func.Args.ElementAt(0); + Assert.NotNull(firstArg.NameIdent); + Assert.NotNull(firstArg.TypeIdent); + Assert.NotNull(firstArg.ColonToken); + firstArg = func.Args.ElementAt(1); + Assert.NotNull(firstArg.NameIdent); + + script = @"func(a:Number):"; + parseResult = UserDefinitions.Parse(script, parserOptions); + func = parseResult.UDFs.FirstOrDefault(udf => udf.Ident.Name == "func"); + + Assert.False(func.IsParseValid); + Assert.NotNull(func); + Assert.Single(func.Args); + Assert.NotNull(func.ReturnTypeColonToken); + Assert.Null(func.ReturnType); + Assert.Null(func.Body); + + script = @"func(a:Number):Number"; + parseResult = UserDefinitions.Parse(script, parserOptions); + func = parseResult.UDFs.FirstOrDefault(udf => udf.Ident.Name == "func"); + + Assert.False(func.IsParseValid); + Assert.NotNull(func); + Assert.Single(func.Args); + Assert.NotNull(func.ReturnTypeColonToken); + Assert.NotNull(func.ReturnType); + Assert.Null(func.Body); + + script = @"func(a:Number):Number = 1;"; + parseResult = UserDefinitions.Parse(script, parserOptions); + func = parseResult.UDFs.FirstOrDefault(udf => udf.Ident.Name == "func"); + + Assert.NotNull(func); + Assert.Single(func.Args); + Assert.NotNull(func.ReturnTypeColonToken); + Assert.NotNull(func.ReturnType); + Assert.NotNull(func.Body); + Assert.True(func.IsParseValid); + } + + // Show definitions directly on symbol tables + [Fact] + public void Basic() + { + var st1 = SymbolTable.WithPrimitiveTypes(); + st1.AddUserDefinedFunction("Foo1(x: Number): Number = x*2;"); + st1.AddUserDefinedFunction("Foo2(x: Number): Number = Foo1(x)+1;"); + + var engine = new Engine(); + var check = engine.Check("Foo2(3)", symbolTable: st1); + Assert.True(check.IsSuccess); + Assert.Equal(FormulaType.Number, check.ReturnType); + + // A different symbol table can have same function name with different type. + var st2 = SymbolTable.WithPrimitiveTypes(); + st2.AddUserDefinedFunction("Foo2(x: Number): Text = x;"); + check = engine.Check("Foo2(3)", symbolTable: st2); + Assert.True(check.IsSuccess); + Assert.Equal(FormulaType.String, check.ReturnType); + } + + [Fact] + public void DefineEmpty() + { + // Empty symbol table doesn't get builtins. + var st = SymbolTable.WithPrimitiveTypes(); + st.AddUserDefinedFunction("Foo1(x: Number): Number = x;"); // ok + Assert.Throws(() => st.AddUserDefinedFunction("Foo2(x: Number): Number = Abs(x);")); + } + + // Show definitions on public symbol tables + [Fact] + public void BasicEngine() + { + var extra = new SymbolTable(); + extra.AddVariable("K1", FormulaType.Number); + + var engine = new Engine(); + engine.AddUserDefinedFunction("Foo1(x: Number): Number = Abs(K1);", symbolTable: extra); + + var check = engine.Check("Foo1(3)"); + Assert.True(check.IsSuccess); + Assert.Equal(FormulaType.Number, check.ReturnType); + } + + [Fact] + public void TestUserDefinedFunctionCloning() + { + var parserOptions = new ParserOptions() + { + AllowsSideEffects = false + }; + + var script = "Add(a: Number, b: Number):Number = a + b;"; + + var parseResult = UserDefinitions.Parse(script, parserOptions); + + var nameResolver = ReadOnlySymbolTable.NewDefault(BuiltinFunctionsCore._library, FormulaType.PrimitiveTypes); + + var udfs = UserDefinedFunction.CreateFunctions(parseResult.UDFs.Where(udf => udf.IsParseValid), nameResolver, out var errors); + errors.AddRange(parseResult.Errors ?? Enumerable.Empty()); + + var func = udfs.FirstOrDefault(); + Assert.NotNull(func); + + var glue = new Glue2DocumentBinderGlue(); + var texlFunctionSet = new TexlFunctionSet(udfs); + + Assert.Single(udfs); + + var udf = udfs.First(); + var binding = udf.BindBody(ReadOnlySymbolTable.Compose(nameResolver, ReadOnlySymbolTable.NewDefault(texlFunctionSet)), glue, BindingConfig.Default); + var clonedFunc = func.WithBinding(nameResolver, glue, out binding); + Assert.NotNull(clonedFunc); + Assert.NotNull(binding); + + Assert.NotEqual(func, clonedFunc); + } + + [Theory] + [InlineData("x = $\"{\"}\";", 1, 0, 0)] + [InlineData("x = First([$\"{ {a:1,b:2,c:3}.a }]).Value;", 1, 0, 0)] + [InlineData("x = $\"{\"1$\"}.{\"}\";\r\nudf():Text = $\"{\"}\";\r\ny = 2;", 2, 1, 0)] + [InlineData("x = $\"{$\"{$\"{$\"{.12e4}\"}}\"}\";\r\nudf():Text = $\"{\"}\";\r\ny = 2;", 2, 1, 0)] + [InlineData("x = $\"{$\"{$\"{$\"{.12e4}\"}\"}\"}{$\"Another nested}\";\r\nudf():Text = $\"{\"}\";\r\ny = 2;", 2, 1, 0)] + public void TestUDF(string formula, int nfCount, int udfCount, int validUdfCount) + { + var parserOptions = new ParserOptions() + { + AllowsSideEffects = false + }; + + var parseResult = UserDefinitions.Parse(formula, parserOptions); + var udfs = UserDefinedFunction.CreateFunctions(parseResult.UDFs.Where(udf => udf.IsParseValid), _primitiveTypes, out var errors); + errors.AddRange(parseResult.Errors ?? Enumerable.Empty()); + + Assert.Equal(nfCount, parseResult.NamedFormulas.Count()); + Assert.Equal(udfCount, parseResult.UDFs.Count()); + Assert.Equal(validUdfCount, udfs.Count()); + Assert.Contains(errors, e => e.MessageKey == "ErrBadToken"); + } + + [Theory] + [InlineData("Foo(x: Number): None = Abs(x);")] + [InlineData("Foo(x: None): Number = Abs(x);")] + [InlineData("Foo(x: Decimal): Number = Abs(x);")] + [InlineData("Foo(x: Number): Decimal = Abs(x);")] + [InlineData("Foo(x: DateTimeTZInd): Decimal = Abs(x);")] + [InlineData("Foo(x: Number): DateTimeTZInd = Abs(x);")] + public void TestUDFsWithRestrictedTypes(string script) + { + var parserOptions = new ParserOptions() + { + AllowsSideEffects = false + }; + + var parseResult = UserDefinitions.Parse(script, parserOptions); + var udfs = UserDefinedFunction.CreateFunctions(parseResult.UDFs.Where(udf => udf.IsParseValid), _primitiveTypes, out var errors); + errors.AddRange(parseResult.Errors ?? Enumerable.Empty()); + + Assert.Contains(errors, x => x.MessageKey == "ErrUDF_InvalidReturnType" || x.MessageKey == "ErrUDF_InvalidParamType"); + } + + [Theory] + [InlineData("Set(x: Number):Number = x + 1;", true)] + [InlineData("Count():Number = 5;", false)] + public void TestUDFsReservedNames(string script, bool expectedError) + { + var parserOptions = new ParserOptions() + { + AllowsSideEffects = false + }; + + var parseResult = UserDefinitions.Parse(script, parserOptions); + var udfs = UserDefinedFunction.CreateFunctions(parseResult.UDFs.Where(udf => udf.IsParseValid), _primitiveTypes, out var errors); + errors.AddRange(parseResult.Errors ?? Enumerable.Empty()); + + if (expectedError) + { + Assert.Contains(errors, x => x.MessageKey == "ErrUDF_FunctionNameRestricted"); + } + else + { + Assert.True(errors.Count() == 0); + } + } + + [Fact] + public void TestUDFsReservedNamesTracking() + { + var parserOptions = new ParserOptions() + { + AllowsSideEffects = false + }; + + // Adding a restricted UDF name is a breaking change, this test will need to be updated and a conversion will be needed for existing scenarios + var restrictedUDFNames = new HashSet + { + "Type", "IsType", "AsType", "Set", "Collect", "ClearCollect", + "UpdateContext", "Navigate", + }; + + foreach (var func in BuiltinFunctionsCore._library.FunctionNames.Union(BuiltinFunctionsCore.OtherKnownFunctions)) + { + var script = $"{func}():Boolean = true;"; + var parseResult = UserDefinitions.Parse(script, parserOptions); + var udfs = UserDefinedFunction.CreateFunctions(parseResult.UDFs.Where(udf => udf.IsParseValid), _primitiveTypes, out var errors); + errors.AddRange(parseResult.Errors ?? Enumerable.Empty()); + if (!restrictedUDFNames.Contains(func)) + { + Assert.True(errors.Count() == 0); + } + else + { + Assert.Contains(errors, x => x.MessageKey == "ErrUDF_FunctionNameRestricted"); + } + } + } + } +} diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/UserDefinedTypeTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/UserDefinedTypeTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/UserDefinedTypeTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/UserDefinedTypeTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/VersionHashTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/VersionHashTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/VersionHashTests.cs rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/VersionHashTests.cs diff --git a/src/tests/Microsoft.PowerFx.Core.Tests/icon.png b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/icon.png similarity index 100% rename from src/tests/Microsoft.PowerFx.Core.Tests/icon.png rename to src/tests/Microsoft.PowerFx.Core.Tests.Shared/icon.png diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/BlobTestFunctions.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/BlobTestFunctions.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/BlobTestFunctions.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/BlobTestFunctions.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/BlobTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/BlobTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/BlobTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/BlobTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/ClearFunctionTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/ClearFunctionTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/ClearFunctionTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/ClearFunctionTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/CollectFunctionTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/CollectFunctionTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/CollectFunctionTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/CollectFunctionTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/ConfigTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/ConfigTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/ConfigTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/ConfigTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/CustomFunctions.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/CustomFunctions.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/CustomFunctions.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/CustomFunctions.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/DatabaseSimulationTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/DatabaseSimulationTests.cs similarity index 99% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/DatabaseSimulationTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/DatabaseSimulationTests.cs index 03a7a192d..955bae163 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/DatabaseSimulationTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/DatabaseSimulationTests.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; -using System.Linq; +using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -73,12 +73,12 @@ namespace Microsoft.PowerFx.Interpreter.Tests // Won't complete - should throw cancellation task await Assert.ThrowsAnyAsync(async () => await run.EvalAsync(cts.Token, runtimeConfig).ConfigureAwait(false)).ConfigureAwait(false); } - } - + } + [Theory] - [InlineData("Set(x, Table)", "Set(#$PowerFxResolvedObject$#, #$fne$#)")] - [InlineData("With({t:Table},t)", "With({ #$fieldname$#:#$fne$# }, #$fne$#)")] - [InlineData("ForAll(Table, ThisRecord.Value)", "ForAll(#$fne$#, #$fne$#.#$righthandid$#)")] + [InlineData("Set(x, Table)", "Set(#$PowerFxResolvedObject$#, #$fne$#)")] + [InlineData("With({t:Table},t)", "With({ #$fieldname$#:#$fne$# }, #$fne$#)")] + [InlineData("ForAll(Table, ThisRecord.Value)", "ForAll(#$fne$#, #$fne$#.#$righthandid$#)")] public async Task TestExpandedStucturalPrint(string expr, string anonymized) { var databaseTable = DatabaseTable.CreateTestTable(patchDelay: 0); @@ -88,48 +88,48 @@ namespace Microsoft.PowerFx.Interpreter.Tests symbols.EnableMutationFunctions(); var engine = new RecalcEngine(); - var runtimeConfig = new SymbolValues(symbols); - + var runtimeConfig = new SymbolValues(symbols); + engine.UpdateVariable("x", TableValue.NewTable(RecordType.Empty())); runtimeConfig.Set(slot, databaseTable); - CheckResult check = engine.Check(expr, symbolTable: symbols, options: new ParserOptions() { AllowsSideEffects = true }); - + CheckResult check = engine.Check(expr, symbolTable: symbols, options: new ParserOptions() { AllowsSideEffects = true }); + Assert.Equal(anonymized, check.ApplyGetLogging()); - } - + } + [Theory] - [InlineData("Set(x, Table)")] + [InlineData("Set(x, Table)")] public async Task SkipExpandableSetSemanticsFeatureTest(string expr) { var databaseTable = DatabaseTable.CreateTestTable(patchDelay: 0); var symbols = new SymbolTable(); var slot = symbols.AddVariable("Table", DatabaseTable.TestTableType, mutable: true); - symbols.EnableMutationFunctions(); - - // Temporary feature to unblock Cards team -#pragma warning disable CS0612 // Type or member is obsolete - var config = new PowerFxConfig(Features.PowerFxV1AllowSetExpandedTypes); -#pragma warning restore CS0612 // Type or member is obsolete + symbols.EnableMutationFunctions(); + + // Temporary feature to unblock Cards team +#pragma warning disable CS0612 // Type or member is obsolete + var config = new PowerFxConfig(Features.PowerFxV1AllowSetExpandedTypes); +#pragma warning restore CS0612 // Type or member is obsolete var engine = new RecalcEngine(config); - var runtimeConfig = new SymbolValues(symbols); - + var runtimeConfig = new SymbolValues(symbols); + engine.UpdateVariable("x", TableValue.NewTable(RecordType.Empty())); runtimeConfig.Set(slot, databaseTable); - var check = engine.Check(expr, symbolTable: symbols, options: new ParserOptions() { AllowsSideEffects = true }); - - // This will be success due to SkipExpandableSetSemantics feature that loosens some Set semantics conditions. - Assert.True(check.IsSuccess); - Assert.Contains(check.Errors, err => err.IsWarning && err.MessageKey == "WrnSetExpandableType"); - - var result = await check.GetEvaluator().EvalAsync(CancellationToken.None, symbolValues: symbols.CreateValues()).ConfigureAwait(false); + var check = engine.Check(expr, symbolTable: symbols, options: new ParserOptions() { AllowsSideEffects = true }); + + // This will be success due to SkipExpandableSetSemantics feature that loosens some Set semantics conditions. + Assert.True(check.IsSuccess); + Assert.Contains(check.Errors, err => err.IsWarning && err.MessageKey == "WrnSetExpandableType"); + + var result = await check.GetEvaluator().EvalAsync(CancellationToken.None, symbolValues: symbols.CreateValues()).ConfigureAwait(false); Assert.IsType(result); - } - + } + [Theory] - [InlineData("Set(x, Table)")] + [InlineData("Set(x, Table)")] public async Task BlockSetExpandableTest(string expr) { var databaseTable = DatabaseTable.CreateTestTable(patchDelay: 0); @@ -139,13 +139,13 @@ namespace Microsoft.PowerFx.Interpreter.Tests symbols.EnableMutationFunctions(); var engine = new RecalcEngine(); - var runtimeConfig = new SymbolValues(symbols); - + var runtimeConfig = new SymbolValues(symbols); + engine.UpdateVariable("x", TableValue.NewTable(RecordType.Empty())); runtimeConfig.Set(slot, databaseTable); - var check = engine.Check(expr, symbolTable: symbols, options: new ParserOptions() { AllowsSideEffects = true }); - Assert.False(check.IsSuccess); + var check = engine.Check(expr, symbolTable: symbols, options: new ParserOptions() { AllowsSideEffects = true }); + Assert.False(check.IsSuccess); Assert.Contains(check.Errors, err => !err.IsWarning && (err.MessageKey == "ErrSetVariableWithRelationshipNotAllowTable" || err.MessageKey == "ErrSetVariableWithRelationshipNotAllowRecord")); } @@ -172,17 +172,17 @@ namespace Microsoft.PowerFx.Interpreter.Tests : base(irContext, records) { PatchDelay = patchDelay; - } - - // Doesn't perform a copy. Not needed for testing purposes and - // prevents this class from being replaced by a standard InMemoryTableValue - public override bool TryShallowCopy(out FormulaValue copy) - { - copy = null; - return false; - } - - public override bool CanShallowCopy => false; + } + + // Doesn't perform a copy. Not needed for testing purposes and + // prevents this class from being replaced by a standard InMemoryTableValue + public override bool TryShallowCopy(out FormulaValue copy) + { + copy = null; + return false; + } + + public override bool CanShallowCopy => false; protected override async Task> PatchCoreAsync(RecordValue baseRecord, RecordValue changeRecord, CancellationToken cancellationToken) { @@ -237,22 +237,22 @@ namespace Microsoft.PowerFx.Interpreter.Tests var st = Environment.StackTrace; if (st.Contains("Microsoft.PowerFx.SymbolContext.GetScopeVar") || - st.Contains("Microsoft.PowerFx.Types.CollectionTableValue`1.Matches")) + st.Contains("Microsoft.PowerFx.Types.CollectionTableValue`1.Matches")) { return base.TryGetFieldAsync(fieldType, fieldName, cancellationToken); } throw new NotImplementedException("Cannot call TryGetField"); - } - - // Doesn't perform a copy. Not needed for testing purposes and - // prevents this class from being replaced by a standard InMemoryRecordValue - public override bool TryShallowCopy(out FormulaValue copy) - { - copy = null; - return false; - } - + } + + // Doesn't perform a copy. Not needed for testing purposes and + // prevents this class from being replaced by a standard InMemoryRecordValue + public override bool TryShallowCopy(out FormulaValue copy) + { + copy = null; + return false; + } + public override bool CanShallowCopy => false; } diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/DatabaseTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/DatabaseTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/DatabaseTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/DatabaseTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/DateTimeUTCTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/DateTimeUTCTests.cs similarity index 99% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/DateTimeUTCTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/DateTimeUTCTests.cs index 2e5bed9db..73e3aa8ae 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/DateTimeUTCTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/DateTimeUTCTests.cs @@ -320,16 +320,16 @@ namespace Microsoft.PowerFx.Interpreter.Tests public double GetDouble() { return ((NumberValue)_value).Value; - } - + } + public decimal GetDecimal() { return ((DecimalValue)_value).Value; - } - - public string GetUntypedNumber() - { - return ((StringValue)_value).Value; + } + + public string GetUntypedNumber() + { + return ((StringValue)_value).Value; } public string GetString() @@ -340,13 +340,13 @@ namespace Microsoft.PowerFx.Interpreter.Tests public bool TryGetProperty(string value, out IUntypedObject result) { throw new NotImplementedException(); - } - - public bool TryGetPropertyNames(out IEnumerable result) - { - result = null; - return false; - } + } + + public bool TryGetPropertyNames(out IEnumerable result) + { + result = null; + return false; + } } } } diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/DateValueLocaleTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/DateValueLocaleTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/DateValueLocaleTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/DateValueLocaleTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/DeferredTypeTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/DeferredTypeTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/DeferredTypeTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/DeferredTypeTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/DependencyVisitorTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/DependencyVisitorTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/DependencyVisitorTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/DependencyVisitorTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/DisplayNameTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/DisplayNameTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/DisplayNameTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/DisplayNameTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/Docs/DocTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Docs/DocTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/Docs/DocTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Docs/DocTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/Docs/InterpreterBase.json b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Docs/InterpreterBase.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/Docs/InterpreterBase.json rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Docs/InterpreterBase.json diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/ErrorFunctionTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/ErrorFunctionTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/ErrorFunctionTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/ErrorFunctionTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/FileExpressionEvaluationTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/FileExpressionEvaluationTests.cs similarity index 98% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/FileExpressionEvaluationTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/FileExpressionEvaluationTests.cs index 10af961ae..9e713d157 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/FileExpressionEvaluationTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/FileExpressionEvaluationTests.cs @@ -1,105 +1,105 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -using System; -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using Microsoft.CodeAnalysis; -using Microsoft.PowerFx.Core.IR; +using Microsoft.PowerFx.Core.IR; using Microsoft.PowerFx.Core.Tests; using Microsoft.PowerFx.Interpreter.Tests.XUnitExtensions; -using Microsoft.PowerFx.Types; +using Microsoft.PowerFx.Types; using Xunit; -using Xunit.Abstractions; +using Xunit.Abstractions; using static Microsoft.PowerFx.Interpreter.Tests.ExpressionEvaluationTests; namespace Microsoft.PowerFx.Interpreter.Tests { public class FileExpressionEvaluationTests : PowerFxTest - { - public readonly ITestOutputHelper Console; - - public FileExpressionEvaluationTests(ITestOutputHelper output) - { - Console = output; - } - - // File expression tests are run multiple times for the different ways a host can use Power Fx. - // - // 1. Features.PowerFxV1 without NumberIsFloat - the main way that most hosts will use Power Fx. - // 2. Feautres.PowerFxV1 with NumberIsFloat - for hosts that wish to use floating point instead of Decimal. - // 3. Default Canvas features with NumberIsFloat - the current default for Canvas apps. Canvas - // has an internal relationship with the compiler that allows it to run with a different mix of features. - // 4. No features with NumberIsFloat (occasional) - important for back compat convertes in Canvas as the - // back compat converters depend on the feature mix being the same as when the original app was serialized. - // - // See the README.md in the ExpressionTestCases directory for more details. - + { + public readonly ITestOutputHelper Console; + + public FileExpressionEvaluationTests(ITestOutputHelper output) + { + Console = output; + } + + // File expression tests are run multiple times for the different ways a host can use Power Fx. + // + // 1. Features.PowerFxV1 without NumberIsFloat - the main way that most hosts will use Power Fx. + // 2. Feautres.PowerFxV1 with NumberIsFloat - for hosts that wish to use floating point instead of Decimal. + // 3. Default Canvas features with NumberIsFloat - the current default for Canvas apps. Canvas + // has an internal relationship with the compiler that allows it to run with a different mix of features. + // 4. No features with NumberIsFloat (occasional) - important for back compat convertes in Canvas as the + // back compat converters depend on the feature mix being the same as when the original app was serialized. + // + // See the README.md in the ExpressionTestCases directory for more details. + // Canvas currently does not support decimal, but since this interpreter does, we can run tests with decimal here. - [TxtFileData("ExpressionTestCases", "InterpreterExpressionTestCases", nameof(InterpreterRunner), "TableSyntaxDoesntWrapRecords,ConsistentOneColumnTableResult,NumberIsFloat,DecimalSupport")] + [TxtFileData("ExpressionTestCases", "InterpreterExpressionTestCases", nameof(InterpreterRunner), "TableSyntaxDoesntWrapRecords,ConsistentOneColumnTableResult,NumberIsFloat,DecimalSupport")] [InterpreterTheory] public void Canvas_Float(ExpressionTestCase testCase) - { - // current default features in Canvas abc - var features = new Features() - { - TableSyntaxDoesntWrapRecords = true, - ConsistentOneColumnTableResult = true - }; - - RunExpressionTestCase(testCase, features, numberIsFloat: true, Console); - } - + { + // current default features in Canvas abc + var features = new Features() + { + TableSyntaxDoesntWrapRecords = true, + ConsistentOneColumnTableResult = true + }; + + RunExpressionTestCase(testCase, features, numberIsFloat: true, Console); + } + // Canvas currently does not support decimal, but since this interpreter does, we can run tests with decimal here. - [TxtFileData("ExpressionTestCases", "InterpreterExpressionTestCases", nameof(InterpreterRunner), "TableSyntaxDoesntWrapRecords,ConsistentOneColumnTableResult,PowerFxV1CompatibilityRules,NumberIsFloat,DecimalSupport")] + [TxtFileData("ExpressionTestCases", "InterpreterExpressionTestCases", nameof(InterpreterRunner), "TableSyntaxDoesntWrapRecords,ConsistentOneColumnTableResult,PowerFxV1CompatibilityRules,NumberIsFloat,DecimalSupport")] [InterpreterTheory] public void Canvas_Float_PFxV1(ExpressionTestCase testCase) - { - // current default features in Canvas abc - var features = new Features() - { - TableSyntaxDoesntWrapRecords = true, - ConsistentOneColumnTableResult = true, - PowerFxV1CompatibilityRules = true, - }; - - RunExpressionTestCase(testCase, features, numberIsFloat: true, Console); - } - + { + // current default features in Canvas abc + var features = new Features() + { + TableSyntaxDoesntWrapRecords = true, + ConsistentOneColumnTableResult = true, + PowerFxV1CompatibilityRules = true, + }; + + RunExpressionTestCase(testCase, features, numberIsFloat: true, Console); + } + [InterpreterTheory] [TxtFileData("ExpressionTestCases", "InterpreterExpressionTestCases", nameof(InterpreterRunner), "PowerFxV1,disable:NumberIsFloat,DecimalSupport")] public void V1_Decimal(ExpressionTestCase testCase) - { - RunExpressionTestCase(testCase, Features.PowerFxV1, numberIsFloat: false, Console); - } - + { + RunExpressionTestCase(testCase, Features.PowerFxV1, numberIsFloat: false, Console); + } + // Although we are using numbers as floats by default, since this interpreter supports decimal, we can run tests with decimal here. - [TxtFileData("ExpressionTestCases", "InterpreterExpressionTestCases", nameof(InterpreterRunner), "PowerFxV1,NumberIsFloat,DecimalSupport")] + [TxtFileData("ExpressionTestCases", "InterpreterExpressionTestCases", nameof(InterpreterRunner), "PowerFxV1,NumberIsFloat,DecimalSupport")] [InterpreterTheory] public void V1_Float(ExpressionTestCase testCase) - { - RunExpressionTestCase(testCase, Features.PowerFxV1, numberIsFloat: true, Console); - } - -#if false - // This does not need to be run every time, but should be run periodically. - // Keeping this clean ensures that back compat converters in Canvas continue to function properly. + { + RunExpressionTestCase(testCase, Features.PowerFxV1, numberIsFloat: true, Console); + } + +#if false + // This does not need to be run every time, but should be run periodically. + // Keeping this clean ensures that back compat converters in Canvas continue to function properly. [InterpreterTheory] [TxtFileData("ExpressionTestCases", "InterpreterExpressionTestCases", nameof(InterpreterRunner), "NumberIsFloat")] public void None_Float(ExpressionTestCase testCase) - { - RunExpressionTestCase(testCase, Features.None, numberIsFloat: true); - } -#endif - - private void RunExpressionTestCase(ExpressionTestCase testCase, Features features, bool numberIsFloat, ITestOutputHelper output) - { - // This is running against embedded resources, so if you're updating the .txt files, - // make sure they build is actually copying them over. + { + RunExpressionTestCase(testCase, Features.None, numberIsFloat: true); + } +#endif + + private void RunExpressionTestCase(ExpressionTestCase testCase, Features features, bool numberIsFloat, ITestOutputHelper output) + { + // This is running against embedded resources, so if you're updating the .txt files, + // make sure they build is actually copying them over. Assert.True(testCase.FailMessage == null, testCase.FailMessage); - var runner = new InterpreterRunner() { NumberIsFloat = numberIsFloat, Features = features, Log = (msg) => output.WriteLine(msg) }; + var runner = new InterpreterRunner() { NumberIsFloat = numberIsFloat, Features = features, Log = (msg) => output.WriteLine(msg) }; var (result, msg) = runner.RunTestCase(testCase); var prefix = $"Test {Path.GetFileName(testCase.SourceFile)}:{testCase.SourceLine}: "; @@ -115,7 +115,7 @@ namespace Microsoft.PowerFx.Interpreter.Tests case TestResult.Skip: Skip.If(true, prefix + msg); break; - } + } } #if false @@ -137,65 +137,65 @@ namespace Microsoft.PowerFx.Interpreter.Tests testRunner.Tests.RemoveAll(x => x.SourceLine != line); } - var result = testRunner.RunTests(); - if (result.Fail > 0) - { - Assert.True(false, result.Output); - } - else - { - Console.WriteLine(result.Output); + var result = testRunner.RunTests(); + if (result.Fail > 0) + { + Assert.True(false, result.Output); } - } + else + { + Console.WriteLine(result.Output); + } + } #endif - // Run cases in MutationScripts + // Run cases in MutationScripts // // Normal tests have each line as an independent test case. - // Whereas these are fed into a repl and each file maintains state. - // - // These tests are run twice, as they are for the non-mutation tests, for both V1 and non-V1 compatibility. - [Theory] - [ReplFileSimpleList("MutationScripts")] + // Whereas these are fed into a repl and each file maintains state. + // + // These tests are run twice, as they are for the non-mutation tests, for both V1 and non-V1 compatibility. + [Theory] + [ReplFileSimpleList("MutationScripts")] public void RunMutationTests_V1(string file) - { + { RunMutationTestFile(file, Features.PowerFxV1, "PowerFxV1"); - } - - [Theory] - [ReplFileSimpleList("MutationScripts")] + } + + [Theory] + [ReplFileSimpleList("MutationScripts")] public void RunMutationTests_Canvas(string file) - { - var features = new Features() - { - TableSyntaxDoesntWrapRecords = true, - ConsistentOneColumnTableResult = true, - }; - - // disable:PowerFxV1CompatibilityRules will force the tests specifically for those behaviors to be excluded from this run. - // DecimalSupport allows tests that are written with Float and Decimal functions to operate; it is not itself a feature - RunMutationTestFile(file, features, "disable:PowerFxV1CompatibilityRules,TableSyntaxDoesntWrapRecords,ConsistentOneColumnTableResult,DecimalSupport"); - } - - private void RunMutationTestFile(string file, Features features, string setup) - { - var path = Path.Combine(System.Environment.CurrentDirectory, "MutationScripts", file); - - var config = new PowerFxConfig(features) { SymbolTable = UserInfoTestSetup.GetUserInfoSymbolTable() }; + { + var features = new Features() + { + TableSyntaxDoesntWrapRecords = true, + ConsistentOneColumnTableResult = true, + }; + + // disable:PowerFxV1CompatibilityRules will force the tests specifically for those behaviors to be excluded from this run. + // DecimalSupport allows tests that are written with Float and Decimal functions to operate; it is not itself a feature + RunMutationTestFile(file, features, "disable:PowerFxV1CompatibilityRules,TableSyntaxDoesntWrapRecords,ConsistentOneColumnTableResult,DecimalSupport"); + } + + private void RunMutationTestFile(string file, Features features, string setup) + { + var path = Path.Combine(System.Environment.CurrentDirectory, "MutationScripts", file); + + var config = new PowerFxConfig(features) { SymbolTable = UserInfoTestSetup.GetUserInfoSymbolTable() }; config.SymbolTable.EnableMutationFunctions(); - var engine = new RecalcEngine(config); - - var rc = new RuntimeConfig(); - rc.SetUserInfo(UserInfoTestSetup.UserInfo); - - var runner = new ReplRunner(engine); - runner._repl.EnableUserObject(); - runner._repl.UserInfo = UserInfoTestSetup.UserInfo.UserInfo; - - // runner._repl.InnerServices = rc.ServiceProvider; - - var testRunner = new TestRunner(runner); - + var engine = new RecalcEngine(config); + + var rc = new RuntimeConfig(); + rc.SetUserInfo(UserInfoTestSetup.UserInfo); + + var runner = new ReplRunner(engine); + runner._repl.EnableUserObject(); + runner._repl.UserInfo = UserInfoTestSetup.UserInfo.UserInfo; + + // runner._repl.InnerServices = rc.ServiceProvider; + + var testRunner = new TestRunner(runner); + testRunner.AddFile(TestRunner.ParseSetupString(setup), path); var result = testRunner.RunTests(); @@ -203,16 +203,16 @@ namespace Microsoft.PowerFx.Interpreter.Tests if (result.Fail > 0) { Assert.True(false, result.Output); - } - else - { - Console.WriteLine(result.Output); - } - } - - // Since test discovery runs in a separate process, run a dedicated - // parse pass as a single unit test to verify all the .txt will parse. - // This doesn't actually run any tests. + } + else + { + Console.WriteLine(result.Output); + } + } + + // Since test discovery runs in a separate process, run a dedicated + // parse pass as a single unit test to verify all the .txt will parse. + // This doesn't actually run any tests. [Fact] public void ScanForTxtParseErrors() { diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/FlagTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/FlagTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/FlagTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/FlagTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/FunctionCompilationTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/FunctionCompilationTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/FunctionCompilationTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/FunctionCompilationTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/FunctionDefinitionTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/FunctionDefinitionTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/FunctionDefinitionTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/FunctionDefinitionTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/GetTokensUtilsTest.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/GetTokensUtilsTest.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/GetTokensUtilsTest.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/GetTokensUtilsTest.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/Helpers/AsyncFunctionHelperBase.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Helpers/AsyncFunctionHelperBase.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/Helpers/AsyncFunctionHelperBase.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Helpers/AsyncFunctionHelperBase.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/Helpers/AsyncFunctionsHelper.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Helpers/AsyncFunctionsHelper.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/Helpers/AsyncFunctionsHelper.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Helpers/AsyncFunctionsHelper.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/Helpers/AsyncVerify.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Helpers/AsyncVerify.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/Helpers/AsyncVerify.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Helpers/AsyncVerify.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/Helpers/ParserOptionsExtensions.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Helpers/ParserOptionsExtensions.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/Helpers/ParserOptionsExtensions.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Helpers/ParserOptionsExtensions.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/Helpers/SingleThreadedGovernor.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Helpers/SingleThreadedGovernor.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/Helpers/SingleThreadedGovernor.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Helpers/SingleThreadedGovernor.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/Helpers/WaitForFunctionsHelper.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Helpers/WaitForFunctionsHelper.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/Helpers/WaitForFunctionsHelper.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Helpers/WaitForFunctionsHelper.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/IRTests/IRTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/IRTests/IRTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/IRTests/IRTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/IRTests/IRTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/IRTests/RewritingIRVisitorTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/IRTests/RewritingIRVisitorTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/IRTests/RewritingIRVisitorTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/IRTests/RewritingIRVisitorTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/IntellisenseTests/CustomFunctionSignatureTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/IntellisenseTests/CustomFunctionSignatureTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/IntellisenseTests/CustomFunctionSignatureTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/IntellisenseTests/CustomFunctionSignatureTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/IntellisenseTests/TestSignatures/10001.json b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/IntellisenseTests/TestSignatures/10001.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/IntellisenseTests/TestSignatures/10001.json rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/IntellisenseTests/TestSignatures/10001.json diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/IntellisenseTests/TestSignatures/10002.json b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/IntellisenseTests/TestSignatures/10002.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/IntellisenseTests/TestSignatures/10002.json rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/IntellisenseTests/TestSignatures/10002.json diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/InterpreterExpressionTestCases/BasicCoercion_V1CompatDisabled_Overrides.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/InterpreterExpressionTestCases/BasicCoercion_V1CompatDisabled_Overrides.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/InterpreterExpressionTestCases/BasicCoercion_V1CompatDisabled_Overrides.txt rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/InterpreterExpressionTestCases/BasicCoercion_V1CompatDisabled_Overrides.txt diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/InterpreterExpressionTestCases/InterpreterError.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/InterpreterExpressionTestCases/InterpreterError.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/InterpreterExpressionTestCases/InterpreterError.txt rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/InterpreterExpressionTestCases/InterpreterError.txt diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/InterpreterExpressionTestCases/MatchFunctions_RegexNotEnabled.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/InterpreterExpressionTestCases/MatchFunctions_RegexNotEnabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/InterpreterExpressionTestCases/MatchFunctions_RegexNotEnabled.txt rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/InterpreterExpressionTestCases/MatchFunctions_RegexNotEnabled.txt diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/InterpreterExpressionTestCases/OpMatrix_Eq_Float_V1CompatDisabled_Overrides.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/InterpreterExpressionTestCases/OpMatrix_Eq_Float_V1CompatDisabled_Overrides.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/InterpreterExpressionTestCases/OpMatrix_Eq_Float_V1CompatDisabled_Overrides.txt rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/InterpreterExpressionTestCases/OpMatrix_Eq_Float_V1CompatDisabled_Overrides.txt diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/InterpreterExpressionTestCases/OpMatrix_Geq_Float_V1CompatDisabled_Overrides.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/InterpreterExpressionTestCases/OpMatrix_Geq_Float_V1CompatDisabled_Overrides.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/InterpreterExpressionTestCases/OpMatrix_Geq_Float_V1CompatDisabled_Overrides.txt rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/InterpreterExpressionTestCases/OpMatrix_Geq_Float_V1CompatDisabled_Overrides.txt diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/InterpreterExpressionTestCases/OpMatrix_Gt_Float_V1CompatDisabled_Overrides.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/InterpreterExpressionTestCases/OpMatrix_Gt_Float_V1CompatDisabled_Overrides.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/InterpreterExpressionTestCases/OpMatrix_Gt_Float_V1CompatDisabled_Overrides.txt rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/InterpreterExpressionTestCases/OpMatrix_Gt_Float_V1CompatDisabled_Overrides.txt diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/InterpreterExpressionTestCases/OpMatrix_Leq_Float_V1CompatDisabled_Overrides.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/InterpreterExpressionTestCases/OpMatrix_Leq_Float_V1CompatDisabled_Overrides.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/InterpreterExpressionTestCases/OpMatrix_Leq_Float_V1CompatDisabled_Overrides.txt rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/InterpreterExpressionTestCases/OpMatrix_Leq_Float_V1CompatDisabled_Overrides.txt diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/InterpreterExpressionTestCases/OpMatrix_Lt_Float_V1CompatDisabled_Overrides.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/InterpreterExpressionTestCases/OpMatrix_Lt_Float_V1CompatDisabled_Overrides.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/InterpreterExpressionTestCases/OpMatrix_Lt_Float_V1CompatDisabled_Overrides.txt rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/InterpreterExpressionTestCases/OpMatrix_Lt_Float_V1CompatDisabled_Overrides.txt diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/InterpreterExpressionTestCases/OpMatrix_Neq_Float_V1CompatDisabled_Overrides.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/InterpreterExpressionTestCases/OpMatrix_Neq_Float_V1CompatDisabled_Overrides.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/InterpreterExpressionTestCases/OpMatrix_Neq_Float_V1CompatDisabled_Overrides.txt rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/InterpreterExpressionTestCases/OpMatrix_Neq_Float_V1CompatDisabled_Overrides.txt diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/InterpreterExpressionTestCases/ParallelAsync.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/InterpreterExpressionTestCases/ParallelAsync.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/InterpreterExpressionTestCases/ParallelAsync.txt rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/InterpreterExpressionTestCases/ParallelAsync.txt diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/InterpreterExpressionTestCases/StronglyTypedEnum_BuiltInEnums_PreV1_Overrides.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/InterpreterExpressionTestCases/StronglyTypedEnum_BuiltInEnums_PreV1_Overrides.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/InterpreterExpressionTestCases/StronglyTypedEnum_BuiltInEnums_PreV1_Overrides.txt rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/InterpreterExpressionTestCases/StronglyTypedEnum_BuiltInEnums_PreV1_Overrides.txt diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/InterpreterExpressionTestCases/StronglyTypedEnums_PreV1_Overrides.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/InterpreterExpressionTestCases/StronglyTypedEnums_PreV1_Overrides.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/InterpreterExpressionTestCases/StronglyTypedEnums_PreV1_Overrides.txt rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/InterpreterExpressionTestCases/StronglyTypedEnums_PreV1_Overrides.txt diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/InterpreterExpressionTestCases/inScalar_V1CompatDisabled_Overrides.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/InterpreterExpressionTestCases/inScalar_V1CompatDisabled_Overrides.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/InterpreterExpressionTestCases/inScalar_V1CompatDisabled_Overrides.txt rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/InterpreterExpressionTestCases/inScalar_V1CompatDisabled_Overrides.txt diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/InterpreterExpressionTestCases/inTable_V1CompatDisabled_Overrides.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/InterpreterExpressionTestCases/inTable_V1CompatDisabled_Overrides.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/InterpreterExpressionTestCases/inTable_V1CompatDisabled_Overrides.txt rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/InterpreterExpressionTestCases/inTable_V1CompatDisabled_Overrides.txt diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/InterpreterSuggestTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/InterpreterSuggestTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/InterpreterSuggestTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/InterpreterSuggestTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/BlankHandler.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/BlankHandler.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/BlankHandler.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/BlankHandler.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/CompletionResult.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/CompletionResult.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/CompletionResult.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/CompletionResult.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/EditorContextScopeTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/EditorContextScopeTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/EditorContextScopeTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/EditorContextScopeTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/JsonRpcCodeActionResponse.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/JsonRpcCodeActionResponse.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/JsonRpcCodeActionResponse.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/JsonRpcCodeActionResponse.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/JsonRpcCommandExecutedResponse.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/JsonRpcCommandExecutedResponse.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/JsonRpcCommandExecutedResponse.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/JsonRpcCommandExecutedResponse.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/JsonRpcCompletionResponse.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/JsonRpcCompletionResponse.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/JsonRpcCompletionResponse.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/JsonRpcCompletionResponse.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/JsonRpcError.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/JsonRpcError.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/JsonRpcError.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/JsonRpcError.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/JsonRpcErrorResponse.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/JsonRpcErrorResponse.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/JsonRpcErrorResponse.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/JsonRpcErrorResponse.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/JsonRpcInitialFixupResponse.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/JsonRpcInitialFixupResponse.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/JsonRpcInitialFixupResponse.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/JsonRpcInitialFixupResponse.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/JsonRpcPublishControlTokensNotification.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/JsonRpcPublishControlTokensNotification.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/JsonRpcPublishControlTokensNotification.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/JsonRpcPublishControlTokensNotification.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/JsonRpcPublishDiagnosticsNotification.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/JsonRpcPublishDiagnosticsNotification.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/JsonRpcPublishDiagnosticsNotification.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/JsonRpcPublishDiagnosticsNotification.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/JsonRpcPublishExpressionTypeNotification.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/JsonRpcPublishExpressionTypeNotification.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/JsonRpcPublishExpressionTypeNotification.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/JsonRpcPublishExpressionTypeNotification.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/JsonRpcPublishTokensNotification.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/JsonRpcPublishTokensNotification.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/JsonRpcPublishTokensNotification.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/JsonRpcPublishTokensNotification.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/JsonRpcSignatureHelpResponse.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/JsonRpcSignatureHelpResponse.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/JsonRpcSignatureHelpResponse.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/JsonRpcSignatureHelpResponse.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/LSPTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/LSPTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/LSPTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/LSPTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/LegacyLanguageServerTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/LegacyLanguageServerTests.cs similarity index 99% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/LegacyLanguageServerTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/LegacyLanguageServerTests.cs index de6240893..30a22450d 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/LegacyLanguageServerTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/LegacyLanguageServerTests.cs @@ -35,11 +35,11 @@ using Xunit.Abstractions; using static Microsoft.PowerFx.Tests.BindingEngineTests; namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol.Tests -{ - /// - /// Do not write tests in this file. - /// Write any new test in RedesignedLanguageServerTest folder. - /// Look there for examples. +{ + /// + /// Do not write tests in this file. + /// Write any new test in RedesignedLanguageServerTest folder. + /// Look there for examples. /// public class LegacyLanguageServerTests : PowerFxTest { @@ -1583,11 +1583,11 @@ namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol.Tests { Explanation = sb.ToString() }; - } - - public override void PreHandleNl2Fx(CustomNL2FxParams nl2FxRequestParams, NL2FxParameters nl2fxParameters, LanguageServerOperationContext operationContext) - { - // do nothing + } + + public override void PreHandleNl2Fx(CustomNL2FxParams nl2FxRequestParams, NL2FxParameters nl2fxParameters, LanguageServerOperationContext operationContext) + { + // do nothing } } diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/MockDataSourceEngine.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/MockDataSourceEngine.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/MockDataSourceEngine.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/MockDataSourceEngine.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/MockSqlEngine.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/MockSqlEngine.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/MockSqlEngine.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/MockSqlEngine.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/MockSymbolTable.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/MockSymbolTable.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/MockSymbolTable.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/MockSymbolTable.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/PositionRangeHelperTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/PositionRangeHelperTests.cs similarity index 99% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/PositionRangeHelperTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/PositionRangeHelperTests.cs index 5bd2be869..ac1da495a 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/PositionRangeHelperTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/PositionRangeHelperTests.cs @@ -74,8 +74,8 @@ namespace Microsoft.PowerFx.Interpreter.Tests.LanguageServiceProtocol // Assert Assert.Equal(startIndex, result.startIndex); Assert.Equal(endIndex, result.endIndex); - } - + } + [Theory] [InlineData("{1}", 1)] [InlineData("12{3}45", 3)] diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/PublicSurfaceTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/PublicSurfaceTests.cs similarity index 99% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/PublicSurfaceTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/PublicSurfaceTests.cs index 403fe8cd9..a321ae9a3 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/PublicSurfaceTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/PublicSurfaceTests.cs @@ -40,13 +40,13 @@ namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol "Microsoft.PowerFx.LanguageServerProtocol.INLHandlerFactory", "Microsoft.PowerFx.LanguageServerProtocol.IPowerFxScopeFx2NL", "Microsoft.PowerFx.LanguageServerProtocol.Fx2NLParameters", - "Microsoft.PowerFx.LanguageServerProtocol.UsageHints", - "Microsoft.PowerFx.LanguageServerProtocol.LanguageServerInput", - "Microsoft.PowerFx.LanguageServerProtocol.LanguageServerOutput", - "Microsoft.PowerFx.LanguageServerProtocol.LanguageServerOutputBuilder", - "Microsoft.PowerFx.LanguageServerProtocol.ILanguageServerLogger", - "Microsoft.PowerFx.LanguageServerProtocol.IHostTaskExecutor", - "Microsoft.PowerFx.LanguageServerProtocol.Handlers.LanguageServerOperationContext", + "Microsoft.PowerFx.LanguageServerProtocol.UsageHints", + "Microsoft.PowerFx.LanguageServerProtocol.LanguageServerInput", + "Microsoft.PowerFx.LanguageServerProtocol.LanguageServerOutput", + "Microsoft.PowerFx.LanguageServerProtocol.LanguageServerOutputBuilder", + "Microsoft.PowerFx.LanguageServerProtocol.ILanguageServerLogger", + "Microsoft.PowerFx.LanguageServerProtocol.IHostTaskExecutor", + "Microsoft.PowerFx.LanguageServerProtocol.Handlers.LanguageServerOperationContext", // Internal "Microsoft.PowerFx.LanguageServerProtocol.JsonRpcHelper", diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/CancellationTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/CancellationTests.cs similarity index 97% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/CancellationTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/CancellationTests.cs index 95f7d8473..15bbfab04 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/CancellationTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/CancellationTests.cs @@ -1,42 +1,42 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -using System.Threading; -using System.Threading.Tasks; -using Microsoft.PowerFx.LanguageServerProtocol; -using Microsoft.PowerFx.LanguageServerProtocol.Protocol; -using Microsoft.PowerFx.Types; -using Xunit; - -namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol -{ - public partial class LanguageServerTestBase - { - [Fact] - public async Task TestNl2FxIsCanceledCorrectly() - { - // Arrange - var documentUri = "powerfx://app?context=1"; - var engine = new Engine(); - var symbols = new SymbolTable(); - symbols.AddVariable("Score", FormulaType.Number); - var scope = engine.CreateEditorScope(symbols: symbols); - var scopeFactory = new TestPowerFxScopeFactory((string documentUri) => scope); - Init(new InitParams(scopeFactory: scopeFactory)); - var nl2FxHandler = CreateAndConfigureNl2FxHandler(); - nl2FxHandler.ThrowOnCancellation = true; - nl2FxHandler.Nl2FxDelayTime = 800; - var payload = NL2FxMessageJson(documentUri); - using var source = new CancellationTokenSource(); - source.CancelAfter(500); - - // Act - var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload, source.Token).ConfigureAwait(false); - - // Assert - AssertErrorPayload(rawResponse, payload.id, JsonRpcHelper.ErrorCode.RequestCancelled); - Assert.NotEmpty(TestServer.UnhandledExceptions); - Assert.Equal(1, nl2FxHandler.PreHandleNl2FxCallCount); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.PowerFx.LanguageServerProtocol; +using Microsoft.PowerFx.LanguageServerProtocol.Protocol; +using Microsoft.PowerFx.Types; +using Xunit; + +namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol +{ + public partial class LanguageServerTestBase + { + [Fact] + public async Task TestNl2FxIsCanceledCorrectly() + { + // Arrange + var documentUri = "powerfx://app?context=1"; + var engine = new Engine(); + var symbols = new SymbolTable(); + symbols.AddVariable("Score", FormulaType.Number); + var scope = engine.CreateEditorScope(symbols: symbols); + var scopeFactory = new TestPowerFxScopeFactory((string documentUri) => scope); + Init(new InitParams(scopeFactory: scopeFactory)); + var nl2FxHandler = CreateAndConfigureNl2FxHandler(); + nl2FxHandler.ThrowOnCancellation = true; + nl2FxHandler.Nl2FxDelayTime = 800; + var payload = NL2FxMessageJson(documentUri); + using var source = new CancellationTokenSource(); + source.CancelAfter(500); + + // Act + var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload, source.Token).ConfigureAwait(false); + + // Assert + AssertErrorPayload(rawResponse, payload.id, JsonRpcHelper.ErrorCode.RequestCancelled); + Assert.NotEmpty(TestServer.UnhandledExceptions); + Assert.Equal(1, nl2FxHandler.PreHandleNl2FxCallCount); + } + } +} diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/CodeActionTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/CodeActionTests.cs similarity index 97% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/CodeActionTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/CodeActionTests.cs index b7babb0ae..0fec6b0bd 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/CodeActionTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/CodeActionTests.cs @@ -1,245 +1,245 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.PowerFx.Intellisense; -using Microsoft.PowerFx.Interpreter.Tests.LanguageServiceProtocol; -using Microsoft.PowerFx.LanguageServerProtocol; -using Microsoft.PowerFx.LanguageServerProtocol.Protocol; -using Microsoft.PowerFx.Tests.LanguageServiceProtocol.Tests; -using Xunit; - -namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol -{ - public partial class LanguageServerTestBase - { - private class DummyQuickFixHandler : CodeFixHandler - { - public override async Task> SuggestFixesAsync(Engine engine, CheckResult checkResult, CancellationToken cancel) - { - return new CodeFixSuggestion[] - { - new CodeFixSuggestion - { - SuggestedText = "TestText1", - Title = "TestTitle1" - } - }; - } - } - - // Failure from one handler shouldn't block others. - public class ExceptionQuickFixHandler : CodeFixHandler - { - public int _counter = 0; - - public override async Task> SuggestFixesAsync(Engine engine, CheckResult checkResult, CancellationToken cancel) - { - _counter++; - throw new Exception($"expected failure"); - } - } - - [Fact] - public async Task TestCodeAction() - { - // Arrange - var failHandler = new ExceptionQuickFixHandler(); - var engine = new Engine(new PowerFxConfig()); - var editor = engine.CreateEditorScope(); - editor.AddQuickFixHandler(new DummyQuickFixHandler()); - editor.AddQuickFixHandler(failHandler); - var scopeFactory = new TestPowerFxScopeFactory((string documentUri) => editor); - Init(new InitParams(scopeFactory: scopeFactory)); - var codeActionParams = new CodeActionParams() - { - TextDocument = GetTextDocument("powerfx://test?expression=IsBlank(&context={\"A\":1,\"B\":[1,2,3]}"), - Range = new LanguageServerProtocol.Protocol.Range() - { - Start = new Position - { - Line = 0, - Character = 0 - }, - End = new Position - { - Line = 0, - Character = 10 - } - }, - Context = new CodeActionContext() { Only = new[] { CodeActionKind.QuickFix } } - }; - var payload = GetCodeActionPayload(codeActionParams); - - // Act - var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); - - var response = AssertAndGetResponsePayload>(rawResponse, payload.id); - Assert.NotEmpty(response); - Assert.Contains(CodeActionKind.QuickFix, response.Keys); - Assert.True(response[CodeActionKind.QuickFix].Length == 1, "Quick fix didn't return expected suggestion."); - Assert.Equal("TestTitle1", response[CodeActionKind.QuickFix][0].Title); - Assert.NotEmpty(response[CodeActionKind.QuickFix][0].Edit.Changes); - Assert.Contains(codeActionParams.TextDocument.Uri, response[CodeActionKind.QuickFix][0].Edit.Changes.Keys); - Assert.Equal("TestText1", response[CodeActionKind.QuickFix][0].Edit.Changes[codeActionParams.TextDocument.Uri][0].NewText); - - // Fail handler was invokde, but didn't block us. - Assert.Equal(1, failHandler._counter); // Invoked - Assert.Single(TestServer.UnhandledExceptions); - } - - // Test a codefix using a customization, ICodeFixHandler - [Fact] - public async Task TestCodeActionWithHandlerAndExpressionInUriOrInTextDocument() - { - // Blank(A) is error, should change to IsBlank(A) - var original = "Blank(A)"; - var updated = "IsBlank(A)"; - foreach (var addExprToUri in new bool[] { true, false }) - { - var codeActionParams = new CodeActionParams - { - TextDocument = addExprToUri ? - GetTextDocument(GetUri("context={\"A\":1,\"B\":[1,2,3]}&expression=" + original)) : - GetTextDocument(GetUri("context={\"A\":1,\"B\":[1,2,3]}")), - Text = addExprToUri ? null : original, - Range = SemanticTokensRelatedTestsHelper.CreateRange(0, 0, 0, 10), - Context = GetDefaultCodeActionContext() - }; - await TestCodeActionWithHandlerCore(codeActionParams, updated).ConfigureAwait(false); - } - - await TestCodeActionWithHandlerCore( - new CodeActionParams - { - TextDocument = GetTextDocument(GetUri("context={\"A\":1,\"B\":[1,2,3]}&expression=Max(1,2")), - Text = original, - Range = SemanticTokensRelatedTestsHelper.CreateRange(0, 0, 0, 10), - Context = GetDefaultCodeActionContext() - }, updated).ConfigureAwait(false); - } - - private async Task TestCodeActionWithHandlerCore(CodeActionParams codeActionParams, string updatedExpr) - { - var engine = new RecalcEngine(); - var scopeFactory = new TestPowerFxScopeFactory((string documentUri) => - { - var scope = engine.CreateEditorScope(); - scope.AddQuickFixHandler(new BlankHandler()); - return scope; - }); - Init(new InitParams(scopeFactory: scopeFactory)); - - var payload = GetCodeActionPayload(codeActionParams); - var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); - - var response = AssertAndGetResponsePayload>(rawResponse, payload.id); - Assert.NotEmpty(response); - Assert.Contains(CodeActionKind.QuickFix, response.Keys); - Assert.True(response[CodeActionKind.QuickFix].Length == 1, "Quick fix didn't return expected suggestion."); - Assert.Equal(BlankHandler.Title, response[CodeActionKind.QuickFix][0].Title); - Assert.NotEmpty(response[CodeActionKind.QuickFix][0].Edit.Changes); - Assert.Contains(codeActionParams.TextDocument.Uri, response[CodeActionKind.QuickFix][0].Edit.Changes.Keys); - - Assert.Equal(updatedExpr, response[CodeActionKind.QuickFix][0].Edit.Changes[codeActionParams.TextDocument.Uri][0].NewText); - Assert.Empty(TestServer.UnhandledExceptions); - } - - [Fact] - public async Task TestCodeActionCommandExecuted() - { - var engine = new RecalcEngine(); - - var scopeFactory = new TestPowerFxScopeFactory((string documentUri) => - { - var scope = engine.CreateEditorScope(); - scope.AddQuickFixHandler(new BlankHandler()); - return scope; - }); - Init(new InitParams(scopeFactory: scopeFactory)); - - var documentUri = "powerfx://test?expression=Blank(A)&context={\"A\":1,\"B\":[1,2,3]}"; - var codeActionsParams1 = new CodeActionParams() - { - TextDocument = GetTextDocument(documentUri), - Range = new LanguageServerProtocol.Protocol.Range() - { - Start = new Position - { - Line = 0, - Character = 0 - }, - End = new Position - { - Line = 0, - Character = 10 - } - }, - Context = new CodeActionContext() { Only = new[] { CodeActionKind.QuickFix } } - }; - var payload = GetCodeActionPayload(codeActionsParams1); - var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); - var response = AssertAndGetResponsePayload>(rawResponse, payload.id); - Assert.NotEmpty(response); - - var codeActionResult = response[CodeActionKind.QuickFix][0]; - - Assert.NotNull(codeActionResult); - Assert.NotNull(codeActionResult.ActionResultContext); - Assert.Equal(typeof(BlankHandler).FullName, codeActionResult.ActionResultContext.HandlerName); - Assert.Equal("Suggestion", codeActionResult.ActionResultContext.ActionIdentifier); - var commandExecutedParams = new CommandExecutedParams() - { - TextDocument = new TextDocumentIdentifier() - { - Uri = documentUri - }, - Command = CommandName.CodeActionApplied, - Argument = JsonRpcHelper.Serialize(codeActionResult) - }; - var commandExecutedPayload = GetRequestPayload(commandExecutedParams, CustomProtocolNames.CommandExecuted); - rawResponse = await TestServer.OnDataReceivedAsync(commandExecutedPayload.payload).ConfigureAwait(false); - Assert.True(string.IsNullOrEmpty(rawResponse)); - - commandExecutedParams = new CommandExecutedParams() - { - TextDocument = new TextDocumentIdentifier() - { - Uri = documentUri - }, - Command = CommandName.CodeActionApplied, - Argument = string.Empty - }; - commandExecutedPayload = GetRequestPayload(commandExecutedParams, CustomProtocolNames.CommandExecuted); - rawResponse = await TestServer.OnDataReceivedAsync(commandExecutedPayload.payload).ConfigureAwait(false); - AssertErrorPayload(rawResponse, commandExecutedPayload.id, JsonRpcHelper.ErrorCode.PropertyValueRequired); - - codeActionResult.ActionResultContext = null; - commandExecutedParams = new CommandExecutedParams() - { - TextDocument = new TextDocumentIdentifier() - { - Uri = documentUri - }, - Command = CommandName.CodeActionApplied, - Argument = JsonRpcHelper.Serialize(codeActionResult) - }; - commandExecutedPayload = GetRequestPayload(commandExecutedParams, CustomProtocolNames.CommandExecuted); - rawResponse = await TestServer.OnDataReceivedAsync(commandExecutedPayload.payload).ConfigureAwait(false); - AssertErrorPayload(rawResponse, commandExecutedPayload.id, JsonRpcHelper.ErrorCode.PropertyValueRequired); - } - - private static CodeActionContext GetDefaultCodeActionContext() - { - return new CodeActionContext() { Only = new[] { CodeActionKind.QuickFix } }; - } - - private static (string payload, string id) GetCodeActionPayload(CodeActionParams codeActionParams) - { - return GetRequestPayload(codeActionParams, TextDocumentNames.CodeAction); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.PowerFx.Intellisense; +using Microsoft.PowerFx.Interpreter.Tests.LanguageServiceProtocol; +using Microsoft.PowerFx.LanguageServerProtocol; +using Microsoft.PowerFx.LanguageServerProtocol.Protocol; +using Microsoft.PowerFx.Tests.LanguageServiceProtocol.Tests; +using Xunit; + +namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol +{ + public partial class LanguageServerTestBase + { + private class DummyQuickFixHandler : CodeFixHandler + { + public override async Task> SuggestFixesAsync(Engine engine, CheckResult checkResult, CancellationToken cancel) + { + return new CodeFixSuggestion[] + { + new CodeFixSuggestion + { + SuggestedText = "TestText1", + Title = "TestTitle1" + } + }; + } + } + + // Failure from one handler shouldn't block others. + public class ExceptionQuickFixHandler : CodeFixHandler + { + public int _counter = 0; + + public override async Task> SuggestFixesAsync(Engine engine, CheckResult checkResult, CancellationToken cancel) + { + _counter++; + throw new Exception($"expected failure"); + } + } + + [Fact] + public async Task TestCodeAction() + { + // Arrange + var failHandler = new ExceptionQuickFixHandler(); + var engine = new Engine(new PowerFxConfig()); + var editor = engine.CreateEditorScope(); + editor.AddQuickFixHandler(new DummyQuickFixHandler()); + editor.AddQuickFixHandler(failHandler); + var scopeFactory = new TestPowerFxScopeFactory((string documentUri) => editor); + Init(new InitParams(scopeFactory: scopeFactory)); + var codeActionParams = new CodeActionParams() + { + TextDocument = GetTextDocument("powerfx://test?expression=IsBlank(&context={\"A\":1,\"B\":[1,2,3]}"), + Range = new LanguageServerProtocol.Protocol.Range() + { + Start = new Position + { + Line = 0, + Character = 0 + }, + End = new Position + { + Line = 0, + Character = 10 + } + }, + Context = new CodeActionContext() { Only = new[] { CodeActionKind.QuickFix } } + }; + var payload = GetCodeActionPayload(codeActionParams); + + // Act + var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); + + var response = AssertAndGetResponsePayload>(rawResponse, payload.id); + Assert.NotEmpty(response); + Assert.Contains(CodeActionKind.QuickFix, response.Keys); + Assert.True(response[CodeActionKind.QuickFix].Length == 1, "Quick fix didn't return expected suggestion."); + Assert.Equal("TestTitle1", response[CodeActionKind.QuickFix][0].Title); + Assert.NotEmpty(response[CodeActionKind.QuickFix][0].Edit.Changes); + Assert.Contains(codeActionParams.TextDocument.Uri, response[CodeActionKind.QuickFix][0].Edit.Changes.Keys); + Assert.Equal("TestText1", response[CodeActionKind.QuickFix][0].Edit.Changes[codeActionParams.TextDocument.Uri][0].NewText); + + // Fail handler was invokde, but didn't block us. + Assert.Equal(1, failHandler._counter); // Invoked + Assert.Single(TestServer.UnhandledExceptions); + } + + // Test a codefix using a customization, ICodeFixHandler + [Fact] + public async Task TestCodeActionWithHandlerAndExpressionInUriOrInTextDocument() + { + // Blank(A) is error, should change to IsBlank(A) + var original = "Blank(A)"; + var updated = "IsBlank(A)"; + foreach (var addExprToUri in new bool[] { true, false }) + { + var codeActionParams = new CodeActionParams + { + TextDocument = addExprToUri ? + GetTextDocument(GetUri("context={\"A\":1,\"B\":[1,2,3]}&expression=" + original)) : + GetTextDocument(GetUri("context={\"A\":1,\"B\":[1,2,3]}")), + Text = addExprToUri ? null : original, + Range = SemanticTokensRelatedTestsHelper.CreateRange(0, 0, 0, 10), + Context = GetDefaultCodeActionContext() + }; + await TestCodeActionWithHandlerCore(codeActionParams, updated).ConfigureAwait(false); + } + + await TestCodeActionWithHandlerCore( + new CodeActionParams + { + TextDocument = GetTextDocument(GetUri("context={\"A\":1,\"B\":[1,2,3]}&expression=Max(1,2")), + Text = original, + Range = SemanticTokensRelatedTestsHelper.CreateRange(0, 0, 0, 10), + Context = GetDefaultCodeActionContext() + }, updated).ConfigureAwait(false); + } + + private async Task TestCodeActionWithHandlerCore(CodeActionParams codeActionParams, string updatedExpr) + { + var engine = new RecalcEngine(); + var scopeFactory = new TestPowerFxScopeFactory((string documentUri) => + { + var scope = engine.CreateEditorScope(); + scope.AddQuickFixHandler(new BlankHandler()); + return scope; + }); + Init(new InitParams(scopeFactory: scopeFactory)); + + var payload = GetCodeActionPayload(codeActionParams); + var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); + + var response = AssertAndGetResponsePayload>(rawResponse, payload.id); + Assert.NotEmpty(response); + Assert.Contains(CodeActionKind.QuickFix, response.Keys); + Assert.True(response[CodeActionKind.QuickFix].Length == 1, "Quick fix didn't return expected suggestion."); + Assert.Equal(BlankHandler.Title, response[CodeActionKind.QuickFix][0].Title); + Assert.NotEmpty(response[CodeActionKind.QuickFix][0].Edit.Changes); + Assert.Contains(codeActionParams.TextDocument.Uri, response[CodeActionKind.QuickFix][0].Edit.Changes.Keys); + + Assert.Equal(updatedExpr, response[CodeActionKind.QuickFix][0].Edit.Changes[codeActionParams.TextDocument.Uri][0].NewText); + Assert.Empty(TestServer.UnhandledExceptions); + } + + [Fact] + public async Task TestCodeActionCommandExecuted() + { + var engine = new RecalcEngine(); + + var scopeFactory = new TestPowerFxScopeFactory((string documentUri) => + { + var scope = engine.CreateEditorScope(); + scope.AddQuickFixHandler(new BlankHandler()); + return scope; + }); + Init(new InitParams(scopeFactory: scopeFactory)); + + var documentUri = "powerfx://test?expression=Blank(A)&context={\"A\":1,\"B\":[1,2,3]}"; + var codeActionsParams1 = new CodeActionParams() + { + TextDocument = GetTextDocument(documentUri), + Range = new LanguageServerProtocol.Protocol.Range() + { + Start = new Position + { + Line = 0, + Character = 0 + }, + End = new Position + { + Line = 0, + Character = 10 + } + }, + Context = new CodeActionContext() { Only = new[] { CodeActionKind.QuickFix } } + }; + var payload = GetCodeActionPayload(codeActionsParams1); + var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); + var response = AssertAndGetResponsePayload>(rawResponse, payload.id); + Assert.NotEmpty(response); + + var codeActionResult = response[CodeActionKind.QuickFix][0]; + + Assert.NotNull(codeActionResult); + Assert.NotNull(codeActionResult.ActionResultContext); + Assert.Equal(typeof(BlankHandler).FullName, codeActionResult.ActionResultContext.HandlerName); + Assert.Equal("Suggestion", codeActionResult.ActionResultContext.ActionIdentifier); + var commandExecutedParams = new CommandExecutedParams() + { + TextDocument = new TextDocumentIdentifier() + { + Uri = documentUri + }, + Command = CommandName.CodeActionApplied, + Argument = JsonRpcHelper.Serialize(codeActionResult) + }; + var commandExecutedPayload = GetRequestPayload(commandExecutedParams, CustomProtocolNames.CommandExecuted); + rawResponse = await TestServer.OnDataReceivedAsync(commandExecutedPayload.payload).ConfigureAwait(false); + Assert.True(string.IsNullOrEmpty(rawResponse)); + + commandExecutedParams = new CommandExecutedParams() + { + TextDocument = new TextDocumentIdentifier() + { + Uri = documentUri + }, + Command = CommandName.CodeActionApplied, + Argument = string.Empty + }; + commandExecutedPayload = GetRequestPayload(commandExecutedParams, CustomProtocolNames.CommandExecuted); + rawResponse = await TestServer.OnDataReceivedAsync(commandExecutedPayload.payload).ConfigureAwait(false); + AssertErrorPayload(rawResponse, commandExecutedPayload.id, JsonRpcHelper.ErrorCode.PropertyValueRequired); + + codeActionResult.ActionResultContext = null; + commandExecutedParams = new CommandExecutedParams() + { + TextDocument = new TextDocumentIdentifier() + { + Uri = documentUri + }, + Command = CommandName.CodeActionApplied, + Argument = JsonRpcHelper.Serialize(codeActionResult) + }; + commandExecutedPayload = GetRequestPayload(commandExecutedParams, CustomProtocolNames.CommandExecuted); + rawResponse = await TestServer.OnDataReceivedAsync(commandExecutedPayload.payload).ConfigureAwait(false); + AssertErrorPayload(rawResponse, commandExecutedPayload.id, JsonRpcHelper.ErrorCode.PropertyValueRequired); + } + + private static CodeActionContext GetDefaultCodeActionContext() + { + return new CodeActionContext() { Only = new[] { CodeActionKind.QuickFix } }; + } + + private static (string payload, string id) GetCodeActionPayload(CodeActionParams codeActionParams) + { + return GetRequestPayload(codeActionParams, TextDocumentNames.CodeAction); + } + } +} diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/CompletionsTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/CompletionsTests.cs similarity index 98% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/CompletionsTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/CompletionsTests.cs index 3aabaf2a4..da62dbbf0 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/CompletionsTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/CompletionsTests.cs @@ -1,175 +1,175 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.PowerFx.LanguageServerProtocol; -using Microsoft.PowerFx.LanguageServerProtocol.Protocol; -using Microsoft.PowerFx.Tests.LanguageServiceProtocol.Tests; -using Xunit; - -namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol -{ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.PowerFx.LanguageServerProtocol; +using Microsoft.PowerFx.LanguageServerProtocol.Protocol; +using Microsoft.PowerFx.Tests.LanguageServiceProtocol.Tests; +using Xunit; + +namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol +{ public partial class LanguageServerTestBase - { - [Theory] - [InlineData("Color.AliceBl", 13, false)] - [InlineData("Behavior(); Color.AliceBl", 25, true)] - - // $$$ This test generates an internal error as we use an behavior function but we have no way to check its presence - [InlineData("Behavior(); Color.AliceBl", 25, false)] - public async Task TestCompletionWithExpressionInUriOrNotInUri(string text, int offset, bool withAllowSideEffects) - { - foreach (var addExprToUri in new bool[] { true, false }) - { - var params1 = new CompletionParams() - { - TextDocument = addExprToUri ? GetTextDocument(GetUri("expression=" + text)) : GetTextDocument(), - Text = addExprToUri ? null : text, - Position = GetPosition(offset), - Context = GetCompletionContext() - }; - var params2 = new CompletionParams() - { - TextDocument = addExprToUri ? - GetTextDocument(GetUri("expression=Color.&context={\"A\":\"ABC\",\"B\":{\"Inner\":123}}")) : - GetTextDocument(GetUri("context={\"A\":\"ABC\",\"B\":{\"Inner\":123}}")), - Text = addExprToUri ? null : "Color.", - Position = GetPosition(7), - Context = GetCompletionContext() - }; - var params3 = new CompletionParams() - { - TextDocument = addExprToUri ? GetTextDocument(GetUri("expression={a:{},b:{},c:{}}.")) : GetTextDocument(), - Text = addExprToUri ? null : "{a:{},b:{},c:{}}.", - Position = GetPosition(17), - Context = GetCompletionContext() - }; - await TestCompletionCore(params1, params2, params3, withAllowSideEffects).ConfigureAwait(false); - } - } - - [Fact] - public async Task TestCompletionWithExpressionBothInUriAndTextDocument() - { - int offset = 25; - string text = "Behavior(); Color.AliceBl"; - string uriText = "Max(1,"; - bool withAllowSideEffects = false; - - var params1 = new CompletionParams() - { - TextDocument = GetTextDocument(GetUri("expression=" + uriText)), - Text = text, - Position = GetPosition(offset), - Context = GetCompletionContext() - }; - var params2 = new CompletionParams() - { - TextDocument = GetTextDocument(GetUri("context={\"A\":\"ABC\",\"B\":{\"Inner\":123}}&expression=1+1")), - Text = "Color.", - Position = GetPosition(7), - Context = GetCompletionContext() - }; - var params3 = new CompletionParams() - { - TextDocument = GetTextDocument(GetUri("expression=Color.")), - Text = "{a:{},b:{},c:{}}.", - Position = GetPosition(17), - Context = GetCompletionContext() - }; - await TestCompletionCore(params1, params2, params3, withAllowSideEffects).ConfigureAwait(false); - } - - private async Task TestCompletionCore(CompletionParams params1, CompletionParams params2, CompletionParams params3, bool withAllowSideEffects) - { - Init(new InitParams(options: GetParserOptions(withAllowSideEffects))); - - // test good formula - var payload = GetCompletionPayload(params1); - var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); - var response = AssertAndGetResponsePayload(rawResponse, payload.id); - var foundItems = response.Items.Where(item => item.Label == "AliceBlue"); - Assert.True(Enumerable.Count(foundItems) == 1, "AliceBlue should be found from suggestion result"); - Assert.Equal("AliceBlue", foundItems.First().InsertText); - Assert.Equal("000", foundItems.First().SortText); - - payload = GetCompletionPayload(params2); - rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); - response = AssertAndGetResponsePayload(rawResponse, payload.id); - foundItems = response.Items.Where(item => item.Label == "AliceBlue"); - Assert.Equal(CompletionItemKind.Variable, foundItems.First().Kind); - Assert.True(Enumerable.Count(foundItems) == 1, "AliceBlue should be found from suggestion result"); - Assert.Equal("AliceBlue", foundItems.First().InsertText); - Assert.Equal("000", foundItems.First().SortText); - - payload = GetCompletionPayload(params3); - rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); - response = AssertAndGetResponsePayload(rawResponse, payload.id); - foundItems = response.Items.Where(item => item.Label == "a"); - Assert.True(Enumerable.Count(foundItems) == 1, "'a' should be found from suggestion result"); - Assert.Equal(CompletionItemKind.Variable, foundItems.First().Kind); - Assert.Equal("a", foundItems.First().InsertText); - Assert.Equal("000", foundItems.First().SortText); - - foundItems = response.Items.Where(item => item.Label == "b"); - Assert.True(Enumerable.Count(foundItems) == 1, "'b' should be found from suggestion result"); - Assert.Equal(CompletionItemKind.Variable, foundItems.First().Kind); - Assert.Equal("b", foundItems.First().InsertText); - Assert.Equal("001", foundItems.First().SortText); - - foundItems = response.Items.Where(item => item.Label == "c"); - Assert.True(Enumerable.Count(foundItems) == 1, "'c' should be found from suggestion result"); - Assert.Equal(CompletionItemKind.Variable, foundItems.First().Kind); - Assert.Equal("c", foundItems.First().InsertText); - Assert.Equal("002", foundItems.First().SortText); - - // missing 'expression' in documentUri - payload = GetCompletionPayload(new CompletionParams() - { - TextDocument = GetTextDocument("powerfx://test"), - Position = GetPosition(1), - Context = GetCompletionContext() - }); - var errorResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); - AssertErrorPayload(errorResponse, payload.id, JsonRpcHelper.ErrorCode.InvalidParams); - } - - [Theory] - [InlineData("'A", 1)] - [InlineData("'Acc", 1)] - public async Task TestCompletionWithIdentifierDelimiter(string text, int offset) - { - var scopeFactory = new TestPowerFxScopeFactory((string documentUri) => new MockDataSourceEngine()); - Init(new InitParams(scopeFactory: scopeFactory)); - var params1 = new CompletionParams() - { - TextDocument = GetTextDocument(GetUri("expression=" + text)), - Text = text, - Position = GetPosition(offset), - Context = GetCompletionContext() - }; - var payload = GetCompletionPayload(params1); - var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); - var response = AssertAndGetResponsePayload(rawResponse, payload.id); - var foundItems = response.Items.Where(item => item.Label == "'Account'"); - Assert.True(Enumerable.Count(foundItems) == 1, "'Account' should be found from suggestion result"); - - // Test that the Identifier delimiter is ignored in case of insertText, - // when preceding character is also the same identifier delimiter - Assert.Equal("Account'", foundItems.First().InsertText); - Assert.Equal("000", foundItems.First().SortText); - } - - private static (string payload, string id) GetCompletionPayload(CompletionParams completionParams) - { - return GetRequestPayload(completionParams, TextDocumentNames.Completion); - } - - private static CompletionContext GetCompletionContext() - { - return new CompletionContext(); + { + [Theory] + [InlineData("Color.AliceBl", 13, false)] + [InlineData("Behavior(); Color.AliceBl", 25, true)] + + // $$$ This test generates an internal error as we use an behavior function but we have no way to check its presence + [InlineData("Behavior(); Color.AliceBl", 25, false)] + public async Task TestCompletionWithExpressionInUriOrNotInUri(string text, int offset, bool withAllowSideEffects) + { + foreach (var addExprToUri in new bool[] { true, false }) + { + var params1 = new CompletionParams() + { + TextDocument = addExprToUri ? GetTextDocument(GetUri("expression=" + text)) : GetTextDocument(), + Text = addExprToUri ? null : text, + Position = GetPosition(offset), + Context = GetCompletionContext() + }; + var params2 = new CompletionParams() + { + TextDocument = addExprToUri ? + GetTextDocument(GetUri("expression=Color.&context={\"A\":\"ABC\",\"B\":{\"Inner\":123}}")) : + GetTextDocument(GetUri("context={\"A\":\"ABC\",\"B\":{\"Inner\":123}}")), + Text = addExprToUri ? null : "Color.", + Position = GetPosition(7), + Context = GetCompletionContext() + }; + var params3 = new CompletionParams() + { + TextDocument = addExprToUri ? GetTextDocument(GetUri("expression={a:{},b:{},c:{}}.")) : GetTextDocument(), + Text = addExprToUri ? null : "{a:{},b:{},c:{}}.", + Position = GetPosition(17), + Context = GetCompletionContext() + }; + await TestCompletionCore(params1, params2, params3, withAllowSideEffects).ConfigureAwait(false); + } } - } -} + + [Fact] + public async Task TestCompletionWithExpressionBothInUriAndTextDocument() + { + int offset = 25; + string text = "Behavior(); Color.AliceBl"; + string uriText = "Max(1,"; + bool withAllowSideEffects = false; + + var params1 = new CompletionParams() + { + TextDocument = GetTextDocument(GetUri("expression=" + uriText)), + Text = text, + Position = GetPosition(offset), + Context = GetCompletionContext() + }; + var params2 = new CompletionParams() + { + TextDocument = GetTextDocument(GetUri("context={\"A\":\"ABC\",\"B\":{\"Inner\":123}}&expression=1+1")), + Text = "Color.", + Position = GetPosition(7), + Context = GetCompletionContext() + }; + var params3 = new CompletionParams() + { + TextDocument = GetTextDocument(GetUri("expression=Color.")), + Text = "{a:{},b:{},c:{}}.", + Position = GetPosition(17), + Context = GetCompletionContext() + }; + await TestCompletionCore(params1, params2, params3, withAllowSideEffects).ConfigureAwait(false); + } + + private async Task TestCompletionCore(CompletionParams params1, CompletionParams params2, CompletionParams params3, bool withAllowSideEffects) + { + Init(new InitParams(options: GetParserOptions(withAllowSideEffects))); + + // test good formula + var payload = GetCompletionPayload(params1); + var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); + var response = AssertAndGetResponsePayload(rawResponse, payload.id); + var foundItems = response.Items.Where(item => item.Label == "AliceBlue"); + Assert.True(Enumerable.Count(foundItems) == 1, "AliceBlue should be found from suggestion result"); + Assert.Equal("AliceBlue", foundItems.First().InsertText); + Assert.Equal("000", foundItems.First().SortText); + + payload = GetCompletionPayload(params2); + rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); + response = AssertAndGetResponsePayload(rawResponse, payload.id); + foundItems = response.Items.Where(item => item.Label == "AliceBlue"); + Assert.Equal(CompletionItemKind.Variable, foundItems.First().Kind); + Assert.True(Enumerable.Count(foundItems) == 1, "AliceBlue should be found from suggestion result"); + Assert.Equal("AliceBlue", foundItems.First().InsertText); + Assert.Equal("000", foundItems.First().SortText); + + payload = GetCompletionPayload(params3); + rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); + response = AssertAndGetResponsePayload(rawResponse, payload.id); + foundItems = response.Items.Where(item => item.Label == "a"); + Assert.True(Enumerable.Count(foundItems) == 1, "'a' should be found from suggestion result"); + Assert.Equal(CompletionItemKind.Variable, foundItems.First().Kind); + Assert.Equal("a", foundItems.First().InsertText); + Assert.Equal("000", foundItems.First().SortText); + + foundItems = response.Items.Where(item => item.Label == "b"); + Assert.True(Enumerable.Count(foundItems) == 1, "'b' should be found from suggestion result"); + Assert.Equal(CompletionItemKind.Variable, foundItems.First().Kind); + Assert.Equal("b", foundItems.First().InsertText); + Assert.Equal("001", foundItems.First().SortText); + + foundItems = response.Items.Where(item => item.Label == "c"); + Assert.True(Enumerable.Count(foundItems) == 1, "'c' should be found from suggestion result"); + Assert.Equal(CompletionItemKind.Variable, foundItems.First().Kind); + Assert.Equal("c", foundItems.First().InsertText); + Assert.Equal("002", foundItems.First().SortText); + + // missing 'expression' in documentUri + payload = GetCompletionPayload(new CompletionParams() + { + TextDocument = GetTextDocument("powerfx://test"), + Position = GetPosition(1), + Context = GetCompletionContext() + }); + var errorResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); + AssertErrorPayload(errorResponse, payload.id, JsonRpcHelper.ErrorCode.InvalidParams); + } + + [Theory] + [InlineData("'A", 1)] + [InlineData("'Acc", 1)] + public async Task TestCompletionWithIdentifierDelimiter(string text, int offset) + { + var scopeFactory = new TestPowerFxScopeFactory((string documentUri) => new MockDataSourceEngine()); + Init(new InitParams(scopeFactory: scopeFactory)); + var params1 = new CompletionParams() + { + TextDocument = GetTextDocument(GetUri("expression=" + text)), + Text = text, + Position = GetPosition(offset), + Context = GetCompletionContext() + }; + var payload = GetCompletionPayload(params1); + var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); + var response = AssertAndGetResponsePayload(rawResponse, payload.id); + var foundItems = response.Items.Where(item => item.Label == "'Account'"); + Assert.True(Enumerable.Count(foundItems) == 1, "'Account' should be found from suggestion result"); + + // Test that the Identifier delimiter is ignored in case of insertText, + // when preceding character is also the same identifier delimiter + Assert.Equal("Account'", foundItems.First().InsertText); + Assert.Equal("000", foundItems.First().SortText); + } + + private static (string payload, string id) GetCompletionPayload(CompletionParams completionParams) + { + return GetRequestPayload(completionParams, TextDocumentNames.Completion); + } + + private static CompletionContext GetCompletionContext() + { + return new CompletionContext(); + } + } +} diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/Fx2NLTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/Fx2NLTests.cs similarity index 97% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/Fx2NLTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/Fx2NLTests.cs index 44498b2ca..30d2a7f96 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/Fx2NLTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/Fx2NLTests.cs @@ -1,231 +1,231 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -using System; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.PowerFx.LanguageServerProtocol; -using Microsoft.PowerFx.LanguageServerProtocol.Handlers; -using Microsoft.PowerFx.LanguageServerProtocol.Protocol; -using Microsoft.PowerFx.Types; -using Xunit; - -namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol -{ - internal class TestFx2NlHandler : NLHandler - { - public bool _supportsNL2Fx = true; - public bool _supportsFx2NL = true; - - public override bool SupportsNL2Fx => _supportsNL2Fx; - - public override bool SupportsFx2NL => _supportsFx2NL; - - public const string ModelIdStr = "Model123"; - - // Set the expected expression to return. - public string Expected { get; set; } - - public bool Throw { get; set; } - - public bool Delay { get; set; } = false; - - public bool SupportsParameterHints { get; set; } = false; - - public TestFx2NlHandler() - { - } - -#pragma warning disable CS0672 // Member overrides obsolete member - public override async Task Fx2NLAsync(CheckResult check, CancellationToken cancel) -#pragma warning restore CS0672 // Member overrides obsolete member - { - if (this.Delay) - { - await Task.Delay(100, cancel).ConfigureAwait(false); - } - - if (this.Throw) - { - throw new InvalidOperationException($"Simulated error"); - } - - var sb = new StringBuilder(); - sb.Append(check.ApplyParse().Text); - sb.Append(": "); - sb.Append(check.IsSuccess); - sb.Append(": "); - sb.Append(this.Expected); - - var result = new CustomFx2NLResult - { - Explanation = sb.ToString() - }; - - return result; - } - - public override async Task Fx2NLAsync(CheckResult check, Fx2NLParameters hints, CancellationToken cancel) - { - if (!this.SupportsParameterHints) - { -#pragma warning disable CS0618 // Type or member is obsolete - return await Fx2NLAsync(check, cancel).ConfigureAwait(false); -#pragma warning restore CS0618 // Type or member is obsolete - } - - { - await Task.Delay(100, cancel).ConfigureAwait(false); - } - - if (this.Throw) - { - throw new InvalidOperationException($"Simulated error"); - } - - var sb = new StringBuilder(); - sb.Append(hints?.UsageHints?.ControlName); - sb.Append("; "); - sb.Append(hints?.UsageHints?.ControlKind); - - sb.Append("; "); - sb.Append(hints?.UsageHints?.PropertyName); - - return new CustomFx2NLResult - { - Explanation = sb.ToString() - }; - } - } - - public partial class LanguageServerTestBase - { - [Fact] - public async Task TestFx2NL() - { - // Arrange - var documentUri = "powerfx://app?context=1"; - var expectedExpr = "sentence"; - - var engine = new Engine(); - var symbols = new SymbolTable(); - symbols.AddVariable("Score", FormulaType.Number); - var scope = engine.CreateEditorScope(symbols: symbols); - var scopeFactory = new TestPowerFxScopeFactory((string documentUri) => scope); - Init(new InitParams(scopeFactory: scopeFactory)); - var handler = CreateAndConfigureFx2NlHandler(); - handler.Delay = true; - handler.Expected = expectedExpr; - handler.SupportsParameterHints = false; - - // Act - var payload = Fx2NlMessageJson(documentUri); - var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); - var response = AssertAndGetResponsePayload(rawResponse, payload.id); - - // Assert - // result has expected concat with symbols. - Assert.Equal("Score > 3: True: sentence", response.Explanation); - } - - [Fact] - public async Task TestFx2NLUsageHints() - { - // Arrange - var documentUri = "powerfx://app?context=1"; - var expectedExpr = "sentence"; - - var engine = new Engine(); - var symbols = new SymbolTable(); - symbols.AddVariable("Score", FormulaType.Number); - var scope = new EditorContextScope(engine, null, symbols) - { - UsageHints = new UsageHints - { - ControlKind = "Button", - ControlName = "MyButton", - PropertyName = "Select" - } - }; - var scopeFactory = new TestPowerFxScopeFactory((string documentUri) => scope); - Init(new InitParams(scopeFactory: scopeFactory)); - var handler = CreateAndConfigureFx2NlHandler(); - handler.Delay = true; - handler.Expected = expectedExpr; - handler.SupportsParameterHints = true; - - // Act - var payload = Fx2NlMessageJson(documentUri); - var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); - var response = AssertAndGetResponsePayload(rawResponse, payload.id); - - // Assert - // result has expected concat with symbols. - Assert.Equal("MyButton; Button; Select", response.Explanation); - } - - [Fact] - public async Task TestFx2NlMissingHandler() - { - // Arrange - HandlerFactory.SetHandler(CustomProtocolNames.FX2NL, null); - var documentUri = "powerfx://app?context={\"A\":1,\"B\":[1,2,3]}"; - - // Act - var payload = Fx2NlMessageJson(documentUri); - var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); - AssertErrorPayload(rawResponse, payload.id, JsonRpcHelper.ErrorCode.MethodNotFound); - } - - [Fact] - public async Task TestFx2NLThrows() - { - // Arrange - var documentUri = "powerfx://app?context=1"; - var engine = new Engine(); - var symbols = new SymbolTable(); - symbols.AddVariable("Score", FormulaType.Number); - var scope = engine.CreateEditorScope(symbols: symbols); - var scopeFactory = new TestPowerFxScopeFactory((string documentUri) => scope); - Init(new InitParams(scopeFactory: scopeFactory)); - var handler = CreateAndConfigureFx2NlHandler(); - handler.Delay = true; - handler.Throw = true; - handler.SupportsParameterHints = false; - - // Act - var payload = Fx2NlMessageJson(documentUri); - var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); - - // Assert - AssertErrorPayload(rawResponse, payload.id, JsonRpcHelper.ErrorCode.InternalError); - Assert.NotEmpty(TestServer.UnhandledExceptions); - } - - private static (string payload, string id) Fx2NlMessageJson(string documentUri, string context = null) - { - var fx2NlParams = new CustomFx2NLParams() - { - TextDocument = new TextDocumentItem() - { - Uri = documentUri, - LanguageId = "powerfx", - Version = 1 - }, - Expression = "Score > 3", - Context = context - }; - - return GetRequestPayload(fx2NlParams, CustomProtocolNames.FX2NL); - } - - private TestFx2NlHandler CreateAndConfigureFx2NlHandler() - { - var fx2NlHandler = new TestFx2NlHandler(); - HandlerFactory.SetHandler(CustomProtocolNames.FX2NL, new Fx2NlLanguageServerOperationHandler(new BackwardsCompatibleNLHandlerFactory(fx2NlHandler))); - - return fx2NlHandler; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.PowerFx.LanguageServerProtocol; +using Microsoft.PowerFx.LanguageServerProtocol.Handlers; +using Microsoft.PowerFx.LanguageServerProtocol.Protocol; +using Microsoft.PowerFx.Types; +using Xunit; + +namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol +{ + internal class TestFx2NlHandler : NLHandler + { + public bool _supportsNL2Fx = true; + public bool _supportsFx2NL = true; + + public override bool SupportsNL2Fx => _supportsNL2Fx; + + public override bool SupportsFx2NL => _supportsFx2NL; + + public const string ModelIdStr = "Model123"; + + // Set the expected expression to return. + public string Expected { get; set; } + + public bool Throw { get; set; } + + public bool Delay { get; set; } = false; + + public bool SupportsParameterHints { get; set; } = false; + + public TestFx2NlHandler() + { + } + +#pragma warning disable CS0672 // Member overrides obsolete member + public override async Task Fx2NLAsync(CheckResult check, CancellationToken cancel) +#pragma warning restore CS0672 // Member overrides obsolete member + { + if (this.Delay) + { + await Task.Delay(100, cancel).ConfigureAwait(false); + } + + if (this.Throw) + { + throw new InvalidOperationException($"Simulated error"); + } + + var sb = new StringBuilder(); + sb.Append(check.ApplyParse().Text); + sb.Append(": "); + sb.Append(check.IsSuccess); + sb.Append(": "); + sb.Append(this.Expected); + + var result = new CustomFx2NLResult + { + Explanation = sb.ToString() + }; + + return result; + } + + public override async Task Fx2NLAsync(CheckResult check, Fx2NLParameters hints, CancellationToken cancel) + { + if (!this.SupportsParameterHints) + { +#pragma warning disable CS0618 // Type or member is obsolete + return await Fx2NLAsync(check, cancel).ConfigureAwait(false); +#pragma warning restore CS0618 // Type or member is obsolete + } + + { + await Task.Delay(100, cancel).ConfigureAwait(false); + } + + if (this.Throw) + { + throw new InvalidOperationException($"Simulated error"); + } + + var sb = new StringBuilder(); + sb.Append(hints?.UsageHints?.ControlName); + sb.Append("; "); + sb.Append(hints?.UsageHints?.ControlKind); + + sb.Append("; "); + sb.Append(hints?.UsageHints?.PropertyName); + + return new CustomFx2NLResult + { + Explanation = sb.ToString() + }; + } + } + + public partial class LanguageServerTestBase + { + [Fact] + public async Task TestFx2NL() + { + // Arrange + var documentUri = "powerfx://app?context=1"; + var expectedExpr = "sentence"; + + var engine = new Engine(); + var symbols = new SymbolTable(); + symbols.AddVariable("Score", FormulaType.Number); + var scope = engine.CreateEditorScope(symbols: symbols); + var scopeFactory = new TestPowerFxScopeFactory((string documentUri) => scope); + Init(new InitParams(scopeFactory: scopeFactory)); + var handler = CreateAndConfigureFx2NlHandler(); + handler.Delay = true; + handler.Expected = expectedExpr; + handler.SupportsParameterHints = false; + + // Act + var payload = Fx2NlMessageJson(documentUri); + var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); + var response = AssertAndGetResponsePayload(rawResponse, payload.id); + + // Assert + // result has expected concat with symbols. + Assert.Equal("Score > 3: True: sentence", response.Explanation); + } + + [Fact] + public async Task TestFx2NLUsageHints() + { + // Arrange + var documentUri = "powerfx://app?context=1"; + var expectedExpr = "sentence"; + + var engine = new Engine(); + var symbols = new SymbolTable(); + symbols.AddVariable("Score", FormulaType.Number); + var scope = new EditorContextScope(engine, null, symbols) + { + UsageHints = new UsageHints + { + ControlKind = "Button", + ControlName = "MyButton", + PropertyName = "Select" + } + }; + var scopeFactory = new TestPowerFxScopeFactory((string documentUri) => scope); + Init(new InitParams(scopeFactory: scopeFactory)); + var handler = CreateAndConfigureFx2NlHandler(); + handler.Delay = true; + handler.Expected = expectedExpr; + handler.SupportsParameterHints = true; + + // Act + var payload = Fx2NlMessageJson(documentUri); + var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); + var response = AssertAndGetResponsePayload(rawResponse, payload.id); + + // Assert + // result has expected concat with symbols. + Assert.Equal("MyButton; Button; Select", response.Explanation); + } + + [Fact] + public async Task TestFx2NlMissingHandler() + { + // Arrange + HandlerFactory.SetHandler(CustomProtocolNames.FX2NL, null); + var documentUri = "powerfx://app?context={\"A\":1,\"B\":[1,2,3]}"; + + // Act + var payload = Fx2NlMessageJson(documentUri); + var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); + AssertErrorPayload(rawResponse, payload.id, JsonRpcHelper.ErrorCode.MethodNotFound); + } + + [Fact] + public async Task TestFx2NLThrows() + { + // Arrange + var documentUri = "powerfx://app?context=1"; + var engine = new Engine(); + var symbols = new SymbolTable(); + symbols.AddVariable("Score", FormulaType.Number); + var scope = engine.CreateEditorScope(symbols: symbols); + var scopeFactory = new TestPowerFxScopeFactory((string documentUri) => scope); + Init(new InitParams(scopeFactory: scopeFactory)); + var handler = CreateAndConfigureFx2NlHandler(); + handler.Delay = true; + handler.Throw = true; + handler.SupportsParameterHints = false; + + // Act + var payload = Fx2NlMessageJson(documentUri); + var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); + + // Assert + AssertErrorPayload(rawResponse, payload.id, JsonRpcHelper.ErrorCode.InternalError); + Assert.NotEmpty(TestServer.UnhandledExceptions); + } + + private static (string payload, string id) Fx2NlMessageJson(string documentUri, string context = null) + { + var fx2NlParams = new CustomFx2NLParams() + { + TextDocument = new TextDocumentItem() + { + Uri = documentUri, + LanguageId = "powerfx", + Version = 1 + }, + Expression = "Score > 3", + Context = context + }; + + return GetRequestPayload(fx2NlParams, CustomProtocolNames.FX2NL); + } + + private TestFx2NlHandler CreateAndConfigureFx2NlHandler() + { + var fx2NlHandler = new TestFx2NlHandler(); + HandlerFactory.SetHandler(CustomProtocolNames.FX2NL, new Fx2NlLanguageServerOperationHandler(new BackwardsCompatibleNLHandlerFactory(fx2NlHandler))); + + return fx2NlHandler; + } + } +} diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/GetCustomCapibilitiesTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/GetCustomCapibilitiesTests.cs similarity index 97% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/GetCustomCapibilitiesTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/GetCustomCapibilitiesTests.cs index 980f8a511..65aee7bbd 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/GetCustomCapibilitiesTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/GetCustomCapibilitiesTests.cs @@ -1,64 +1,64 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -using System.Threading.Tasks; -using Microsoft.PowerFx.LanguageServerProtocol; -using Microsoft.PowerFx.LanguageServerProtocol.Handlers; -using Microsoft.PowerFx.LanguageServerProtocol.Protocol; -using Xunit; - -namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol -{ - internal class TestGetCustomCapibilitiesHandler : NLHandler - { - public override bool SupportsFx2NL { get; } - - public override bool SupportsNL2Fx { get; } - - public TestGetCustomCapibilitiesHandler(bool supportsFx2NL, bool supportsNL2Fx) - { - SupportsFx2NL = supportsFx2NL; - SupportsNL2Fx = supportsNL2Fx; - } - } - - public partial class LanguageServerTestBase - { - [Theory] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(true, true)] - [InlineData(false, false)] - [InlineData(false, false, true)] - public async Task TestGetCapabilities(bool supportNL2Fx, bool supportFx2NL, bool dontRegister = false) - { - // Arrange - var documentUri = "powerfx://app"; - var testNLHandler = new TestGetCustomCapibilitiesHandler(supportFx2NL, supportNL2Fx); - if (dontRegister) - { - testNLHandler = null; - } - - HandlerFactory.SetHandler(CustomProtocolNames.GetCapabilities, new GetCustomCapabilitiesLanguageServerOperationHandler(new BackwardsCompatibleNLHandlerFactory(testNLHandler))); - var payload = GetRequestPayload( - new CustomGetCapabilitiesParams() - { - TextDocument = new TextDocumentItem() - { - Uri = documentUri, - LanguageId = "powerfx", - Version = 1 - } - }, CustomProtocolNames.GetCapabilities); - - // Act - var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); - - // Assert: result has expected concat with symbols. - var response = AssertAndGetResponsePayload(rawResponse, payload.id); - Assert.Equal(supportNL2Fx, response.SupportsNL2Fx); - Assert.Equal(supportFx2NL, response.SupportsFx2NL); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System.Threading.Tasks; +using Microsoft.PowerFx.LanguageServerProtocol; +using Microsoft.PowerFx.LanguageServerProtocol.Handlers; +using Microsoft.PowerFx.LanguageServerProtocol.Protocol; +using Xunit; + +namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol +{ + internal class TestGetCustomCapibilitiesHandler : NLHandler + { + public override bool SupportsFx2NL { get; } + + public override bool SupportsNL2Fx { get; } + + public TestGetCustomCapibilitiesHandler(bool supportsFx2NL, bool supportsNL2Fx) + { + SupportsFx2NL = supportsFx2NL; + SupportsNL2Fx = supportsNL2Fx; + } + } + + public partial class LanguageServerTestBase + { + [Theory] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + [InlineData(false, false)] + [InlineData(false, false, true)] + public async Task TestGetCapabilities(bool supportNL2Fx, bool supportFx2NL, bool dontRegister = false) + { + // Arrange + var documentUri = "powerfx://app"; + var testNLHandler = new TestGetCustomCapibilitiesHandler(supportFx2NL, supportNL2Fx); + if (dontRegister) + { + testNLHandler = null; + } + + HandlerFactory.SetHandler(CustomProtocolNames.GetCapabilities, new GetCustomCapabilitiesLanguageServerOperationHandler(new BackwardsCompatibleNLHandlerFactory(testNLHandler))); + var payload = GetRequestPayload( + new CustomGetCapabilitiesParams() + { + TextDocument = new TextDocumentItem() + { + Uri = documentUri, + LanguageId = "powerfx", + Version = 1 + } + }, CustomProtocolNames.GetCapabilities); + + // Act + var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); + + // Assert: result has expected concat with symbols. + var response = AssertAndGetResponsePayload(rawResponse, payload.id); + Assert.Equal(supportNL2Fx, response.SupportsNL2Fx); + Assert.Equal(supportFx2NL, response.SupportsFx2NL); + } + } +} diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/InitialFixupTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/InitialFixupTests.cs similarity index 97% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/InitialFixupTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/InitialFixupTests.cs index f15d0b82c..923bd161c 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/InitialFixupTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/InitialFixupTests.cs @@ -1,56 +1,56 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -using System.Threading; -using System.Threading.Tasks; -using Microsoft.PowerFx.LanguageServerProtocol.Protocol; -using Microsoft.PowerFx.Tests.LanguageServiceProtocol.Tests; -using Xunit; - -namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol -{ - public partial class LanguageServerTestBase - { - [Fact] - public async Task TestInitialFixup() - { - var scopeFactory = new TestPowerFxScopeFactory((string documentUri) => new MockSqlEngine()); - - Init(new InitParams(scopeFactory: scopeFactory)); - var documentUri = "powerfx://app?context={\"A\":1,\"B\":[1,2,3]}"; - var payload = GetRequestPayload( - new InitialFixupParams() - { - TextDocument = new TextDocumentItem() - { - Uri = documentUri, - LanguageId = "powerfx", - Version = 1, - Text = "new_price * new_quantity" - } - }, CustomProtocolNames.InitialFixup); - var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); - var response = AssertAndGetResponsePayload(rawResponse, payload.id); - - Assert.Equal(documentUri, response.Uri); - Assert.Equal("Price * Quantity", response.Text); - - // no change - payload = GetRequestPayload( - new InitialFixupParams() - { - TextDocument = new TextDocumentItem() - { - Uri = documentUri, - LanguageId = "powerfx", - Version = 1, - Text = "Price * Quantity" - } - }, CustomProtocolNames.InitialFixup); - rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); - response = AssertAndGetResponsePayload(rawResponse, payload.id); - Assert.Equal(documentUri, response.Uri); - Assert.Equal("Price * Quantity", response.Text); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.PowerFx.LanguageServerProtocol.Protocol; +using Microsoft.PowerFx.Tests.LanguageServiceProtocol.Tests; +using Xunit; + +namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol +{ + public partial class LanguageServerTestBase + { + [Fact] + public async Task TestInitialFixup() + { + var scopeFactory = new TestPowerFxScopeFactory((string documentUri) => new MockSqlEngine()); + + Init(new InitParams(scopeFactory: scopeFactory)); + var documentUri = "powerfx://app?context={\"A\":1,\"B\":[1,2,3]}"; + var payload = GetRequestPayload( + new InitialFixupParams() + { + TextDocument = new TextDocumentItem() + { + Uri = documentUri, + LanguageId = "powerfx", + Version = 1, + Text = "new_price * new_quantity" + } + }, CustomProtocolNames.InitialFixup); + var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); + var response = AssertAndGetResponsePayload(rawResponse, payload.id); + + Assert.Equal(documentUri, response.Uri); + Assert.Equal("Price * Quantity", response.Text); + + // no change + payload = GetRequestPayload( + new InitialFixupParams() + { + TextDocument = new TextDocumentItem() + { + Uri = documentUri, + LanguageId = "powerfx", + Version = 1, + Text = "Price * Quantity" + } + }, CustomProtocolNames.InitialFixup); + rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); + response = AssertAndGetResponsePayload(rawResponse, payload.id); + Assert.Equal(documentUri, response.Uri); + Assert.Equal("Price * Quantity", response.Text); + } + } +} diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/InvalidScenarioTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/InvalidScenarioTests.cs similarity index 97% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/InvalidScenarioTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/InvalidScenarioTests.cs index d7d8d129e..224a671aa 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/InvalidScenarioTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/InvalidScenarioTests.cs @@ -1,125 +1,125 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -using System; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.PowerFx.Core.Utils; -using Microsoft.PowerFx.LanguageServerProtocol.Handlers; -using Microsoft.PowerFx.LanguageServerProtocol.Protocol; -using Xunit; - -namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol -{ - internal class ErrorThrowingHandler : ILanguageServerOperationHandler - { - public bool IsRequest => false; - - public string LspMethod => TextDocumentNames.DidOpen; - - public async Task HandleAsync(LanguageServerOperationContext operationContext, CancellationToken cancellationToken) - { - await Task.Delay(200, cancellationToken).ConfigureAwait(false); - throw new Exception("Test Exception"); - } - } - - public partial class LanguageServerTestBase - { - [Fact] - public async Task TestTopParseError() - { - var rawResponse = await TestServer.OnDataReceivedAsync("parse error").ConfigureAwait(false); - AssertErrorPayload(rawResponse, null, LanguageServerProtocol.JsonRpcHelper.ErrorCode.ParseError); - } - - // Exceptions can be thrown oob, test we can register a hook and receive. - // Check for exceptions if the scope object we call back to throws - [Fact] - public async Task TestLogCallbackExceptions() - { - // Arrange - HandlerFactory.SetHandler(TextDocumentNames.DidOpen, new ErrorThrowingHandler()); - - // Act - var payload = GetDidOpenPayload( - new DidOpenTextDocumentParams() - { - TextDocument = new TextDocumentItem() - { - Uri = "https://none", - LanguageId = "powerfx", - Version = 1, - Text = "123" - } - }); - var rawResponse = await TestServer.OnDataReceivedAsync(payload).ConfigureAwait(false); - - // Assert - var error = AssertErrorPayload(rawResponse, null, LanguageServerProtocol.JsonRpcHelper.ErrorCode.InternalError); - Assert.NotEmpty(TestServer.UnhandledExceptions); - Assert.Equal(TestServer.UnhandledExceptions[0].GetDetailedExceptionMessage(), error.message); - } - - [Fact] - public async Task TestLanguageServerCommunication() - { - // bad payload - var response1 = await TestServer.OnDataReceivedAsync(JsonSerializer.Serialize(new { })).ConfigureAwait(false); - - // bad jsonrpc payload - var response2 = await TestServer.OnDataReceivedAsync(JsonSerializer.Serialize(new - { - jsonrpc = "2.0" - })).ConfigureAwait(false); - - // bad notification payload - var response3 = await TestServer.OnDataReceivedAsync(JsonSerializer.Serialize(new - { - jsonrpc = "2.0", - method = "unknown", - @params = "unkown" - })).ConfigureAwait(false); - - // bad request payload - var response4 = await TestServer.OnDataReceivedAsync(JsonSerializer.Serialize(new - { - jsonrpc = "2.0", - id = "abc", - method = "unknown", - @params = "unkown" - })).ConfigureAwait(false); - - // verify we have 4 json rpc responeses - AssertErrorPayload(response1, null, LanguageServerProtocol.JsonRpcHelper.ErrorCode.InvalidRequest); - AssertErrorPayload(response2, null, LanguageServerProtocol.JsonRpcHelper.ErrorCode.InvalidRequest); - AssertErrorPayload(response3, null, LanguageServerProtocol.JsonRpcHelper.ErrorCode.MethodNotFound); - AssertErrorPayload(response4, "abc", LanguageServerProtocol.JsonRpcHelper.ErrorCode.MethodNotFound); - } - - [Fact] - public async Task TestHandlerIsMissing() - { - // Arrange - HandlerFactory.SetHandler(TextDocumentNames.DidOpen, null); - - // Act - var payload = GetDidOpenPayload( - new DidOpenTextDocumentParams() - { - TextDocument = new TextDocumentItem() - { - Uri = "https://none", - LanguageId = "powerfx", - Version = 1, - Text = "123" - } - }); - var rawResponse = await TestServer.OnDataReceivedAsync(payload).ConfigureAwait(false); - - // Assert - AssertErrorPayload(rawResponse, null, LanguageServerProtocol.JsonRpcHelper.ErrorCode.MethodNotFound); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.PowerFx.Core.Utils; +using Microsoft.PowerFx.LanguageServerProtocol.Handlers; +using Microsoft.PowerFx.LanguageServerProtocol.Protocol; +using Xunit; + +namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol +{ + internal class ErrorThrowingHandler : ILanguageServerOperationHandler + { + public bool IsRequest => false; + + public string LspMethod => TextDocumentNames.DidOpen; + + public async Task HandleAsync(LanguageServerOperationContext operationContext, CancellationToken cancellationToken) + { + await Task.Delay(200, cancellationToken).ConfigureAwait(false); + throw new Exception("Test Exception"); + } + } + + public partial class LanguageServerTestBase + { + [Fact] + public async Task TestTopParseError() + { + var rawResponse = await TestServer.OnDataReceivedAsync("parse error").ConfigureAwait(false); + AssertErrorPayload(rawResponse, null, LanguageServerProtocol.JsonRpcHelper.ErrorCode.ParseError); + } + + // Exceptions can be thrown oob, test we can register a hook and receive. + // Check for exceptions if the scope object we call back to throws + [Fact] + public async Task TestLogCallbackExceptions() + { + // Arrange + HandlerFactory.SetHandler(TextDocumentNames.DidOpen, new ErrorThrowingHandler()); + + // Act + var payload = GetDidOpenPayload( + new DidOpenTextDocumentParams() + { + TextDocument = new TextDocumentItem() + { + Uri = "https://none", + LanguageId = "powerfx", + Version = 1, + Text = "123" + } + }); + var rawResponse = await TestServer.OnDataReceivedAsync(payload).ConfigureAwait(false); + + // Assert + var error = AssertErrorPayload(rawResponse, null, LanguageServerProtocol.JsonRpcHelper.ErrorCode.InternalError); + Assert.NotEmpty(TestServer.UnhandledExceptions); + Assert.Equal(TestServer.UnhandledExceptions[0].GetDetailedExceptionMessage(), error.message); + } + + [Fact] + public async Task TestLanguageServerCommunication() + { + // bad payload + var response1 = await TestServer.OnDataReceivedAsync(JsonSerializer.Serialize(new { })).ConfigureAwait(false); + + // bad jsonrpc payload + var response2 = await TestServer.OnDataReceivedAsync(JsonSerializer.Serialize(new + { + jsonrpc = "2.0" + })).ConfigureAwait(false); + + // bad notification payload + var response3 = await TestServer.OnDataReceivedAsync(JsonSerializer.Serialize(new + { + jsonrpc = "2.0", + method = "unknown", + @params = "unkown" + })).ConfigureAwait(false); + + // bad request payload + var response4 = await TestServer.OnDataReceivedAsync(JsonSerializer.Serialize(new + { + jsonrpc = "2.0", + id = "abc", + method = "unknown", + @params = "unkown" + })).ConfigureAwait(false); + + // verify we have 4 json rpc responeses + AssertErrorPayload(response1, null, LanguageServerProtocol.JsonRpcHelper.ErrorCode.InvalidRequest); + AssertErrorPayload(response2, null, LanguageServerProtocol.JsonRpcHelper.ErrorCode.InvalidRequest); + AssertErrorPayload(response3, null, LanguageServerProtocol.JsonRpcHelper.ErrorCode.MethodNotFound); + AssertErrorPayload(response4, "abc", LanguageServerProtocol.JsonRpcHelper.ErrorCode.MethodNotFound); + } + + [Fact] + public async Task TestHandlerIsMissing() + { + // Arrange + HandlerFactory.SetHandler(TextDocumentNames.DidOpen, null); + + // Act + var payload = GetDidOpenPayload( + new DidOpenTextDocumentParams() + { + TextDocument = new TextDocumentItem() + { + Uri = "https://none", + LanguageId = "powerfx", + Version = 1, + Text = "123" + } + }); + var rawResponse = await TestServer.OnDataReceivedAsync(payload).ConfigureAwait(false); + + // Assert + AssertErrorPayload(rawResponse, null, LanguageServerProtocol.JsonRpcHelper.ErrorCode.MethodNotFound); + } + } +} diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/LanguageServerForTesting.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/LanguageServerForTesting.cs similarity index 97% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/LanguageServerForTesting.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/LanguageServerForTesting.cs index 8c79a048c..a5a237904 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/LanguageServerForTesting.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/LanguageServerForTesting.cs @@ -1,25 +1,25 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -using System; -using System.Collections.Generic; -using Microsoft.PowerFx.Core; -using Microsoft.PowerFx.LanguageServerProtocol; -using Microsoft.PowerFx.LanguageServerProtocol.Handlers; - -namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol -{ - internal class LanguageServerForTesting : LanguageServer - { - private readonly List _unhandledExceptions = new List(); - - public List UnhandledExceptions => _unhandledExceptions; - - public LanguageServerForTesting(IPowerFxScopeFactory scopeFactory, ILanguageServerOperationHandlerFactory handlerFactory, IHostTaskExecutor hostTaskExecutor, ILanguageServerLogger logger) - : base(scopeFactory, hostTaskExecutor, logger) - { - HandlerFactory = handlerFactory; - LogUnhandledExceptionHandler += (e) => _unhandledExceptions.Add(e); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using Microsoft.PowerFx.Core; +using Microsoft.PowerFx.LanguageServerProtocol; +using Microsoft.PowerFx.LanguageServerProtocol.Handlers; + +namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol +{ + internal class LanguageServerForTesting : LanguageServer + { + private readonly List _unhandledExceptions = new List(); + + public List UnhandledExceptions => _unhandledExceptions; + + public LanguageServerForTesting(IPowerFxScopeFactory scopeFactory, ILanguageServerOperationHandlerFactory handlerFactory, IHostTaskExecutor hostTaskExecutor, ILanguageServerLogger logger) + : base(scopeFactory, hostTaskExecutor, logger) + { + HandlerFactory = handlerFactory; + LogUnhandledExceptionHandler += (e) => _unhandledExceptions.Add(e); + } + } +} diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/LanguageServerTestBase.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/LanguageServerTestBase.cs similarity index 97% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/LanguageServerTestBase.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/LanguageServerTestBase.cs index 80c77f659..1c4452bdd 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/LanguageServerTestBase.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/LanguageServerTestBase.cs @@ -1,238 +1,238 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.Json; -using System.Web; -using Microsoft.PowerFx.Core; -using Microsoft.PowerFx.Core.Tests; -using Microsoft.PowerFx.LanguageServerProtocol; -using Microsoft.PowerFx.LanguageServerProtocol.Protocol; -using Microsoft.PowerFx.Types; -using Xunit; -using static Microsoft.PowerFx.Tests.BindingEngineTests; - -namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol -{ - public record InitParams(Features features = null, ParserOptions options = null, IPowerFxScopeFactory scopeFactory = null); - - public record LSPError(JsonRpcHelper.ErrorCode code, string message = null); - - public partial class LanguageServerTestBase : PowerFxTest - { - private LanguageServerForTesting TestServer { get; set; } - - private TestHandlerFactory HandlerFactory { get; set; } - - public TestLogger Logger { get; set; } - - public TestHostTaskExecutor HostTaskExecutor { get; set; } - - public LanguageServerTestBase() - : base() - { - Init(); - } - - internal void Init(InitParams initParams) - { - var config = new PowerFxConfig(features: initParams?.features ?? Features.None); - config.AddFunction(new BehaviorFunction()); - config.AddFunction(new AISummarizeFunction()); - - var engine = new Engine(config); - HandlerFactory = new TestHandlerFactory(); - HostTaskExecutor = new TestHostTaskExecutor(); - var random = new Random(); - var useHostTaskExecutor = random.Next(0, 2) == 1; - - var scopeFactory = initParams?.scopeFactory ?? new TestPowerFxScopeFactory( - (string documentUri) => engine.CreateEditorScope(initParams?.options, GetFromUri(documentUri))); - - TestServer = new LanguageServerForTesting(scopeFactory, HandlerFactory, useHostTaskExecutor ? HostTaskExecutor : null, Logger); - } - - internal void Init() - { - Init(new InitParams()); - } - - // The convention for getting the context from the documentUri is arbitrary and determined by the host. - internal static ReadOnlySymbolTable GetFromUri(string documentUri) - { - var uriObj = new Uri(documentUri); - var json = HttpUtility.ParseQueryString(uriObj.Query).Get("context"); - json ??= "{}"; - - var record = (RecordValue)FormulaValueJSON.FromJson(json); - return ReadOnlySymbolTable.NewFromRecord(record.Type); - } - - internal static LSPError AssertErrorPayload(string response, string id, JsonRpcHelper.ErrorCode expectedCode) - { - Assert.NotNull(response); - var deserializedResponse = JsonDocument.Parse(response); - var root = deserializedResponse.RootElement; - Assert.True(root.TryGetProperty("id", out var responseId)); - Assert.Equal(id, responseId.GetString()); - Assert.True(root.TryGetProperty("error", out var errElement)); - Assert.True(errElement.TryGetProperty("code", out var codeElement)); - var code = (JsonRpcHelper.ErrorCode)codeElement.GetInt32(); - Assert.Equal(expectedCode, code); - Assert.True(root.TryGetProperty("fxVersion", out var fxVersionElement)); - Assert.Equal(Engine.AssemblyVersion, fxVersionElement.GetString()); - string message = null; - if (errElement.TryGetProperty("message", out var messageElement)) - { - message = messageElement.GetString(); - } - - return new LSPError(code, message); - } - - internal static string GetOutputAtIndexInSerializedResponse(string response, string id = null, string method = null, int index = -1) - { - Assert.NotNull(response); - try - { - var possiblyArray = JsonSerializer.Deserialize>(response, LanguageServerHelper.DefaultJsonSerializerOptions); - if (possiblyArray == null || possiblyArray.Count == 0) - { - return response; - } - - if (index >= 0 && index < possiblyArray.Count) - { - response = possiblyArray[index]; - } - else if (method != null) - { - var match = possiblyArray.Where(item => item.Contains(method)).FirstOrDefault(); - if (match != null || match != default) - { - response = match; - } - } - else - { - var match = possiblyArray.Where(item => item.Contains(id)).FirstOrDefault(); - if (match != null || match != default) - { - response = match; - } - } - } - catch (JsonException) - { - // ignore - } - - return response; - } - - internal static T AssertAndGetResponsePayload(string response, string id, int index = -1) - { - response = GetOutputAtIndexInSerializedResponse(response, id, null, index); - var deserializedResponse = JsonDocument.Parse(response); - var root = deserializedResponse.RootElement; - root.TryGetProperty("id", out var responseId); - Assert.Equal(id, responseId.GetString()); - root.TryGetProperty("result", out var resultElement); - Assert.True(root.TryGetProperty("fxVersion", out var fxVersionElement)); - Assert.Equal(Engine.AssemblyVersion, fxVersionElement.GetString()); - var paramsObj = JsonSerializer.Deserialize(resultElement.GetRawText(), LanguageServerHelper.DefaultJsonSerializerOptions); - return paramsObj; - } - - internal static T AssertAndGetNotificationParams(string response, string method, int index = -1) - { - Assert.NotNull(response); - response = GetOutputAtIndexInSerializedResponse(response, null, method, index); - var notification = GetOutputAtIndexInSerializedResponse(response); - var deserializedNotification = JsonDocument.Parse(notification); - var root = deserializedNotification.RootElement; - Assert.True(root.TryGetProperty("method", out var methodElement)); - Assert.Equal(method, methodElement.GetString()); - Assert.True(root.TryGetProperty("params", out var paramsElement)); - T paramsObj; - - if (method == CustomProtocolNames.PublishExpressionType) - { - paramsObj = JsonRpcHelper.Deserialize(paramsElement.GetRawText()); - } - else - { - paramsObj = JsonSerializer.Deserialize(paramsElement.GetRawText(), LanguageServerHelper.DefaultJsonSerializerOptions); - } - - return paramsObj; - } - - internal static string GetUri(string queryParams = null) - { - var uriBuilder = new UriBuilder("powerfx://app") - { - Query = queryParams ?? string.Empty - }; - return uriBuilder.Uri.AbsoluteUri; - } - - internal static (string payload, string id) GetRequestPayload(T paramsObj, string method, string id = null) - { - id ??= Guid.NewGuid().ToString(); - var payload = JsonSerializer.Serialize( - new - { - jsonrpc = "2.0", - id, - method, - @params = paramsObj - }, LanguageServerHelper.DefaultJsonSerializerOptions); - return (payload, id); - } - - internal static string GetNotificationPayload(T paramsObj, string method) - { - var payload = JsonSerializer.Serialize( - new - { - jsonrpc = "2.0", - method, - @params = paramsObj - }, LanguageServerHelper.DefaultJsonSerializerOptions); - return payload; - } - - internal static TextDocumentIdentifier GetTextDocument(string uri = null) - { - return new TextDocumentIdentifier() { Uri = uri ?? GetUri() }; - } - - internal static string GetExpression(LanguageServerRequestBaseParams requestParams) - { - if (requestParams?.Text != null) - { - return requestParams.Text; - } - - var uri = new Uri(requestParams.TextDocument.Uri); - return HttpUtility.ParseQueryString(uri.Query).Get("expression"); - } - - internal static Position GetPosition(int offset, int line = 0) - { - return new Position() - { - Line = line, - Character = offset - }; - } - - internal static ParserOptions GetParserOptions(bool withAllowSideEffects) - { - return withAllowSideEffects ? new ParserOptions() { AllowsSideEffects = true } : null; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Web; +using Microsoft.PowerFx.Core; +using Microsoft.PowerFx.Core.Tests; +using Microsoft.PowerFx.LanguageServerProtocol; +using Microsoft.PowerFx.LanguageServerProtocol.Protocol; +using Microsoft.PowerFx.Types; +using Xunit; +using static Microsoft.PowerFx.Tests.BindingEngineTests; + +namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol +{ + public record InitParams(Features features = null, ParserOptions options = null, IPowerFxScopeFactory scopeFactory = null); + + public record LSPError(JsonRpcHelper.ErrorCode code, string message = null); + + public partial class LanguageServerTestBase : PowerFxTest + { + private LanguageServerForTesting TestServer { get; set; } + + private TestHandlerFactory HandlerFactory { get; set; } + + public TestLogger Logger { get; set; } + + public TestHostTaskExecutor HostTaskExecutor { get; set; } + + public LanguageServerTestBase() + : base() + { + Init(); + } + + internal void Init(InitParams initParams) + { + var config = new PowerFxConfig(features: initParams?.features ?? Features.None); + config.AddFunction(new BehaviorFunction()); + config.AddFunction(new AISummarizeFunction()); + + var engine = new Engine(config); + HandlerFactory = new TestHandlerFactory(); + HostTaskExecutor = new TestHostTaskExecutor(); + var random = new Random(); + var useHostTaskExecutor = random.Next(0, 2) == 1; + + var scopeFactory = initParams?.scopeFactory ?? new TestPowerFxScopeFactory( + (string documentUri) => engine.CreateEditorScope(initParams?.options, GetFromUri(documentUri))); + + TestServer = new LanguageServerForTesting(scopeFactory, HandlerFactory, useHostTaskExecutor ? HostTaskExecutor : null, Logger); + } + + internal void Init() + { + Init(new InitParams()); + } + + // The convention for getting the context from the documentUri is arbitrary and determined by the host. + internal static ReadOnlySymbolTable GetFromUri(string documentUri) + { + var uriObj = new Uri(documentUri); + var json = HttpUtility.ParseQueryString(uriObj.Query).Get("context"); + json ??= "{}"; + + var record = (RecordValue)FormulaValueJSON.FromJson(json); + return ReadOnlySymbolTable.NewFromRecord(record.Type); + } + + internal static LSPError AssertErrorPayload(string response, string id, JsonRpcHelper.ErrorCode expectedCode) + { + Assert.NotNull(response); + var deserializedResponse = JsonDocument.Parse(response); + var root = deserializedResponse.RootElement; + Assert.True(root.TryGetProperty("id", out var responseId)); + Assert.Equal(id, responseId.GetString()); + Assert.True(root.TryGetProperty("error", out var errElement)); + Assert.True(errElement.TryGetProperty("code", out var codeElement)); + var code = (JsonRpcHelper.ErrorCode)codeElement.GetInt32(); + Assert.Equal(expectedCode, code); + Assert.True(root.TryGetProperty("fxVersion", out var fxVersionElement)); + Assert.Equal(Engine.AssemblyVersion, fxVersionElement.GetString()); + string message = null; + if (errElement.TryGetProperty("message", out var messageElement)) + { + message = messageElement.GetString(); + } + + return new LSPError(code, message); + } + + internal static string GetOutputAtIndexInSerializedResponse(string response, string id = null, string method = null, int index = -1) + { + Assert.NotNull(response); + try + { + var possiblyArray = JsonSerializer.Deserialize>(response, LanguageServerHelper.DefaultJsonSerializerOptions); + if (possiblyArray == null || possiblyArray.Count == 0) + { + return response; + } + + if (index >= 0 && index < possiblyArray.Count) + { + response = possiblyArray[index]; + } + else if (method != null) + { + var match = possiblyArray.Where(item => item.Contains(method)).FirstOrDefault(); + if (match != null || match != default) + { + response = match; + } + } + else + { + var match = possiblyArray.Where(item => item.Contains(id)).FirstOrDefault(); + if (match != null || match != default) + { + response = match; + } + } + } + catch (JsonException) + { + // ignore + } + + return response; + } + + internal static T AssertAndGetResponsePayload(string response, string id, int index = -1) + { + response = GetOutputAtIndexInSerializedResponse(response, id, null, index); + var deserializedResponse = JsonDocument.Parse(response); + var root = deserializedResponse.RootElement; + root.TryGetProperty("id", out var responseId); + Assert.Equal(id, responseId.GetString()); + root.TryGetProperty("result", out var resultElement); + Assert.True(root.TryGetProperty("fxVersion", out var fxVersionElement)); + Assert.Equal(Engine.AssemblyVersion, fxVersionElement.GetString()); + var paramsObj = JsonSerializer.Deserialize(resultElement.GetRawText(), LanguageServerHelper.DefaultJsonSerializerOptions); + return paramsObj; + } + + internal static T AssertAndGetNotificationParams(string response, string method, int index = -1) + { + Assert.NotNull(response); + response = GetOutputAtIndexInSerializedResponse(response, null, method, index); + var notification = GetOutputAtIndexInSerializedResponse(response); + var deserializedNotification = JsonDocument.Parse(notification); + var root = deserializedNotification.RootElement; + Assert.True(root.TryGetProperty("method", out var methodElement)); + Assert.Equal(method, methodElement.GetString()); + Assert.True(root.TryGetProperty("params", out var paramsElement)); + T paramsObj; + + if (method == CustomProtocolNames.PublishExpressionType) + { + paramsObj = JsonRpcHelper.Deserialize(paramsElement.GetRawText()); + } + else + { + paramsObj = JsonSerializer.Deserialize(paramsElement.GetRawText(), LanguageServerHelper.DefaultJsonSerializerOptions); + } + + return paramsObj; + } + + internal static string GetUri(string queryParams = null) + { + var uriBuilder = new UriBuilder("powerfx://app") + { + Query = queryParams ?? string.Empty + }; + return uriBuilder.Uri.AbsoluteUri; + } + + internal static (string payload, string id) GetRequestPayload(T paramsObj, string method, string id = null) + { + id ??= Guid.NewGuid().ToString(); + var payload = JsonSerializer.Serialize( + new + { + jsonrpc = "2.0", + id, + method, + @params = paramsObj + }, LanguageServerHelper.DefaultJsonSerializerOptions); + return (payload, id); + } + + internal static string GetNotificationPayload(T paramsObj, string method) + { + var payload = JsonSerializer.Serialize( + new + { + jsonrpc = "2.0", + method, + @params = paramsObj + }, LanguageServerHelper.DefaultJsonSerializerOptions); + return payload; + } + + internal static TextDocumentIdentifier GetTextDocument(string uri = null) + { + return new TextDocumentIdentifier() { Uri = uri ?? GetUri() }; + } + + internal static string GetExpression(LanguageServerRequestBaseParams requestParams) + { + if (requestParams?.Text != null) + { + return requestParams.Text; + } + + var uri = new Uri(requestParams.TextDocument.Uri); + return HttpUtility.ParseQueryString(uri.Query).Get("expression"); + } + + internal static Position GetPosition(int offset, int line = 0) + { + return new Position() + { + Line = line, + Character = offset + }; + } + + internal static ParserOptions GetParserOptions(bool withAllowSideEffects) + { + return withAllowSideEffects ? new ParserOptions() { AllowsSideEffects = true } : null; + } + } +} diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/Nl2FxTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/Nl2FxTests.cs similarity index 97% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/Nl2FxTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/Nl2FxTests.cs index f6c38f3da..144f2f363 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/Nl2FxTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/Nl2FxTests.cs @@ -1,216 +1,216 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -using System; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.PowerFx.LanguageServerProtocol; -using Microsoft.PowerFx.LanguageServerProtocol.Handlers; -using Microsoft.PowerFx.LanguageServerProtocol.Protocol; -using Microsoft.PowerFx.Types; -using Xunit; - -namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol -{ - internal class TestNL2FxHandler : NLHandler - { - public bool _supportsNL2Fx = true; - public bool _supportsFx2NL = true; - - public override bool SupportsNL2Fx => _supportsNL2Fx; - - public override bool SupportsFx2NL => _supportsFx2NL; - - public const string ModelIdStr = "Model123"; - - // Set on call to NL2Fx - public string _log; - - // Set the expected expression to return. - public string Expected { get; set; } - - public bool Throw { get; set; } - - public int? Nl2FxDelayTime { get; set; } = null; - - public bool ThrowOnCancellation { get; set; } = false; - - public int PreHandleNl2FxCallCount { get; set; } = 0; - - public TestNL2FxHandler() - { - } - - public override async Task NL2FxAsync(NL2FxParameters request, CancellationToken cancel) - { - if (Nl2FxDelayTime.HasValue) - { - await Task.Delay(Nl2FxDelayTime.Value, CancellationToken.None).ConfigureAwait(false); - } - - if (this.Throw) - { - throw new InvalidOperationException($"Simulated error"); - } - - if (this.ThrowOnCancellation) - { - cancel.ThrowIfCancellationRequested(); - } - - var nl2FxParameters = request; - - Assert.NotNull(nl2FxParameters.Engine); - - var sb = new StringBuilder(); - sb.Append(nl2FxParameters.Sentence); - sb.Append(": "); - - sb.Append(this.Expected); - sb.Append(": "); - - foreach (var sym in nl2FxParameters.SymbolSummary.SuggestedSymbols) - { - sb.Append($"{sym.BestName},{sym.Type}"); - } - - _log = sb.ToString(); - - var nl2FxResult = new CustomNL2FxResult - { - Expressions = new CustomNL2FxResultItem[] - { - new CustomNL2FxResultItem - { - Expression = this.Expected, - ModelId = ModelIdStr - } - } - }; - - return nl2FxResult; - } - - public override void PreHandleNl2Fx(CustomNL2FxParams nl2FxRequestParams, NL2FxParameters nl2fxParameters, LanguageServerOperationContext operationContext) - { - this.PreHandleNl2FxCallCount++; - } - } - - public partial class LanguageServerTestBase - { - [Theory] - [InlineData("Score < 50", true, "#$PowerFxResolvedObject$# < #$decimal$#")] - [InlineData("missing < 50", false, "#$firstname$# < #$decimal$#")] // doesn't compile, should get filtered out by LSP - public async Task TestNL2FX(string expectedExpr, bool success, string anonExpr = null) - { - // Arrange - var documentUri = "powerfx://app?context=1"; - var engine = new Engine(); - var symbols = new SymbolTable(); - symbols.AddVariable("Score", FormulaType.Number); - var scope = engine.CreateEditorScope(symbols: symbols); - var scopeFactory = new TestPowerFxScopeFactory((string documentUri) => scope); - Init(new InitParams(scopeFactory: scopeFactory)); - var nl2FxHandler = CreateAndConfigureNl2FxHandler(); - nl2FxHandler.Nl2FxDelayTime = 100; - nl2FxHandler.Expected = expectedExpr; - - // Act - var payload = NL2FxMessageJson(documentUri); - var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); - var response = AssertAndGetResponsePayload(rawResponse, payload.id); - - // Assert - // result has expected concat with symbols. - var items = response.Expressions; - - Assert.Single(items); - var expression = items[0]; - - if (anonExpr != null) - { - Assert.Equal(anonExpr, items[0].AnonymizedExpression); - } - - if (success) - { - Assert.Equal("my sentence: Score < 50: Score,Number", nl2FxHandler._log); - - Assert.Equal(expectedExpr, expression.Expression); - Assert.Null(expression.RawExpression); - } - else - { - // Even though model returned an expression, it didn't compile, so it should be filtered out by LSP. - Assert.Null(expression.Expression); - Assert.Equal(expectedExpr, expression.RawExpression); - } - - Assert.Equal(TestNL2FxHandler.ModelIdStr, expression.ModelId); - Assert.Equal(1, nl2FxHandler.PreHandleNl2FxCallCount); - } - - [Fact] - public async Task TestNL2FXMissingHandler() - { - // Arrange - HandlerFactory.SetHandler(CustomProtocolNames.NL2FX, null); - var documentUri = "powerfx://app?context={\"A\":1,\"B\":[1,2,3]}"; - - // Act - var payload = NL2FxMessageJson(documentUri); - var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); - AssertErrorPayload(rawResponse, payload.id, JsonRpcHelper.ErrorCode.MethodNotFound); - } - - [Fact] - public async Task TestNL2FXHandlerThrows() - { - // Arrange - var documentUri = "powerfx://app?context=1"; - var engine = new Engine(); - var symbols = new SymbolTable(); - symbols.AddVariable("Score", FormulaType.Number); - var scope = engine.CreateEditorScope(symbols: symbols); - var scopeFactory = new TestPowerFxScopeFactory((string documentUri) => scope); - Init(new InitParams(scopeFactory: scopeFactory)); - var nl2FxHandler = CreateAndConfigureNl2FxHandler(); - nl2FxHandler.Nl2FxDelayTime = 100; - nl2FxHandler.Throw = true; - - // Act - var payload = NL2FxMessageJson(documentUri); - var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); - - // Assert - AssertErrorPayload(rawResponse, payload.id, JsonRpcHelper.ErrorCode.InternalError); - Assert.NotEmpty(TestServer.UnhandledExceptions); - Assert.Equal(1, nl2FxHandler.PreHandleNl2FxCallCount); - } - - private static (string payload, string id) NL2FxMessageJson(string documentUri) - { - var nl2FxParams = new CustomNL2FxParams() - { - TextDocument = new TextDocumentItem() - { - Uri = documentUri, - LanguageId = "powerfx", - Version = 1 - }, - Sentence = "my sentence" - }; - - return GetRequestPayload(nl2FxParams, CustomProtocolNames.NL2FX); - } - - private TestNL2FxHandler CreateAndConfigureNl2FxHandler() - { - var nl2FxHandler = new TestNL2FxHandler(); - HandlerFactory.SetHandler(CustomProtocolNames.NL2FX, new Nl2FxLanguageServerOperationHandler(new BackwardsCompatibleNLHandlerFactory(nl2FxHandler))); - return nl2FxHandler; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.PowerFx.LanguageServerProtocol; +using Microsoft.PowerFx.LanguageServerProtocol.Handlers; +using Microsoft.PowerFx.LanguageServerProtocol.Protocol; +using Microsoft.PowerFx.Types; +using Xunit; + +namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol +{ + internal class TestNL2FxHandler : NLHandler + { + public bool _supportsNL2Fx = true; + public bool _supportsFx2NL = true; + + public override bool SupportsNL2Fx => _supportsNL2Fx; + + public override bool SupportsFx2NL => _supportsFx2NL; + + public const string ModelIdStr = "Model123"; + + // Set on call to NL2Fx + public string _log; + + // Set the expected expression to return. + public string Expected { get; set; } + + public bool Throw { get; set; } + + public int? Nl2FxDelayTime { get; set; } = null; + + public bool ThrowOnCancellation { get; set; } = false; + + public int PreHandleNl2FxCallCount { get; set; } = 0; + + public TestNL2FxHandler() + { + } + + public override async Task NL2FxAsync(NL2FxParameters request, CancellationToken cancel) + { + if (Nl2FxDelayTime.HasValue) + { + await Task.Delay(Nl2FxDelayTime.Value, CancellationToken.None).ConfigureAwait(false); + } + + if (this.Throw) + { + throw new InvalidOperationException($"Simulated error"); + } + + if (this.ThrowOnCancellation) + { + cancel.ThrowIfCancellationRequested(); + } + + var nl2FxParameters = request; + + Assert.NotNull(nl2FxParameters.Engine); + + var sb = new StringBuilder(); + sb.Append(nl2FxParameters.Sentence); + sb.Append(": "); + + sb.Append(this.Expected); + sb.Append(": "); + + foreach (var sym in nl2FxParameters.SymbolSummary.SuggestedSymbols) + { + sb.Append($"{sym.BestName},{sym.Type}"); + } + + _log = sb.ToString(); + + var nl2FxResult = new CustomNL2FxResult + { + Expressions = new CustomNL2FxResultItem[] + { + new CustomNL2FxResultItem + { + Expression = this.Expected, + ModelId = ModelIdStr + } + } + }; + + return nl2FxResult; + } + + public override void PreHandleNl2Fx(CustomNL2FxParams nl2FxRequestParams, NL2FxParameters nl2fxParameters, LanguageServerOperationContext operationContext) + { + this.PreHandleNl2FxCallCount++; + } + } + + public partial class LanguageServerTestBase + { + [Theory] + [InlineData("Score < 50", true, "#$PowerFxResolvedObject$# < #$decimal$#")] + [InlineData("missing < 50", false, "#$firstname$# < #$decimal$#")] // doesn't compile, should get filtered out by LSP + public async Task TestNL2FX(string expectedExpr, bool success, string anonExpr = null) + { + // Arrange + var documentUri = "powerfx://app?context=1"; + var engine = new Engine(); + var symbols = new SymbolTable(); + symbols.AddVariable("Score", FormulaType.Number); + var scope = engine.CreateEditorScope(symbols: symbols); + var scopeFactory = new TestPowerFxScopeFactory((string documentUri) => scope); + Init(new InitParams(scopeFactory: scopeFactory)); + var nl2FxHandler = CreateAndConfigureNl2FxHandler(); + nl2FxHandler.Nl2FxDelayTime = 100; + nl2FxHandler.Expected = expectedExpr; + + // Act + var payload = NL2FxMessageJson(documentUri); + var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); + var response = AssertAndGetResponsePayload(rawResponse, payload.id); + + // Assert + // result has expected concat with symbols. + var items = response.Expressions; + + Assert.Single(items); + var expression = items[0]; + + if (anonExpr != null) + { + Assert.Equal(anonExpr, items[0].AnonymizedExpression); + } + + if (success) + { + Assert.Equal("my sentence: Score < 50: Score,Number", nl2FxHandler._log); + + Assert.Equal(expectedExpr, expression.Expression); + Assert.Null(expression.RawExpression); + } + else + { + // Even though model returned an expression, it didn't compile, so it should be filtered out by LSP. + Assert.Null(expression.Expression); + Assert.Equal(expectedExpr, expression.RawExpression); + } + + Assert.Equal(TestNL2FxHandler.ModelIdStr, expression.ModelId); + Assert.Equal(1, nl2FxHandler.PreHandleNl2FxCallCount); + } + + [Fact] + public async Task TestNL2FXMissingHandler() + { + // Arrange + HandlerFactory.SetHandler(CustomProtocolNames.NL2FX, null); + var documentUri = "powerfx://app?context={\"A\":1,\"B\":[1,2,3]}"; + + // Act + var payload = NL2FxMessageJson(documentUri); + var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); + AssertErrorPayload(rawResponse, payload.id, JsonRpcHelper.ErrorCode.MethodNotFound); + } + + [Fact] + public async Task TestNL2FXHandlerThrows() + { + // Arrange + var documentUri = "powerfx://app?context=1"; + var engine = new Engine(); + var symbols = new SymbolTable(); + symbols.AddVariable("Score", FormulaType.Number); + var scope = engine.CreateEditorScope(symbols: symbols); + var scopeFactory = new TestPowerFxScopeFactory((string documentUri) => scope); + Init(new InitParams(scopeFactory: scopeFactory)); + var nl2FxHandler = CreateAndConfigureNl2FxHandler(); + nl2FxHandler.Nl2FxDelayTime = 100; + nl2FxHandler.Throw = true; + + // Act + var payload = NL2FxMessageJson(documentUri); + var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); + + // Assert + AssertErrorPayload(rawResponse, payload.id, JsonRpcHelper.ErrorCode.InternalError); + Assert.NotEmpty(TestServer.UnhandledExceptions); + Assert.Equal(1, nl2FxHandler.PreHandleNl2FxCallCount); + } + + private static (string payload, string id) NL2FxMessageJson(string documentUri) + { + var nl2FxParams = new CustomNL2FxParams() + { + TextDocument = new TextDocumentItem() + { + Uri = documentUri, + LanguageId = "powerfx", + Version = 1 + }, + Sentence = "my sentence" + }; + + return GetRequestPayload(nl2FxParams, CustomProtocolNames.NL2FX); + } + + private TestNL2FxHandler CreateAndConfigureNl2FxHandler() + { + var nl2FxHandler = new TestNL2FxHandler(); + HandlerFactory.SetHandler(CustomProtocolNames.NL2FX, new Nl2FxLanguageServerOperationHandler(new BackwardsCompatibleNLHandlerFactory(nl2FxHandler))); + return nl2FxHandler; + } + } +} diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/OnDidChangeTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/OnDidChangeTests.cs similarity index 97% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/OnDidChangeTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/OnDidChangeTests.cs index dc14ebdc1..57bb87f2e 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/OnDidChangeTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/OnDidChangeTests.cs @@ -1,109 +1,109 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -using System.Text.Json; -using System.Threading.Tasks; -using Microsoft.PowerFx.Core.Localization; -using Microsoft.PowerFx.LanguageServerProtocol.Handlers; -using Microsoft.PowerFx.LanguageServerProtocol.Protocol; -using Xunit; - -namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol -{ - internal class TestOnChangeHandler - { - public int CallCounts = 0; - - public void OnDidChange(DidChangeTextDocumentParams didChangeTextDocumentParams) - { - CallCounts++; - } - } - - public partial class LanguageServerTestBase - { - [Theory] - [InlineData("A+CountRows(B)", false)] - [InlineData("Behavior(); A+CountRows(B)", true)] - public async Task TestDidChange(string text, bool withAllowSideEffects) - { - Init(new InitParams(options: GetParserOptions(withAllowSideEffects))); - var handler = CreateAndConfigureOnChangeHandler(); - - // test good formula - var payload = GetNotificationPayload( - new DidChangeTextDocumentParams() - { - TextDocument = new VersionedTextDocumentIdentifier() - { - Uri = "powerfx://app?context={\"A\":1,\"B\":[1,2,3]}", - Version = 1, - }, - ContentChanges = new TextDocumentContentChangeEvent[] - { - new TextDocumentContentChangeEvent() { Text = text } - } - }, TextDocumentNames.DidChange); - var rawResponse = await TestServer.OnDataReceivedAsync(payload).ConfigureAwait(false); - var notification = GetDiagnosticsParams(rawResponse); - Assert.Equal("powerfx://app?context={\"A\":1,\"B\":[1,2,3]}", notification.Uri); - Assert.Empty(notification.Diagnostics); - - // test bad formula - payload = GetNotificationPayload( - new DidChangeTextDocumentParams() - { - TextDocument = new VersionedTextDocumentIdentifier() - { - Uri = "powerfx://app", - Version = 1, - }, - ContentChanges = new TextDocumentContentChangeEvent[] - { - new TextDocumentContentChangeEvent() { Text = "AA" } - } - }, - TextDocumentNames.DidChange); - rawResponse = await TestServer.OnDataReceivedAsync(payload).ConfigureAwait(false); - notification = GetDiagnosticsParams(rawResponse); - Assert.Equal("powerfx://app", notification.Uri); - Assert.Single(notification.Diagnostics); - Assert.Equal("Name isn't valid. 'AA' isn't recognized.", notification.Diagnostics[0].Message); - - // some invalid cases - rawResponse = await TestServer.OnDataReceivedAsync(JsonSerializer.Serialize(new { })).ConfigureAwait(false); - AssertErrorPayload(rawResponse, null, LanguageServerProtocol.JsonRpcHelper.ErrorCode.InvalidRequest); - - rawResponse = await TestServer.OnDataReceivedAsync(JsonSerializer.Serialize(new - { - jsonrpc = "2.0", - method = "textDocument/didChange" - })).ConfigureAwait(false); - AssertErrorPayload(rawResponse, null, LanguageServerProtocol.JsonRpcHelper.ErrorCode.InvalidRequest); - - rawResponse = await TestServer.OnDataReceivedAsync(JsonSerializer.Serialize(new - { - jsonrpc = "2.0", - method = "textDocument/didChange", - @params = string.Empty - })).ConfigureAwait(false); - AssertErrorPayload(rawResponse, null, LanguageServerProtocol.JsonRpcHelper.ErrorCode.ParseError); - - Assert.True(handler.CallCounts == 2); - } - - private static PublishDiagnosticsParams GetDiagnosticsParams(string response) - { - return AssertAndGetNotificationParams(response, TextDocumentNames.PublishDiagnostics); - } - - private TestOnChangeHandler CreateAndConfigureOnChangeHandler() - { +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.PowerFx.Core.Localization; +using Microsoft.PowerFx.LanguageServerProtocol.Handlers; +using Microsoft.PowerFx.LanguageServerProtocol.Protocol; +using Xunit; + +namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol +{ + internal class TestOnChangeHandler + { + public int CallCounts = 0; + + public void OnDidChange(DidChangeTextDocumentParams didChangeTextDocumentParams) + { + CallCounts++; + } + } + + public partial class LanguageServerTestBase + { + [Theory] + [InlineData("A+CountRows(B)", false)] + [InlineData("Behavior(); A+CountRows(B)", true)] + public async Task TestDidChange(string text, bool withAllowSideEffects) + { + Init(new InitParams(options: GetParserOptions(withAllowSideEffects))); + var handler = CreateAndConfigureOnChangeHandler(); + + // test good formula + var payload = GetNotificationPayload( + new DidChangeTextDocumentParams() + { + TextDocument = new VersionedTextDocumentIdentifier() + { + Uri = "powerfx://app?context={\"A\":1,\"B\":[1,2,3]}", + Version = 1, + }, + ContentChanges = new TextDocumentContentChangeEvent[] + { + new TextDocumentContentChangeEvent() { Text = text } + } + }, TextDocumentNames.DidChange); + var rawResponse = await TestServer.OnDataReceivedAsync(payload).ConfigureAwait(false); + var notification = GetDiagnosticsParams(rawResponse); + Assert.Equal("powerfx://app?context={\"A\":1,\"B\":[1,2,3]}", notification.Uri); + Assert.Empty(notification.Diagnostics); + + // test bad formula + payload = GetNotificationPayload( + new DidChangeTextDocumentParams() + { + TextDocument = new VersionedTextDocumentIdentifier() + { + Uri = "powerfx://app", + Version = 1, + }, + ContentChanges = new TextDocumentContentChangeEvent[] + { + new TextDocumentContentChangeEvent() { Text = "AA" } + } + }, + TextDocumentNames.DidChange); + rawResponse = await TestServer.OnDataReceivedAsync(payload).ConfigureAwait(false); + notification = GetDiagnosticsParams(rawResponse); + Assert.Equal("powerfx://app", notification.Uri); + Assert.Single(notification.Diagnostics); + Assert.Equal("Name isn't valid. 'AA' isn't recognized.", notification.Diagnostics[0].Message); + + // some invalid cases + rawResponse = await TestServer.OnDataReceivedAsync(JsonSerializer.Serialize(new { })).ConfigureAwait(false); + AssertErrorPayload(rawResponse, null, LanguageServerProtocol.JsonRpcHelper.ErrorCode.InvalidRequest); + + rawResponse = await TestServer.OnDataReceivedAsync(JsonSerializer.Serialize(new + { + jsonrpc = "2.0", + method = "textDocument/didChange" + })).ConfigureAwait(false); + AssertErrorPayload(rawResponse, null, LanguageServerProtocol.JsonRpcHelper.ErrorCode.InvalidRequest); + + rawResponse = await TestServer.OnDataReceivedAsync(JsonSerializer.Serialize(new + { + jsonrpc = "2.0", + method = "textDocument/didChange", + @params = string.Empty + })).ConfigureAwait(false); + AssertErrorPayload(rawResponse, null, LanguageServerProtocol.JsonRpcHelper.ErrorCode.ParseError); + + Assert.True(handler.CallCounts == 2); + } + + private static PublishDiagnosticsParams GetDiagnosticsParams(string response) + { + return AssertAndGetNotificationParams(response, TextDocumentNames.PublishDiagnostics); + } + + private TestOnChangeHandler CreateAndConfigureOnChangeHandler() + { var handler = new TestOnChangeHandler(); - HandlerFactory.SetHandler(TextDocumentNames.DidChange, new OnDidChangeLanguageServerNotificationHandler(handler.OnDidChange)); - return handler; - } - + HandlerFactory.SetHandler(TextDocumentNames.DidChange, new OnDidChangeLanguageServerNotificationHandler(handler.OnDidChange)); + return handler; + } + private void CheckBehaviorError(string response, bool expectBehaviorError, out Diagnostic[] diags) { diags = GetDiagnosticsParams(response).Diagnostics; @@ -116,6 +116,6 @@ namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol { Assert.DoesNotContain(diags, d => d.Message == StringResources.GetErrorResource(TexlStrings.ErrBehaviorPropertyExpected).GetSingleValue(ErrorResource.ShortMessageTag)); } - } - } -} + } + } +} diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/OnDidOpenTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/OnDidOpenTests.cs similarity index 97% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/OnDidOpenTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/OnDidOpenTests.cs index ae5d490b0..f7ce440cb 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/OnDidOpenTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/OnDidOpenTests.cs @@ -1,302 +1,302 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.PowerFx.Intellisense; -using Microsoft.PowerFx.LanguageServerProtocol; -using Microsoft.PowerFx.LanguageServerProtocol.Protocol; -using Microsoft.PowerFx.Types; -using Xunit; - -namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol -{ - public partial class LanguageServerTestBase - { - private async Task TestPublishDiagnostics(string uri, string method, string formula, Diagnostic[] expectedDiagnostics) - { - var payload = GetDidOpenPayload(new DidOpenTextDocumentParams() - { - TextDocument = new TextDocumentItem() - { - Uri = uri, - LanguageId = "powerfx", - Version = 1, - Text = formula - } - }); - var rawResponse = await TestServer.OnDataReceivedAsync(payload).ConfigureAwait(false); - var notification = GetDiagnosticsParams(rawResponse); - Assert.Equal(uri, notification.Uri); - Assert.Equal(expectedDiagnostics.Length, notification.Diagnostics.Length); - - var diagnosticsSet = new HashSet(expectedDiagnostics); - for (var i = 0; i < expectedDiagnostics.Length; i++) - { - var expectedDiagnostic = expectedDiagnostics[i]; - var actualDiagnostic = notification.Diagnostics[i]; - Assert.True(diagnosticsSet.Where(x => x.Message == actualDiagnostic.Message).Count() == 1); - diagnosticsSet.RemoveWhere(x => x.Message == actualDiagnostic.Message); - } - - Assert.True(diagnosticsSet.Count() == 0); - } - - [Theory] - [InlineData("A+CountRows(B)", "{\"A\":1,\"B\":[1,2,3]}")] - public async Task TestDidOpenValidFormula(string formula, string context = null) - { - var uri = $"powerfx://app{(context != null ? "powerfx://app?context=" + context : string.Empty)}"; - await TestPublishDiagnostics(uri, "textDocument/didOpen", formula, new Diagnostic[0]).ConfigureAwait(false); - } - - [Theory] - [InlineData("AA", "Name isn't valid. 'AA' isn't recognized.")] - [InlineData("1+CountRowss", "Name isn't valid. 'CountRowss' isn't recognized.")] - [InlineData("CountRows(2)", "Invalid argument type (Decimal). Expecting a Table value instead.", "The function 'CountRows' has some invalid arguments.")] - public async Task TestDidOpenErroneousFormula(string formula, params string[] expectedErrors) - { - var expectedDiagnostics = expectedErrors.Select(error => new Diagnostic() - { - Message = error, - Severity = DiagnosticSeverity.Error - }).ToArray(); - await TestPublishDiagnostics("powerfx://app", "textDocument/didOpen", formula, expectedDiagnostics).ConfigureAwait(false); - } - - [Fact] - public async Task TestDidOpenSeverityFormula() - { - var formula = "Count([\"test\"])"; - var expectedDiagnostics = new[] - { - new Diagnostic() - { - Message = "Invalid schema, expected a column of Number values for 'Value'.", - Severity = DiagnosticSeverity.Warning - }, - new Diagnostic() - { - Message = "The function 'Count' has some invalid arguments.", - Severity = DiagnosticSeverity.Error - }, - }; - await TestPublishDiagnostics("powerfx://app", "textDocument/didOpen", formula, expectedDiagnostics).ConfigureAwait(false); - } - - [Theory] - [InlineData("Concatenate(", 12, false, false)] - [InlineData("Behavior(); Concatenate(", 24, true, false)] - [InlineData("Behavior(); Concatenate(", 24, false, true)] - public async Task TestDidOpenWithErrors(string text, int offset, bool withAllowSideEffects, bool expectBehaviorError) - { - Init(new InitParams(options: GetParserOptions(withAllowSideEffects))); - var payload = GetDidOpenPayload(new DidOpenTextDocumentParams() - { - TextDocument = new TextDocumentItem() - { - Uri = "powerfx://app", - LanguageId = "powerfx", - Version = 1, - Text = text - } - }); - var rawResponse = await TestServer.OnDataReceivedAsync(payload).ConfigureAwait(false); - CheckBehaviorError(rawResponse, expectBehaviorError, out var diags); - - var diag = diags.First(d => d.Message == "Unexpected characters. The formula contains 'Eof' where 'ParenClose' is expected."); - - Assert.Equal(offset, diag.Range.Start.Character); - Assert.Equal(offset, diag.Range.End.Character); - } - - [Theory] - [InlineData("A+CountRows(B)", 3, false, false)] - [InlineData("Behavior(); A+CountRows(B)", 4, true, false)] - [InlineData("Behavior(); A+CountRows(B)", 4, false, true)] - public async Task TestPublishTokens(string text, int count, bool withAllowSideEffects, bool expectBehaviorError) - { - Init(new InitParams(options: GetParserOptions(withAllowSideEffects))); - - // getTokensFlags = 0x0 (none), 0x1 (tokens inside expression), 0x2 (all functions) - var documentUri = "powerfx://app?context={\"A\":1,\"B\":[1,2,3]}&getTokensFlags=1"; - var payload = GetDidOpenPayload(new DidOpenTextDocumentParams() - { - TextDocument = new TextDocumentItem() - { - Uri = documentUri, - LanguageId = "powerfx", - Version = 1, - Text = text - } - }); - - var rawResponse = await TestServer.OnDataReceivedAsync(payload).ConfigureAwait(false); - var response = GetPublishTokensParams(rawResponse); - Assert.Equal(documentUri, response.Uri); - Assert.Equal(count, response.Tokens.Count); - Assert.Equal(TokenResultType.Variable, response.Tokens["A"]); - Assert.Equal(TokenResultType.Variable, response.Tokens["B"]); - Assert.Equal(TokenResultType.Function, response.Tokens["CountRows"]); - - CheckBehaviorError(rawResponse, expectBehaviorError, out _); - - if (count == 4) - { - Assert.Equal(TokenResultType.Function, response.Tokens["Behavior"]); - } - - // getTokensFlags = 0x0 (none), 0x1 (tokens inside expression), 0x2 (all functions) - documentUri = "powerfx://app?context={\"A\":1,\"B\":[1,2,3]}&getTokensFlags=2"; - payload = GetNotificationPayload( - new DidChangeTextDocumentParams() - { - TextDocument = new VersionedTextDocumentIdentifier() - { - Uri = documentUri, - Version = 1, - }, - ContentChanges = new TextDocumentContentChangeEvent[] - { - new TextDocumentContentChangeEvent() { Text = text } - } - }, TextDocumentNames.DidChange); - - rawResponse = await TestServer.OnDataReceivedAsync(payload).ConfigureAwait(false); - response = GetPublishTokensParams(rawResponse); - - Assert.Equal(documentUri, response.Uri); - Assert.Equal(0, Enumerable.Count(response.Tokens.Where(it => it.Value != TokenResultType.Function))); - Assert.Equal(TokenResultType.Function, response.Tokens["Abs"]); - Assert.Equal(TokenResultType.Function, response.Tokens["Clock.AmPm"]); - Assert.Equal(TokenResultType.Function, response.Tokens["CountRows"]); - Assert.Equal(TokenResultType.Function, response.Tokens["VarP"]); - Assert.Equal(TokenResultType.Function, response.Tokens["Year"]); - - CheckBehaviorError(rawResponse, expectBehaviorError, out _); - - // getTokensFlags = 0x0 (none), 0x1 (tokens inside expression), 0x2 (all functions) - documentUri = "powerfx://app?context={\"A\":1,\"B\":[1,2,3]}&getTokensFlags=3"; - payload = GetNotificationPayload( - new DidChangeTextDocumentParams() - { - TextDocument = new VersionedTextDocumentIdentifier() - { - Uri = documentUri, - Version = 1, - }, - ContentChanges = new TextDocumentContentChangeEvent[] - { - new TextDocumentContentChangeEvent() { Text = text } - } - }, TextDocumentNames.DidChange); - - rawResponse = await TestServer.OnDataReceivedAsync(payload).ConfigureAwait(false); - response = GetPublishTokensParams(rawResponse); - - Assert.Equal(documentUri, response.Uri); - Assert.Equal(TokenResultType.Variable, response.Tokens["A"]); - Assert.Equal(TokenResultType.Variable, response.Tokens["B"]); - Assert.Equal(TokenResultType.Function, response.Tokens["Abs"]); - Assert.Equal(TokenResultType.Function, response.Tokens["Clock.AmPm"]); - Assert.Equal(TokenResultType.Function, response.Tokens["CountRows"]); - Assert.Equal(TokenResultType.Function, response.Tokens["VarP"]); - Assert.Equal(TokenResultType.Function, response.Tokens["Year"]); - - CheckBehaviorError(rawResponse, expectBehaviorError, out _); - } - - [Theory] - [InlineData("{\"A\": 1 }", "A+2", typeof(DecimalType))] - [InlineData("{}", "\"hi\"", typeof(StringType))] - [InlineData("{}", "", typeof(BlankType))] - [InlineData("{}", "{ A: 1 }", typeof(KnownRecordType))] - [InlineData("{}", "[1, 2, 3]", typeof(TableType))] - [InlineData("{}", "true", typeof(BooleanType))] - public async Task TestPublishExpressionType(string context, string expression, System.Type expectedType) - { - var documentUri = $"powerfx://app?context={context}&getExpressionType=true"; - var payload = GetDidOpenPayload(new DidOpenTextDocumentParams() - { - TextDocument = new TextDocumentItem() - { - Uri = documentUri, - LanguageId = "powerfx", - Version = 1, - Text = expression - } - }); - var rawResponse = await TestServer.OnDataReceivedAsync(payload).ConfigureAwait(false); - var response = GetPublishExpressionTypeParams(rawResponse); - - Assert.Equal(documentUri, response.Uri); - Assert.IsType(expectedType, response.Type); - } - - [Theory] - [InlineData("{\"A\": 1 }", "invalid+A")] - [InlineData("{}", "B")] - [InlineData("{}", "+")] - public async Task TestPublishExpressionType_Null(string context, string expression) - { - var documentUri = $"powerfx://app?context={context}&getExpressionType=true"; - var payload = GetDidOpenPayload(new DidOpenTextDocumentParams() - { - TextDocument = new TextDocumentItem() - { - Uri = documentUri, - LanguageId = "powerfx", - Version = 1, - Text = expression - } - }); - var rawResponse = await TestServer.OnDataReceivedAsync(payload).ConfigureAwait(false); - var response = GetPublishExpressionTypeParams(rawResponse); - Assert.Equal(documentUri, response.Uri); - Assert.Null(response.Type); - } - - [Theory] - [InlineData(false, "{}", "{ A: 1 }", @"{""Type"":""Record"",""Fields"":{""A"":{""Type"":""Decimal""}}}")] - [InlineData(false, "{}", "[1, 2]", @"{""Type"":""Table"",""Fields"":{""Value"":{""Type"":""Decimal""}}}")] - [InlineData(true, "{}", "[{ A: 1 }, { B: true }]", @"{""Type"":""Table"",""Fields"":{""A"":{""Type"":""Decimal""},""B"":{""Type"":""Boolean""}}}")] - [InlineData(false, "{}", "[{ A: 1 }, { B: true }]", @"{""Type"":""Table"",""Fields"":{""Value"":{""Type"":""Record"",""Fields"":{""A"":{""Type"":""Decimal""},""B"":{""Type"":""Boolean""}}}}}")] - [InlineData(false, "{}", "{A: 1, B: { C: { D: \"Qwerty\" }, E: true } }", @"{""Type"":""Record"",""Fields"":{""A"":{""Type"":""Decimal""},""B"":{""Type"":""Record"",""Fields"":{""C"":{""Type"":""Record"",""Fields"":{""D"":{""Type"":""String""}}},""E"":{""Type"":""Boolean""}}}}}")] - [InlineData(false, "{}", "{ type: 123 }", @"{""Type"":""Record"",""Fields"":{""type"":{""Type"":""Decimal""}}}")] - public async Task TestPublishExpressionType_AggregateShapes(bool tableSyntaxDoesntWrapRecords, string context, string expression, string expectedTypeJson) - { - Init(new InitParams(features: new Features { TableSyntaxDoesntWrapRecords = tableSyntaxDoesntWrapRecords })); - var documentUri = $"powerfx://app?context={context}&getExpressionType=true"; - var payload = GetDidOpenPayload(new DidOpenTextDocumentParams() - { - TextDocument = new TextDocumentItem() - { - Uri = documentUri, - LanguageId = "powerfx", - Version = 1, - Text = expression - } - }); - var rawResponse = await TestServer.OnDataReceivedAsync(payload).ConfigureAwait(false); - var response = GetPublishExpressionTypeParams(rawResponse); - Assert.Equal(documentUri, response.Uri); - Assert.Equal(expectedTypeJson, JsonRpcHelper.Serialize(response.Type)); - } - - private static string GetDidOpenPayload(DidOpenTextDocumentParams openTextDocumentParams) - { - return GetNotificationPayload(openTextDocumentParams, TextDocumentNames.DidOpen); - } - - private static PublishTokensParams GetPublishTokensParams(string response) - { - return AssertAndGetNotificationParams(response, CustomProtocolNames.PublishTokens); - } - - private static PublishExpressionTypeParams GetPublishExpressionTypeParams(string response) - { - return AssertAndGetNotificationParams(response, CustomProtocolNames.PublishExpressionType); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.PowerFx.Intellisense; +using Microsoft.PowerFx.LanguageServerProtocol; +using Microsoft.PowerFx.LanguageServerProtocol.Protocol; +using Microsoft.PowerFx.Types; +using Xunit; + +namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol +{ + public partial class LanguageServerTestBase + { + private async Task TestPublishDiagnostics(string uri, string method, string formula, Diagnostic[] expectedDiagnostics) + { + var payload = GetDidOpenPayload(new DidOpenTextDocumentParams() + { + TextDocument = new TextDocumentItem() + { + Uri = uri, + LanguageId = "powerfx", + Version = 1, + Text = formula + } + }); + var rawResponse = await TestServer.OnDataReceivedAsync(payload).ConfigureAwait(false); + var notification = GetDiagnosticsParams(rawResponse); + Assert.Equal(uri, notification.Uri); + Assert.Equal(expectedDiagnostics.Length, notification.Diagnostics.Length); + + var diagnosticsSet = new HashSet(expectedDiagnostics); + for (var i = 0; i < expectedDiagnostics.Length; i++) + { + var expectedDiagnostic = expectedDiagnostics[i]; + var actualDiagnostic = notification.Diagnostics[i]; + Assert.True(diagnosticsSet.Where(x => x.Message == actualDiagnostic.Message).Count() == 1); + diagnosticsSet.RemoveWhere(x => x.Message == actualDiagnostic.Message); + } + + Assert.True(diagnosticsSet.Count() == 0); + } + + [Theory] + [InlineData("A+CountRows(B)", "{\"A\":1,\"B\":[1,2,3]}")] + public async Task TestDidOpenValidFormula(string formula, string context = null) + { + var uri = $"powerfx://app{(context != null ? "powerfx://app?context=" + context : string.Empty)}"; + await TestPublishDiagnostics(uri, "textDocument/didOpen", formula, new Diagnostic[0]).ConfigureAwait(false); + } + + [Theory] + [InlineData("AA", "Name isn't valid. 'AA' isn't recognized.")] + [InlineData("1+CountRowss", "Name isn't valid. 'CountRowss' isn't recognized.")] + [InlineData("CountRows(2)", "Invalid argument type (Decimal). Expecting a Table value instead.", "The function 'CountRows' has some invalid arguments.")] + public async Task TestDidOpenErroneousFormula(string formula, params string[] expectedErrors) + { + var expectedDiagnostics = expectedErrors.Select(error => new Diagnostic() + { + Message = error, + Severity = DiagnosticSeverity.Error + }).ToArray(); + await TestPublishDiagnostics("powerfx://app", "textDocument/didOpen", formula, expectedDiagnostics).ConfigureAwait(false); + } + + [Fact] + public async Task TestDidOpenSeverityFormula() + { + var formula = "Count([\"test\"])"; + var expectedDiagnostics = new[] + { + new Diagnostic() + { + Message = "Invalid schema, expected a column of Number values for 'Value'.", + Severity = DiagnosticSeverity.Warning + }, + new Diagnostic() + { + Message = "The function 'Count' has some invalid arguments.", + Severity = DiagnosticSeverity.Error + }, + }; + await TestPublishDiagnostics("powerfx://app", "textDocument/didOpen", formula, expectedDiagnostics).ConfigureAwait(false); + } + + [Theory] + [InlineData("Concatenate(", 12, false, false)] + [InlineData("Behavior(); Concatenate(", 24, true, false)] + [InlineData("Behavior(); Concatenate(", 24, false, true)] + public async Task TestDidOpenWithErrors(string text, int offset, bool withAllowSideEffects, bool expectBehaviorError) + { + Init(new InitParams(options: GetParserOptions(withAllowSideEffects))); + var payload = GetDidOpenPayload(new DidOpenTextDocumentParams() + { + TextDocument = new TextDocumentItem() + { + Uri = "powerfx://app", + LanguageId = "powerfx", + Version = 1, + Text = text + } + }); + var rawResponse = await TestServer.OnDataReceivedAsync(payload).ConfigureAwait(false); + CheckBehaviorError(rawResponse, expectBehaviorError, out var diags); + + var diag = diags.First(d => d.Message == "Unexpected characters. The formula contains 'Eof' where 'ParenClose' is expected."); + + Assert.Equal(offset, diag.Range.Start.Character); + Assert.Equal(offset, diag.Range.End.Character); + } + + [Theory] + [InlineData("A+CountRows(B)", 3, false, false)] + [InlineData("Behavior(); A+CountRows(B)", 4, true, false)] + [InlineData("Behavior(); A+CountRows(B)", 4, false, true)] + public async Task TestPublishTokens(string text, int count, bool withAllowSideEffects, bool expectBehaviorError) + { + Init(new InitParams(options: GetParserOptions(withAllowSideEffects))); + + // getTokensFlags = 0x0 (none), 0x1 (tokens inside expression), 0x2 (all functions) + var documentUri = "powerfx://app?context={\"A\":1,\"B\":[1,2,3]}&getTokensFlags=1"; + var payload = GetDidOpenPayload(new DidOpenTextDocumentParams() + { + TextDocument = new TextDocumentItem() + { + Uri = documentUri, + LanguageId = "powerfx", + Version = 1, + Text = text + } + }); + + var rawResponse = await TestServer.OnDataReceivedAsync(payload).ConfigureAwait(false); + var response = GetPublishTokensParams(rawResponse); + Assert.Equal(documentUri, response.Uri); + Assert.Equal(count, response.Tokens.Count); + Assert.Equal(TokenResultType.Variable, response.Tokens["A"]); + Assert.Equal(TokenResultType.Variable, response.Tokens["B"]); + Assert.Equal(TokenResultType.Function, response.Tokens["CountRows"]); + + CheckBehaviorError(rawResponse, expectBehaviorError, out _); + + if (count == 4) + { + Assert.Equal(TokenResultType.Function, response.Tokens["Behavior"]); + } + + // getTokensFlags = 0x0 (none), 0x1 (tokens inside expression), 0x2 (all functions) + documentUri = "powerfx://app?context={\"A\":1,\"B\":[1,2,3]}&getTokensFlags=2"; + payload = GetNotificationPayload( + new DidChangeTextDocumentParams() + { + TextDocument = new VersionedTextDocumentIdentifier() + { + Uri = documentUri, + Version = 1, + }, + ContentChanges = new TextDocumentContentChangeEvent[] + { + new TextDocumentContentChangeEvent() { Text = text } + } + }, TextDocumentNames.DidChange); + + rawResponse = await TestServer.OnDataReceivedAsync(payload).ConfigureAwait(false); + response = GetPublishTokensParams(rawResponse); + + Assert.Equal(documentUri, response.Uri); + Assert.Equal(0, Enumerable.Count(response.Tokens.Where(it => it.Value != TokenResultType.Function))); + Assert.Equal(TokenResultType.Function, response.Tokens["Abs"]); + Assert.Equal(TokenResultType.Function, response.Tokens["Clock.AmPm"]); + Assert.Equal(TokenResultType.Function, response.Tokens["CountRows"]); + Assert.Equal(TokenResultType.Function, response.Tokens["VarP"]); + Assert.Equal(TokenResultType.Function, response.Tokens["Year"]); + + CheckBehaviorError(rawResponse, expectBehaviorError, out _); + + // getTokensFlags = 0x0 (none), 0x1 (tokens inside expression), 0x2 (all functions) + documentUri = "powerfx://app?context={\"A\":1,\"B\":[1,2,3]}&getTokensFlags=3"; + payload = GetNotificationPayload( + new DidChangeTextDocumentParams() + { + TextDocument = new VersionedTextDocumentIdentifier() + { + Uri = documentUri, + Version = 1, + }, + ContentChanges = new TextDocumentContentChangeEvent[] + { + new TextDocumentContentChangeEvent() { Text = text } + } + }, TextDocumentNames.DidChange); + + rawResponse = await TestServer.OnDataReceivedAsync(payload).ConfigureAwait(false); + response = GetPublishTokensParams(rawResponse); + + Assert.Equal(documentUri, response.Uri); + Assert.Equal(TokenResultType.Variable, response.Tokens["A"]); + Assert.Equal(TokenResultType.Variable, response.Tokens["B"]); + Assert.Equal(TokenResultType.Function, response.Tokens["Abs"]); + Assert.Equal(TokenResultType.Function, response.Tokens["Clock.AmPm"]); + Assert.Equal(TokenResultType.Function, response.Tokens["CountRows"]); + Assert.Equal(TokenResultType.Function, response.Tokens["VarP"]); + Assert.Equal(TokenResultType.Function, response.Tokens["Year"]); + + CheckBehaviorError(rawResponse, expectBehaviorError, out _); + } + + [Theory] + [InlineData("{\"A\": 1 }", "A+2", typeof(DecimalType))] + [InlineData("{}", "\"hi\"", typeof(StringType))] + [InlineData("{}", "", typeof(BlankType))] + [InlineData("{}", "{ A: 1 }", typeof(KnownRecordType))] + [InlineData("{}", "[1, 2, 3]", typeof(TableType))] + [InlineData("{}", "true", typeof(BooleanType))] + public async Task TestPublishExpressionType(string context, string expression, System.Type expectedType) + { + var documentUri = $"powerfx://app?context={context}&getExpressionType=true"; + var payload = GetDidOpenPayload(new DidOpenTextDocumentParams() + { + TextDocument = new TextDocumentItem() + { + Uri = documentUri, + LanguageId = "powerfx", + Version = 1, + Text = expression + } + }); + var rawResponse = await TestServer.OnDataReceivedAsync(payload).ConfigureAwait(false); + var response = GetPublishExpressionTypeParams(rawResponse); + + Assert.Equal(documentUri, response.Uri); + Assert.IsType(expectedType, response.Type); + } + + [Theory] + [InlineData("{\"A\": 1 }", "invalid+A")] + [InlineData("{}", "B")] + [InlineData("{}", "+")] + public async Task TestPublishExpressionType_Null(string context, string expression) + { + var documentUri = $"powerfx://app?context={context}&getExpressionType=true"; + var payload = GetDidOpenPayload(new DidOpenTextDocumentParams() + { + TextDocument = new TextDocumentItem() + { + Uri = documentUri, + LanguageId = "powerfx", + Version = 1, + Text = expression + } + }); + var rawResponse = await TestServer.OnDataReceivedAsync(payload).ConfigureAwait(false); + var response = GetPublishExpressionTypeParams(rawResponse); + Assert.Equal(documentUri, response.Uri); + Assert.Null(response.Type); + } + + [Theory] + [InlineData(false, "{}", "{ A: 1 }", @"{""Type"":""Record"",""Fields"":{""A"":{""Type"":""Decimal""}}}")] + [InlineData(false, "{}", "[1, 2]", @"{""Type"":""Table"",""Fields"":{""Value"":{""Type"":""Decimal""}}}")] + [InlineData(true, "{}", "[{ A: 1 }, { B: true }]", @"{""Type"":""Table"",""Fields"":{""A"":{""Type"":""Decimal""},""B"":{""Type"":""Boolean""}}}")] + [InlineData(false, "{}", "[{ A: 1 }, { B: true }]", @"{""Type"":""Table"",""Fields"":{""Value"":{""Type"":""Record"",""Fields"":{""A"":{""Type"":""Decimal""},""B"":{""Type"":""Boolean""}}}}}")] + [InlineData(false, "{}", "{A: 1, B: { C: { D: \"Qwerty\" }, E: true } }", @"{""Type"":""Record"",""Fields"":{""A"":{""Type"":""Decimal""},""B"":{""Type"":""Record"",""Fields"":{""C"":{""Type"":""Record"",""Fields"":{""D"":{""Type"":""String""}}},""E"":{""Type"":""Boolean""}}}}}")] + [InlineData(false, "{}", "{ type: 123 }", @"{""Type"":""Record"",""Fields"":{""type"":{""Type"":""Decimal""}}}")] + public async Task TestPublishExpressionType_AggregateShapes(bool tableSyntaxDoesntWrapRecords, string context, string expression, string expectedTypeJson) + { + Init(new InitParams(features: new Features { TableSyntaxDoesntWrapRecords = tableSyntaxDoesntWrapRecords })); + var documentUri = $"powerfx://app?context={context}&getExpressionType=true"; + var payload = GetDidOpenPayload(new DidOpenTextDocumentParams() + { + TextDocument = new TextDocumentItem() + { + Uri = documentUri, + LanguageId = "powerfx", + Version = 1, + Text = expression + } + }); + var rawResponse = await TestServer.OnDataReceivedAsync(payload).ConfigureAwait(false); + var response = GetPublishExpressionTypeParams(rawResponse); + Assert.Equal(documentUri, response.Uri); + Assert.Equal(expectedTypeJson, JsonRpcHelper.Serialize(response.Type)); + } + + private static string GetDidOpenPayload(DidOpenTextDocumentParams openTextDocumentParams) + { + return GetNotificationPayload(openTextDocumentParams, TextDocumentNames.DidOpen); + } + + private static PublishTokensParams GetPublishTokensParams(string response) + { + return AssertAndGetNotificationParams(response, CustomProtocolNames.PublishTokens); + } + + private static PublishExpressionTypeParams GetPublishExpressionTypeParams(string response) + { + return AssertAndGetNotificationParams(response, CustomProtocolNames.PublishExpressionType); + } + } +} diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/SemanticTokensTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/SemanticTokensTests.cs similarity index 97% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/SemanticTokensTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/SemanticTokensTests.cs index 167ad2382..5faf1350c 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/SemanticTokensTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/SemanticTokensTests.cs @@ -1,404 +1,404 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -using System.Collections.Generic; -using System.Linq; -using System.Text.Json; -using System.Threading.Tasks; -using Microsoft.PowerFx.Core.Texl.Intellisense; -using Microsoft.PowerFx.Interpreter.Tests.LanguageServiceProtocol; -using Microsoft.PowerFx.LanguageServerProtocol; -using Microsoft.PowerFx.LanguageServerProtocol.Protocol; -using Microsoft.PowerFx.LanguageServerProtocol.Schemas; -using Xunit; - -namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol -{ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.PowerFx.Core.Texl.Intellisense; +using Microsoft.PowerFx.Interpreter.Tests.LanguageServiceProtocol; +using Microsoft.PowerFx.LanguageServerProtocol; +using Microsoft.PowerFx.LanguageServerProtocol.Protocol; +using Microsoft.PowerFx.LanguageServerProtocol.Schemas; +using Xunit; + +namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol +{ public partial class LanguageServerTestBase - { - #region Full Document Semantic Tokens Tests - [Fact] - public async Task TestCorrectFullSemanticTokensAreReturnedWithExpressionInUri() - { - await TestCorrectFullSemanticTokensAreReturned(new SemanticTokensParams - { - TextDocument = GetTextDocument(GetUri("expression=Max(1, 2, 3)")) - }).ConfigureAwait(false); - } - - [Fact] - public async Task TestCorrectFullSemanticTokensAreReturnedWithExpressionNotInUri() - { - await TestCorrectFullSemanticTokensAreReturned(new SemanticTokensParams - { - TextDocument = GetTextDocument(), - Text = "Max(1, 2, 3)" - }).ConfigureAwait(false); - } - - [Fact] - public async Task TestCorrectFullSemanticTokensAreReturnedWithExpressionInBothUriAndTextDocument() - { - var expression = "Max(1, 2, 3)"; - var semanticTokenParams = new SemanticTokensParams - { - TextDocument = GetTextDocument(GetUri("expression=Color.White")), - Text = expression - }; - await TestCorrectFullSemanticTokensAreReturned(semanticTokenParams).ConfigureAwait(false); - } - - private async Task TestCorrectFullSemanticTokensAreReturned(SemanticTokensParams semanticTokensParams) - { - // Arrange - Init(); - var expression = GetExpression(semanticTokensParams); - Assert.Equal("Max(1, 2, 3)", expression); - var payload = GetFullDocumentSemanticTokensRequestPayload(semanticTokensParams); - - // Act - var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); - - // Assert - var response = AssertAndGetSemanticTokensResponse(rawResponse, payload.id); - Assert.NotEmpty(response.Data); - var decodedTokens = SemanticTokensRelatedTestsHelper.DecodeEncodedSemanticTokensPartially(response, expression); - Assert.Single(decodedTokens.Where(tok => tok.TokenType == TokenType.Function)); - Assert.Equal(3, decodedTokens.Where(tok => tok.TokenType == TokenType.NumLit || tok.TokenType == TokenType.DecLit).Count()); - } - - [Theory] - [InlineData("Create", TokenType.Function, TokenType.BoolLit)] - [InlineData(null)] - [InlineData("")] - [InlineData("[2, 3")] - [InlineData("Create", TokenType.StrInterpStart, TokenType.BinaryOp, TokenType.NumLit, TokenType.DecLit, TokenType.Control)] - [InlineData("[]")] - [InlineData("1,2]")] - [InlineData("[98]")] - [InlineData("Create", TokenType.Lim)] - [InlineData("Create", TokenType.BoolLit, TokenType.BinaryOp, TokenType.Function, TokenType.Lim)] - [InlineData("Create", TokenType.Lim, TokenType.BinaryOp, TokenType.BoolLit)] - [InlineData(" ")] - [InlineData("NotPresent")] - internal async Task TestCorrectFullSemanticTokensAreReturnedWithCertainTokenTypesSkipped(string tokenTypesToSkipParam, params TokenType[] tokenTypesToSkip) - { - // Arrange - var expression = "1+-2;true;\"String Literal\";Sum(1,2);Max(1,2,3);$\"1 + 2 = {3}\";// This is Comment"; - var expectedTypes = new List { TokenType.DecLit, TokenType.BoolLit, TokenType.Comment, TokenType.Function, TokenType.StrInterpStart, TokenType.IslandEnd, TokenType.IslandStart, TokenType.StrLit, TokenType.StrInterpEnd, TokenType.Delimiter, TokenType.BinaryOp }; - if (tokenTypesToSkip.Length > 0) - { - expectedTypes = expectedTypes.Where(expectedType => !tokenTypesToSkip.Contains(expectedType)).ToList(); - } - - if (tokenTypesToSkipParam == "Create") - { - tokenTypesToSkipParam = JsonSerializer.Serialize(tokenTypesToSkip.Select(tokType => (int)tokType).ToList()); - } - - var semanticTokenParams = new SemanticTokensParams - { - TextDocument = GetTextDocument(GetUri(tokenTypesToSkipParam == "NotPresent" ? string.Empty : "tokenTypesToSkip=" + tokenTypesToSkipParam)), - Text = expression - }; - var payload = GetFullDocumentSemanticTokensRequestPayload(semanticTokenParams); - - // Act - var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); - - // Assert - var response = AssertAndGetSemanticTokensResponse(rawResponse, payload.id); - Assert.NotEmpty(response.Data); - var decodedTokens = SemanticTokensRelatedTestsHelper.DecodeEncodedSemanticTokensPartially(response, expression); - var actualTypes = decodedTokens.Select(tok => tok.TokenType).Distinct().ToList(); - Assert.Equal(expectedTypes.OrderBy(type => type), actualTypes.OrderBy(type => type)); - } - - [Fact] - public async Task TestErrorResponseReturnedWhenUriIsNullForFullSemanticTokensRequest() - { - // Arrange - var semanticTokenParams = new SemanticTokensParams - { - TextDocument = new TextDocumentIdentifier() { Uri = null } - }; - var payload = GetFullDocumentSemanticTokensRequestPayload(semanticTokenParams); - - // Act - var response = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); - - // Assert - AssertErrorPayload(response, payload.id, JsonRpcHelper.ErrorCode.ParseError); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task TestEmptyFullSemanticTokensResponseReturnedWhenExpressionIsInvalid(bool isNotNull) - { - // Arrange - string expression = string.Empty; - var semanticTokenParams = new SemanticTokensParams - { - TextDocument = GetTextDocument(), - Text = isNotNull ? expression : null - }; - var payload = GetFullDocumentSemanticTokensRequestPayload(semanticTokenParams); - - // Act - var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); - - // Assert - var response = AssertAndGetSemanticTokensResponse(rawResponse, payload.id); - Assert.Empty(response.Data); - } - - [Fact] - public async Task TestFullSemanticTokensResponseReturnedWithDefaultEOL() - { - // Arrange - string expression = "Sum(\n1,1\n)"; - var semanticTokenParams = new SemanticTokensParams - { - TextDocument = GetTextDocument(), - Text = expression - }; - var payload = GetFullDocumentSemanticTokensRequestPayload(semanticTokenParams); - - // Act - var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); - - // Assert - var response = AssertAndGetSemanticTokensResponse(rawResponse, payload.id); - Assert.NotEmpty(response.Data); - Assert.Equal(expression.Where(c => c == '\n').Count(), SemanticTokensRelatedTestsHelper.DetermineNumberOfLinesThatTokensAreSpreadAcross(response)); - } - - [Theory] - [InlineData("9 + 9", 0)] - [InlineData("Max(10, 20, 30)", 0)] - [InlineData("Color.AliceBlue", 0)] - [InlineData("9 + 9\n Max(1, 3, 19); \n Color.AliceBlue", 0)] - [InlineData("Label2.Text", 1)] - [InlineData("NestedLabel1", 1)] - [InlineData("Label2.Text;\nNestedLabel1", 2)] - public async Task TestPublishControlTokensNotification(string expression, int expectedNumberOfControlTokens) - { - // Arrange - var checkResult = SemanticTokensRelatedTestsHelper.GetCheckResultWithControlSymbols(expression); - var scopeFactory = new TestPowerFxScopeFactory( - (string documentUri) => new EditorContextScope( - (expr) => checkResult)); - Init(new InitParams(scopeFactory: scopeFactory)); - var payload = GetFullDocumentSemanticTokensRequestPayload(new SemanticTokensParams - { - TextDocument = GetTextDocument(GetUri("&version=someVersionId")), - Text = expression - }); - - // Act - var response = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); - - // Assert - var notificationParams = AssertAndGetNotificationParams(response, CustomProtocolNames.PublishControlTokens); - Assert.Equal("someVersionId", notificationParams.Version); - var controlTokenList = notificationParams.Controls; - Assert.Equal(expectedNumberOfControlTokens, controlTokenList.Count()); - foreach (var controlToken in controlTokenList) - { - Assert.Equal(typeof(ControlToken), controlToken.GetType()); - } - } - - private static (string payload, string id) GetFullDocumentSemanticTokensRequestPayload(SemanticTokensParams semanticTokenParams, string id = null) - { - return GetRequestPayload(semanticTokenParams, TextDocumentNames.FullDocumentSemanticTokens, id); - } - - #endregion - - #region Range Document Semantic Tokens Tests - [Theory] - [InlineData("Create", TokenType.Function, TokenType.BoolLit)] - [InlineData(null)] - [InlineData("")] - [InlineData("[2, 3")] - [InlineData("Create", TokenType.StrInterpStart, TokenType.BinaryOp, TokenType.NumLit, TokenType.DecLit, TokenType.Control)] - [InlineData("[]")] - [InlineData("1,2]")] - [InlineData("[98]")] - [InlineData("Create", TokenType.Lim)] - [InlineData("Create", TokenType.BoolLit, TokenType.BinaryOp, TokenType.Function, TokenType.Lim)] - [InlineData("Create", TokenType.Lim, TokenType.BinaryOp, TokenType.BoolLit)] - [InlineData(" ")] - [InlineData("NotPresent")] - internal async Task TestCorrectRangeSemanticTokensAreReturnedWithCertainTokenTypesSkipped(string tokenTypesToSkipParam, params TokenType[] tokenTypesToSkip) - { - // Arrange & Assert - var expression = "1+1+1+1+1+1+1+1;Sqrt(1);1+-2;true;\n\"String Literal\";Sum(1,2);Max(1,2,3);$\"1 + 2 = {3}\";// This is Comment;//This is comment2;false"; - var range = SemanticTokensRelatedTestsHelper.CreateRange(1, 2, 3, 35); - var expectedTypes = new List { TokenType.DecLit, TokenType.BoolLit, TokenType.Function, TokenType.StrLit, TokenType.Delimiter, TokenType.BinaryOp }; - if (tokenTypesToSkip.Length > 0) - { - expectedTypes = expectedTypes.Where(expectedType => !tokenTypesToSkip.Contains(expectedType)).ToList(); - } - - if (tokenTypesToSkipParam == "Create") - { - tokenTypesToSkipParam = JsonSerializer.Serialize(tokenTypesToSkip.Select(tokType => (int)tokType).ToList()); - } - - var semanticTokenParams = new SemanticTokensRangeParams - { - TextDocument = GetTextDocument(GetUri(tokenTypesToSkipParam == "NotPresent" ? string.Empty : "tokenTypesToSkip=" + tokenTypesToSkipParam)), - Text = expression, - Range = range - }; - var payload = GetRangeDocumentSemanticTokensRequestPayload(semanticTokenParams); - - // Act - var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); - - // Assert - var response = AssertAndGetSemanticTokensResponse(rawResponse, payload.id); - Assert.NotEmpty(response.Data); - var decodedTokens = SemanticTokensRelatedTestsHelper.DecodeEncodedSemanticTokensPartially(response, expression); - var actualTypes = decodedTokens.Select(tok => tok.TokenType).Distinct().ToList(); - Assert.Equal(expectedTypes.OrderBy(type => type), actualTypes.OrderBy(type => type)); - } - - [Theory] - [InlineData(1, 4, 1, 20, false, false)] - [InlineData(1, 6, 1, 20, true, false)] - [InlineData(1, 1, 1, 12, false, true)] - [InlineData(1, 5, 1, 24, true, false)] - [InlineData(1, 5, 1, 15, true, true)] - [InlineData(1, 9, 1, 14, true, true)] - [InlineData(1, 1, 3, 34, false, false)] - [InlineData(1, 3, 2, 12, false, true)] - [InlineData(2, 11, 3, 3, true, true)] - [InlineData(1, 24, 3, 17, false, false)] - public async Task TestCorrectRangeSemanticTokensAreReturned(int startLine, int startLineCol, int endLine, int endLineCol, bool tokenDoesNotAlignOnLeft, bool tokenDoesNotAlignOnRight) - { - // Arrange - Init(); - var expression = "If(Len(Phone_Number) < 10,\nNotify(\"Invalid Phone\nNumber\"),Notify(\"Valid Phone No\"))"; - var eol = "\n"; - var semanticTokenParams = new SemanticTokensRangeParams - { - TextDocument = GetTextDocument(), - Text = expression, - Range = SemanticTokensRelatedTestsHelper.CreateRange(startLine, endLine, startLineCol, endLineCol) - }; - var payload = GetRangeDocumentSemanticTokensRequestPayload(semanticTokenParams); - - // Act - var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); - - // Assert - var response = AssertAndGetSemanticTokensResponse(rawResponse, payload.id); - var (startIndex, endIndex) = semanticTokenParams.Range.ConvertRangeToPositions(expression, eol); - var decodedResponse = SemanticTokensRelatedTestsHelper.DecodeEncodedSemanticTokensPartially(response, expression, eol); - - var leftMostTok = decodedResponse.Min(tok => tok.StartIndex); - var rightMostTok = decodedResponse.Max(tok => tok.EndIndex); - - Assert.All(decodedResponse, (tok) => Assert.False(tok.EndIndex <= leftMostTok || tok.StartIndex >= rightMostTok)); - if (tokenDoesNotAlignOnLeft) - { - Assert.True(leftMostTok < startIndex); - } - else - { - Assert.True(leftMostTok >= startIndex); - } - - if (tokenDoesNotAlignOnRight) - { - Assert.True(rightMostTok > endIndex); - } - else - { - Assert.True(rightMostTok <= endIndex); - } - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task TestEmptyRangeSemanticTokensResponseReturnedWhenExpressionIsInvalid(bool isNotNull) - { - // Arrange - string expression = string.Empty; - var semanticTokenParams = new SemanticTokensRangeParams - { - TextDocument = GetTextDocument(), - Text = isNotNull ? expression : null, - Range = SemanticTokensRelatedTestsHelper.CreateRange(1, 1, 1, 4) - }; - var payload = GetRangeDocumentSemanticTokensRequestPayload(semanticTokenParams); - - // Act - var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); - - // Assert - var response = AssertAndGetSemanticTokensResponse(rawResponse, payload.id); - Assert.Empty(response.Data); - } - - [Fact] - public async Task TestErrorResponseReturnedWhenUriIsNullForRangeSemanticTokensRequest() - { - // Arrange - var semanticTokenParams = new SemanticTokensRangeParams - { - TextDocument = new TextDocumentIdentifier() { Uri = null }, - Range = SemanticTokensRelatedTestsHelper.CreateRange(1, 1, 1, 4) - }; - var payload = GetRangeDocumentSemanticTokensRequestPayload(semanticTokenParams); - - // Act - var response = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); - - // Assert - AssertErrorPayload(response, payload.id, JsonRpcHelper.ErrorCode.ParseError); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task TestEmptyRangeSemanticTokensResponseReturnedWhenRangeIsNullOrInvalid(bool isNull) - { - // Arrange - var expression = "If(Len(Phone_Number) < 10,\nNotify(\"Invalid Phone\nNumber\"),Notify(\"Valid Phone No\"))"; - var semanticTokenParams = new SemanticTokensRangeParams - { - TextDocument = GetTextDocument(), - Text = expression, - Range = isNull ? null : SemanticTokensRelatedTestsHelper.CreateRange(expression.Length + 2, 2, 1, 2) - }; - var payload = GetRangeDocumentSemanticTokensRequestPayload(semanticTokenParams); - - // Act - var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); - - // Assert - var response = AssertAndGetSemanticTokensResponse(rawResponse, payload.id); - Assert.Empty(response.Data); - } - - private static (string payload, string id) GetRangeDocumentSemanticTokensRequestPayload(SemanticTokensRangeParams semanticTokenRangeParams, string id = null) - { - return GetRequestPayload(semanticTokenRangeParams, TextDocumentNames.RangeDocumentSemanticTokens, id); - } - #endregion - - private static SemanticTokensResponse AssertAndGetSemanticTokensResponse(string response, string id) - { - var tokensResponse = AssertAndGetResponsePayload(response, id); - Assert.NotNull(tokensResponse); - Assert.NotNull(tokensResponse.Data); - return tokensResponse; - } - } -} + { + #region Full Document Semantic Tokens Tests + [Fact] + public async Task TestCorrectFullSemanticTokensAreReturnedWithExpressionInUri() + { + await TestCorrectFullSemanticTokensAreReturned(new SemanticTokensParams + { + TextDocument = GetTextDocument(GetUri("expression=Max(1, 2, 3)")) + }).ConfigureAwait(false); + } + + [Fact] + public async Task TestCorrectFullSemanticTokensAreReturnedWithExpressionNotInUri() + { + await TestCorrectFullSemanticTokensAreReturned(new SemanticTokensParams + { + TextDocument = GetTextDocument(), + Text = "Max(1, 2, 3)" + }).ConfigureAwait(false); + } + + [Fact] + public async Task TestCorrectFullSemanticTokensAreReturnedWithExpressionInBothUriAndTextDocument() + { + var expression = "Max(1, 2, 3)"; + var semanticTokenParams = new SemanticTokensParams + { + TextDocument = GetTextDocument(GetUri("expression=Color.White")), + Text = expression + }; + await TestCorrectFullSemanticTokensAreReturned(semanticTokenParams).ConfigureAwait(false); + } + + private async Task TestCorrectFullSemanticTokensAreReturned(SemanticTokensParams semanticTokensParams) + { + // Arrange + Init(); + var expression = GetExpression(semanticTokensParams); + Assert.Equal("Max(1, 2, 3)", expression); + var payload = GetFullDocumentSemanticTokensRequestPayload(semanticTokensParams); + + // Act + var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); + + // Assert + var response = AssertAndGetSemanticTokensResponse(rawResponse, payload.id); + Assert.NotEmpty(response.Data); + var decodedTokens = SemanticTokensRelatedTestsHelper.DecodeEncodedSemanticTokensPartially(response, expression); + Assert.Single(decodedTokens.Where(tok => tok.TokenType == TokenType.Function)); + Assert.Equal(3, decodedTokens.Where(tok => tok.TokenType == TokenType.NumLit || tok.TokenType == TokenType.DecLit).Count()); + } + + [Theory] + [InlineData("Create", TokenType.Function, TokenType.BoolLit)] + [InlineData(null)] + [InlineData("")] + [InlineData("[2, 3")] + [InlineData("Create", TokenType.StrInterpStart, TokenType.BinaryOp, TokenType.NumLit, TokenType.DecLit, TokenType.Control)] + [InlineData("[]")] + [InlineData("1,2]")] + [InlineData("[98]")] + [InlineData("Create", TokenType.Lim)] + [InlineData("Create", TokenType.BoolLit, TokenType.BinaryOp, TokenType.Function, TokenType.Lim)] + [InlineData("Create", TokenType.Lim, TokenType.BinaryOp, TokenType.BoolLit)] + [InlineData(" ")] + [InlineData("NotPresent")] + internal async Task TestCorrectFullSemanticTokensAreReturnedWithCertainTokenTypesSkipped(string tokenTypesToSkipParam, params TokenType[] tokenTypesToSkip) + { + // Arrange + var expression = "1+-2;true;\"String Literal\";Sum(1,2);Max(1,2,3);$\"1 + 2 = {3}\";// This is Comment"; + var expectedTypes = new List { TokenType.DecLit, TokenType.BoolLit, TokenType.Comment, TokenType.Function, TokenType.StrInterpStart, TokenType.IslandEnd, TokenType.IslandStart, TokenType.StrLit, TokenType.StrInterpEnd, TokenType.Delimiter, TokenType.BinaryOp }; + if (tokenTypesToSkip.Length > 0) + { + expectedTypes = expectedTypes.Where(expectedType => !tokenTypesToSkip.Contains(expectedType)).ToList(); + } + + if (tokenTypesToSkipParam == "Create") + { + tokenTypesToSkipParam = JsonSerializer.Serialize(tokenTypesToSkip.Select(tokType => (int)tokType).ToList()); + } + + var semanticTokenParams = new SemanticTokensParams + { + TextDocument = GetTextDocument(GetUri(tokenTypesToSkipParam == "NotPresent" ? string.Empty : "tokenTypesToSkip=" + tokenTypesToSkipParam)), + Text = expression + }; + var payload = GetFullDocumentSemanticTokensRequestPayload(semanticTokenParams); + + // Act + var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); + + // Assert + var response = AssertAndGetSemanticTokensResponse(rawResponse, payload.id); + Assert.NotEmpty(response.Data); + var decodedTokens = SemanticTokensRelatedTestsHelper.DecodeEncodedSemanticTokensPartially(response, expression); + var actualTypes = decodedTokens.Select(tok => tok.TokenType).Distinct().ToList(); + Assert.Equal(expectedTypes.OrderBy(type => type), actualTypes.OrderBy(type => type)); + } + + [Fact] + public async Task TestErrorResponseReturnedWhenUriIsNullForFullSemanticTokensRequest() + { + // Arrange + var semanticTokenParams = new SemanticTokensParams + { + TextDocument = new TextDocumentIdentifier() { Uri = null } + }; + var payload = GetFullDocumentSemanticTokensRequestPayload(semanticTokenParams); + + // Act + var response = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); + + // Assert + AssertErrorPayload(response, payload.id, JsonRpcHelper.ErrorCode.ParseError); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task TestEmptyFullSemanticTokensResponseReturnedWhenExpressionIsInvalid(bool isNotNull) + { + // Arrange + string expression = string.Empty; + var semanticTokenParams = new SemanticTokensParams + { + TextDocument = GetTextDocument(), + Text = isNotNull ? expression : null + }; + var payload = GetFullDocumentSemanticTokensRequestPayload(semanticTokenParams); + + // Act + var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); + + // Assert + var response = AssertAndGetSemanticTokensResponse(rawResponse, payload.id); + Assert.Empty(response.Data); + } + + [Fact] + public async Task TestFullSemanticTokensResponseReturnedWithDefaultEOL() + { + // Arrange + string expression = "Sum(\n1,1\n)"; + var semanticTokenParams = new SemanticTokensParams + { + TextDocument = GetTextDocument(), + Text = expression + }; + var payload = GetFullDocumentSemanticTokensRequestPayload(semanticTokenParams); + + // Act + var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); + + // Assert + var response = AssertAndGetSemanticTokensResponse(rawResponse, payload.id); + Assert.NotEmpty(response.Data); + Assert.Equal(expression.Where(c => c == '\n').Count(), SemanticTokensRelatedTestsHelper.DetermineNumberOfLinesThatTokensAreSpreadAcross(response)); + } + + [Theory] + [InlineData("9 + 9", 0)] + [InlineData("Max(10, 20, 30)", 0)] + [InlineData("Color.AliceBlue", 0)] + [InlineData("9 + 9\n Max(1, 3, 19); \n Color.AliceBlue", 0)] + [InlineData("Label2.Text", 1)] + [InlineData("NestedLabel1", 1)] + [InlineData("Label2.Text;\nNestedLabel1", 2)] + public async Task TestPublishControlTokensNotification(string expression, int expectedNumberOfControlTokens) + { + // Arrange + var checkResult = SemanticTokensRelatedTestsHelper.GetCheckResultWithControlSymbols(expression); + var scopeFactory = new TestPowerFxScopeFactory( + (string documentUri) => new EditorContextScope( + (expr) => checkResult)); + Init(new InitParams(scopeFactory: scopeFactory)); + var payload = GetFullDocumentSemanticTokensRequestPayload(new SemanticTokensParams + { + TextDocument = GetTextDocument(GetUri("&version=someVersionId")), + Text = expression + }); + + // Act + var response = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); + + // Assert + var notificationParams = AssertAndGetNotificationParams(response, CustomProtocolNames.PublishControlTokens); + Assert.Equal("someVersionId", notificationParams.Version); + var controlTokenList = notificationParams.Controls; + Assert.Equal(expectedNumberOfControlTokens, controlTokenList.Count()); + foreach (var controlToken in controlTokenList) + { + Assert.Equal(typeof(ControlToken), controlToken.GetType()); + } + } + + private static (string payload, string id) GetFullDocumentSemanticTokensRequestPayload(SemanticTokensParams semanticTokenParams, string id = null) + { + return GetRequestPayload(semanticTokenParams, TextDocumentNames.FullDocumentSemanticTokens, id); + } + + #endregion + + #region Range Document Semantic Tokens Tests + [Theory] + [InlineData("Create", TokenType.Function, TokenType.BoolLit)] + [InlineData(null)] + [InlineData("")] + [InlineData("[2, 3")] + [InlineData("Create", TokenType.StrInterpStart, TokenType.BinaryOp, TokenType.NumLit, TokenType.DecLit, TokenType.Control)] + [InlineData("[]")] + [InlineData("1,2]")] + [InlineData("[98]")] + [InlineData("Create", TokenType.Lim)] + [InlineData("Create", TokenType.BoolLit, TokenType.BinaryOp, TokenType.Function, TokenType.Lim)] + [InlineData("Create", TokenType.Lim, TokenType.BinaryOp, TokenType.BoolLit)] + [InlineData(" ")] + [InlineData("NotPresent")] + internal async Task TestCorrectRangeSemanticTokensAreReturnedWithCertainTokenTypesSkipped(string tokenTypesToSkipParam, params TokenType[] tokenTypesToSkip) + { + // Arrange & Assert + var expression = "1+1+1+1+1+1+1+1;Sqrt(1);1+-2;true;\n\"String Literal\";Sum(1,2);Max(1,2,3);$\"1 + 2 = {3}\";// This is Comment;//This is comment2;false"; + var range = SemanticTokensRelatedTestsHelper.CreateRange(1, 2, 3, 35); + var expectedTypes = new List { TokenType.DecLit, TokenType.BoolLit, TokenType.Function, TokenType.StrLit, TokenType.Delimiter, TokenType.BinaryOp }; + if (tokenTypesToSkip.Length > 0) + { + expectedTypes = expectedTypes.Where(expectedType => !tokenTypesToSkip.Contains(expectedType)).ToList(); + } + + if (tokenTypesToSkipParam == "Create") + { + tokenTypesToSkipParam = JsonSerializer.Serialize(tokenTypesToSkip.Select(tokType => (int)tokType).ToList()); + } + + var semanticTokenParams = new SemanticTokensRangeParams + { + TextDocument = GetTextDocument(GetUri(tokenTypesToSkipParam == "NotPresent" ? string.Empty : "tokenTypesToSkip=" + tokenTypesToSkipParam)), + Text = expression, + Range = range + }; + var payload = GetRangeDocumentSemanticTokensRequestPayload(semanticTokenParams); + + // Act + var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); + + // Assert + var response = AssertAndGetSemanticTokensResponse(rawResponse, payload.id); + Assert.NotEmpty(response.Data); + var decodedTokens = SemanticTokensRelatedTestsHelper.DecodeEncodedSemanticTokensPartially(response, expression); + var actualTypes = decodedTokens.Select(tok => tok.TokenType).Distinct().ToList(); + Assert.Equal(expectedTypes.OrderBy(type => type), actualTypes.OrderBy(type => type)); + } + + [Theory] + [InlineData(1, 4, 1, 20, false, false)] + [InlineData(1, 6, 1, 20, true, false)] + [InlineData(1, 1, 1, 12, false, true)] + [InlineData(1, 5, 1, 24, true, false)] + [InlineData(1, 5, 1, 15, true, true)] + [InlineData(1, 9, 1, 14, true, true)] + [InlineData(1, 1, 3, 34, false, false)] + [InlineData(1, 3, 2, 12, false, true)] + [InlineData(2, 11, 3, 3, true, true)] + [InlineData(1, 24, 3, 17, false, false)] + public async Task TestCorrectRangeSemanticTokensAreReturned(int startLine, int startLineCol, int endLine, int endLineCol, bool tokenDoesNotAlignOnLeft, bool tokenDoesNotAlignOnRight) + { + // Arrange + Init(); + var expression = "If(Len(Phone_Number) < 10,\nNotify(\"Invalid Phone\nNumber\"),Notify(\"Valid Phone No\"))"; + var eol = "\n"; + var semanticTokenParams = new SemanticTokensRangeParams + { + TextDocument = GetTextDocument(), + Text = expression, + Range = SemanticTokensRelatedTestsHelper.CreateRange(startLine, endLine, startLineCol, endLineCol) + }; + var payload = GetRangeDocumentSemanticTokensRequestPayload(semanticTokenParams); + + // Act + var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); + + // Assert + var response = AssertAndGetSemanticTokensResponse(rawResponse, payload.id); + var (startIndex, endIndex) = semanticTokenParams.Range.ConvertRangeToPositions(expression, eol); + var decodedResponse = SemanticTokensRelatedTestsHelper.DecodeEncodedSemanticTokensPartially(response, expression, eol); + + var leftMostTok = decodedResponse.Min(tok => tok.StartIndex); + var rightMostTok = decodedResponse.Max(tok => tok.EndIndex); + + Assert.All(decodedResponse, (tok) => Assert.False(tok.EndIndex <= leftMostTok || tok.StartIndex >= rightMostTok)); + if (tokenDoesNotAlignOnLeft) + { + Assert.True(leftMostTok < startIndex); + } + else + { + Assert.True(leftMostTok >= startIndex); + } + + if (tokenDoesNotAlignOnRight) + { + Assert.True(rightMostTok > endIndex); + } + else + { + Assert.True(rightMostTok <= endIndex); + } + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task TestEmptyRangeSemanticTokensResponseReturnedWhenExpressionIsInvalid(bool isNotNull) + { + // Arrange + string expression = string.Empty; + var semanticTokenParams = new SemanticTokensRangeParams + { + TextDocument = GetTextDocument(), + Text = isNotNull ? expression : null, + Range = SemanticTokensRelatedTestsHelper.CreateRange(1, 1, 1, 4) + }; + var payload = GetRangeDocumentSemanticTokensRequestPayload(semanticTokenParams); + + // Act + var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); + + // Assert + var response = AssertAndGetSemanticTokensResponse(rawResponse, payload.id); + Assert.Empty(response.Data); + } + + [Fact] + public async Task TestErrorResponseReturnedWhenUriIsNullForRangeSemanticTokensRequest() + { + // Arrange + var semanticTokenParams = new SemanticTokensRangeParams + { + TextDocument = new TextDocumentIdentifier() { Uri = null }, + Range = SemanticTokensRelatedTestsHelper.CreateRange(1, 1, 1, 4) + }; + var payload = GetRangeDocumentSemanticTokensRequestPayload(semanticTokenParams); + + // Act + var response = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); + + // Assert + AssertErrorPayload(response, payload.id, JsonRpcHelper.ErrorCode.ParseError); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task TestEmptyRangeSemanticTokensResponseReturnedWhenRangeIsNullOrInvalid(bool isNull) + { + // Arrange + var expression = "If(Len(Phone_Number) < 10,\nNotify(\"Invalid Phone\nNumber\"),Notify(\"Valid Phone No\"))"; + var semanticTokenParams = new SemanticTokensRangeParams + { + TextDocument = GetTextDocument(), + Text = expression, + Range = isNull ? null : SemanticTokensRelatedTestsHelper.CreateRange(expression.Length + 2, 2, 1, 2) + }; + var payload = GetRangeDocumentSemanticTokensRequestPayload(semanticTokenParams); + + // Act + var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); + + // Assert + var response = AssertAndGetSemanticTokensResponse(rawResponse, payload.id); + Assert.Empty(response.Data); + } + + private static (string payload, string id) GetRangeDocumentSemanticTokensRequestPayload(SemanticTokensRangeParams semanticTokenRangeParams, string id = null) + { + return GetRequestPayload(semanticTokenRangeParams, TextDocumentNames.RangeDocumentSemanticTokens, id); + } + #endregion + + private static SemanticTokensResponse AssertAndGetSemanticTokensResponse(string response, string id) + { + var tokensResponse = AssertAndGetResponsePayload(response, id); + Assert.NotNull(tokensResponse); + Assert.NotNull(tokensResponse.Data); + return tokensResponse; + } + } +} diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/SignatureHelpTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/SignatureHelpTests.cs similarity index 98% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/SignatureHelpTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/SignatureHelpTests.cs index 306d12b46..b4046d8ee 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/SignatureHelpTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/SignatureHelpTests.cs @@ -1,145 +1,145 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -using System.Linq; -using System.Text.Json; -using System.Threading.Tasks; -using Microsoft.PowerFx.LanguageServerProtocol; -using Microsoft.PowerFx.LanguageServerProtocol.Protocol; -using Xunit; - -namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol -{ - public partial class LanguageServerTestBase - { - // Ensure the disclaimer shows up in a signature. - [Fact] - public async Task TestSignatureDisclaimers() - { - // Arrange - var text = "AISummarize("; - var signatureHelpParams1 = new SignatureHelpParams - { - TextDocument = GetTextDocument(GetUri("expression=" + text)), - Text = text, - Position = GetPosition(text.Length), - Context = GetSignatureHelpContext("(") - }; - - Init(new InitParams(options: GetParserOptions(false))); - - // test good formula - var payload = GetSignatureHelpPayload(signatureHelpParams1); - var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); - var response = AssertAndGetResponsePayload(rawResponse, payload.id); - var sig = response.Signatures.Single(); - Assert.Equal("AISummarize()", sig.Label); - - var je = (JsonElement)sig.Documentation; - var markdown = JsonSerializer.Deserialize(je.ToString(), LanguageServerHelper.DefaultJsonSerializerOptions); - Assert.Equal("markdown", markdown.Kind); - Assert.StartsWith("Create and set a global variable", markdown.Value); // function's normal description - Assert.Contains("**Disclaimer:** AI-generated content", markdown.Value); // disclaimer appended. - } - - [Theory] - [InlineData("Power(", 6, "Power(2,", 8, false)] - [InlineData("Behavior(); Power(", 18, "Behavior(); Power(2,", 20, true)] - - // This tests generates an internal error as we use an behavior function but we have no way to check its presence - [InlineData("Behavior(); Power(", 18, "Behavior(); Power(2,", 20, false)] - public async Task TestSignatureHelpWithExpressionInUriAndNotInUri(string text, int offset, string text2, int offset2, bool withAllowSideEffects) - { - foreach (var addExprToUri in new bool[] { true, false }) - { - var signatureHelpParams1 = new SignatureHelpParams - { - TextDocument = addExprToUri ? GetTextDocument(GetUri("expression=" + text)) : GetTextDocument(), - Text = addExprToUri ? null : text, - Position = GetPosition(offset), - Context = GetSignatureHelpContext("(") - }; - var signatureHelpParams2 = new SignatureHelpParams - { - TextDocument = addExprToUri ? GetTextDocument(GetUri("expression=" + text2)) : GetTextDocument(), - Text = addExprToUri ? null : text2, - Position = GetPosition(offset2), - Context = GetSignatureHelpContext(",") - }; - await TestSignatureHelpCore(signatureHelpParams1, signatureHelpParams2, withAllowSideEffects).ConfigureAwait(false); - } - } - - [Fact] - public async Task TestSignatureHelpWithExpressionInBothUriAndTextDocument() - { - var signatureHelpParams1 = new SignatureHelpParams - { - TextDocument = GetTextDocument(GetUri("expression=Max(")), - Text = "Power(", - Position = GetPosition(6), - Context = GetSignatureHelpContext("(") - }; - var signatureHelpParams2 = new SignatureHelpParams - { - TextDocument = GetTextDocument(GetUri("expression=Max(")), - Text = "Behavior(); Power(2,", - Position = GetPosition(20), - Context = GetSignatureHelpContext(",") - }; - await TestSignatureHelpCore(signatureHelpParams1, signatureHelpParams2, true).ConfigureAwait(false); - } - - private async Task TestSignatureHelpCore(SignatureHelpParams signatureHelpParams1, SignatureHelpParams signatureHelpParams2, bool withAllowSideEffects) - { - Init(new InitParams(options: GetParserOptions(withAllowSideEffects))); - - // test good formula - var payload = GetSignatureHelpPayload(signatureHelpParams1); - var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); - var response = AssertAndGetResponsePayload(rawResponse, payload.id); - Assert.Equal(0U, response.ActiveSignature); - Assert.Equal(0U, response.ActiveParameter); - var foundItems = response.Signatures.Where(item => item.Label.StartsWith("Power")); - Assert.True(Enumerable.Count(foundItems) >= 1, "Power should be found from signatures result"); - Assert.Equal(2, foundItems.First().Parameters.Length); - Assert.Equal("base", foundItems.First().Parameters[0].Label); - Assert.Equal("exponent", foundItems.First().Parameters[1].Label); - - payload = GetSignatureHelpPayload(signatureHelpParams2); - rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); - response = AssertAndGetResponsePayload(rawResponse, payload.id); - Assert.Equal(0U, response.ActiveSignature); - Assert.Equal(1U, response.ActiveParameter); - foundItems = response.Signatures.Where(item => item.Label.StartsWith("Power")); - Assert.True(Enumerable.Count(foundItems) >= 1, "Power should be found from signatures result"); - Assert.Equal(2, foundItems.First().Parameters.Length); - Assert.Equal("base", foundItems.First().Parameters[0].Label); - Assert.Equal("exponent", foundItems.First().Parameters[1].Label); - - // missing 'expression' in documentUri - payload = GetSignatureHelpPayload(new SignatureHelpParams() - { - Context = GetSignatureHelpContext("("), - TextDocument = GetTextDocument("powerfx://test"), - Position = GetPosition(0), - }); - var errorResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); - AssertErrorPayload(errorResponse, payload.id, JsonRpcHelper.ErrorCode.InvalidParams); - } - - private static (string payload, string id) GetSignatureHelpPayload(SignatureHelpParams signatureHelpParams) - { - return GetRequestPayload(signatureHelpParams, TextDocumentNames.SignatureHelp); - } - - private static SignatureHelpContext GetSignatureHelpContext(string triggerChar) - { - return new SignatureHelpContext - { - TriggerKind = SignatureHelpTriggerKind.TriggerCharacter, - TriggerCharacter = triggerChar - }; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.PowerFx.LanguageServerProtocol; +using Microsoft.PowerFx.LanguageServerProtocol.Protocol; +using Xunit; + +namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol +{ + public partial class LanguageServerTestBase + { + // Ensure the disclaimer shows up in a signature. + [Fact] + public async Task TestSignatureDisclaimers() + { + // Arrange + var text = "AISummarize("; + var signatureHelpParams1 = new SignatureHelpParams + { + TextDocument = GetTextDocument(GetUri("expression=" + text)), + Text = text, + Position = GetPosition(text.Length), + Context = GetSignatureHelpContext("(") + }; + + Init(new InitParams(options: GetParserOptions(false))); + + // test good formula + var payload = GetSignatureHelpPayload(signatureHelpParams1); + var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); + var response = AssertAndGetResponsePayload(rawResponse, payload.id); + var sig = response.Signatures.Single(); + Assert.Equal("AISummarize()", sig.Label); + + var je = (JsonElement)sig.Documentation; + var markdown = JsonSerializer.Deserialize(je.ToString(), LanguageServerHelper.DefaultJsonSerializerOptions); + Assert.Equal("markdown", markdown.Kind); + Assert.StartsWith("Create and set a global variable", markdown.Value); // function's normal description + Assert.Contains("**Disclaimer:** AI-generated content", markdown.Value); // disclaimer appended. + } + + [Theory] + [InlineData("Power(", 6, "Power(2,", 8, false)] + [InlineData("Behavior(); Power(", 18, "Behavior(); Power(2,", 20, true)] + + // This tests generates an internal error as we use an behavior function but we have no way to check its presence + [InlineData("Behavior(); Power(", 18, "Behavior(); Power(2,", 20, false)] + public async Task TestSignatureHelpWithExpressionInUriAndNotInUri(string text, int offset, string text2, int offset2, bool withAllowSideEffects) + { + foreach (var addExprToUri in new bool[] { true, false }) + { + var signatureHelpParams1 = new SignatureHelpParams + { + TextDocument = addExprToUri ? GetTextDocument(GetUri("expression=" + text)) : GetTextDocument(), + Text = addExprToUri ? null : text, + Position = GetPosition(offset), + Context = GetSignatureHelpContext("(") + }; + var signatureHelpParams2 = new SignatureHelpParams + { + TextDocument = addExprToUri ? GetTextDocument(GetUri("expression=" + text2)) : GetTextDocument(), + Text = addExprToUri ? null : text2, + Position = GetPosition(offset2), + Context = GetSignatureHelpContext(",") + }; + await TestSignatureHelpCore(signatureHelpParams1, signatureHelpParams2, withAllowSideEffects).ConfigureAwait(false); + } + } + + [Fact] + public async Task TestSignatureHelpWithExpressionInBothUriAndTextDocument() + { + var signatureHelpParams1 = new SignatureHelpParams + { + TextDocument = GetTextDocument(GetUri("expression=Max(")), + Text = "Power(", + Position = GetPosition(6), + Context = GetSignatureHelpContext("(") + }; + var signatureHelpParams2 = new SignatureHelpParams + { + TextDocument = GetTextDocument(GetUri("expression=Max(")), + Text = "Behavior(); Power(2,", + Position = GetPosition(20), + Context = GetSignatureHelpContext(",") + }; + await TestSignatureHelpCore(signatureHelpParams1, signatureHelpParams2, true).ConfigureAwait(false); + } + + private async Task TestSignatureHelpCore(SignatureHelpParams signatureHelpParams1, SignatureHelpParams signatureHelpParams2, bool withAllowSideEffects) + { + Init(new InitParams(options: GetParserOptions(withAllowSideEffects))); + + // test good formula + var payload = GetSignatureHelpPayload(signatureHelpParams1); + var rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); + var response = AssertAndGetResponsePayload(rawResponse, payload.id); + Assert.Equal(0U, response.ActiveSignature); + Assert.Equal(0U, response.ActiveParameter); + var foundItems = response.Signatures.Where(item => item.Label.StartsWith("Power")); + Assert.True(Enumerable.Count(foundItems) >= 1, "Power should be found from signatures result"); + Assert.Equal(2, foundItems.First().Parameters.Length); + Assert.Equal("base", foundItems.First().Parameters[0].Label); + Assert.Equal("exponent", foundItems.First().Parameters[1].Label); + + payload = GetSignatureHelpPayload(signatureHelpParams2); + rawResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); + response = AssertAndGetResponsePayload(rawResponse, payload.id); + Assert.Equal(0U, response.ActiveSignature); + Assert.Equal(1U, response.ActiveParameter); + foundItems = response.Signatures.Where(item => item.Label.StartsWith("Power")); + Assert.True(Enumerable.Count(foundItems) >= 1, "Power should be found from signatures result"); + Assert.Equal(2, foundItems.First().Parameters.Length); + Assert.Equal("base", foundItems.First().Parameters[0].Label); + Assert.Equal("exponent", foundItems.First().Parameters[1].Label); + + // missing 'expression' in documentUri + payload = GetSignatureHelpPayload(new SignatureHelpParams() + { + Context = GetSignatureHelpContext("("), + TextDocument = GetTextDocument("powerfx://test"), + Position = GetPosition(0), + }); + var errorResponse = await TestServer.OnDataReceivedAsync(payload.payload).ConfigureAwait(false); + AssertErrorPayload(errorResponse, payload.id, JsonRpcHelper.ErrorCode.InvalidParams); + } + + private static (string payload, string id) GetSignatureHelpPayload(SignatureHelpParams signatureHelpParams) + { + return GetRequestPayload(signatureHelpParams, TextDocumentNames.SignatureHelp); + } + + private static SignatureHelpContext GetSignatureHelpContext(string triggerChar) + { + return new SignatureHelpContext + { + TriggerKind = SignatureHelpTriggerKind.TriggerCharacter, + TriggerCharacter = triggerChar + }; + } + } +} diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/TestHandlerFactory.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/TestHandlerFactory.cs similarity index 97% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/TestHandlerFactory.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/TestHandlerFactory.cs index 523cc0445..e77b7f174 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/TestHandlerFactory.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/TestHandlerFactory.cs @@ -1,58 +1,58 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -using System.Collections.Generic; -using Microsoft.PowerFx.LanguageServerProtocol.Handlers; -using Microsoft.PowerFx.LanguageServerProtocol.Protocol; - -namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol -{ - internal class TestHandlerFactory : ILanguageServerOperationHandlerFactory - { - private readonly Dictionary _handlers = new (); - - public TestHandlerFactory SetHandler(string method, ILanguageServerOperationHandler handler) - { - _handlers[method] = handler; - return this; - } - - public ILanguageServerOperationHandler GetHandler(string method, HandlerCreationContext creationContext) - { - if (_handlers.TryGetValue(method, out var handler)) - { - return handler; - } - - switch (method) - { - case CustomProtocolNames.NL2FX: - return new Nl2FxLanguageServerOperationHandler(null); - case CustomProtocolNames.FX2NL: - return new Fx2NlLanguageServerOperationHandler(null); - case CustomProtocolNames.GetCapabilities: - return new GetCustomCapabilitiesLanguageServerOperationHandler(null); - case TextDocumentNames.Completion: - return new CompletionsLanguageServerOperationHandler(); - case TextDocumentNames.SignatureHelp: - return new SignatureHelpLanguageServerOperationHandler(); - case TextDocumentNames.RangeDocumentSemanticTokens: - return new RangeSemanticTokensLanguageServerOperationHandler(); - case TextDocumentNames.FullDocumentSemanticTokens: - return new BaseSemanticTokensLanguageServerOperationHandler(); - case TextDocumentNames.CodeAction: - return new CodeActionsLanguageServerOperationHandler(creationContext.onLogUnhandledExceptionHandler); - case TextDocumentNames.DidChange: - return new OnDidChangeLanguageServerNotificationHandler(null); - case TextDocumentNames.DidOpen: - return new OnDidOpenLanguageServerNotificationHandler(); - case CustomProtocolNames.CommandExecuted: - return new CommandExecutedLanguageServerOperationHandler(); - case CustomProtocolNames.InitialFixup: - return new InitialFixupLanguageServerOperationHandler(); - default: - return null; - } - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System.Collections.Generic; +using Microsoft.PowerFx.LanguageServerProtocol.Handlers; +using Microsoft.PowerFx.LanguageServerProtocol.Protocol; + +namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol +{ + internal class TestHandlerFactory : ILanguageServerOperationHandlerFactory + { + private readonly Dictionary _handlers = new (); + + public TestHandlerFactory SetHandler(string method, ILanguageServerOperationHandler handler) + { + _handlers[method] = handler; + return this; + } + + public ILanguageServerOperationHandler GetHandler(string method, HandlerCreationContext creationContext) + { + if (_handlers.TryGetValue(method, out var handler)) + { + return handler; + } + + switch (method) + { + case CustomProtocolNames.NL2FX: + return new Nl2FxLanguageServerOperationHandler(null); + case CustomProtocolNames.FX2NL: + return new Fx2NlLanguageServerOperationHandler(null); + case CustomProtocolNames.GetCapabilities: + return new GetCustomCapabilitiesLanguageServerOperationHandler(null); + case TextDocumentNames.Completion: + return new CompletionsLanguageServerOperationHandler(); + case TextDocumentNames.SignatureHelp: + return new SignatureHelpLanguageServerOperationHandler(); + case TextDocumentNames.RangeDocumentSemanticTokens: + return new RangeSemanticTokensLanguageServerOperationHandler(); + case TextDocumentNames.FullDocumentSemanticTokens: + return new BaseSemanticTokensLanguageServerOperationHandler(); + case TextDocumentNames.CodeAction: + return new CodeActionsLanguageServerOperationHandler(creationContext.onLogUnhandledExceptionHandler); + case TextDocumentNames.DidChange: + return new OnDidChangeLanguageServerNotificationHandler(null); + case TextDocumentNames.DidOpen: + return new OnDidOpenLanguageServerNotificationHandler(); + case CustomProtocolNames.CommandExecuted: + return new CommandExecutedLanguageServerOperationHandler(); + case CustomProtocolNames.InitialFixup: + return new InitialFixupLanguageServerOperationHandler(); + default: + return null; + } + } + } +} diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/TestHostTaskExecutor.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/TestHostTaskExecutor.cs similarity index 97% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/TestHostTaskExecutor.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/TestHostTaskExecutor.cs index fa978bcaf..13e78493c 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/TestHostTaskExecutor.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/TestHostTaskExecutor.cs @@ -1,21 +1,21 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -using System; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.PowerFx.LanguageServerProtocol; -using Microsoft.PowerFx.LanguageServerProtocol.Handlers; - -namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol -{ - public class TestHostTaskExecutor : IHostTaskExecutor - { - public async Task ExecuteTaskAsync(Func> task, LanguageServerOperationContext operationContext, CancellationToken cancellationToken, TOutput defaultOutput = default) - { - // Simulate a delay in the task execution - await Task.Delay(30, cancellationToken).ConfigureAwait(false); - return await task().ConfigureAwait(false); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.PowerFx.LanguageServerProtocol; +using Microsoft.PowerFx.LanguageServerProtocol.Handlers; + +namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol +{ + public class TestHostTaskExecutor : IHostTaskExecutor + { + public async Task ExecuteTaskAsync(Func> task, LanguageServerOperationContext operationContext, CancellationToken cancellationToken, TOutput defaultOutput = default) + { + // Simulate a delay in the task execution + await Task.Delay(30, cancellationToken).ConfigureAwait(false); + return await task().ConfigureAwait(false); + } + } +} diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/TestLogger.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/TestLogger.cs similarity index 96% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/TestLogger.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/TestLogger.cs index bb18f5cdf..a8e6b887d 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/RedesignedLanguageServerTests/TestLogger.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/RedesignedLanguageServerTests/TestLogger.cs @@ -1,36 +1,36 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -using System; -using System.Collections.Generic; -using Microsoft.PowerFx.LanguageServerProtocol; - -namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol -{ - public class TestLogger : ILanguageServerLogger - { - private readonly List _messages = new (); - - public List Messages => _messages; - - public void LogError(string message, object data = null) - { - _messages.Add(message); - } - - public void LogException(Exception exception, object data = null) - { - _messages.Add(exception.Message); - } - - public void LogInformation(string message, object data = null) - { - _messages.Add(message); - } - - public void LogWarning(string message, object data = null) - { - _messages.Add(message); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using Microsoft.PowerFx.LanguageServerProtocol; + +namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol +{ + public class TestLogger : ILanguageServerLogger + { + private readonly List _messages = new (); + + public List Messages => _messages; + + public void LogError(string message, object data = null) + { + _messages.Add(message); + } + + public void LogException(Exception exception, object data = null) + { + _messages.Add(exception.Message); + } + + public void LogInformation(string message, object data = null) + { + _messages.Add(message); + } + + public void LogWarning(string message, object data = null) + { + _messages.Add(message); + } + } +} diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/SemanticTokensEncoderTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/SemanticTokensEncoderTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/SemanticTokensEncoderTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/SemanticTokensEncoderTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/SemanticTokensRelatedTestsHelper.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/SemanticTokensRelatedTestsHelper.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/SemanticTokensRelatedTestsHelper.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/SemanticTokensRelatedTestsHelper.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/TestLanguageServer.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/TestLanguageServer.cs similarity index 99% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/TestLanguageServer.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/TestLanguageServer.cs index 031f63cf9..0e77f71b0 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/TestLanguageServer.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/TestLanguageServer.cs @@ -12,29 +12,29 @@ namespace Microsoft.PowerFx.Tests.LanguageServiceProtocol { public class TestLanguageServer : LanguageServer { - public TestLanguageServer(ITestOutputHelper output, SendToClient sendToClient, IPowerFxScopeFactory scopeFactory, INLHandlerFactory nlHandlerFactory = null) -#pragma warning disable CS0618 // Type or member is obsolete - : base(sendToClient, scopeFactory, (string s) => output.WriteLine(s)) -#pragma warning restore CS0618 // Type or member is obsolete + public TestLanguageServer(ITestOutputHelper output, SendToClient sendToClient, IPowerFxScopeFactory scopeFactory, INLHandlerFactory nlHandlerFactory = null) +#pragma warning disable CS0618 // Type or member is obsolete + : base(sendToClient, scopeFactory, (string s) => output.WriteLine(s)) +#pragma warning restore CS0618 // Type or member is obsolete { - NLHandlerFactory = nlHandlerFactory; + NLHandlerFactory = nlHandlerFactory; } public int TestGetCharPosition(string expression, int position) => PositionRangeHelper.GetCharPosition(expression, position); - public int TestGetPosition(string expression, int line, int character) => PositionRangeHelper.GetPosition(expression, line, character); - - // Language Server class (parent of this) marks OnDataReceived Obsolete - // This caused a lot of compiler warnings in LanguageServerTests.cs - // since all of them use this OnDataReceived - // To avoid many supress, hide the base OnDataRecieved - // With this one below which in turn calls OnDataRecieved from base - // And suppresses the call at one place only - public new void OnDataReceived(string jsonRpcPayload) - { -#pragma warning disable CS0618 // Type or member is obsolete - base.OnDataReceived(jsonRpcPayload); -#pragma warning restore CS0618 // Type or member is obsolete + public int TestGetPosition(string expression, int line, int character) => PositionRangeHelper.GetPosition(expression, line, character); + + // Language Server class (parent of this) marks OnDataReceived Obsolete + // This caused a lot of compiler warnings in LanguageServerTests.cs + // since all of them use this OnDataReceived + // To avoid many supress, hide the base OnDataRecieved + // With this one below which in turn calls OnDataRecieved from base + // And suppresses the call at one place only + public new void OnDataReceived(string jsonRpcPayload) + { +#pragma warning disable CS0618 // Type or member is obsolete + base.OnDataReceived(jsonRpcPayload); +#pragma warning restore CS0618 // Type or member is obsolete } } } diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/TestPowerFxScopeFactory.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/TestPowerFxScopeFactory.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageServiceProtocol/TestPowerFxScopeFactory.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageServiceProtocol/TestPowerFxScopeFactory.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageTest.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageTest.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/LanguageTest.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/LanguageTest.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/MarshalTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MarshalTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/MarshalTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MarshalTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Microsoft.PowerFx.Interpreter.Tests.Shared.projitems b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Microsoft.PowerFx.Interpreter.Tests.Shared.projitems new file mode 100644 index 000000000..164145e0f --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Microsoft.PowerFx.Interpreter.Tests.Shared.projitems @@ -0,0 +1,37 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 0daa552e-5906-42c3-846f-2646ee2c33d3 + + + Microsoft.PowerFx.Interpreter.Tests.Shared + + + + + + + + + + + + + PreserveNewest + + + IntellisenseTests\TestSignatures\%(FileName)%(Extension) + PreserveNewest + + + + Always + + + + Always + + + \ No newline at end of file diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Microsoft.PowerFx.Interpreter.Tests.Shared.shproj b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Microsoft.PowerFx.Interpreter.Tests.Shared.shproj new file mode 100644 index 000000000..1775e8cfe --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Microsoft.PowerFx.Interpreter.Tests.Shared.shproj @@ -0,0 +1,13 @@ + + + + 0daa552e-5906-42c3-846f-2646ee2c33d3 + 14.0 + + + + + + + + diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationFunctionsTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationFunctionsTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationFunctionsTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationFunctionsTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/AndOr_V1Compat.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/AndOr_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/AndOr_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/AndOr_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/Assert.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/Assert.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/Assert.txt rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/Assert.txt diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/ClearCollect.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/ClearCollect.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/ClearCollect.txt rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/ClearCollect.txt diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/Clear_V1Compat.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/Clear_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/Clear_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/Clear_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/Clear_V1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/Clear_V1CompatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/Clear_V1CompatDisabled.txt rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/Clear_V1CompatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/Coalesce_CoalesceShortCircuit.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/Coalesce_CoalesceShortCircuit.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/Coalesce_CoalesceShortCircuit.txt rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/Coalesce_CoalesceShortCircuit.txt diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/Collect.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/Collect.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/Collect.txt rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/Collect.txt diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/DeepMutation_V1Compat.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/DeepMutation_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/DeepMutation_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/DeepMutation_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/FilterFunctions.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/FilterFunctions.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/FilterFunctions.txt rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/FilterFunctions.txt diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/ForAllMutate.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/ForAllMutate.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/ForAllMutate.txt rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/ForAllMutate.txt diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/If.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/If.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/If.txt rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/If.txt diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/IfError.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/IfError.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/IfError.txt rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/IfError.txt diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/Patch_V1Compat.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/Patch_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/Patch_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/Patch_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/Remove_V1Compat.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/Remove_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/Remove_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/Remove_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/Remove_V1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/Remove_V1CompatDisabled.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/Remove_V1CompatDisabled.txt rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/Remove_V1CompatDisabled.txt diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/Set_V1Compat.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/Set_V1Compat.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/Set_V1Compat.txt rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/Set_V1Compat.txt diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/Simple1.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/Simple1.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/Simple1.txt rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/Simple1.txt diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/Switch.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/Switch.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/Switch.txt rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/Switch.txt diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/User.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/User.txt similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/MutationScripts/User.txt rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/User.txt diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/OptionSetTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/OptionSetTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/OptionSetTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/OptionSetTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/PADIntegrationTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/PADIntegrationTests.cs similarity index 99% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/PADIntegrationTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/PADIntegrationTests.cs index c86c1114c..705ca728b 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/PADIntegrationTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/PADIntegrationTests.cs @@ -62,10 +62,10 @@ namespace Microsoft.PowerFx.Tests var robinTable = cache.Marshal(table); - engine._symbolValues.Add("robintable", robinTable); - - // with number is float - + engine._symbolValues.Add("robintable", robinTable); + + // with number is float + var opts = new ParserOptions() { NumberIsFloat = true }; var result1 = engine.Eval("Value(Index(robintable, 1).Column1)", options: opts); // 101 @@ -75,10 +75,10 @@ namespace Microsoft.PowerFx.Tests Assert.Equal("str202", result2.ToObject()); var result3 = engine.Eval("Sum(robintable, Value(ThisRecord.Column1))", options: opts); - Assert.Equal(101d + 201 + 301, result3.ToObject()); - - // without number is float - + Assert.Equal(101d + 201 + 301, result3.ToObject()); + + // without number is float + var result1m = engine.Eval("Value(Index(robintable, 1).Column1)"); // 101 var result2m = engine.Eval("Text(Index(robintable, 2).Column2)"); // "str202" @@ -119,8 +119,8 @@ namespace Microsoft.PowerFx.Tests var robinTable = cache.Marshal(table); engine._symbolValues.Add("robintable", robinTable); - - var opts = new ParserOptions() { NumberIsFloat = true }; + + var opts = new ParserOptions() { NumberIsFloat = true }; var result1 = engine.Eval("Index(robintable, 2).Scores", options: opts); // 20 var result2 = engine.Eval("Index(robintable, 3).Names", options: opts); // "name3" @@ -132,14 +132,14 @@ namespace Microsoft.PowerFx.Tests Assert.Equal(60d, result3.ToObject()); // Access field not on the table - var result4 = engine.Eval( + var result4 = engine.Eval( @" First( Table( First(robintable), { Other : 5} - )).Other", - options: opts); + )).Other", + options: opts); Assert.IsType(result4); @@ -152,7 +152,7 @@ namespace Microsoft.PowerFx.Tests Assert.Equal(3, table.Rows.Count); - var result5 = engine.Eval("Remove(robintable, {Scores:20, Names:\"name2\"});robintable", options: opt); + var result5 = engine.Eval("Remove(robintable, {Scores:20, Names:\"name2\"});robintable", options: opt); Assert.Equal("Table({Names:\"name1\",Scores:10},{Names:\"name3\",Scores:30})", ((DataTableValue)result5).Dump()); // Is table object affected? diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/PADRobinExecutionContextTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/PADRobinExecutionContextTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/PADRobinExecutionContextTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/PADRobinExecutionContextTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/PadUntypedObjectTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/PadUntypedObjectTests.cs similarity index 99% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/PadUntypedObjectTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/PadUntypedObjectTests.cs index 2eea0ef79..8146ee5bf 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/PadUntypedObjectTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/PadUntypedObjectTests.cs @@ -125,15 +125,15 @@ namespace Microsoft.PowerFx.Interpreter.Tests float => (double)(float)Cell, _ => throw new NotImplementedException() }; - } - + } + public decimal GetDecimal() - { + { throw new NotImplementedException(); - } - + } + public string GetUntypedNumber() - { + { throw new NotImplementedException(); } @@ -163,12 +163,12 @@ namespace Microsoft.PowerFx.Interpreter.Tests var cell = DataRow[propertyName]; result = new PadUntypedObject(cell); return true; - } - - public bool TryGetPropertyNames(out IEnumerable result) - { - result = null; - return false; - } + } + + public bool TryGetPropertyNames(out IEnumerable result) + { + result = null; + return false; + } } } diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/PatchFunctionTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/PatchFunctionTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/PatchFunctionTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/PatchFunctionTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/PowerFxEvaluationTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/PowerFxEvaluationTests.cs similarity index 97% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/PowerFxEvaluationTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/PowerFxEvaluationTests.cs index 68405b51c..465bbe2a4 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/PowerFxEvaluationTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/PowerFxEvaluationTests.cs @@ -1,884 +1,884 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.PowerFx.Core; -using Microsoft.PowerFx.Core.Functions; -using Microsoft.PowerFx.Core.Tests; -using Microsoft.PowerFx.Core.Types; -using Microsoft.PowerFx.Core.Types.Enums; -using Microsoft.PowerFx.Core.Utils; -using Microsoft.PowerFx.Interpreter.Tests.Helpers; -using Microsoft.PowerFx.Logging; -using Microsoft.PowerFx.Tests; -using Microsoft.PowerFx.Types; - -namespace Microsoft.PowerFx.Interpreter.Tests -{ - public class ExpressionEvaluationTests : PowerFxTest - { - // Each setup handler can define 4 types of specific actions to define the PowerFxConfig, Parameters and Engine configuration - // - initPfxConfig: to define the initial PowerFxConfig - // - updatePfxConfig: to update the PowerFxConfig, like enabling functions - // this function returns an 'object' which will be sent to 'parameters' function if defined - // if initPfxConfig is null, PowerFxConfig is created with default settings - // - parameters: to define the parameters for the test case - // receives an 'object' if defined in updatePfxConfig - // - configureEngine: to configure the engine - // Each of these actions are executed in order (init, update, param, configure) - internal static Dictionary initPfxConfig, - Func updatePfxConfig, - Func parameters, - Action configureEngine)> SetupHandlers = new () - { - { "AllEnumsSetup", (AllEnumsSetup, null, null, null) }, - { "AllEnumsPlusTestEnumsSetup", (AllEnumsPlusTestEnumsSetup, null, null, null) }, - { "Blob", (null, BlobSetup, null, null) }, - { "DecimalSupport", (null, null, null, null) }, // Decimal is enabled in the C# interpreter - { "EnableJsonFunctions", (null, EnableJsonFunctions, null, null) }, - { "MutationFunctionsTestSetup", (null, null, null, MutationFunctionsTestSetup) }, - { "OptionSetSortTestSetup", (null, OptionSetSortTestSetup, null, null) }, - { "OptionSetTestSetup", (null, OptionSetTestSetup1, OptionSetTestSetup2, null) }, - { "RegEx", (null, RegExSetup, null, null) }, - { "TraceSetup", (null, null, null, TraceSetup) }, - }; - - private static object EnableJsonFunctions(PowerFxConfig config, SymbolTable symbolTable) - { - config.EnableJsonFunctions(); - return null; - } - - private static object RegExSetup(PowerFxConfig config, SymbolTable symbolTable) - { -#pragma warning disable CS0618 // Type or member is obsolete - config.EnableRegExFunctions(new TimeSpan(0, 0, 5)); -#pragma warning restore CS0618 // Type or member is obsolete - - return null; - } - - private static object BlobSetup(PowerFxConfig config, SymbolTable symbolTable) - { - config.AddBlobTestFunctions(); - config.EnableSetFunction(); - - return new List<(ISymbolSlot slot, FormulaValue value)>() - { - AddBlankVar(symbolTable, FormulaType.Blob, "blob", true), - AddBlankVar(symbolTable, FormulaType.String, "str", true) - }; - } - - private static (ISymbolSlot slot, FormulaValue value) AddBlankVar(SymbolTable symbolTable, FormulaType type, string varName, bool mutable) - { - return (symbolTable.AddVariable(varName, type, mutable, varName), FormulaValue.NewBlank(type)); - } - - private static PowerFxConfig AllEnumsSetup(PowerFxConfig config) - { - var store = new EnumStoreBuilder().WithDefaultEnums(); - var newConfig = PowerFxConfig.BuildWithEnumStore(store, new TexlFunctionSet(), config.Features); - - // There are likewise no built in functions that take Boolean backed option sets as parameters - newConfig.AddFunction(new TestXORBooleanFunction()); - newConfig.AddFunction(config.Features.StronglyTypedBuiltinEnums ? new STE_TestXORYesNoFunction() : new Boolean_TestXORYesNoFunction()); - newConfig.AddFunction(new TestColorInvertFunction()); - newConfig.AddFunction(config.Features.StronglyTypedBuiltinEnums ? new STE_TestColorBlueRampInvertFunction() : new Color_TestColorBlueRampInvertFunction()); - - return newConfig; - } - - private static PowerFxConfig AllEnumsPlusTestEnumsSetup(PowerFxConfig config) - { - var store = new EnumStoreBuilder().WithDefaultEnums(); - - // There are no built in enums with boolean values and only one with colors. Adding these for testing purposes. - store.TestOnly_WithCustomEnum(_testYesNo, append: true); - store.TestOnly_WithCustomEnum(_testYeaNay, append: true); - store.TestOnly_WithCustomEnum(_testBooleanNoCoerce, append: true); - store.TestOnly_WithCustomEnum(_testNumberCoerceTo, append: true); - store.TestOnly_WithCustomEnum(_testNumberCompareNumeric, append: true); - store.TestOnly_WithCustomEnum(_testNumberCompareNumericCoerceFrom, append: true); - store.TestOnly_WithCustomEnum(_testBlueRampColors, append: true); - store.TestOnly_WithCustomEnum(_testRedRampColors, append: true); - - var newConfig = PowerFxConfig.BuildWithEnumStore(store, new TexlFunctionSet(), config.Features); - - // There are likewise no built in functions that take Boolean backed option sets as parameters - newConfig.AddFunction(new TestXORBooleanFunction()); - newConfig.AddFunction(config.Features.StronglyTypedBuiltinEnums ? new STE_TestXORYesNoFunction() : new Boolean_TestXORYesNoFunction()); - newConfig.AddFunction(config.Features.StronglyTypedBuiltinEnums ? new STE_TestXORNoCoerceFunction() : new Boolean_TestXORNoCoerceFunction()); - newConfig.AddFunction(new TestColorInvertFunction()); - newConfig.AddFunction(config.Features.StronglyTypedBuiltinEnums ? new STE_TestColorBlueRampInvertFunction() : new Color_TestColorBlueRampInvertFunction()); - - return newConfig; - } - - private static readonly EnumSymbol _testYesNo = new EnumSymbol( - new DName("TestYesNo"), - DType.Boolean, - new Dictionary() - { - { "Yes", true }, - { "No", false } - }, - canCoerceFromBackingKind: true, - canCoerceToBackingKind: true); - - private static readonly EnumSymbol _testYeaNay = new EnumSymbol( - new DName("TestYeaNay"), - DType.Boolean, - new Dictionary() - { - { "Yea", true }, - { "Nay", false } - }, - canCoerceFromBackingKind: true, - canCoerceToBackingKind: true); - - private static readonly EnumSymbol _testBooleanNoCoerce = new EnumSymbol( - new DName("TestBooleanNoCoerce"), - DType.Boolean, - new Dictionary() - { - { "SuperTrue", true }, - { "SuperFalse", false } - }); - - private static readonly EnumSymbol _testNumberCoerceTo = new EnumSymbol( - new DName("TestNumberCoerceTo"), - DType.Number, - new Dictionary() - { - { "X", 10 }, - { "V", 5 }, - { "V2", 5 }, // intentionally the same value, should compare on value and not label - { "I", 1 } - }, - canCoerceToBackingKind: true); - - private static readonly EnumSymbol _testNumberCompareNumeric = new EnumSymbol( - new DName("TestNumberCompareNumeric"), - DType.Number, - new Dictionary() - { - { "X", 10 }, - { "V", 5 }, - { "V2", 5 }, // intentionally the same value, should compare on value and not label - { "I", 1 } - }, - canCompareNumeric: true); - - private static readonly EnumSymbol _testNumberCompareNumericCoerceFrom = new EnumSymbol( - new DName("TestNumberCompareNumericCoerceFrom"), - DType.Number, - new Dictionary() - { - { "X", 10 }, - { "V", 5 }, - { "V2", 5 }, // intentionally the same value, should compare on value and not label - { "I", 1 } - }, - canCompareNumeric: true, - canCoerceFromBackingKind: true); - - private static readonly EnumSymbol _testBlueRampColors = new EnumSymbol( - new DName("TestBlueRamp"), - DType.Color, - new Dictionary() - { - { "Blue100", (double)0xFF0000FFU }, - { "Blue75", (double)0xFF3F3FFFU }, - { "Blue50", (double)0xFF7F7FFFU }, - { "Blue25", (double)0xFFBFBFFFU }, - { "Blue0", (double)0xFFFFFFFFU } - }); - - private static readonly EnumSymbol _testRedRampColors = new EnumSymbol( - new DName("TestRedRamp"), - DType.Color, - new Dictionary() - { - { "Red100", (double)0xFFFF0000U }, - { "Red75", (double)0xFFFF3F3FU }, - { "Red50", (double)0xFFFF7F7FU }, - { "Red25", (double)0xFFFFBFBFU }, - { "Red0", (double)0xFFFFFFFFU } - }); - - private class TestXORBooleanFunction : ReflectionFunction - { - public TestXORBooleanFunction() - : base("TestXORBoolean", FormulaType.Boolean, new[] { FormulaType.Boolean, FormulaType.Boolean }) - { - } - - public FormulaValue Execute(BooleanValue x, BooleanValue y) - { - return BooleanValue.New(x.Value ^ y.Value); - } - } - - private class TestColorInvertFunction : ReflectionFunction - { - public TestColorInvertFunction() - : base("TestColorInvert", FormulaType.Color, new[] { FormulaType.Color }) - { - } - - public FormulaValue Execute(ColorValue x) - { - return ColorValue.New(System.Drawing.Color.FromArgb(x.Value.A, x.Value.R ^ 0xff, x.Value.G ^ 0xff, x.Value.B ^ 0xff)); - } - } - - private class STE_TestColorBlueRampInvertFunction : ReflectionFunction - { - public STE_TestColorBlueRampInvertFunction() - : base("TestColorBlueRampInvert", FormulaType.Color, new[] { _testBlueRampColors.FormulaType }) - { - } - - public FormulaValue Execute(OptionSetValue x) - { - var value = Convert.ToUInt32((double)x.ExecutionValue); - var c = Color.FromArgb( - (byte)((value >> 24) & 0xFF), - (byte)((value >> 16) & 0xFF), - (byte)((value >> 8) & 0xFF), - (byte)(value & 0xFF)); - return ColorValue.New(Color.FromArgb(c.A, c.R ^ 0xff, c.G ^ 0xff, c.B ^ 0xff)); - } - } - - private class Color_TestColorBlueRampInvertFunction : ReflectionFunction - { - public Color_TestColorBlueRampInvertFunction() - : base("TestColorBlueRampInvert", FormulaType.Color, new[] { FormulaType.Color }) - { - } - - public FormulaValue Execute(ColorValue x) - { - return ColorValue.New(Color.FromArgb(x.Value.A, x.Value.R ^ 0xff, x.Value.G ^ 0xff, x.Value.B ^ 0xff)); - } - } - - private class STE_TestXORYesNoFunction : ReflectionFunction - { - public STE_TestXORYesNoFunction() - : base("TestXORYesNo", FormulaType.Boolean, new[] { _testYesNo.FormulaType, _testYesNo.FormulaType }) - { - } - - public FormulaValue Execute(OptionSetValue x, OptionSetValue y) - { - return BooleanValue.New((bool)x.ExecutionValue ^ (bool)y.ExecutionValue); - } - } - - // Reflection functions don't know how to coerce an enum to a Boolean, if STE is turned off - private class Boolean_TestXORYesNoFunction : ReflectionFunction - { - public Boolean_TestXORYesNoFunction() - : base("TestXORYesNo", FormulaType.Boolean, new[] { FormulaType.Boolean, FormulaType.Boolean }) - { - } - - public FormulaValue Execute(BooleanValue x, BooleanValue y) - { - return BooleanValue.New(x.Value ^ y.Value); - } - } - - private class STE_TestXORNoCoerceFunction : ReflectionFunction - { - public STE_TestXORNoCoerceFunction() - : base("TestXORNoCoerce", FormulaType.Boolean, new[] { _testBooleanNoCoerce.FormulaType, _testBooleanNoCoerce.FormulaType }) - { - } - - public FormulaValue Execute(OptionSetValue x, OptionSetValue y) - { - return BooleanValue.New((bool)x.ExecutionValue ^ (bool)y.ExecutionValue); - } - } - - private class Boolean_TestXORNoCoerceFunction : ReflectionFunction - { - public Boolean_TestXORNoCoerceFunction() - : base("TestXORNoCoerce", FormulaType.Boolean, new[] { FormulaType.Boolean, FormulaType.Boolean }) - { - } - - public FormulaValue Execute(BooleanValue x, BooleanValue y) - { - return BooleanValue.New(x.Value ^ y.Value); - } - } - - private static object OptionSetTestSetup1(PowerFxConfig config, SymbolTable symbolTable) - { - OptionSet optionSet = new OptionSet("OptionSet", DisplayNameUtility.MakeUnique(new Dictionary() - { - { "option_1", "Option1" }, - { "option_2", "Option2" }, - { "option-3", "Option-3" }, - { "Is", "Option IS" }, // Reserved word - { "Self", "Option SELF" }, // Keyword - { "is", "Option is low case" }, // Not a reserved word - { "self", "Option self low case" }, // Not a keyword - })); - - OptionSet otherOptionSet = new OptionSet("OtherOptionSet", DisplayNameUtility.MakeUnique(new Dictionary() - { - { "99", "OptionA" }, - { "112", "OptionB" }, - { "35694", "OptionC" }, - { "123412983", "OptionD" }, - })); - - config.AddOptionSet(optionSet); - config.AddOptionSet(otherOptionSet); - - return (optionSet, otherOptionSet); - } - - private static RecordValue OptionSetTestSetup2(object obj) - { - (OptionSet optionSet, OptionSet otherOptionSet) = ((OptionSet, OptionSet))obj; - - optionSet.TryGetValue(new DName("option_1"), out var o1Val); - otherOptionSet.TryGetValue(new DName("123412983"), out var o2Val); - - RecordValue parameters = FormulaValue.NewRecordFromFields( - new NamedValue("TopOptionSetField", o1Val), - new NamedValue("Nested", FormulaValue.NewRecordFromFields( - new NamedValue("InnerOtherOptionSet", o2Val)))); - - return parameters; - } - - private static object OptionSetSortTestSetup(PowerFxConfig config, SymbolTable symbolTable) - { - var optionSet = new OptionSet("OptionSet", DisplayNameUtility.MakeUnique(new Dictionary() - { - { "option_1", "Option1" }, - { "option_2", "Option2" } - })); - - config.AddOptionSet(optionSet); - - optionSet.TryGetValue(new DName("option_1"), out var o1Val); - optionSet.TryGetValue(new DName("option_2"), out var o2Val); - - var r1 = FormulaValue.NewRecordFromFields(new NamedValue("OptionSetField1", o1Val), new NamedValue("StrField1", FormulaValue.New("test1"))); - var r2 = FormulaValue.NewRecordFromFields(new NamedValue("OptionSetField1", o2Val), new NamedValue("StrField1", FormulaValue.New("test2"))); - var r3 = FormulaValue.NewRecordFromFields(new NamedValue("OptionSetField1", o1Val), new NamedValue("StrField1", FormulaValue.New("test3"))); - var r4 = FormulaValue.NewRecordFromFields(new NamedValue("OptionSetField1", o2Val), new NamedValue("StrField1", FormulaValue.New("test4"))); - - // Testing with missing/blank option set field is throwing an exception. Once that is resolved uncomment and fix the test case in Sort.txt - var r5 = FormulaValue.NewRecordFromFields(new NamedValue("StrField1", FormulaValue.New("test5"))); - - var rType = RecordType.Empty() - .Add(new NamedFormulaType("OptionSetField1", FormulaType.OptionSetValue, "DisplayNameField1")) - .Add(new NamedFormulaType("StrField1", FormulaType.String, "DisplayNameField2")); - - var t1 = FormulaValue.NewTable(rType, r1, r2); - var t2 = FormulaValue.NewTable(rType, r1, r2, r3, r4); - var t3 = FormulaValue.NewTable(rType, r1, r2, r3, r5, r4); - - var symbol = config.SymbolTable; - symbol.AddConstant("t1", t1); - symbol.AddConstant("t2", t2); - symbol.AddConstant("t3", t3); - - return null; - } - - private static void MutationFunctionsTestSetup(RecalcEngine engine, bool numberIsFloat) - { - /* - * Record r1 => {![Field1:n, Field2:s, Field3:d, Field4:b]} - * Record r2 => {![Field1:n, Field2:s, Field3:d, Field4:b]} - * Record rwr1 => {![Field1:n, Field2:![Field2_1:n, Field2_2:s, Field2_3:![Field2_3_1:n, Field2_3_2:s]], Field3:b]} - * Record rwr2 => {![Field1:n, Field2:![Field2_1:n, Field2_2:s, Field2_3:![Field2_3_1:n, Field2_3_2:s]], Field3:b]} - * Record rwr3 => {![Field1:n, Field2:![Field2_1:n, Field2_2:s, Field2_3:![Field2_3_1:n, Field2_3_2:s]], Field3:b]} - * Record r_empty => {} - * Record r3 => ![a:*[b:n]] - * Table t1(r1) => Type (Field1, Field2, Field3, Field4) - * Table t2(rwr1, rwr2, rwr3) - * Table t_empty: *[Value:n] = [] - * Table t_empty2: *[Value:n] = [] - * Table t_an_bs: *[a:n,b:s] = [] - * Table t_name: *[name:s] = [] - * Table t_bs1: *[b:s] - * Table t_bs2: *[b:s] - */ - - var numberType = numberIsFloat ? FormulaType.Number : FormulaType.Decimal; - - Func newNumber = number => numberIsFloat ? FormulaValue.New(number) : FormulaValue.New((decimal)number); - - var rType = RecordType.Empty() - .Add(new NamedFormulaType("Field1", numberType, "DisplayNameField1")) - .Add(new NamedFormulaType("Field2", FormulaType.String, "DisplayNameField2")) - .Add(new NamedFormulaType("Field3", FormulaType.DateTime, "DisplayNameField3")) - .Add(new NamedFormulaType("Field4", FormulaType.Boolean, "DisplayNameField4")); - - var r1Fields = new List() - { - new NamedValue("Field1", newNumber(1)), - new NamedValue("Field2", FormulaValue.New("earth")), - new NamedValue("Field3", FormulaValue.New(DateTime.Parse("1/1/2022").Date)), - new NamedValue("Field4", FormulaValue.New(true)) - }; - - var r2Fields = new List() - { - new NamedValue("Field1", newNumber(2)), - new NamedValue("Field2", FormulaValue.New("moon")), - new NamedValue("Field3", FormulaValue.New(DateTime.Parse("2/1/2022").Date)), - new NamedValue("Field4", FormulaValue.New(false)) - }; - - var r1 = FormulaValue.NewRecordFromFields(rType, r1Fields); - var r2 = FormulaValue.NewRecordFromFields(rType, r2Fields); - var rEmpty = RecordValue.Empty(); - - var t1 = FormulaValue.NewTable(rType, new List() { r1 }); - -#pragma warning disable SA1117 // Parameters should be on same line or separate lines - var recordWithRecordType = RecordType.Empty() - .Add(new NamedFormulaType("Field1", numberType, "DisplayNameField1")) - .Add(new NamedFormulaType("Field2", RecordType.Empty() - .Add(new NamedFormulaType("Field2_1", numberType, "DisplayNameField2_1")) - .Add(new NamedFormulaType("Field2_2", FormulaType.String, "DisplayNameField2_2")) - .Add(new NamedFormulaType("Field2_3", RecordType.Empty() - .Add(new NamedFormulaType("Field2_3_1", numberType, "DisplayNameField2_3_1")) - .Add(new NamedFormulaType("Field2_3_2", FormulaType.String, "DisplayNameField2_3_2")), - "DisplayNameField2_3")), - "DisplayNameField2")) - .Add(new NamedFormulaType("Field3", FormulaType.Boolean, "DisplayNameField3")); -#pragma warning restore SA1117 // Parameters should be on same line or separate lines - - var recordWithRecordFields1 = new List() - { - new NamedValue("Field1", newNumber(1)), - new NamedValue("Field2", FormulaValue.NewRecordFromFields(new List() - { - new NamedValue("Field2_1", newNumber(121)), - new NamedValue("Field2_2", FormulaValue.New("2_2")), - new NamedValue("Field2_3", FormulaValue.NewRecordFromFields(new List() - { - new NamedValue("Field2_3_1", newNumber(1231)), - new NamedValue("Field2_3_2", FormulaValue.New("common")), - })) - })), - new NamedValue("Field3", FormulaValue.New(false)) - }; - - var recordWithRecordFields2 = new List() - { - new NamedValue("Field1", newNumber(2)), - new NamedValue("Field2", FormulaValue.NewRecordFromFields(new List() - { - new NamedValue("Field2_1", newNumber(221)), - new NamedValue("Field2_2", FormulaValue.New("2_2")), - new NamedValue("Field2_3", FormulaValue.NewRecordFromFields(new List() - { - new NamedValue("Field2_3_1", newNumber(2231)), - new NamedValue("Field2_3_2", FormulaValue.New("common")), - })) - })), - new NamedValue("Field3", FormulaValue.New(false)) - }; - - var recordWithRecordFields3 = new List() - { - new NamedValue("Field1", newNumber(3)), - new NamedValue("Field2", FormulaValue.NewRecordFromFields(new List() - { - new NamedValue("Field2_1", newNumber(321)), - new NamedValue("Field2_2", FormulaValue.New("2_2")), - new NamedValue("Field2_3", FormulaValue.NewRecordFromFields(new List() - { - new NamedValue("Field2_3_1", newNumber(3231)), - new NamedValue("Field2_3_2", FormulaValue.New("common")), - })) - })), - new NamedValue("Field3", FormulaValue.New(true)) - }; - - var recordWithRecord1 = FormulaValue.NewRecordFromFields(recordWithRecordType, recordWithRecordFields1); - var recordWithRecord2 = FormulaValue.NewRecordFromFields(recordWithRecordType, recordWithRecordFields2); - var recordWithRecord3 = FormulaValue.NewRecordFromFields(recordWithRecordType, recordWithRecordFields3); - - var t2 = FormulaValue.NewTable(recordWithRecordType, new List() - { - recordWithRecord1, - recordWithRecord2, - recordWithRecord3 - }); - - var symbol = engine._symbolTable; - - symbol.EnableMutationFunctions(); - - engine.UpdateVariable("t1", t1); - engine.UpdateVariable("r1", r1); - - engine.UpdateVariable("r2", r2); - engine.UpdateVariable("t2", t2); - engine.UpdateVariable("rwr1", recordWithRecord1); - engine.UpdateVariable("rwr2", recordWithRecord2); - engine.UpdateVariable("rwr3", recordWithRecord3); - engine.UpdateVariable("r_empty", rEmpty); - - var valueTableType = TableType.Empty().Add("Value", numberType); - var tEmpty = FormulaValue.NewTable(valueTableType.ToRecord()); - var tEmpty2 = FormulaValue.NewTable(valueTableType.ToRecord()); - engine.UpdateVariable("t_empty", tEmpty); - engine.UpdateVariable("t_empty2", tEmpty2); - - var abTableType = TableType.Empty().Add("a", numberType).Add("b", FormulaType.String); - engine.UpdateVariable("t_an_bs", FormulaValue.NewTable(abTableType.ToRecord())); - - var nameTableType = TableType.Empty().Add("name", FormulaType.String); - engine.UpdateVariable("t_name", FormulaValue.NewTable(nameTableType.ToRecord())); - - var r3Table = TableType.Empty().Add("b", numberType); - var r3Type = RecordType.Empty() - .Add(new NamedFormulaType("a", r3Table, "DisplayNamea")); - - var r3Fields = new List() - { - new NamedValue("a", FormulaValue.NewTable(r3Table.ToRecord())), - }; - - var r3Empty = FormulaValue.NewRecordFromFields(r3Type, r3Fields); - engine.UpdateVariable("r3", r3Empty); - - var t_bsType = TableType.Empty().Add("b", FormulaType.String); - engine.UpdateVariable("t_bs1", FormulaValue.NewTable(t_bsType.ToRecord())); - engine.UpdateVariable("t_bs2", FormulaValue.NewTable(t_bsType.ToRecord())); - } - - private static void TraceSetup(RecalcEngine engine, bool numberIsFloat) - { - var rType = RecordType.Empty() - .Add("message", FormulaType.String) - .Add("severity", FormulaType.Decimal) - .Add("customRecord", FormulaType.String); - - var rFields = new NamedValue[] - { - new NamedValue("message", FormulaValue.NewBlank()), - new NamedValue("severity", FormulaValue.New(0)), - new NamedValue("customRecord", FormulaValue.New(string.Empty)) - }; - - var rValue = FormulaValue.NewRecordFromFields(rType, rFields); - - engine.UpdateVariable("traceRecord", rValue); - } - - private class Tracer : ITracer - { - private readonly RecordValue _result; - - public Tracer(RecordValue result) - { - _result = result; - } - - public Task LogAsync(string message, TraceSeverity severity, RecordValue customRecord, CancellationToken ct) - { - _result.UpdateField("message", FormulaValue.New(message)); - _result.UpdateField("severity", FormulaValue.New((int)severity)); - _result.UpdateField("customRecord", FormulaValue.New(customRecord.ToExpression())); - return Task.CompletedTask; - } - } - - // Interpret each test case independently - // Supports #setup directives. - internal class InterpreterRunner : BaseRunner - { - // For async tests, run in special mode. - - // This does _not_ change evaluation semantics, but does verify .Result isn't called by checking - - // task completion status.. - - private async Task RunVerifyAsync(string expr, PowerFxConfig config, InternalSetup setup) - { - var verify = new AsyncVerify(); - - // Add Async(),WaitFor() functions - var asyncHelper = new AsyncFunctionsHelper(verify); - config.AddFunction(asyncHelper.GetFunction()); - - var waitForHelper = new WaitForFunctionsHelper(verify); - config.AddFunction(waitForHelper.GetFunction()); - - config.EnableSetFunction(); - var engine = new RecalcEngine(config); - engine.UpdateVariable("varNumber", 9999); - - // Run in special mode that ensures we're not calling .Result - var result = await verify.EvalAsync(engine, expr, setup).ConfigureAwait(false); - return result; - } - - protected override async Task RunAsyncInternal(string expr, string setupHandlerName) - { - RecalcEngine engine = null; - RecordValue parameters = null; - var iSetup = InternalSetup.Parse(setupHandlerName, Features, NumberIsFloat); - - var config = new PowerFxConfig(features: iSetup.Features); - config.EnableJsonFunctions(); - - if (iSetup.HandlerNames?.Any(hn => string.Equals(hn, "AsyncTestSetup", StringComparison.OrdinalIgnoreCase)) == true) - { - return new RunResult(await RunVerifyAsync(expr, config, iSetup).ConfigureAwait(false)); - } - - List> runtimeConfiguration = new List>(); - SymbolTable symbolTable = new SymbolTable(); - List<(ISymbolSlot slot, FormulaValue value)> slots = new (); - - if (iSetup.HandlerNames != null && iSetup.HandlerNames.Any()) - { - // Execute actions in order - foreach (var k in iSetup.HandlerNames.Select(hn => SetupHandlers[hn]).OrderBy(kvp => kvp.initPfxConfig != null ? 1 : kvp.updatePfxConfig != null ? 2 : kvp.parameters != null ? 3 : 4)) - { - if (k.initPfxConfig != null) - { - config = k.initPfxConfig(config); - } - - object o = k.updatePfxConfig?.Invoke(config, symbolTable); - - if (o is List<(ISymbolSlot slot, FormulaValue value)> slotList) - { - slots.AddRange(slotList); - } - - if (k.parameters != null) - { - parameters = k.parameters(o); - } - - if (k.configureEngine != null) - { - engine ??= new RecalcEngine(config); - k.configureEngine(engine, NumberIsFloat); - } - } - - engine ??= new RecalcEngine(config); - } - else - { - engine = new RecalcEngine(config); - parameters = null; - } - - if (parameters == null) - { - parameters = RecordValue.Empty(); - } - - var symbolTableFromParams = ReadOnlySymbolTable.NewFromRecord(parameters.Type); - var combinedSymbolTable = new ComposedReadOnlySymbolTable(symbolTableFromParams, symbolTable); - - // These tests are only run in en-US locale for now - var options = iSetup.Flags.ToParserOptions(new CultureInfo("en-US")); - var check = engine.Check(expr, options: options, symbolTable: combinedSymbolTable); - if (!check.IsSuccess) - { - return new RunResult(check); - } - - Log?.Invoke($"IR: {check.PrintIR()}"); - - var symbolValuesFromParams = SymbolValues.NewFromRecord(symbolTableFromParams, parameters); - var symbolValues = new SymbolValues(symbolTable); - - foreach ((ISymbolSlot slot, FormulaValue value) in slots) - { - symbolValues.Set(slot, value); - } - - var composedSymbolValues = SymbolValues.Compose(symbolValuesFromParams, symbolValues); - var runtimeConfig = new RuntimeConfig(composedSymbolValues); - - if (iSetup.TimeZoneInfo != null) - { - runtimeConfig.AddService(iSetup.TimeZoneInfo); - } - - if (engine.TryGetByName("traceRecord", out _)) - { - var traceRecord = engine.GetValue("traceRecord"); - if (traceRecord != null) - { - runtimeConfig.AddService(new Tracer((RecordValue)traceRecord)); - } - } - - foreach (Action rc in runtimeConfiguration) - { - rc(runtimeConfig); - } - - // Ensure tests can run with governor on. - // Some tests that use large memory can disable via: - // #SETUP: DisableMemChecks - if (!iSetup.DisableMemoryChecks) - { - var kbytes = 1000; - var mem = new SingleThreadedGovernor(10 * 1000 * kbytes); - runtimeConfig.AddService(mem); - } - - var newValue = await check.GetEvaluator().EvalAsync(CancellationToken.None, runtimeConfig).ConfigureAwait(false); - - // UntypedObjectType type is currently not supported for serialization. - if (newValue.Type is UntypedObjectType) - { - return new RunResult(newValue); - } - - FormulaValue newValueDeserialized; - - var sb = new StringBuilder(); - var settings = new FormulaValueSerializerSettings() - { - UseCompactRepresentation = true, - }; - newValue.ToExpression(sb, settings); - - try - { - // Serialization test. Serialized expression must produce an identical result. - // Serialization can't use TextFirst if enabled for the test, strings for example would have the wrong syntax. - options.TextFirst = false; - newValueDeserialized = await engine.EvalAsync(sb.ToString(), CancellationToken.None, options, runtimeConfig: runtimeConfig).ConfigureAwait(false); - } - catch (InvalidOperationException e) - { - // If we failed because of range limitations with decimal, retry with NumberIsFloat enabled - // This is for tests that return 1e100 as a result, verifying proper floating point operation - if (!NumberIsFloat && e.Message.Contains("value is too large")) - { - // Serialization test. Serialized expression must produce an identical result. - options.NumberIsFloat = true; - newValueDeserialized = await engine.EvalAsync(sb.ToString(), CancellationToken.None, options, runtimeConfig: runtimeConfig).ConfigureAwait(false); - } - else if (e.Message.Contains("Name isn't valid. 'CalculatedOptionSetValue' isn't recognized.")) - { - // This will only be displayed if the result of a formula is an option set value, for example - // "StartOfWeek.Tuesday" on a line by itself. In the case, the line below effectively skips the - // deserialization test as we can't serialize that value, it is a runtime only value. - newValueDeserialized = newValue; - } - else - { - throw; - } - } - - return new RunResult(newValueDeserialized, newValue); - } - } - - // Run through a .txt in sequence, allowing Set() functions that can create state. - // Useful for testing mutation functions. - // The .txt format still provides an expected return value after each expression. - internal class ReplRunner : BaseRunner - { - private readonly RecalcEngine _engine; - - // Repl engine does all the policy around declaring variables via Set(). -#pragma warning disable CS0618 // Type or member is obsolete - public readonly PowerFxREPL _repl; -#pragma warning restore CS0618 // Type or member is obsolete - - public ReplRunner(RecalcEngine engine) - { - _engine = engine; - -#pragma warning disable CS0618 // Type or member is obsolete - _repl = new PowerFxREPL - { - Engine = _engine, - AllowSetDefinitions = true - }; -#pragma warning restore CS0618 // Type or member is obsolete - } - - protected override async Task RunAsyncInternal(string expr, string setupHandlerName = null) - { - var replResult = await _repl.HandleCommandAsync(expr, default).ConfigureAwait(false); - - // .txt output format - if there are any Set(), compare those. - if (replResult.DeclaredVars.Count > 0) - { - var newVar = replResult.DeclaredVars.First().Value; - var runResult = new RunResult(newVar); - return runResult; - } - - var check = replResult.CheckResult; - if (!check.IsSuccess) - { - return new RunResult(check); - } - - var result = replResult.EvalResult; - return new RunResult(result); - } - } - } - - public static class UserInfoTestSetup - { - public static BasicUserInfo UserInfo = new BasicUserInfo - { - FullName = "Susan Burk", - Email = "susan@contoso.com", - DataverseUserId = new Guid("aa1d4f65-044f-4928-a95f-30d4c8ebf118"), - TeamsMemberId = "29:1DUjC5z4ttsBQa0fX2O7B0IDu30R", - }; - - public static SymbolTable GetUserInfoSymbolTable() - { - var props = new Dictionary - { - { "FullName", UserInfo.FullName }, - { "Email", UserInfo.Email }, - { "DataverseUserId", UserInfo.DataverseUserId }, - { "TeamsMemberId", UserInfo.TeamsMemberId } - }; - - var allKeys = props.Keys.ToArray(); - SymbolTable userSymbolTable = new SymbolTable(); - - userSymbolTable.AddUserInfoObject(allKeys); - - return userSymbolTable; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.PowerFx.Core; +using Microsoft.PowerFx.Core.Functions; +using Microsoft.PowerFx.Core.Tests; +using Microsoft.PowerFx.Core.Types; +using Microsoft.PowerFx.Core.Types.Enums; +using Microsoft.PowerFx.Core.Utils; +using Microsoft.PowerFx.Interpreter.Tests.Helpers; +using Microsoft.PowerFx.Logging; +using Microsoft.PowerFx.Tests; +using Microsoft.PowerFx.Types; + +namespace Microsoft.PowerFx.Interpreter.Tests +{ + public class ExpressionEvaluationTests : PowerFxTest + { + // Each setup handler can define 4 types of specific actions to define the PowerFxConfig, Parameters and Engine configuration + // - initPfxConfig: to define the initial PowerFxConfig + // - updatePfxConfig: to update the PowerFxConfig, like enabling functions + // this function returns an 'object' which will be sent to 'parameters' function if defined + // if initPfxConfig is null, PowerFxConfig is created with default settings + // - parameters: to define the parameters for the test case + // receives an 'object' if defined in updatePfxConfig + // - configureEngine: to configure the engine + // Each of these actions are executed in order (init, update, param, configure) + internal static Dictionary initPfxConfig, + Func updatePfxConfig, + Func parameters, + Action configureEngine)> SetupHandlers = new () + { + { "AllEnumsSetup", (AllEnumsSetup, null, null, null) }, + { "AllEnumsPlusTestEnumsSetup", (AllEnumsPlusTestEnumsSetup, null, null, null) }, + { "Blob", (null, BlobSetup, null, null) }, + { "DecimalSupport", (null, null, null, null) }, // Decimal is enabled in the C# interpreter + { "EnableJsonFunctions", (null, EnableJsonFunctions, null, null) }, + { "MutationFunctionsTestSetup", (null, null, null, MutationFunctionsTestSetup) }, + { "OptionSetSortTestSetup", (null, OptionSetSortTestSetup, null, null) }, + { "OptionSetTestSetup", (null, OptionSetTestSetup1, OptionSetTestSetup2, null) }, + { "RegEx", (null, RegExSetup, null, null) }, + { "TraceSetup", (null, null, null, TraceSetup) }, + }; + + private static object EnableJsonFunctions(PowerFxConfig config, SymbolTable symbolTable) + { + config.EnableJsonFunctions(); + return null; + } + + private static object RegExSetup(PowerFxConfig config, SymbolTable symbolTable) + { +#pragma warning disable CS0618 // Type or member is obsolete + config.EnableRegExFunctions(new TimeSpan(0, 0, 5)); +#pragma warning restore CS0618 // Type or member is obsolete + + return null; + } + + private static object BlobSetup(PowerFxConfig config, SymbolTable symbolTable) + { + config.AddBlobTestFunctions(); + config.EnableSetFunction(); + + return new List<(ISymbolSlot slot, FormulaValue value)>() + { + AddBlankVar(symbolTable, FormulaType.Blob, "blob", true), + AddBlankVar(symbolTable, FormulaType.String, "str", true) + }; + } + + private static (ISymbolSlot slot, FormulaValue value) AddBlankVar(SymbolTable symbolTable, FormulaType type, string varName, bool mutable) + { + return (symbolTable.AddVariable(varName, type, mutable, varName), FormulaValue.NewBlank(type)); + } + + private static PowerFxConfig AllEnumsSetup(PowerFxConfig config) + { + var store = new EnumStoreBuilder().WithDefaultEnums(); + var newConfig = PowerFxConfig.BuildWithEnumStore(store, new TexlFunctionSet(), config.Features); + + // There are likewise no built in functions that take Boolean backed option sets as parameters + newConfig.AddFunction(new TestXORBooleanFunction()); + newConfig.AddFunction(config.Features.StronglyTypedBuiltinEnums ? new STE_TestXORYesNoFunction() : new Boolean_TestXORYesNoFunction()); + newConfig.AddFunction(new TestColorInvertFunction()); + newConfig.AddFunction(config.Features.StronglyTypedBuiltinEnums ? new STE_TestColorBlueRampInvertFunction() : new Color_TestColorBlueRampInvertFunction()); + + return newConfig; + } + + private static PowerFxConfig AllEnumsPlusTestEnumsSetup(PowerFxConfig config) + { + var store = new EnumStoreBuilder().WithDefaultEnums(); + + // There are no built in enums with boolean values and only one with colors. Adding these for testing purposes. + store.TestOnly_WithCustomEnum(_testYesNo, append: true); + store.TestOnly_WithCustomEnum(_testYeaNay, append: true); + store.TestOnly_WithCustomEnum(_testBooleanNoCoerce, append: true); + store.TestOnly_WithCustomEnum(_testNumberCoerceTo, append: true); + store.TestOnly_WithCustomEnum(_testNumberCompareNumeric, append: true); + store.TestOnly_WithCustomEnum(_testNumberCompareNumericCoerceFrom, append: true); + store.TestOnly_WithCustomEnum(_testBlueRampColors, append: true); + store.TestOnly_WithCustomEnum(_testRedRampColors, append: true); + + var newConfig = PowerFxConfig.BuildWithEnumStore(store, new TexlFunctionSet(), config.Features); + + // There are likewise no built in functions that take Boolean backed option sets as parameters + newConfig.AddFunction(new TestXORBooleanFunction()); + newConfig.AddFunction(config.Features.StronglyTypedBuiltinEnums ? new STE_TestXORYesNoFunction() : new Boolean_TestXORYesNoFunction()); + newConfig.AddFunction(config.Features.StronglyTypedBuiltinEnums ? new STE_TestXORNoCoerceFunction() : new Boolean_TestXORNoCoerceFunction()); + newConfig.AddFunction(new TestColorInvertFunction()); + newConfig.AddFunction(config.Features.StronglyTypedBuiltinEnums ? new STE_TestColorBlueRampInvertFunction() : new Color_TestColorBlueRampInvertFunction()); + + return newConfig; + } + + private static readonly EnumSymbol _testYesNo = new EnumSymbol( + new DName("TestYesNo"), + DType.Boolean, + new Dictionary() + { + { "Yes", true }, + { "No", false } + }, + canCoerceFromBackingKind: true, + canCoerceToBackingKind: true); + + private static readonly EnumSymbol _testYeaNay = new EnumSymbol( + new DName("TestYeaNay"), + DType.Boolean, + new Dictionary() + { + { "Yea", true }, + { "Nay", false } + }, + canCoerceFromBackingKind: true, + canCoerceToBackingKind: true); + + private static readonly EnumSymbol _testBooleanNoCoerce = new EnumSymbol( + new DName("TestBooleanNoCoerce"), + DType.Boolean, + new Dictionary() + { + { "SuperTrue", true }, + { "SuperFalse", false } + }); + + private static readonly EnumSymbol _testNumberCoerceTo = new EnumSymbol( + new DName("TestNumberCoerceTo"), + DType.Number, + new Dictionary() + { + { "X", 10 }, + { "V", 5 }, + { "V2", 5 }, // intentionally the same value, should compare on value and not label + { "I", 1 } + }, + canCoerceToBackingKind: true); + + private static readonly EnumSymbol _testNumberCompareNumeric = new EnumSymbol( + new DName("TestNumberCompareNumeric"), + DType.Number, + new Dictionary() + { + { "X", 10 }, + { "V", 5 }, + { "V2", 5 }, // intentionally the same value, should compare on value and not label + { "I", 1 } + }, + canCompareNumeric: true); + + private static readonly EnumSymbol _testNumberCompareNumericCoerceFrom = new EnumSymbol( + new DName("TestNumberCompareNumericCoerceFrom"), + DType.Number, + new Dictionary() + { + { "X", 10 }, + { "V", 5 }, + { "V2", 5 }, // intentionally the same value, should compare on value and not label + { "I", 1 } + }, + canCompareNumeric: true, + canCoerceFromBackingKind: true); + + private static readonly EnumSymbol _testBlueRampColors = new EnumSymbol( + new DName("TestBlueRamp"), + DType.Color, + new Dictionary() + { + { "Blue100", (double)0xFF0000FFU }, + { "Blue75", (double)0xFF3F3FFFU }, + { "Blue50", (double)0xFF7F7FFFU }, + { "Blue25", (double)0xFFBFBFFFU }, + { "Blue0", (double)0xFFFFFFFFU } + }); + + private static readonly EnumSymbol _testRedRampColors = new EnumSymbol( + new DName("TestRedRamp"), + DType.Color, + new Dictionary() + { + { "Red100", (double)0xFFFF0000U }, + { "Red75", (double)0xFFFF3F3FU }, + { "Red50", (double)0xFFFF7F7FU }, + { "Red25", (double)0xFFFFBFBFU }, + { "Red0", (double)0xFFFFFFFFU } + }); + + private class TestXORBooleanFunction : ReflectionFunction + { + public TestXORBooleanFunction() + : base("TestXORBoolean", FormulaType.Boolean, new[] { FormulaType.Boolean, FormulaType.Boolean }) + { + } + + public FormulaValue Execute(BooleanValue x, BooleanValue y) + { + return BooleanValue.New(x.Value ^ y.Value); + } + } + + private class TestColorInvertFunction : ReflectionFunction + { + public TestColorInvertFunction() + : base("TestColorInvert", FormulaType.Color, new[] { FormulaType.Color }) + { + } + + public FormulaValue Execute(ColorValue x) + { + return ColorValue.New(System.Drawing.Color.FromArgb(x.Value.A, x.Value.R ^ 0xff, x.Value.G ^ 0xff, x.Value.B ^ 0xff)); + } + } + + private class STE_TestColorBlueRampInvertFunction : ReflectionFunction + { + public STE_TestColorBlueRampInvertFunction() + : base("TestColorBlueRampInvert", FormulaType.Color, new[] { _testBlueRampColors.FormulaType }) + { + } + + public FormulaValue Execute(OptionSetValue x) + { + var value = Convert.ToUInt32((double)x.ExecutionValue); + var c = Color.FromArgb( + (byte)((value >> 24) & 0xFF), + (byte)((value >> 16) & 0xFF), + (byte)((value >> 8) & 0xFF), + (byte)(value & 0xFF)); + return ColorValue.New(Color.FromArgb(c.A, c.R ^ 0xff, c.G ^ 0xff, c.B ^ 0xff)); + } + } + + private class Color_TestColorBlueRampInvertFunction : ReflectionFunction + { + public Color_TestColorBlueRampInvertFunction() + : base("TestColorBlueRampInvert", FormulaType.Color, new[] { FormulaType.Color }) + { + } + + public FormulaValue Execute(ColorValue x) + { + return ColorValue.New(Color.FromArgb(x.Value.A, x.Value.R ^ 0xff, x.Value.G ^ 0xff, x.Value.B ^ 0xff)); + } + } + + private class STE_TestXORYesNoFunction : ReflectionFunction + { + public STE_TestXORYesNoFunction() + : base("TestXORYesNo", FormulaType.Boolean, new[] { _testYesNo.FormulaType, _testYesNo.FormulaType }) + { + } + + public FormulaValue Execute(OptionSetValue x, OptionSetValue y) + { + return BooleanValue.New((bool)x.ExecutionValue ^ (bool)y.ExecutionValue); + } + } + + // Reflection functions don't know how to coerce an enum to a Boolean, if STE is turned off + private class Boolean_TestXORYesNoFunction : ReflectionFunction + { + public Boolean_TestXORYesNoFunction() + : base("TestXORYesNo", FormulaType.Boolean, new[] { FormulaType.Boolean, FormulaType.Boolean }) + { + } + + public FormulaValue Execute(BooleanValue x, BooleanValue y) + { + return BooleanValue.New(x.Value ^ y.Value); + } + } + + private class STE_TestXORNoCoerceFunction : ReflectionFunction + { + public STE_TestXORNoCoerceFunction() + : base("TestXORNoCoerce", FormulaType.Boolean, new[] { _testBooleanNoCoerce.FormulaType, _testBooleanNoCoerce.FormulaType }) + { + } + + public FormulaValue Execute(OptionSetValue x, OptionSetValue y) + { + return BooleanValue.New((bool)x.ExecutionValue ^ (bool)y.ExecutionValue); + } + } + + private class Boolean_TestXORNoCoerceFunction : ReflectionFunction + { + public Boolean_TestXORNoCoerceFunction() + : base("TestXORNoCoerce", FormulaType.Boolean, new[] { FormulaType.Boolean, FormulaType.Boolean }) + { + } + + public FormulaValue Execute(BooleanValue x, BooleanValue y) + { + return BooleanValue.New(x.Value ^ y.Value); + } + } + + private static object OptionSetTestSetup1(PowerFxConfig config, SymbolTable symbolTable) + { + OptionSet optionSet = new OptionSet("OptionSet", DisplayNameUtility.MakeUnique(new Dictionary() + { + { "option_1", "Option1" }, + { "option_2", "Option2" }, + { "option-3", "Option-3" }, + { "Is", "Option IS" }, // Reserved word + { "Self", "Option SELF" }, // Keyword + { "is", "Option is low case" }, // Not a reserved word + { "self", "Option self low case" }, // Not a keyword + })); + + OptionSet otherOptionSet = new OptionSet("OtherOptionSet", DisplayNameUtility.MakeUnique(new Dictionary() + { + { "99", "OptionA" }, + { "112", "OptionB" }, + { "35694", "OptionC" }, + { "123412983", "OptionD" }, + })); + + config.AddOptionSet(optionSet); + config.AddOptionSet(otherOptionSet); + + return (optionSet, otherOptionSet); + } + + private static RecordValue OptionSetTestSetup2(object obj) + { + (OptionSet optionSet, OptionSet otherOptionSet) = ((OptionSet, OptionSet))obj; + + optionSet.TryGetValue(new DName("option_1"), out var o1Val); + otherOptionSet.TryGetValue(new DName("123412983"), out var o2Val); + + RecordValue parameters = FormulaValue.NewRecordFromFields( + new NamedValue("TopOptionSetField", o1Val), + new NamedValue("Nested", FormulaValue.NewRecordFromFields( + new NamedValue("InnerOtherOptionSet", o2Val)))); + + return parameters; + } + + private static object OptionSetSortTestSetup(PowerFxConfig config, SymbolTable symbolTable) + { + var optionSet = new OptionSet("OptionSet", DisplayNameUtility.MakeUnique(new Dictionary() + { + { "option_1", "Option1" }, + { "option_2", "Option2" } + })); + + config.AddOptionSet(optionSet); + + optionSet.TryGetValue(new DName("option_1"), out var o1Val); + optionSet.TryGetValue(new DName("option_2"), out var o2Val); + + var r1 = FormulaValue.NewRecordFromFields(new NamedValue("OptionSetField1", o1Val), new NamedValue("StrField1", FormulaValue.New("test1"))); + var r2 = FormulaValue.NewRecordFromFields(new NamedValue("OptionSetField1", o2Val), new NamedValue("StrField1", FormulaValue.New("test2"))); + var r3 = FormulaValue.NewRecordFromFields(new NamedValue("OptionSetField1", o1Val), new NamedValue("StrField1", FormulaValue.New("test3"))); + var r4 = FormulaValue.NewRecordFromFields(new NamedValue("OptionSetField1", o2Val), new NamedValue("StrField1", FormulaValue.New("test4"))); + + // Testing with missing/blank option set field is throwing an exception. Once that is resolved uncomment and fix the test case in Sort.txt + var r5 = FormulaValue.NewRecordFromFields(new NamedValue("StrField1", FormulaValue.New("test5"))); + + var rType = RecordType.Empty() + .Add(new NamedFormulaType("OptionSetField1", FormulaType.OptionSetValue, "DisplayNameField1")) + .Add(new NamedFormulaType("StrField1", FormulaType.String, "DisplayNameField2")); + + var t1 = FormulaValue.NewTable(rType, r1, r2); + var t2 = FormulaValue.NewTable(rType, r1, r2, r3, r4); + var t3 = FormulaValue.NewTable(rType, r1, r2, r3, r5, r4); + + var symbol = config.SymbolTable; + symbol.AddConstant("t1", t1); + symbol.AddConstant("t2", t2); + symbol.AddConstant("t3", t3); + + return null; + } + + private static void MutationFunctionsTestSetup(RecalcEngine engine, bool numberIsFloat) + { + /* + * Record r1 => {![Field1:n, Field2:s, Field3:d, Field4:b]} + * Record r2 => {![Field1:n, Field2:s, Field3:d, Field4:b]} + * Record rwr1 => {![Field1:n, Field2:![Field2_1:n, Field2_2:s, Field2_3:![Field2_3_1:n, Field2_3_2:s]], Field3:b]} + * Record rwr2 => {![Field1:n, Field2:![Field2_1:n, Field2_2:s, Field2_3:![Field2_3_1:n, Field2_3_2:s]], Field3:b]} + * Record rwr3 => {![Field1:n, Field2:![Field2_1:n, Field2_2:s, Field2_3:![Field2_3_1:n, Field2_3_2:s]], Field3:b]} + * Record r_empty => {} + * Record r3 => ![a:*[b:n]] + * Table t1(r1) => Type (Field1, Field2, Field3, Field4) + * Table t2(rwr1, rwr2, rwr3) + * Table t_empty: *[Value:n] = [] + * Table t_empty2: *[Value:n] = [] + * Table t_an_bs: *[a:n,b:s] = [] + * Table t_name: *[name:s] = [] + * Table t_bs1: *[b:s] + * Table t_bs2: *[b:s] + */ + + var numberType = numberIsFloat ? FormulaType.Number : FormulaType.Decimal; + + Func newNumber = number => numberIsFloat ? FormulaValue.New(number) : FormulaValue.New((decimal)number); + + var rType = RecordType.Empty() + .Add(new NamedFormulaType("Field1", numberType, "DisplayNameField1")) + .Add(new NamedFormulaType("Field2", FormulaType.String, "DisplayNameField2")) + .Add(new NamedFormulaType("Field3", FormulaType.DateTime, "DisplayNameField3")) + .Add(new NamedFormulaType("Field4", FormulaType.Boolean, "DisplayNameField4")); + + var r1Fields = new List() + { + new NamedValue("Field1", newNumber(1)), + new NamedValue("Field2", FormulaValue.New("earth")), + new NamedValue("Field3", FormulaValue.New(DateTime.Parse("1/1/2022").Date)), + new NamedValue("Field4", FormulaValue.New(true)) + }; + + var r2Fields = new List() + { + new NamedValue("Field1", newNumber(2)), + new NamedValue("Field2", FormulaValue.New("moon")), + new NamedValue("Field3", FormulaValue.New(DateTime.Parse("2/1/2022").Date)), + new NamedValue("Field4", FormulaValue.New(false)) + }; + + var r1 = FormulaValue.NewRecordFromFields(rType, r1Fields); + var r2 = FormulaValue.NewRecordFromFields(rType, r2Fields); + var rEmpty = RecordValue.Empty(); + + var t1 = FormulaValue.NewTable(rType, new List() { r1 }); + +#pragma warning disable SA1117 // Parameters should be on same line or separate lines + var recordWithRecordType = RecordType.Empty() + .Add(new NamedFormulaType("Field1", numberType, "DisplayNameField1")) + .Add(new NamedFormulaType("Field2", RecordType.Empty() + .Add(new NamedFormulaType("Field2_1", numberType, "DisplayNameField2_1")) + .Add(new NamedFormulaType("Field2_2", FormulaType.String, "DisplayNameField2_2")) + .Add(new NamedFormulaType("Field2_3", RecordType.Empty() + .Add(new NamedFormulaType("Field2_3_1", numberType, "DisplayNameField2_3_1")) + .Add(new NamedFormulaType("Field2_3_2", FormulaType.String, "DisplayNameField2_3_2")), + "DisplayNameField2_3")), + "DisplayNameField2")) + .Add(new NamedFormulaType("Field3", FormulaType.Boolean, "DisplayNameField3")); +#pragma warning restore SA1117 // Parameters should be on same line or separate lines + + var recordWithRecordFields1 = new List() + { + new NamedValue("Field1", newNumber(1)), + new NamedValue("Field2", FormulaValue.NewRecordFromFields(new List() + { + new NamedValue("Field2_1", newNumber(121)), + new NamedValue("Field2_2", FormulaValue.New("2_2")), + new NamedValue("Field2_3", FormulaValue.NewRecordFromFields(new List() + { + new NamedValue("Field2_3_1", newNumber(1231)), + new NamedValue("Field2_3_2", FormulaValue.New("common")), + })) + })), + new NamedValue("Field3", FormulaValue.New(false)) + }; + + var recordWithRecordFields2 = new List() + { + new NamedValue("Field1", newNumber(2)), + new NamedValue("Field2", FormulaValue.NewRecordFromFields(new List() + { + new NamedValue("Field2_1", newNumber(221)), + new NamedValue("Field2_2", FormulaValue.New("2_2")), + new NamedValue("Field2_3", FormulaValue.NewRecordFromFields(new List() + { + new NamedValue("Field2_3_1", newNumber(2231)), + new NamedValue("Field2_3_2", FormulaValue.New("common")), + })) + })), + new NamedValue("Field3", FormulaValue.New(false)) + }; + + var recordWithRecordFields3 = new List() + { + new NamedValue("Field1", newNumber(3)), + new NamedValue("Field2", FormulaValue.NewRecordFromFields(new List() + { + new NamedValue("Field2_1", newNumber(321)), + new NamedValue("Field2_2", FormulaValue.New("2_2")), + new NamedValue("Field2_3", FormulaValue.NewRecordFromFields(new List() + { + new NamedValue("Field2_3_1", newNumber(3231)), + new NamedValue("Field2_3_2", FormulaValue.New("common")), + })) + })), + new NamedValue("Field3", FormulaValue.New(true)) + }; + + var recordWithRecord1 = FormulaValue.NewRecordFromFields(recordWithRecordType, recordWithRecordFields1); + var recordWithRecord2 = FormulaValue.NewRecordFromFields(recordWithRecordType, recordWithRecordFields2); + var recordWithRecord3 = FormulaValue.NewRecordFromFields(recordWithRecordType, recordWithRecordFields3); + + var t2 = FormulaValue.NewTable(recordWithRecordType, new List() + { + recordWithRecord1, + recordWithRecord2, + recordWithRecord3 + }); + + var symbol = engine._symbolTable; + + symbol.EnableMutationFunctions(); + + engine.UpdateVariable("t1", t1); + engine.UpdateVariable("r1", r1); + + engine.UpdateVariable("r2", r2); + engine.UpdateVariable("t2", t2); + engine.UpdateVariable("rwr1", recordWithRecord1); + engine.UpdateVariable("rwr2", recordWithRecord2); + engine.UpdateVariable("rwr3", recordWithRecord3); + engine.UpdateVariable("r_empty", rEmpty); + + var valueTableType = TableType.Empty().Add("Value", numberType); + var tEmpty = FormulaValue.NewTable(valueTableType.ToRecord()); + var tEmpty2 = FormulaValue.NewTable(valueTableType.ToRecord()); + engine.UpdateVariable("t_empty", tEmpty); + engine.UpdateVariable("t_empty2", tEmpty2); + + var abTableType = TableType.Empty().Add("a", numberType).Add("b", FormulaType.String); + engine.UpdateVariable("t_an_bs", FormulaValue.NewTable(abTableType.ToRecord())); + + var nameTableType = TableType.Empty().Add("name", FormulaType.String); + engine.UpdateVariable("t_name", FormulaValue.NewTable(nameTableType.ToRecord())); + + var r3Table = TableType.Empty().Add("b", numberType); + var r3Type = RecordType.Empty() + .Add(new NamedFormulaType("a", r3Table, "DisplayNamea")); + + var r3Fields = new List() + { + new NamedValue("a", FormulaValue.NewTable(r3Table.ToRecord())), + }; + + var r3Empty = FormulaValue.NewRecordFromFields(r3Type, r3Fields); + engine.UpdateVariable("r3", r3Empty); + + var t_bsType = TableType.Empty().Add("b", FormulaType.String); + engine.UpdateVariable("t_bs1", FormulaValue.NewTable(t_bsType.ToRecord())); + engine.UpdateVariable("t_bs2", FormulaValue.NewTable(t_bsType.ToRecord())); + } + + private static void TraceSetup(RecalcEngine engine, bool numberIsFloat) + { + var rType = RecordType.Empty() + .Add("message", FormulaType.String) + .Add("severity", FormulaType.Decimal) + .Add("customRecord", FormulaType.String); + + var rFields = new NamedValue[] + { + new NamedValue("message", FormulaValue.NewBlank()), + new NamedValue("severity", FormulaValue.New(0)), + new NamedValue("customRecord", FormulaValue.New(string.Empty)) + }; + + var rValue = FormulaValue.NewRecordFromFields(rType, rFields); + + engine.UpdateVariable("traceRecord", rValue); + } + + private class Tracer : ITracer + { + private readonly RecordValue _result; + + public Tracer(RecordValue result) + { + _result = result; + } + + public Task LogAsync(string message, TraceSeverity severity, RecordValue customRecord, CancellationToken ct) + { + _result.UpdateField("message", FormulaValue.New(message)); + _result.UpdateField("severity", FormulaValue.New((int)severity)); + _result.UpdateField("customRecord", FormulaValue.New(customRecord.ToExpression())); + return Task.CompletedTask; + } + } + + // Interpret each test case independently + // Supports #setup directives. + internal class InterpreterRunner : BaseRunner + { + // For async tests, run in special mode. + + // This does _not_ change evaluation semantics, but does verify .Result isn't called by checking + + // task completion status.. + + private async Task RunVerifyAsync(string expr, PowerFxConfig config, InternalSetup setup) + { + var verify = new AsyncVerify(); + + // Add Async(),WaitFor() functions + var asyncHelper = new AsyncFunctionsHelper(verify); + config.AddFunction(asyncHelper.GetFunction()); + + var waitForHelper = new WaitForFunctionsHelper(verify); + config.AddFunction(waitForHelper.GetFunction()); + + config.EnableSetFunction(); + var engine = new RecalcEngine(config); + engine.UpdateVariable("varNumber", 9999); + + // Run in special mode that ensures we're not calling .Result + var result = await verify.EvalAsync(engine, expr, setup).ConfigureAwait(false); + return result; + } + + protected override async Task RunAsyncInternal(string expr, string setupHandlerName) + { + RecalcEngine engine = null; + RecordValue parameters = null; + var iSetup = InternalSetup.Parse(setupHandlerName, Features, NumberIsFloat); + + var config = new PowerFxConfig(features: iSetup.Features); + config.EnableJsonFunctions(); + + if (iSetup.HandlerNames?.Any(hn => string.Equals(hn, "AsyncTestSetup", StringComparison.OrdinalIgnoreCase)) == true) + { + return new RunResult(await RunVerifyAsync(expr, config, iSetup).ConfigureAwait(false)); + } + + List> runtimeConfiguration = new List>(); + SymbolTable symbolTable = new SymbolTable(); + List<(ISymbolSlot slot, FormulaValue value)> slots = new (); + + if (iSetup.HandlerNames != null && iSetup.HandlerNames.Any()) + { + // Execute actions in order + foreach (var k in iSetup.HandlerNames.Select(hn => SetupHandlers[hn]).OrderBy(kvp => kvp.initPfxConfig != null ? 1 : kvp.updatePfxConfig != null ? 2 : kvp.parameters != null ? 3 : 4)) + { + if (k.initPfxConfig != null) + { + config = k.initPfxConfig(config); + } + + object o = k.updatePfxConfig?.Invoke(config, symbolTable); + + if (o is List<(ISymbolSlot slot, FormulaValue value)> slotList) + { + slots.AddRange(slotList); + } + + if (k.parameters != null) + { + parameters = k.parameters(o); + } + + if (k.configureEngine != null) + { + engine ??= new RecalcEngine(config); + k.configureEngine(engine, NumberIsFloat); + } + } + + engine ??= new RecalcEngine(config); + } + else + { + engine = new RecalcEngine(config); + parameters = null; + } + + if (parameters == null) + { + parameters = RecordValue.Empty(); + } + + var symbolTableFromParams = ReadOnlySymbolTable.NewFromRecord(parameters.Type); + var combinedSymbolTable = new ComposedReadOnlySymbolTable(symbolTableFromParams, symbolTable); + + // These tests are only run in en-US locale for now + var options = iSetup.Flags.ToParserOptions(new CultureInfo("en-US")); + var check = engine.Check(expr, options: options, symbolTable: combinedSymbolTable); + if (!check.IsSuccess) + { + return new RunResult(check); + } + + Log?.Invoke($"IR: {check.PrintIR()}"); + + var symbolValuesFromParams = SymbolValues.NewFromRecord(symbolTableFromParams, parameters); + var symbolValues = new SymbolValues(symbolTable); + + foreach ((ISymbolSlot slot, FormulaValue value) in slots) + { + symbolValues.Set(slot, value); + } + + var composedSymbolValues = SymbolValues.Compose(symbolValuesFromParams, symbolValues); + var runtimeConfig = new RuntimeConfig(composedSymbolValues); + + if (iSetup.TimeZoneInfo != null) + { + runtimeConfig.AddService(iSetup.TimeZoneInfo); + } + + if (engine.TryGetByName("traceRecord", out _)) + { + var traceRecord = engine.GetValue("traceRecord"); + if (traceRecord != null) + { + runtimeConfig.AddService(new Tracer((RecordValue)traceRecord)); + } + } + + foreach (Action rc in runtimeConfiguration) + { + rc(runtimeConfig); + } + + // Ensure tests can run with governor on. + // Some tests that use large memory can disable via: + // #SETUP: DisableMemChecks + if (!iSetup.DisableMemoryChecks) + { + var kbytes = 1000; + var mem = new SingleThreadedGovernor(10 * 1000 * kbytes); + runtimeConfig.AddService(mem); + } + + var newValue = await check.GetEvaluator().EvalAsync(CancellationToken.None, runtimeConfig).ConfigureAwait(false); + + // UntypedObjectType type is currently not supported for serialization. + if (newValue.Type is UntypedObjectType) + { + return new RunResult(newValue); + } + + FormulaValue newValueDeserialized; + + var sb = new StringBuilder(); + var settings = new FormulaValueSerializerSettings() + { + UseCompactRepresentation = true, + }; + newValue.ToExpression(sb, settings); + + try + { + // Serialization test. Serialized expression must produce an identical result. + // Serialization can't use TextFirst if enabled for the test, strings for example would have the wrong syntax. + options.TextFirst = false; + newValueDeserialized = await engine.EvalAsync(sb.ToString(), CancellationToken.None, options, runtimeConfig: runtimeConfig).ConfigureAwait(false); + } + catch (InvalidOperationException e) + { + // If we failed because of range limitations with decimal, retry with NumberIsFloat enabled + // This is for tests that return 1e100 as a result, verifying proper floating point operation + if (!NumberIsFloat && e.Message.Contains("value is too large")) + { + // Serialization test. Serialized expression must produce an identical result. + options.NumberIsFloat = true; + newValueDeserialized = await engine.EvalAsync(sb.ToString(), CancellationToken.None, options, runtimeConfig: runtimeConfig).ConfigureAwait(false); + } + else if (e.Message.Contains("Name isn't valid. 'CalculatedOptionSetValue' isn't recognized.")) + { + // This will only be displayed if the result of a formula is an option set value, for example + // "StartOfWeek.Tuesday" on a line by itself. In the case, the line below effectively skips the + // deserialization test as we can't serialize that value, it is a runtime only value. + newValueDeserialized = newValue; + } + else + { + throw; + } + } + + return new RunResult(newValueDeserialized, newValue); + } + } + + // Run through a .txt in sequence, allowing Set() functions that can create state. + // Useful for testing mutation functions. + // The .txt format still provides an expected return value after each expression. + internal class ReplRunner : BaseRunner + { + private readonly RecalcEngine _engine; + + // Repl engine does all the policy around declaring variables via Set(). +#pragma warning disable CS0618 // Type or member is obsolete + public readonly PowerFxREPL _repl; +#pragma warning restore CS0618 // Type or member is obsolete + + public ReplRunner(RecalcEngine engine) + { + _engine = engine; + +#pragma warning disable CS0618 // Type or member is obsolete + _repl = new PowerFxREPL + { + Engine = _engine, + AllowSetDefinitions = true + }; +#pragma warning restore CS0618 // Type or member is obsolete + } + + protected override async Task RunAsyncInternal(string expr, string setupHandlerName = null) + { + var replResult = await _repl.HandleCommandAsync(expr, default).ConfigureAwait(false); + + // .txt output format - if there are any Set(), compare those. + if (replResult.DeclaredVars.Count > 0) + { + var newVar = replResult.DeclaredVars.First().Value; + var runResult = new RunResult(newVar); + return runResult; + } + + var check = replResult.CheckResult; + if (!check.IsSuccess) + { + return new RunResult(check); + } + + var result = replResult.EvalResult; + return new RunResult(result); + } + } + } + + public static class UserInfoTestSetup + { + public static BasicUserInfo UserInfo = new BasicUserInfo + { + FullName = "Susan Burk", + Email = "susan@contoso.com", + DataverseUserId = new Guid("aa1d4f65-044f-4928-a95f-30d4c8ebf118"), + TeamsMemberId = "29:1DUjC5z4ttsBQa0fX2O7B0IDu30R", + }; + + public static SymbolTable GetUserInfoSymbolTable() + { + var props = new Dictionary + { + { "FullName", UserInfo.FullName }, + { "Email", UserInfo.Email }, + { "DataverseUserId", UserInfo.DataverseUserId }, + { "TeamsMemberId", UserInfo.TeamsMemberId } + }; + + var allKeys = props.Keys.ToArray(); + SymbolTable userSymbolTable = new SymbolTable(); + + userSymbolTable.AddUserInfoObject(allKeys); + + return userSymbolTable; + } + } +} diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/PrimitiveMarshallerProviderTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/PrimitiveMarshallerProviderTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/PrimitiveMarshallerProviderTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/PrimitiveMarshallerProviderTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/RecalcEngineAsyncTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/RecalcEngineAsyncTests.cs similarity index 99% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/RecalcEngineAsyncTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/RecalcEngineAsyncTests.cs index 181c0d6ab..224fd3b89 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/RecalcEngineAsyncTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/RecalcEngineAsyncTests.cs @@ -23,20 +23,20 @@ namespace Microsoft.PowerFx.Tests // Intentionally trivial async function that runs synchronously. #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously private static async Task Worker(FormulaValue[] args, CancellationToken cancel) - { - if (args[0] is NumberValue n) - { - var result = FormulaValue.New(n.Value * 2); + { + if (args[0] is NumberValue n) + { + var result = FormulaValue.New(n.Value * 2); return result; - } - else if (args[0] is DecimalValue d) - { - var result = FormulaValue.New(d.Value * 2m); - return result; - } - else - { - throw new ArgumentException(); + } + else if (args[0] is DecimalValue d) + { + var result = FormulaValue.New(d.Value * 2m); + return result; + } + else + { + throw new ArgumentException(); } } #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously @@ -81,8 +81,8 @@ namespace Microsoft.PowerFx.Tests var result = await engine.EvalAsync("If(CustomAsync(3)=6, CustomAsync(1)+CustomAsync(5), 99)", cts.Token).ConfigureAwait(false); Assert.Equal(12.0, result.ToObject()); - } - + } + [Fact] public async Task MultipleAsyncDecimal() { diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/RecalcEngineTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/RecalcEngineTests.cs similarity index 97% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/RecalcEngineTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/RecalcEngineTests.cs index 087e23955..bef7c4b6e 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/RecalcEngineTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/RecalcEngineTests.cs @@ -1,1704 +1,1704 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.PowerFx.Core; -using Microsoft.PowerFx.Core.Localization; -using Microsoft.PowerFx.Core.Tests; -using Microsoft.PowerFx.Core.Tests.Helpers; -using Microsoft.PowerFx.Core.Texl; -using Microsoft.PowerFx.Core.Types; -using Microsoft.PowerFx.Core.Types.Enums; -using Microsoft.PowerFx.Core.Utils; -using Microsoft.PowerFx.Functions; -using Microsoft.PowerFx.Interpreter; -using Microsoft.PowerFx.Types; -using Xunit; -using Xunit.Sdk; - -namespace Microsoft.PowerFx.Tests -{ - public class RecalcEngineTests : PowerFxTest - { - [Fact] - public void PublicSurfaceTests() - { - var asm = typeof(RecalcEngine).Assembly; - - var ns = "Microsoft.PowerFx"; - var nsType = "Microsoft.PowerFx.Types"; - var allowed = new HashSet() - { - $"{ns}.{nameof(CheckResultExtensions)}", - $"{ns}.{nameof(ReadOnlySymbolValues)}", - $"{ns}.{nameof(RecalcEngine)}", - $"{ns}.{nameof(Governor)}", - $"{ns}.{nameof(ReflectionFunction)}", - $"{ns}.{nameof(PowerFxConfigExtensions)}", - $"{ns}.{nameof(IExpressionEvaluator)}", - $"{ns}.{nameof(ITypeMarshallerProvider)}", - $"{ns}.{nameof(ITypeMarshaller)}", - $"{ns}.{nameof(IDynamicTypeMarshaller)}", - $"{ns}.{nameof(ObjectMarshallerProvider)}", - $"{ns}.{nameof(ObjectMarshaller)}", - $"{ns}.{nameof(BasicServiceProvider)}", - $"{ns}.{nameof(IRuntimeConfig)}", - $"{ns}.{nameof(RuntimeConfig)}", - $"{ns}.{nameof(PrimitiveMarshallerProvider)}", - $"{ns}.{nameof(PrimitiveTypeMarshaller)}", - $"{ns}.{nameof(SymbolValues)}", - $"{ns}.{nameof(TableMarshallerProvider)}", - $"{ns}.{nameof(TypeMarshallerCache)}", - $"{ns}.{nameof(TypeMarshallerCacheExtensions)}", - $"{ns}.{nameof(SymbolExtensions)}", - $"{nsType}.{nameof(ObjectRecordValue)}", -#pragma warning disable CS0618 // Type or member is obsolete - $"{nsType}.{nameof(QueryableTableValue)}", -#pragma warning restore CS0618 // Type or member is obsolete - $"{ns}.InterpreterConfigException", - $"{ns}.Interpreter.{nameof(NotDelegableException)}", - $"{ns}.Interpreter.{nameof(CustomFunctionErrorException)}", - $"{ns}.{nameof(TypeCoercionProvider)}", - - // Services for functions. - $"{ns}.Functions.IRandomService", - $"{ns}.Functions.IClockService" - }; - - var sb = new StringBuilder(); - foreach (var type in asm.GetTypes().Where(t => t.IsPublic)) - { - var name = type.FullName; - if (!allowed.Contains(name)) - { - sb.Append(name); - sb.Append("; "); - } - - allowed.Remove(name); - } - - Assert.True(sb.Length == 0, $"Unexpected public types: {sb}"); - - // Types we expect to be in the assembly aren't there. - if (allowed.Count > 0) - { - throw new XunitException("Types missing: " + string.Join(",", allowed.ToArray())); - } - } - - [Fact] - public void EvalWithGlobals() - { - var cache = new TypeMarshallerCache(); - - var engine = new RecalcEngine(); - - var context = cache.NewRecord(new - { - x = 15 - }); - var result = engine.Eval("With({y:2}, x+y)", context); - - Assert.Equal(17m, ((DecimalValue)result).Value); - } - - [Fact] - public void EvalWithoutParse() - { - var engine = new RecalcEngine(); - engine.UpdateVariable("x", 2.0); - - var check = new CheckResult(engine) - .SetText("x*3") - .SetBindingInfo(); - - // Call Evaluator directly. - // Ensure it also pulls engine's symbols. - var run = check.GetEvaluator(); - - var result = run.Eval(); - Assert.Equal(2.0 * 3, result.ToObject()); - } - - /// - /// Test that helps to ensure that RecalcEngine performs evaluation in thread safe manner. - /// - [Fact] - public void EvalInMultipleThreads() - { - var engine = new RecalcEngine(); - Parallel.For( - 0, - 10000, - (i) => - { - Assert.Equal("5", engine.Eval("10-5").ToObject().ToString()); - Assert.Equal("True", engine.Eval("true Or false").ToObject().ToString()); - Assert.Equal("15", engine.Eval("10+5").ToObject().ToString()); - }); - } - - [Fact] - public void BasicRecalc() - { - var engine = new RecalcEngine(); - engine.UpdateVariable("A", 15.0); - engine.SetFormula("B", "A*2", OnUpdate); - AssertUpdate("B-->30;"); - - engine.UpdateVariable("A", 20.0); - AssertUpdate("B-->40;"); - - // Ensure we can update to null. - engine.UpdateVariable("A", FormulaValue.NewBlank(FormulaType.Number)); - AssertUpdate("B-->0;"); - } - - [Fact] - public void BasicRecalcDecimal() - { - var engine = new RecalcEngine(); - engine.UpdateVariable("A", 15m); - engine.SetFormula("B", "A*2", OnUpdate); - AssertUpdate("B-->30;"); - - engine.UpdateVariable("A", 20m); - AssertUpdate("B-->40;"); - - // Ensure we can update to null. - engine.UpdateVariable("A", FormulaValue.NewBlank(FormulaType.Decimal)); - AssertUpdate("B-->0;"); - } - - [Fact] - public void BasicRecalcString() - { - var engine = new RecalcEngine(); - engine.UpdateVariable("A", "abcdef"); - engine.SetFormula("B", "Mid(A,3,2)", OnUpdate); - engine.SetFormula("C", "Len(A)", OnUpdate); - AssertUpdate("B-->cd;C-->6;"); - - engine.UpdateVariable("A", "hello"); - AssertUpdate("B-->ll;C-->5;"); - - // Ensure we can update to null. - engine.UpdateVariable("A", FormulaValue.NewBlank(FormulaType.String)); - AssertUpdate("B-->;C-->0;"); - } - - [Fact] - public void BasicRecalcBoolean() - { - var engine = new RecalcEngine(); - engine.UpdateVariable("A", true); - engine.SetFormula("B", "Not(A)", OnUpdate); - engine.SetFormula("C", "A Or false", OnUpdate); - AssertUpdate("B-->False;C-->True;"); - - engine.UpdateVariable("A", false); - AssertUpdate("B-->True;C-->False;"); - - // Ensure we can update to null. - engine.UpdateVariable("A", FormulaValue.NewBlank(FormulaType.Boolean)); - AssertUpdate("B-->True;C-->False;"); - } - - [Fact] - public void BasicRecalcGuid() - { - var engine = new RecalcEngine(); - engine.UpdateVariable("A", new Guid("0f8fad5b-D9CB-469f-a165-70867728950E")); - engine.SetFormula("B", "A", OnUpdate); - AssertUpdate("B-->0f8fad5b-d9cb-469f-a165-70867728950e;"); - - engine.UpdateVariable("A", new Guid("f9168c5e-CEB2-4FAA-b6bf-329bf39fa1e4")); - AssertUpdate("B-->f9168c5e-ceb2-4faa-b6bf-329bf39fa1e4;"); - - // Ensure we can update to null. - engine.UpdateVariable("A", FormulaValue.NewBlank(FormulaType.Guid)); - AssertUpdate("B-->;"); - } - - [Fact] - public void BasicRecalcDateTime() - { - var engine = new RecalcEngine(); - engine.UpdateVariable("A", new DateTime(2023, 09, 06, 03, 12, 45)); - engine.SetFormula("B", "Hour(DateAdd(A,20,TimeUnit.Minutes))", OnUpdate); - engine.SetFormula("C", "Minute(DateAdd(A,20,TimeUnit.Minutes))", OnUpdate); - AssertUpdate("B-->3;C-->32;"); - - engine.UpdateVariable("A", new DateTime(2023, 09, 06, 12, 45, 45)); - AssertUpdate("B-->13;C-->5;"); - - // Ensure we can update to null. - // null is treated as 0 or DateTime(1899,12,30,0,0,0,0) - engine.UpdateVariable("A", FormulaValue.NewBlank(FormulaType.DateTime)); - AssertUpdate("B-->0;C-->20;"); - } - - [Fact] - public void BasicRecalcTime() - { - var engine = new RecalcEngine(); - engine.UpdateVariable("A", new TimeSpan(03, 12, 45)); - engine.SetFormula("B", "Hour(DateAdd(A,20,TimeUnit.Minutes))", OnUpdate); - engine.SetFormula("C", "Minute(DateAdd(A,20,TimeUnit.Minutes))", OnUpdate); - AssertUpdate("B-->3;C-->32;"); - - engine.UpdateVariable("A", new TimeSpan(12, 45, 45)); - AssertUpdate("B-->13;C-->5;"); - - // Ensure we can update to null. - // null is treated as 0 or Time(0,0,0,0) - engine.UpdateVariable("A", FormulaValue.NewBlank(FormulaType.Time)); - AssertUpdate("B-->0;C-->20;"); - } - - // depend on grand child directly - [Fact] - public void Recalc2() - { - var engine = new RecalcEngine(); - engine.UpdateVariable("A", 1); - engine.SetFormula("B", "A*10", OnUpdate); - AssertUpdate("B-->10;"); - - engine.SetFormula("C", "B+5", OnUpdate); - AssertUpdate("C-->15;"); - - // depend on grand child directly - engine.SetFormula("D", "B+A", OnUpdate); - AssertUpdate("D-->11;"); - - // Updating A will recalc both D and B. - // But D also depends on B, so verify D pulls new value of B. - engine.UpdateVariable("A", 2); - - // Batched up (we don't double fire) - AssertUpdate("B-->20;C-->25;D-->22;"); - } - - [Fact] - public void DeleteFormula() - { - var engine = new RecalcEngine(); - - engine.UpdateVariable("A", 1); - engine.SetFormula("B", "A*10", OnUpdate); - engine.SetFormula("C", "B+5", OnUpdate); - engine.SetFormula("D", "B+A", OnUpdate); - - Assert.Throws(() => - engine.DeleteFormula("X")); - - Assert.Throws(() => - engine.DeleteFormula("B")); - - engine.DeleteFormula("D"); - Assert.False(engine.TryGetByName("D", out var retD)); - - engine.DeleteFormula("C"); - Assert.False(engine.TryGetByName("C", out var retC)); - - // After C and D are deleted, deleting B should pass - engine.DeleteFormula("B"); - - // Ensure B is gone - engine.Check("B"); - Assert.Throws(() => - engine.Check("B").ThrowOnErrors()); - } - - // Don't fire for formulas that aren't touched by an update - [Fact] - public void RecalcNoExtraCallbacks() - { - var engine = new RecalcEngine(); - engine.UpdateVariable("A1", 1); - engine.UpdateVariable("A2", 5); - - engine.SetFormula("B", "A1+A2", OnUpdate); - AssertUpdate("B-->6;"); - - engine.SetFormula("C", "A2*10", OnUpdate); - AssertUpdate("C-->50;"); - - engine.UpdateVariable("A1", 2); - AssertUpdate("B-->7;"); // Don't fire C, not touched - - engine.UpdateVariable("A2", 7); - AssertUpdate("B-->9;C-->70;"); - } - - private static readonly ParserOptions _opts = new ParserOptions { AllowsSideEffects = true }; - - [Fact] - public void SetFormula() - { - var config = new PowerFxConfig(); - config.EnableSetFunction(); - var engine = new RecalcEngine(config); - - engine.UpdateVariable("A", 1m); - engine.SetFormula("B", "A*2", OnUpdate); - AssertUpdate("B-->2;"); - - // Can't set formulas, they're read only - var check = engine.Check("Set(B, 12)"); - Assert.False(check.IsSuccess); - - // Set() function triggers recalc chain. - engine.Eval("Set(A,2)", options: _opts); - AssertUpdate("B-->4;"); - - // Compare Before/After set within an expression. - // Before (A,B) = 2,4 - // After (A,B) = 3,6 - var result = engine.Eval("With({x:A, y:B}, Set(A,3); x & y & A & B)", options: _opts); - Assert.Equal("2436", result.ToObject()); - - AssertUpdate("B-->6;"); - } - - [Fact] - public void UserDefinitionOnUpdateTest() - { - var config = new PowerFxConfig(); - config.EnableSetFunction(); - var engine = new RecalcEngine(config); - - engine.UpdateVariable("A", 1m); - engine.AddUserDefinitions("B=A*2;C=A*B;", onUpdate: OnUpdate); - AssertUpdate("B-->2;C-->2;"); - - // Can't set formulas, they're read only - var check = engine.Check("Set(B, 12)"); - Assert.False(check.IsSuccess); - - // Set() function triggers recalc chain. - engine.Eval("Set(A,10)", options: _opts); - AssertUpdate("B-->20;C-->200;"); - - // Compare Before/After set within an expression. - // Before (A,B) = 10,20 - // After (A,B) = 3,6 - var result = engine.Eval("With({x:A, y:B}, Set(A,3); x & y & A & B)", options: _opts); - Assert.Equal("102036", result.ToObject()); - } - - [Fact] - public void BasicEval() - { - var engine = new RecalcEngine(); - engine.UpdateVariable("M", 10.0); - engine.UpdateVariable("M2", -4); - var result = engine.Eval("M + Abs(M2)"); - Assert.Equal(14.0, ((NumberValue)result).Value); - } - - [Fact] - public void FormulaErrorUndefined() - { - var engine = new RecalcEngine(); - - // formula fails since 'B' is undefined. - Assert.Throws(() => - engine.SetFormula("A", "B*2", OnUpdate)); - } - - [Fact] - public void CantChangeType() - { - var engine = new RecalcEngine(); - engine.UpdateVariable("a", FormulaValue.New(12)); - - // not supported: Can't change a variable's type. - Assert.Throws(() => - engine.UpdateVariable("a", FormulaValue.New("str"))); - } - - [Fact] - public void FormulaCantRedefine() - { - var engine = new RecalcEngine(); - - engine.SetFormula("A", "2", OnUpdate); - - // Can't redefine an existing formula. - Assert.Throws(() => - engine.SetFormula("A", "3", OnUpdate)); - } - - [Theory] - [InlineData( - "func1(x:Number/*comment*/): Number = x * 10;\nfunc2(x:Number): Number = y1 * 10;", - null, - true)] - [InlineData( - "foo(x:Number):Number = If(x=0,foo(1),If(x=1,foo(2),If(x=2,Float(2))));", - "foo(Float(0))", - false, - 2.0)] - [InlineData( - "foo():Blank = foo();", - "foo()", - true)] - [InlineData( - "Add(x: Number, y:Number): Number = x + y; Foo(x: Number): Number = Abs(x);", - "Add(10, Foo(-10))", - false, - 20.0)] - [InlineData( - "Add(x: Number, y:Number): Number = x + y; Foo(x: Number): Number = Abs(x);", - "Add(1 , Add(1 , Add(1 , Add(1 , Add(1 , Foo(-1))))))", - false, - 6.0)] - [InlineData( - "TriplePowerSum(x: Number, y:Number, z:Number): Number = Power(2,x) + Power(2,y) + Power(2,z);", - "TriplePowerSum(1 , 2, 3)", - false, - 14.0)] - - // Recursive calls are not allowed - [InlineData( - "hailstone(x:Number):Number = If(Not(x = 1), If(Mod(x, 2)=0, hailstone(x/2), hailstone(3*x+1)), x);", - "hailstone(Float(192))", - true)] - [InlineData( - "odd(number:Number):Boolean = If(number = 0, false, even(Abs(number)-1)); even(number:Number):Boolean = If(number = 0, true, odd(Abs(number)-1));", - "odd(17)", - true)] - [InlineData( - "odd(number:Number):Boolean = If(number = 0, false, even(Abs(number)-1)); even(number:Number):Boolean = If(number = 0, true, odd(Abs(number)-1));", - "even(17)", - true)] - [InlineData( - "odd(number:Number):Boolean = If(number = 0, false, even(If(number<0,-number,number)-1)); even(number:Decimal):Boolean = If(number = 0, true, odd(If(number<0,-number,number)-1));", - "odd(17)", - true)] - [InlineData( - "odd(number:Number):Boolean = If(number = 0, false, even(If(number<0,-number,number)-1)); even(number:Decimal):Boolean = If(number = 0, true, odd(If(number<0,-number,number)-1));", - "even(17)", - true)] - - // Redefinition is not allowed - [InlineData( - "foo():Blank = foo(); foo():Number = x + 1;", - null, - true)] - - // Syntax error - [InlineData( - "foo():Blank = x[", - null, - true)] - - // Incorrect parameters - [InlineData( - "foo(x:Number):Number = x + 1;", - "foo(False)", - true)] - [InlineData( - "foo(x:Number):Number = x + 1;", - "foo(Table( { Value: \"Strawberry\" }, { Value: \"Vanilla\" } ))", - true)] - [InlineData( - "foo(x:Number):Number = x + 1;", - "foo(Float(1))", - false, - 2.0)] - - public void UserDefinedFunctionTest(string udfExpression, string expression, bool expectedError, double expected = 0) - { - var config = new PowerFxConfig() - { - MaxCallDepth = 100 - }; - var recalcEngine = new RecalcEngine(config); - - try - { - recalcEngine.AddUserDefinedFunction(udfExpression, CultureInfo.InvariantCulture); - - var check = recalcEngine.Check(expression); - - Assert.Equal(check.IsSuccess, !expectedError); - - var result = recalcEngine.Eval(expression); - var fvExpected = FormulaValue.New(expected); - - Assert.Equal(fvExpected.AsDecimal(), result.AsDecimal()); - } - catch (Exception ex) - { - Assert.True(expectedError, ex.Message); - } - } - - [Theory] - [InlineData("foo(x: Number, y:Number):Number = x + y;", "foo(1,2)", 3.0)] - [InlineData("foo(x: Number, y:Number):Number = x - Abs(y);", "foo(myArg,1)", 9.0)] - public void UserDefinedFunctionSymbolTableTest(string script, string expression, double expected) - { - var engine = new RecalcEngine(); - var symbolTable = new SymbolTable(); - - engine.UpdateVariable("myArg", FormulaValue.New(10)); - - symbolTable.AddUserDefinedFunction(script, CultureInfo.InvariantCulture, engine.SupportedFunctions, engine.PrimitiveTypes); - - var check = engine.Check(expression, symbolTable: symbolTable); - var result = check.GetEvaluator().Eval(); - var fvExpected = FormulaValue.New(expected); - - Assert.Equal(fvExpected.AsDecimal(), result.AsDecimal()); - } - - [Theory] - [InlineData("foo(x:Number):Number = x + missingArg1 - missingArg2;")] - [InlineData("foo(x:Number):Number = x + ;")] - public void DefinedFunctionsErrorsTest(string script) - { - var engine = new RecalcEngine(); - - Assert.Throws(() => engine.AddUserDefinedFunction(script, CultureInfo.InvariantCulture)); - } - - // Overloads and conflict - [Theory] - [InlineData("foo(Text:Number):Number = Text;", 1)] // param name conflicts with type name - [InlineData("foo(K1:Number):Number = K1;", 1)] // param takes precedence - [InlineData("foo(param:Number):Number = K1;", 9999)] // param takes precedence - public void FunctionPrecedenceTest(string script, double expected) - { - SymbolTable st = new SymbolTable { DebugName = "Extras" }; - st.AddConstant("K1", FormulaValue.New(9999)); - - var engine = new RecalcEngine(); - engine.AddUserDefinedFunction(script, symbolTable: st); - - var check = engine.Check("foo(1)"); - Assert.True(check.IsSuccess); - Assert.Equal(FormulaType.Number, check.ReturnType); - - var result = check.GetEvaluator().Eval(); - Assert.Equal(expected, result.AsDouble()); - } - - [Theory] - - // Return value with side effectful UDF - [InlineData( - "F1(x:Number) : Number = { Set(a, x); a+1; };", - "F1(123)", - false, - null, - 124)] - - // Mismatch return value with side effectful UDF - [InlineData( - "F1(x:Number) : Boolean = { Set(a, x); Today(); };", - null, - true, - "AddUserDefinedFunction", - 0)] - - public void ImperativeUserDefinedFunctionTest(string udfExpression, string expression, bool expectedError, string expectedMethodFailure, double expected) - { - var config = new PowerFxConfig(); - config.EnableSetFunction(); - var recalcEngine = new RecalcEngine(config); - recalcEngine.UpdateVariable("a", 1m); - - try - { - recalcEngine.AddUserDefinedFunction(udfExpression, CultureInfo.InvariantCulture, symbolTable: recalcEngine.EngineSymbols, allowSideEffects: true); - - var result = recalcEngine.Eval(expression, options: _opts); - var fvExpected = FormulaValue.New(expected); - - Assert.Equal(fvExpected.AsDecimal(), result.AsDecimal()); - Assert.False(expectedError); - } - catch (Exception ex) - { - Assert.True(expectedError, ex.Message); - Assert.Contains(expectedMethodFailure, ex.StackTrace); - } - } - - // Binding to inner functions does not impact outer functions. - [Fact] - public void FunctionInner() - { - // Inner table - SymbolTable stInner = SymbolTable.WithPrimitiveTypes(); - stInner.AddUserDefinedFunction("Func1() : Text = \"inner\";"); - - SymbolTable st = SymbolTable.WithPrimitiveTypes(); - st.AddUserDefinedFunction("Func2() : Text = Func1() & \"2\";", symbolTable: stInner); - - var engine = new RecalcEngine(); - engine.AddUserDefinedFunction("Func1() : Text = \"Outer\";", symbolTable: st); - - // Func1() here should bind to the top-level "outer" one, not the "inner" one. - var result = engine.EvalAsync("Func1() & Func2()", default, symbolTable: st).Result; - var str = ((StringValue)result).Value; - Assert.Equal("Outerinner2", str); - } - - [Fact] - public void PropagateNull() - { - var engine = new RecalcEngine(); - engine.SetFormula("A", expr: "Blank()", OnUpdate); - engine.SetFormula("B", "A", OnUpdate); - - var b = engine.GetValue("B"); - Assert.True(b is BlankValue); - } - - // Record with null values. - [Fact] - public void ChangeRecord() - { - var engine = new RecalcEngine(); - - engine.UpdateVariable("R", FormulaValue.NewRecordFromFields( - new NamedValue("F1", FormulaValue.NewBlank(FormulaType.Number)), - new NamedValue("F2", FormulaValue.New(6.0)))); - - engine.SetFormula("A", "R.F2 + 3 + R.F1", OnUpdate); - AssertUpdate("A-->9;"); - - engine.UpdateVariable("R", FormulaValue.NewRecordFromFields( - new NamedValue("F1", FormulaValue.New(2.0)), - new NamedValue("F2", FormulaValue.New(7.0)))); - AssertUpdate("A-->12;"); - } - - [Fact] - public void ChangeRecord_Decimal() - { - var engine = new RecalcEngine(); - - engine.UpdateVariable("R", FormulaValue.NewRecordFromFields( - new NamedValue("F1", FormulaValue.NewBlank(FormulaType.Decimal)), - new NamedValue("F2", FormulaValue.New(6)))); - - engine.SetFormula("A", "R.F2 + 3 + R.F1", OnUpdate); - AssertUpdate("A-->9;"); - - engine.UpdateVariable("R", FormulaValue.NewRecordFromFields( - new NamedValue("F1", FormulaValue.New(2)), - new NamedValue("F2", FormulaValue.New(7)))); - AssertUpdate("A-->12;"); - } - - [Fact] - public void CheckFunctionCounts() - { - var config = new PowerFxConfig(); - config.EnableJsonFunctions(); - - var engine1 = new Engine(config); - - // Pick a function in core but not implemented in interpreter. - var nyiFunc = BuiltinFunctionsCore.ISOWeekNum; - -#pragma warning disable CS0618 // Type or member is obsolete - Assert.Contains(nyiFunc, engine1.Functions.Functions); -#pragma warning restore CS0618 // Type or member is obsolete - - // RecalcEngine will add the interpreter's functions. - var engine2 = new RecalcEngine(config); - -#pragma warning disable CS0618 // Type or member is obsolete - Assert.DoesNotContain(nyiFunc, engine2.Functions.Functions); -#pragma warning restore CS0618 // Type or member is obsolete - - Assert.True(engine2.FunctionCount > 100); - - // Spot check some known functions - Assert.NotEmpty(engine2.Functions.WithName("Cos")); - Assert.NotEmpty(engine2.Functions.WithName("ParseJSON")); - } - - [Fact] - public void CheckSuccess() - { - var engine = new RecalcEngine(); - var result = engine.Check( - "3*2+x", - RecordType.Empty().Add( - new NamedFormulaType("x", FormulaType.Number))); - - Assert.True(result.IsSuccess); - Assert.True(result.ReturnType is NumberType); - Assert.Single(result.TopLevelIdentifiers); - Assert.Equal("x", result.TopLevelIdentifiers.First()); - } - - [Fact] - public void CanRunWithWarnings() - { - var config = new PowerFxConfig(Features.None); - var engine = new RecalcEngine(config); - - var result = engine.Check("T.Var = 23", RecordType.Empty() - .Add(new NamedFormulaType("T", RecordType.Empty().Add(new NamedFormulaType("Var", FormulaType.String))))); - - Assert.True(result.IsSuccess); - Assert.Equal(1, result.Errors.Count(x => x.IsWarning)); - } - - [Fact] - public void CheckSuccessWarning() - { - var engine = new RecalcEngine(); - - // issues a warning, verify it's still successful. - var result = engine.Check("Filter([1,2,3],true)"); - - Assert.True(result.IsSuccess); - Assert.Equal(1, result.Errors.Count(x => x.Severity == ErrorSeverity.Warning)); - } - - [Fact] - public void CheckParseError() - { - var engine = new RecalcEngine(); - var result = engine.Check("3*1+"); - - Assert.False(result.IsSuccess); - Assert.StartsWith("Error 4-4: Expected an operand", result.Errors.First().ToString()); - } - - [Fact] - public void CheckBindError() - { - var engine = new RecalcEngine(); - var result = engine.Check("3+foo+2"); // foo is undefined - - Assert.False(result.IsSuccess); - Assert.Single(result.Errors); - Assert.StartsWith("Error 2-5: Name isn't valid. 'foo' isn't recognized", result.Errors.First().ToString()); - } - - [Fact] - public void CheckLambdaBindError() - { - var engine = new RecalcEngine(); - var result = engine.Check("Filter([1,2,3] As X, X.Value > foo)"); - - Assert.False(result.IsSuccess); - Assert.Single(result.Errors); - Assert.StartsWith("Error 31-34: Name isn't valid. 'foo' isn't recognized", result.Errors.First().ToString()); - } - - [Fact] - public void CheckDottedBindError() - { - var engine = new RecalcEngine(); - var result = engine.Check("First([1,2,3]).foo"); - - Assert.False(result.IsSuccess); - Assert.Single(result.Errors); - Assert.StartsWith("Error 14-18: Name isn't valid. 'foo' isn't recognized", result.Errors.First().ToString()); - } - - [Fact] - public void CheckDottedBindErrorForSingleColumnAccess() - { - var config = new PowerFxConfig(); - var engine = new Engine(config); - var result = engine.Check("[1,2,3].foo"); - Assert.False(result.IsSuccess); - Assert.Single(result.Errors); - Assert.StartsWith("Error 7-11: Deprecated use of '.'. Please use the 'ShowColumns' function instead.", result.Errors.First().ToString()); - } - - [Fact] - public void CheckDottedBindError2() - { - var engine = new RecalcEngine(); - var result = engine.Check("First([]).Value"); - - Assert.False(result.IsSuccess); - Assert.Single(result.Errors); - Assert.StartsWith("Error 9-15: Name isn't valid. 'Value' isn't recognized", result.Errors.First().ToString()); - } - - [Fact] - public void CheckBindEnum() - { - var engine = new RecalcEngine(new PowerFxConfig(Features.None)); - var result = engine.Check("TimeUnit.Hours"); - - Assert.True(result.IsSuccess); - - // The resultant type will be the underlying type of the enum provided to - // check. In the case of TimeUnit, this is StringType - Assert.True(result.ReturnType is StringType); - Assert.Empty(result.TopLevelIdentifiers); - } - - [Fact] - public void CheckBindErrorWithParseExpression() - { - var engine = new RecalcEngine(); - var result = engine.Check("3+foo+2", RecordType.Empty()); // foo is undefined - - Assert.False(result.IsSuccess); - Assert.Single(result.Errors); - Assert.StartsWith("Error 2-5: Name isn't valid. 'foo' isn't recognized", result.Errors.First().ToString()); - } - - [Fact] - public void CheckSuccessWithParsedExpression() - { - var engine = new RecalcEngine(); - var result = engine.Check( - "3*2+x", - RecordType.Empty().Add( - new NamedFormulaType("x", FormulaType.Number))); - - // Test that parsing worked - Assert.True(result.IsSuccess); - Assert.True(result.ReturnType is NumberType); - Assert.Single(result.TopLevelIdentifiers); - Assert.Equal("x", result.TopLevelIdentifiers.First()); - - // Test evaluation of parsed expression - var recordValue = FormulaValue.NewRecordFromFields( - new NamedValue("x", FormulaValue.New(5.0))); - var formulaValue = result.GetEvaluator().Eval(recordValue); - Assert.Equal(11.0, (double)formulaValue.ToObject()); - } - - // Test Globals + Locals + GetValuator() - [Fact] - public void CheckGlobalAndLocal() - { - var engine = new RecalcEngine(); - engine.UpdateVariable("y", FormulaValue.New(10)); - - var result = engine.Check( - "x+y", - RecordType.Empty().Add( - new NamedFormulaType("x", FormulaType.Number))); - - // Test that parsing worked - Assert.True(result.IsSuccess); - Assert.True(result.ReturnType is NumberType); - - // Test evaluation of parsed expression - var recordValue = FormulaValue.NewRecordFromFields( - new NamedValue("x", FormulaValue.New(5.0))); - - var formulaValue = result.GetEvaluator().Eval(recordValue); - - Assert.Equal(15.0, (double)formulaValue.ToObject()); - } - - [Fact] - public void RecalcEngineMutateConfig() - { - var config = new PowerFxConfig(); - config.SymbolTable.AddFunction(BuiltinFunctionsCore.Blank); - - var recalcEngine = new Engine(config) - { - SupportedFunctions = new SymbolTable() // clear builtins - }; - - var func = BuiltinFunctionsCore.AsType; // Function not already in engine -#pragma warning disable CS0618 // Type or member is obsolete - Assert.DoesNotContain(func, recalcEngine.Functions.Functions); // didn't get auto-added by engine. -#pragma warning restore CS0618 // Type or member is obsolete - - // We can mutate config after engine is created. - var optionSet = new OptionSet("foo", DisplayNameUtility.MakeUnique(new Dictionary() { { "one key", "one value" } })); - config.SymbolTable.AddFunction(func); - config.SymbolTable.AddEntity(optionSet); - - Assert.True(config.TryGetVariable(new DName("foo"), out _)); -#pragma warning disable CS0618 // Type or member is obsolete - Assert.Contains(func, recalcEngine.Functions.Functions); // function was added to the config. -#pragma warning restore CS0618 // Type or member is obsolete - -#pragma warning disable CS0618 // Type or member is obsolete - Assert.DoesNotContain(BuiltinFunctionsCore.Abs, recalcEngine.Functions.Functions); -#pragma warning restore CS0618 // Type or member is obsolete - } - - [Fact] - public void RecalcEngine_AddFunction_Twice() - { - var config = new PowerFxConfig(); - config.AddFunction(BuiltinFunctionsCore.Blank); - - Assert.Throws(() => config.AddFunction(BuiltinFunctionsCore.Blank)); - } - - [Fact] - public void RecalcEngine_FunctionOrdering1() - { - var config = new PowerFxConfig(Features.PowerFxV1); - config.AddFunction(new TestFunctionMultiply()); - config.AddFunction(new TestFunctionSubstract()); - - var engine = new RecalcEngine(config); - var result = engine.Eval("Func(7, 11)"); - - Assert.IsType(result); - - // Multiply function is first and a valid overload so that's the one we use as coercion is valid for this one - Assert.Equal(77.0, (result as NumberValue).Value); - } - - [Fact] - public void RecalcEngine_FunctionOrdering2() - { - var config = new PowerFxConfig(Features.PowerFxV1); - config.AddFunction(new TestFunctionSubstract()); - config.AddFunction(new TestFunctionMultiply()); - - var engine = new RecalcEngine(config); - var result = engine.Eval("Func(7, 11)"); - - Assert.IsType(result); - - // Substract function is first and a valid overload so that's the one we use as coercion is valid for this one - Assert.Equal(-4.0, (result as NumberValue).Value); - } - - private class TestFunctionMultiply : CustomTexlFunction - { - public override bool IsSelfContained => true; - - public TestFunctionMultiply() - : base(DPath.Root, "Func", FunctionCategories.MathAndStat, DType.Number, null, DType.Number, DType.String) - { - } - - public override IEnumerable GetSignatures() - { - yield return new[] { TexlStrings.IsBlankArg1 }; - } - - public override Task InvokeAsync(IServiceProvider serviceProvider, FormulaValue[] args, CancellationToken cancellationToken) - { - var arg0 = args[0] as NumberValue; - var arg1 = args[1] as StringValue; - - return Task.FromResult(NumberValue.New(arg0.Value * double.Parse(arg1.Value))); - } - } - - private class TestFunctionSubstract : CustomTexlFunction - { - public override bool IsSelfContained => true; - - public TestFunctionSubstract() - : base(DPath.Root, "Func", FunctionCategories.MathAndStat, DType.Number, null, DType.String, DType.Number) - { - } - - public override IEnumerable GetSignatures() - { - yield return new[] { TexlStrings.IsBlankArg1 }; - } - - public override Task InvokeAsync(IServiceProvider serviceProvider, FormulaValue[] args, CancellationToken cancellationToken) - { - var arg0 = args[0] as StringValue; - var arg1 = args[1] as NumberValue; - - return Task.FromResult(NumberValue.New(double.Parse(arg0.Value) - arg1.Value)); - } - } - - [Fact] - public void OptionSetChecks() - { - var config = new PowerFxConfig(); - - var optionSet = new OptionSet("OptionSet", DisplayNameUtility.MakeUnique(new Dictionary() - { - { "option_1", "Option1" }, - { "option_2", "Option2" } - })); - - config.AddOptionSet(optionSet); - var recalcEngine = new RecalcEngine(config); - - var checkResult = recalcEngine.Check("OptionSet.Option1 <> OptionSet.Option2"); - Assert.True(checkResult.IsSuccess); - } - - [Theory] - - // Text() returns the display name of the input option set value - [InlineData("Text(OptionSet.option_1)", "Option1")] - [InlineData("Text(OptionSet.Option1)", "Option1")] - [InlineData("Text(Option1)", "Option1")] - [InlineData("Text(If(1<0, Option1))", null)] - - // OptionSetInfo() returns the logical name of the input option set value - [InlineData("OptionSetInfo(OptionSet.option_1)", "option_1")] - [InlineData("OptionSetInfo(OptionSet.Option1)", "option_1")] - [InlineData("OptionSetInfo(Option1)", "option_1")] - [InlineData("OptionSetInfo(If(1<0, Option1))", "")] - public async void OptionSetInfoTests(string expression, string expected) - { - var optionSet = new OptionSet("OptionSet", DisplayNameUtility.MakeUnique(new Dictionary() - { - { "option_1", "Option1" }, - { "option_2", "Option2" } - })); - - optionSet.TryGetValue(new DName("option_1"), out var option1); - - var symbol = new SymbolTable(); - var option1Solt = symbol.AddVariable("Option1", FormulaType.OptionSetValue, null); - var symValues = new SymbolValues(symbol); - symValues.Set(option1Solt, option1); - - var config = new PowerFxConfig() { SymbolTable = symbol }; -#pragma warning disable CS0618 // Type or member is obsolete - config.EnableOptionSetInfo(); -#pragma warning restore CS0618 // Type or member is obsolete - config.AddOptionSet(optionSet); - var recalcEngine = new RecalcEngine(config); - - var result = await recalcEngine.EvalAsync(expression, CancellationToken.None, symValues).ConfigureAwait(false); - Assert.Equal(expected, result.ToObject()); - } - - [Theory] - [InlineData("Text(OptionSet)")] - - [InlineData("OptionSetInfo(OptionSet)")] - [InlineData("OptionSetInfo(\"test\")")] - [InlineData("OptionSetInfo(1)")] - [InlineData("OptionSetInfo(true)")] - [InlineData("OptionSetInfo(Color.Red)")] - public async Task OptionSetInfoNegativeTest(string expression) - { - var optionSet = new OptionSet("OptionSet", DisplayNameUtility.MakeUnique(new Dictionary() - { - { "option_1", "Option1" }, - { "option_2", "Option2" } - })); - - var config = new PowerFxConfig(Features.None); - config.AddOptionSet(optionSet); - var recalcEngine = new RecalcEngine(config); - var checkResult = recalcEngine.Check(expression, RecordType.Empty()); - Assert.False(checkResult.IsSuccess); - } - - [Fact] - public void OptionSetResultType() - { - var config = new PowerFxConfig(); - - var optionSet = new OptionSet("FooOs", DisplayNameUtility.MakeUnique(new Dictionary() - { - { "option_1", "Option1" }, - { "option_2", "Option2" } - })); - - config.AddOptionSet(optionSet); - var recalcEngine = new RecalcEngine(config); - - var checkResult = recalcEngine.Check("FooOs.Option1"); - Assert.True(checkResult.IsSuccess); - var osvaluetype = Assert.IsType(checkResult.ReturnType); - Assert.Equal("FooOs", osvaluetype.OptionSetName); - } - - [Fact] - public void OptionSetChecksWithMakeUniqueCollision() - { - var config = new PowerFxConfig(); - - var optionSet = new OptionSet("OptionSet", DisplayNameUtility.MakeUnique(new Dictionary() - { - { "foo", "Option1" }, - { "bar", "Option2" }, - { "baz", "foo" } - })); - - config.AddEntity(optionSet, new DName("SomeDisplayName")); - var recalcEngine = new RecalcEngine(config); - - var checkResult = recalcEngine.Check("SomeDisplayName.Option1 <> SomeDisplayName.'foo (baz)'"); - Assert.True(checkResult.IsSuccess); - } - - [Fact] - public void EmptyEnumStoreTest() - { - var config = PowerFxConfig.BuildWithEnumStore(new EnumStoreBuilder()); - - var recalcEngine = new RecalcEngine(config); - - var checkResult = recalcEngine.Check("SortOrder.Ascending"); - Assert.True(checkResult.IsSuccess); - Assert.IsType(checkResult.ReturnType); - } - - [Theory] - [InlineData("Text(TestEnum.Choice1)", true)] - [InlineData("\"Label: \" & TestEnum.Choice1", true)] - [InlineData("Value(TestEnum.Choice1)", false)] - [InlineData("TestEnum.Choice1 + 1", false)] - [InlineData("Decimal(TestEnum.Choice1)", false)] - [InlineData("Float(TestEnum.Choice1)", false)] - [InlineData("Boolean(TestEnum.Choice1)", false)] - [InlineData("Boolean([TestEnum.Choice1,TestEnum.Choice2])", false)] - [InlineData("TestEnum.Choice1 And true", false)] - [InlineData("ColorFade(TestEnum.Choice1,10%)", false)] - [InlineData("ColorFade([TestEnum.Choice1,TestEnum.Choice2],10%)", false)] - public void OptionSetBackingTextTests(string expression, bool valid) - { - var enumStoreBuilder = new EnumStoreBuilder(); - enumStoreBuilder.TestOnly_WithCustomEnum(new EnumSymbol( - new DName("TestEnum"), - DType.String, - new Dictionary() - { - { "Choice1", "Choice_1" }, - { "Choice2", "Choice_2" }, - })); - var config = PowerFxConfig.BuildWithEnumStore(enumStoreBuilder, features: Features.PowerFxV1); - var recalcEngine = new RecalcEngine(config); - - var checkResult = recalcEngine.Check(expression, RecordType.Empty()); - Assert.Equal(valid, checkResult.IsSuccess); - } - - [Theory] - [InlineData("Text(TestEnum.Choice1)", true)] - [InlineData("\"Label: \" & TestEnum.Choice1", true)] - [InlineData("Value(TestEnum.Choice1)", true)] - [InlineData("TestEnum.Choice1 + 1", false)] // see https://github.com/microsoft/Power-Fx/issues/2229 - [InlineData("Decimal(TestEnum.Choice1)", true)] - [InlineData("Float(TestEnum.Choice1)", true)] - [InlineData("Boolean(TestEnum.Choice1)", false)] - [InlineData("Boolean([TestEnum.Choice1,TestEnum.Choice2])", false)] - [InlineData("TestEnum.Choice1 And true", false)] - [InlineData("ColorFade(TestEnum.Choice1,10%)", false)] - [InlineData("ColorFade([TestEnum.Choice1,TestEnum.Choice2],10%)", false)] - public void OptionSetBackingNumberTests(string expression, bool valid) - { - var enumStoreBuilder = new EnumStoreBuilder(); - enumStoreBuilder.TestOnly_WithCustomEnum(new EnumSymbol( - new DName("TestEnum"), - DType.Number, - new Dictionary() - { - { "Choice1", 1 }, - { "Choice2", 2 }, - })); - var config = PowerFxConfig.BuildWithEnumStore(enumStoreBuilder, features: Features.PowerFxV1); - var recalcEngine = new RecalcEngine(config); - - var checkResult = recalcEngine.Check(expression, RecordType.Empty()); - Assert.Equal(valid, checkResult.IsSuccess); - } - - [Theory] - [InlineData("Text(TestEnum.Choice1)", true)] - [InlineData("\"Label: \" & TestEnum.Choice1", true)] - [InlineData("Value(TestEnum.Choice1)", false)] - [InlineData("TestEnum.Choice1 + 1", false)] - [InlineData("Decimal(TestEnum.Choice1)", false)] - [InlineData("Float(TestEnum.Choice1)", false)] - [InlineData("Boolean(TestEnum.Choice1)", true)] - [InlineData("Boolean([TestEnum.Choice1,TestEnum.Choice2])", true)] - [InlineData("TestEnum.Choice1 And true", true)] - [InlineData("ColorFade(TestEnum.Choice1,10%)", false)] - [InlineData("ColorFade([TestEnum.Choice1,TestEnum.Choice2],10%)", false)] - public void OptionSetBackingBooleanTests(string expression, bool valid) - { - var enumStoreBuilder = new EnumStoreBuilder(); - enumStoreBuilder.TestOnly_WithCustomEnum(new EnumSymbol( - new DName("TestEnum"), - DType.Boolean, - new Dictionary() - { - { "Choice1", true }, - { "Choice2", false }, - }, - canCoerceToBackingKind: true)); - var config = PowerFxConfig.BuildWithEnumStore(enumStoreBuilder, features: Features.PowerFxV1); - var recalcEngine = new RecalcEngine(config); - - var checkResult = recalcEngine.Check(expression, RecordType.Empty()); - Assert.Equal(valid, checkResult.IsSuccess); - } - - [Theory] - [InlineData("Text(TestEnum.Choice1)", true)] - [InlineData("\"Label: \" & TestEnum.Choice1", true)] - [InlineData("Value(TestEnum.Choice1)", false)] - [InlineData("TestEnum.Choice1 + 1", false)] - [InlineData("Decimal(TestEnum.Choice1)", false)] - [InlineData("Float(TestEnum.Choice1)", false)] - [InlineData("Boolean(TestEnum.Choice1)", false)] - [InlineData("Boolean([TestEnum.Choice1,TestEnum.Choice2])", false)] - [InlineData("TestEnum.Choice1 And true", false)] - [InlineData("ColorFade(TestEnum.Choice1,10%)", false)] - [InlineData("ColorFade([TestEnum.Choice1,TestEnum.Choice2],10%)", false)] - public void OptionSetBackingColorTests(string expression, bool valid) - { - var enumStoreBuilder = new EnumStoreBuilder(); - enumStoreBuilder.TestOnly_WithCustomEnum(new EnumSymbol( - new DName("TestEnum"), - DType.Color, - new Dictionary() - { - { "Choice1", 0 }, - { "Choice2", 255 }, - })); - var config = PowerFxConfig.BuildWithEnumStore(enumStoreBuilder, features: Features.PowerFxV1); - var recalcEngine = new RecalcEngine(config); - - var checkResult = recalcEngine.Check(expression, RecordType.Empty()); - Assert.Equal(valid, checkResult.IsSuccess); - } - - [Fact] - public void TestWithTimeZoneInfo() - { - // CultureInfo not set in PowerFxConfig as we use Symbols - var pfxConfig = new PowerFxConfig(Features.None); - var recalcEngine = new RecalcEngine(pfxConfig); - var symbols = new RuntimeConfig(); - - // 10/30/22 is the date where DST applies in France (https://www.timeanddate.com/time/change/france/paris) - // So adding 2 hours to 1:34am will result in 2:34am - var frTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Romance Standard Time"); - symbols.SetTimeZone(frTimeZone); - - var jaCulture = new CultureInfo("ja-JP"); - symbols.SetCulture(jaCulture); - - Assert.Same(frTimeZone, symbols.GetService()); - Assert.Same(jaCulture, symbols.GetService()); - - var fv = recalcEngine.EvalAsync( - @"Text(DateAdd(DateTimeValue(""dimanche 30 octobre 2022 01:34:03"", ""fr-FR""), ""2"", ""hours""), ""dddd, MMMM dd, yyyy hh:mm:ss"")", - CancellationToken.None, - runtimeConfig: symbols).Result; - - Assert.NotNull(fv); - Assert.IsType(fv); - - // Then we convert the result to Japanese date/time format (English equivalent: "Sunday, October 30, 2022 02:34:03") - Assert.Equal("日曜日, 10月 30, 2022 02:34:03", fv.ToObject()); - } - - [Fact] - public void FunctionServices() - { - var engine = new RecalcEngine(); - var values = new RuntimeConfig(); - values.SetRandom(new TestRandService()); - - // Rand - var result = engine.EvalAsync("Rand()", CancellationToken.None, runtimeConfig: values).Result; - Assert.Equal(0.5, result.ToObject()); - - // 1 service can impact multiple functions. - // It also doesn't replace the function, so existing function logic (errors, range checks, etc) still is used. - // RandBetween maps 0.5 to 6. - result = engine.EvalAsync("RandBetween(1,10)", CancellationToken.None, runtimeConfig: values).Result; - Assert.Equal(6.0m, result.ToObject()); - } - - [Fact] - public async Task FunctionServicesHostBug() - { - // Need to protect against bogus values from a poorly implemented service. - // These are exceptions, not ErrorValues, since it's a host bug. - var engine = new RecalcEngine(); - var values = new RuntimeConfig(); - - // Host bug, service should be 0...1, this is out of range. - var buggyService = new TestRandService { _value = 9999 }; - - values.AddService(buggyService); - - try - { - await engine.EvalAsync("Rand()", CancellationToken.None, runtimeConfig: values).ConfigureAwait(false); - Assert.False(true); // should have thrown on illegal IRandomService service. - } - catch (InvalidOperationException e) - { - var name = typeof(TestRandService).FullName; - Assert.Equal($"IRandomService ({name}) returned an illegal value 9999. Must be between 0 and 1", e.Message); - } - } - - [Fact] - public async Task ExecutingWithRemovedVarFails() - { - var symTable = new SymbolTable(); - var slot = symTable.AddVariable("x", FormulaType.Number, null); - - var engine = new RecalcEngine(); - var result = engine.Check("x+1", symbolTable: symTable); - Assert.True(result.IsSuccess); - - var eval = result.GetEvaluator(); - var symValues = symTable.CreateValues(); - symValues.Set(slot, FormulaValue.New(10.0)); - - var result1 = await eval.EvalAsync(CancellationToken.None, symValues).ConfigureAwait(false); - Assert.Equal(11.0, result1.ToObject()); - - // Adding a variable is ok. - var slotY = symTable.AddVariable("y", FormulaType.Number, null); - result1 = await eval.EvalAsync(CancellationToken.None, symValues).ConfigureAwait(false); - Assert.Equal(11.0, result1.ToObject()); - - // Executing an existing IR fails if it uses a deleted variable. - symTable.RemoveVariable("x"); - await Assert.ThrowsAsync(async () => await eval.EvalAsync(CancellationToken.None, symValues).ConfigureAwait(false)).ConfigureAwait(false); - - // Even re-adding with same type still fails. - // (somebody could have re-added with a different type) - var slot2 = symTable.AddVariable("x", FormulaType.Number, null); - symValues.Set(slot2, FormulaValue.New(20.0)); - - await Assert.ThrowsAsync(async () => await eval.EvalAsync(CancellationToken.None, symValues).ConfigureAwait(false)).ConfigureAwait(false); - } - - // execute w/ missing var (never adding to SymValues) - [Fact] - public async Task ExecutingWithMissingVar() - { - var engine = new Engine(new PowerFxConfig()); - - var recordType = RecordType.Empty() - .Add("x", FormulaType.Number) - .Add("y", FormulaType.Number); - - var result = engine.Check("x+y", recordType); - var eval = result.GetEvaluator(); - - var recordXY = RecordValue.NewRecordFromFields( - new NamedValue("x", FormulaValue.New(10.0)), - new NamedValue("y", FormulaValue.New(100.0))); - - var result2 = eval.Eval(recordXY); - Assert.Equal(110.0, result2.ToObject()); - - // Missing y , treated as blank (0) - var recordX = RecordValue.NewRecordFromFields( - new NamedValue("x", FormulaValue.New(10.0))); - result2 = eval.Eval(recordX); - Assert.Equal(10.0, result2.ToObject()); - } - - [Theory] - [InlineData("ThisRecord.Field2", "_field2")] // row scope, no conflcit - [InlineData("Task", "_fieldTask")] // row scope wins the conflict, it's closer. - [InlineData("[@Task]", "_globalTask")] // global scope - [InlineData("[@Task] & Task", "_globalTask_fieldTask")] // both in same expression - [InlineData("[@Task] & ThisRecord.Task", "_globalTask_fieldTask")] // both, fully unambiguous. - [InlineData("With({Task : true}, Task)", true)] // With() wins, shadows rowscope. - [InlineData("With({Task : true}, ThisRecord.Task)", true)] // With() also has ThisRecord, shadows previous rowscope. - [InlineData("With({Task : true}, [@Task])", "_globalTask")] // Globals. - [InlineData("With({Task : true} As T2, Task)", "_fieldTask")] // As avoids the conflict. - [InlineData("With({Task : true} As T2, ThisRecord.Task)", "_fieldTask")] // As avoids the conflict. - [InlineData("With({Task : true} As T2, ThisRecord.Field2)", "_field2")] // As avoids the conflict. - - // Errors - [InlineData("[@Field2]")] // error, doesn't exist in global scope. - [InlineData("With({Task : true}, ThisRecord.Field2)")] // Error. ThisRecord doesn't union, it refers exclusively to With(). - public void DisambiguationTest(string expr, object expected = null) - { - var engine = new RecalcEngine(); - - // Setup Global "Task", and RowScope with "Task" field. - var record = FormulaValue.NewRecordFromFields( - new NamedValue("Task", FormulaValue.New("_fieldTask")), - new NamedValue("Field2", FormulaValue.New("_field2"))); - - var globals = new SymbolTable(); - var slot = globals.AddVariable("Task", FormulaType.String, null); - - var rowScope = ReadOnlySymbolTable.NewFromRecord(record.Type, allowThisRecord: true); - - // ensure rowScope is listed first since that should get higher priority - var symbols = ReadOnlySymbolTable.Compose(rowScope, globals); - - // Values - var rowValues = ReadOnlySymbolValues.NewFromRecord(rowScope, record); - var globalValues = globals.CreateValues(); - globalValues.Set(slot, FormulaValue.New("_globalTask")); - - var runtimeConfig = new RuntimeConfig - { - Values = symbols.CreateValues(globalValues, rowValues) - }; - - var check = engine.Check(expr, symbolTable: symbols); // never throws - - if (expected == null) - { - Assert.False(check.IsSuccess); - return; - } - - var run = check.GetEvaluator(); - var result = run.Eval(runtimeConfig); - - Assert.Equal(expected, result.ToObject()); - } - - [Fact] - public void GetVariableRecalcEngine() - { - var config = new PowerFxConfig(); - - var engine = new RecalcEngine(config); - engine.UpdateVariable("A", FormulaValue.New(0.0)); - - Assert.True(engine.TryGetVariableType("A", out var type)); - Assert.Equal(FormulaType.Number, type); - - Assert.False(engine.TryGetVariableType("Invalid", out type)); - Assert.Equal(default, type); - - engine.DeleteFormula("A"); - Assert.False(engine.TryGetVariableType("A", out type)); - Assert.Equal(default, type); - } - - [Fact] - public void ComparisonWithMismatchedTypes() - { - foreach ((Features f, ErrorSeverity es) in new[] - { - (Features.PowerFxV1, ErrorSeverity.Severe), - (Features.None, ErrorSeverity.Warning) - }) - { - var config = new PowerFxConfig(f); - var engine = new RecalcEngine(config); - - CheckResult cr = engine.Check(@"If(2 = ""2"", 3, 4 )"); - ExpressionError firstError = cr.Errors.First(); - - Assert.Equal(es, firstError.Severity); - Assert.Equal("Incompatible types for comparison. These types can't be compared: Decimal, Text.", firstError.Message); - } - } - - [Fact] - public void TryGetValueShouldNotThrowOnNonExistingValue() - { - var config = new PowerFxConfig(); - var engine = new RecalcEngine(config); - - var success = engine.TryGetValue("Invalid", out var shouldBeNull); - - Assert.False(success); - Assert.Null(shouldBeNull); - } - - private class TestRandService : IRandomService - { - public double _value = 0.5; - - // Returns between 0 and 1. - public double NextDouble() - { - return _value; - } - } - - [Fact] - public void LookupBuiltinOptionSets() - { - var config = new PowerFxConfig(); - var engine = new RecalcEngine(config); - - // Builtin enums are on engine.SupportedFunctionm - var ok = engine.SupportedFunctions.TryGetSymbolType("Color", out var type); - Assert.True(ok); - - ok = engine.GetCombinedEngineSymbols().TryGetSymbolType("Color", out type); - Assert.True(ok); - - // Wrong type: https://github.com/microsoft/Power-Fx/issues/2342 - } - - [Theory] - [InlineData( - "Point = Type({x : Number, y : Number}); distance(a: Point, b: Point): Number = Sqrt(Power(b.x-a.x, 2) + Power(b.y-a.y, 2));", - "distance({x: 0, y: 0}, {x: 0, y: 5})", - true, - 5.0)] - - // Table types are accepted - [InlineData( - "People = Type([{Id:Number, Age: Number}]); countMinors(p: People): Number = CountRows(Filter(p, Age < 18));", - "countMinors([{Id: 1, Age: 17}, {Id: 2, Age: 21}])", - true, - 1.0)] - [InlineData( - "Numbers = Type([Number]); countEven(nums: Numbers): Number = CountRows(Filter(nums, Mod(Value, 2) = 0));", - "countEven([1,2,3,4,5,6,7,8,9,10])", - true, - 5.0)] - - // Type Aliases are allowed - [InlineData( - "CarYear = Type(Number); Car = Type({Model: Text, ModelYear: CarYear}); createCar(model:Number, year: Number): Car = {Model:model, ModelYear: year};", - "createCar(\"Model Y\", 2024).ModelYear", - true, - 2024.0)] - - // Type definitions order shouldn't matter - [InlineData( - "Person = Type({Id: IdType, Age: Number}); IdType = Type(Number); createUser(id:Number, a: Number): Person = {Id:id, Age: a};", - "createUser(1, 42).Age", - true, - 42.0)] - - // Functions accept record with more/less fields - [InlineData( - "People = Type([{Name: Text, Age: Number}]); countMinors(p: People): Number = CountRows(Filter(p, Age < 18));", - "countMinors([{Name: \"Bob\", Age: 21, Title: \"Engineer\"}, {Name: \"Alice\", Age: 25, Title: \"Manager\"}])", - true, - 0.0)] - [InlineData( - "Employee = Type({Name: Text, Age: Number, Title: Text}); getAge(e: Employee): Number = e.Age;", - "getAge({Name: \"Bob\", Age: 21})", - true, - 21.0)] - [InlineData( - @"Employee = Type({Name: Text, Age: Number, Title: Text}); Employees = Type([Employee]); EmployeeNames = Type([{Name: Text}]); - getNames(e: Employees):EmployeeNames = ShowColumns(e, Name); - getNamesCount(e: EmployeeNames):Number = CountRows(getNames(e));", - "getNamesCount([{Name: \"Jim\", Age:25}, {Name: \"Tony\", Age:42}])", - true, - 2.0)] - [InlineData( - @"Employee = Type({Name: Text, Age: Number, Title: Text}); - getAge(e: Employee): Number = e.Age; - hasNoAge(e: Employee): Number = IsBlank(getAge(e));", - "hasNoAge({Name: \"Bob\", Title: \"CEO\"})", - true, - 1.0)] - - // Types with UDF restricted primitive types resolve successfully - [InlineData( - @"Patient = Type({DOB: DateTimeTZInd, Weight: Decimal, Dummy: None}); - Patients = Type([Patient]); - Dummy():Number = CountRows([]);", - "Dummy()", - true, - 0.0)] - - // Aggregate types with restricted types are not allowed in UDF - [InlineData( - @"Patient = Type({DOB: DateTimeTZInd, Weight: Decimal, Dummy: None}); - Patients = Type([Patient]); - getAnomaly(p: Patients): Patients = Filter(p, Weight < 0);", - "", - false)] - - [InlineData( - @"Patient = Type({Name: Text, Details: {h: Number, w:Decimal}}); - getPatient(): Patient = {Name:""Alice"", Details: {h: 1, w: 2}};", - "", - false)] - - // Cycles not allowed - [InlineData( - "Z = Type([{a: {b: Z}}]);", - "", - false)] - [InlineData( - "X = Type(Y); Y = Type(X);", - "", - false)] - [InlineData( - "C = Type({x: Boolean, y: Date, f: B});B = Type({ x: A }); A = Type([C]);", - "", - false)] - - // Redeclaration not allowed - [InlineData( - "Number = Type(Text);", - "", - false)] - [InlineData( - "Point = Type({x : Number, y : Number}); Point = Type({x : Number, y : Number, z: Number})", - "", - false)] - - // UDFs with body errors should fail - [InlineData( - "S = Type({x:Text}); f():S = ({);", - "", - false)] - - public void UserDefinedTypeTest(string userDefinitions, string evalExpression, bool isValid, double expectedResult = 0) - { - var config = new PowerFxConfig(); - var recalcEngine = new RecalcEngine(config); - var parserOptions = new ParserOptions() - { - AllowsSideEffects = false, - AllowParseAsTypeLiteral = true - }; - - if (isValid) - { - recalcEngine.AddUserDefinitions(userDefinitions, CultureInfo.InvariantCulture); - Assert.Equal(expectedResult, recalcEngine.Eval(evalExpression, options: parserOptions).ToObject()); - } - else - { - Assert.Throws(() => recalcEngine.AddUserDefinitions(userDefinitions, CultureInfo.InvariantCulture)); - } - } - - #region Test - - private readonly StringBuilder _updates = new StringBuilder(); - - private void AssertUpdate(string expected) - { - Assert.Equal(expected, _updates.ToString()); - _updates.Clear(); - } - - private void OnUpdate(string name, FormulaValue newValue) - { - var str = newValue.ToObject()?.ToString(); - - _updates.Append($"{name}-->{str};"); - } - #endregion - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.PowerFx.Core; +using Microsoft.PowerFx.Core.Localization; +using Microsoft.PowerFx.Core.Tests; +using Microsoft.PowerFx.Core.Tests.Helpers; +using Microsoft.PowerFx.Core.Texl; +using Microsoft.PowerFx.Core.Types; +using Microsoft.PowerFx.Core.Types.Enums; +using Microsoft.PowerFx.Core.Utils; +using Microsoft.PowerFx.Functions; +using Microsoft.PowerFx.Interpreter; +using Microsoft.PowerFx.Types; +using Xunit; +using Xunit.Sdk; + +namespace Microsoft.PowerFx.Tests +{ + public class RecalcEngineTests : PowerFxTest + { + [Fact] + public void PublicSurfaceTests() + { + var asm = typeof(RecalcEngine).Assembly; + + var ns = "Microsoft.PowerFx"; + var nsType = "Microsoft.PowerFx.Types"; + var allowed = new HashSet() + { + $"{ns}.{nameof(CheckResultExtensions)}", + $"{ns}.{nameof(ReadOnlySymbolValues)}", + $"{ns}.{nameof(RecalcEngine)}", + $"{ns}.{nameof(Governor)}", + $"{ns}.{nameof(ReflectionFunction)}", + $"{ns}.{nameof(PowerFxConfigExtensions)}", + $"{ns}.{nameof(IExpressionEvaluator)}", + $"{ns}.{nameof(ITypeMarshallerProvider)}", + $"{ns}.{nameof(ITypeMarshaller)}", + $"{ns}.{nameof(IDynamicTypeMarshaller)}", + $"{ns}.{nameof(ObjectMarshallerProvider)}", + $"{ns}.{nameof(ObjectMarshaller)}", + $"{ns}.{nameof(BasicServiceProvider)}", + $"{ns}.{nameof(IRuntimeConfig)}", + $"{ns}.{nameof(RuntimeConfig)}", + $"{ns}.{nameof(PrimitiveMarshallerProvider)}", + $"{ns}.{nameof(PrimitiveTypeMarshaller)}", + $"{ns}.{nameof(SymbolValues)}", + $"{ns}.{nameof(TableMarshallerProvider)}", + $"{ns}.{nameof(TypeMarshallerCache)}", + $"{ns}.{nameof(TypeMarshallerCacheExtensions)}", + $"{ns}.{nameof(SymbolExtensions)}", + $"{nsType}.{nameof(ObjectRecordValue)}", +#pragma warning disable CS0618 // Type or member is obsolete + $"{nsType}.{nameof(QueryableTableValue)}", +#pragma warning restore CS0618 // Type or member is obsolete + $"{ns}.InterpreterConfigException", + $"{ns}.Interpreter.{nameof(NotDelegableException)}", + $"{ns}.Interpreter.{nameof(CustomFunctionErrorException)}", + $"{ns}.{nameof(TypeCoercionProvider)}", + + // Services for functions. + $"{ns}.Functions.IRandomService", + $"{ns}.Functions.IClockService" + }; + + var sb = new StringBuilder(); + foreach (var type in asm.GetTypes().Where(t => t.IsPublic)) + { + var name = type.FullName; + if (!allowed.Contains(name)) + { + sb.Append(name); + sb.Append("; "); + } + + allowed.Remove(name); + } + + Assert.True(sb.Length == 0, $"Unexpected public types: {sb}"); + + // Types we expect to be in the assembly aren't there. + if (allowed.Count > 0) + { + throw new XunitException("Types missing: " + string.Join(",", allowed.ToArray())); + } + } + + [Fact] + public void EvalWithGlobals() + { + var cache = new TypeMarshallerCache(); + + var engine = new RecalcEngine(); + + var context = cache.NewRecord(new + { + x = 15 + }); + var result = engine.Eval("With({y:2}, x+y)", context); + + Assert.Equal(17m, ((DecimalValue)result).Value); + } + + [Fact] + public void EvalWithoutParse() + { + var engine = new RecalcEngine(); + engine.UpdateVariable("x", 2.0); + + var check = new CheckResult(engine) + .SetText("x*3") + .SetBindingInfo(); + + // Call Evaluator directly. + // Ensure it also pulls engine's symbols. + var run = check.GetEvaluator(); + + var result = run.Eval(); + Assert.Equal(2.0 * 3, result.ToObject()); + } + + /// + /// Test that helps to ensure that RecalcEngine performs evaluation in thread safe manner. + /// + [Fact] + public void EvalInMultipleThreads() + { + var engine = new RecalcEngine(); + Parallel.For( + 0, + 10000, + (i) => + { + Assert.Equal("5", engine.Eval("10-5").ToObject().ToString()); + Assert.Equal("True", engine.Eval("true Or false").ToObject().ToString()); + Assert.Equal("15", engine.Eval("10+5").ToObject().ToString()); + }); + } + + [Fact] + public void BasicRecalc() + { + var engine = new RecalcEngine(); + engine.UpdateVariable("A", 15.0); + engine.SetFormula("B", "A*2", OnUpdate); + AssertUpdate("B-->30;"); + + engine.UpdateVariable("A", 20.0); + AssertUpdate("B-->40;"); + + // Ensure we can update to null. + engine.UpdateVariable("A", FormulaValue.NewBlank(FormulaType.Number)); + AssertUpdate("B-->0;"); + } + + [Fact] + public void BasicRecalcDecimal() + { + var engine = new RecalcEngine(); + engine.UpdateVariable("A", 15m); + engine.SetFormula("B", "A*2", OnUpdate); + AssertUpdate("B-->30;"); + + engine.UpdateVariable("A", 20m); + AssertUpdate("B-->40;"); + + // Ensure we can update to null. + engine.UpdateVariable("A", FormulaValue.NewBlank(FormulaType.Decimal)); + AssertUpdate("B-->0;"); + } + + [Fact] + public void BasicRecalcString() + { + var engine = new RecalcEngine(); + engine.UpdateVariable("A", "abcdef"); + engine.SetFormula("B", "Mid(A,3,2)", OnUpdate); + engine.SetFormula("C", "Len(A)", OnUpdate); + AssertUpdate("B-->cd;C-->6;"); + + engine.UpdateVariable("A", "hello"); + AssertUpdate("B-->ll;C-->5;"); + + // Ensure we can update to null. + engine.UpdateVariable("A", FormulaValue.NewBlank(FormulaType.String)); + AssertUpdate("B-->;C-->0;"); + } + + [Fact] + public void BasicRecalcBoolean() + { + var engine = new RecalcEngine(); + engine.UpdateVariable("A", true); + engine.SetFormula("B", "Not(A)", OnUpdate); + engine.SetFormula("C", "A Or false", OnUpdate); + AssertUpdate("B-->False;C-->True;"); + + engine.UpdateVariable("A", false); + AssertUpdate("B-->True;C-->False;"); + + // Ensure we can update to null. + engine.UpdateVariable("A", FormulaValue.NewBlank(FormulaType.Boolean)); + AssertUpdate("B-->True;C-->False;"); + } + + [Fact] + public void BasicRecalcGuid() + { + var engine = new RecalcEngine(); + engine.UpdateVariable("A", new Guid("0f8fad5b-D9CB-469f-a165-70867728950E")); + engine.SetFormula("B", "A", OnUpdate); + AssertUpdate("B-->0f8fad5b-d9cb-469f-a165-70867728950e;"); + + engine.UpdateVariable("A", new Guid("f9168c5e-CEB2-4FAA-b6bf-329bf39fa1e4")); + AssertUpdate("B-->f9168c5e-ceb2-4faa-b6bf-329bf39fa1e4;"); + + // Ensure we can update to null. + engine.UpdateVariable("A", FormulaValue.NewBlank(FormulaType.Guid)); + AssertUpdate("B-->;"); + } + + [Fact] + public void BasicRecalcDateTime() + { + var engine = new RecalcEngine(); + engine.UpdateVariable("A", new DateTime(2023, 09, 06, 03, 12, 45)); + engine.SetFormula("B", "Hour(DateAdd(A,20,TimeUnit.Minutes))", OnUpdate); + engine.SetFormula("C", "Minute(DateAdd(A,20,TimeUnit.Minutes))", OnUpdate); + AssertUpdate("B-->3;C-->32;"); + + engine.UpdateVariable("A", new DateTime(2023, 09, 06, 12, 45, 45)); + AssertUpdate("B-->13;C-->5;"); + + // Ensure we can update to null. + // null is treated as 0 or DateTime(1899,12,30,0,0,0,0) + engine.UpdateVariable("A", FormulaValue.NewBlank(FormulaType.DateTime)); + AssertUpdate("B-->0;C-->20;"); + } + + [Fact] + public void BasicRecalcTime() + { + var engine = new RecalcEngine(); + engine.UpdateVariable("A", new TimeSpan(03, 12, 45)); + engine.SetFormula("B", "Hour(DateAdd(A,20,TimeUnit.Minutes))", OnUpdate); + engine.SetFormula("C", "Minute(DateAdd(A,20,TimeUnit.Minutes))", OnUpdate); + AssertUpdate("B-->3;C-->32;"); + + engine.UpdateVariable("A", new TimeSpan(12, 45, 45)); + AssertUpdate("B-->13;C-->5;"); + + // Ensure we can update to null. + // null is treated as 0 or Time(0,0,0,0) + engine.UpdateVariable("A", FormulaValue.NewBlank(FormulaType.Time)); + AssertUpdate("B-->0;C-->20;"); + } + + // depend on grand child directly + [Fact] + public void Recalc2() + { + var engine = new RecalcEngine(); + engine.UpdateVariable("A", 1); + engine.SetFormula("B", "A*10", OnUpdate); + AssertUpdate("B-->10;"); + + engine.SetFormula("C", "B+5", OnUpdate); + AssertUpdate("C-->15;"); + + // depend on grand child directly + engine.SetFormula("D", "B+A", OnUpdate); + AssertUpdate("D-->11;"); + + // Updating A will recalc both D and B. + // But D also depends on B, so verify D pulls new value of B. + engine.UpdateVariable("A", 2); + + // Batched up (we don't double fire) + AssertUpdate("B-->20;C-->25;D-->22;"); + } + + [Fact] + public void DeleteFormula() + { + var engine = new RecalcEngine(); + + engine.UpdateVariable("A", 1); + engine.SetFormula("B", "A*10", OnUpdate); + engine.SetFormula("C", "B+5", OnUpdate); + engine.SetFormula("D", "B+A", OnUpdate); + + Assert.Throws(() => + engine.DeleteFormula("X")); + + Assert.Throws(() => + engine.DeleteFormula("B")); + + engine.DeleteFormula("D"); + Assert.False(engine.TryGetByName("D", out var retD)); + + engine.DeleteFormula("C"); + Assert.False(engine.TryGetByName("C", out var retC)); + + // After C and D are deleted, deleting B should pass + engine.DeleteFormula("B"); + + // Ensure B is gone + engine.Check("B"); + Assert.Throws(() => + engine.Check("B").ThrowOnErrors()); + } + + // Don't fire for formulas that aren't touched by an update + [Fact] + public void RecalcNoExtraCallbacks() + { + var engine = new RecalcEngine(); + engine.UpdateVariable("A1", 1); + engine.UpdateVariable("A2", 5); + + engine.SetFormula("B", "A1+A2", OnUpdate); + AssertUpdate("B-->6;"); + + engine.SetFormula("C", "A2*10", OnUpdate); + AssertUpdate("C-->50;"); + + engine.UpdateVariable("A1", 2); + AssertUpdate("B-->7;"); // Don't fire C, not touched + + engine.UpdateVariable("A2", 7); + AssertUpdate("B-->9;C-->70;"); + } + + private static readonly ParserOptions _opts = new ParserOptions { AllowsSideEffects = true }; + + [Fact] + public void SetFormula() + { + var config = new PowerFxConfig(); + config.EnableSetFunction(); + var engine = new RecalcEngine(config); + + engine.UpdateVariable("A", 1m); + engine.SetFormula("B", "A*2", OnUpdate); + AssertUpdate("B-->2;"); + + // Can't set formulas, they're read only + var check = engine.Check("Set(B, 12)"); + Assert.False(check.IsSuccess); + + // Set() function triggers recalc chain. + engine.Eval("Set(A,2)", options: _opts); + AssertUpdate("B-->4;"); + + // Compare Before/After set within an expression. + // Before (A,B) = 2,4 + // After (A,B) = 3,6 + var result = engine.Eval("With({x:A, y:B}, Set(A,3); x & y & A & B)", options: _opts); + Assert.Equal("2436", result.ToObject()); + + AssertUpdate("B-->6;"); + } + + [Fact] + public void UserDefinitionOnUpdateTest() + { + var config = new PowerFxConfig(); + config.EnableSetFunction(); + var engine = new RecalcEngine(config); + + engine.UpdateVariable("A", 1m); + engine.AddUserDefinitions("B=A*2;C=A*B;", onUpdate: OnUpdate); + AssertUpdate("B-->2;C-->2;"); + + // Can't set formulas, they're read only + var check = engine.Check("Set(B, 12)"); + Assert.False(check.IsSuccess); + + // Set() function triggers recalc chain. + engine.Eval("Set(A,10)", options: _opts); + AssertUpdate("B-->20;C-->200;"); + + // Compare Before/After set within an expression. + // Before (A,B) = 10,20 + // After (A,B) = 3,6 + var result = engine.Eval("With({x:A, y:B}, Set(A,3); x & y & A & B)", options: _opts); + Assert.Equal("102036", result.ToObject()); + } + + [Fact] + public void BasicEval() + { + var engine = new RecalcEngine(); + engine.UpdateVariable("M", 10.0); + engine.UpdateVariable("M2", -4); + var result = engine.Eval("M + Abs(M2)"); + Assert.Equal(14.0, ((NumberValue)result).Value); + } + + [Fact] + public void FormulaErrorUndefined() + { + var engine = new RecalcEngine(); + + // formula fails since 'B' is undefined. + Assert.Throws(() => + engine.SetFormula("A", "B*2", OnUpdate)); + } + + [Fact] + public void CantChangeType() + { + var engine = new RecalcEngine(); + engine.UpdateVariable("a", FormulaValue.New(12)); + + // not supported: Can't change a variable's type. + Assert.Throws(() => + engine.UpdateVariable("a", FormulaValue.New("str"))); + } + + [Fact] + public void FormulaCantRedefine() + { + var engine = new RecalcEngine(); + + engine.SetFormula("A", "2", OnUpdate); + + // Can't redefine an existing formula. + Assert.Throws(() => + engine.SetFormula("A", "3", OnUpdate)); + } + + [Theory] + [InlineData( + "func1(x:Number/*comment*/): Number = x * 10;\nfunc2(x:Number): Number = y1 * 10;", + null, + true)] + [InlineData( + "foo(x:Number):Number = If(x=0,foo(1),If(x=1,foo(2),If(x=2,Float(2))));", + "foo(Float(0))", + false, + 2.0)] + [InlineData( + "foo():Blank = foo();", + "foo()", + true)] + [InlineData( + "Add(x: Number, y:Number): Number = x + y; Foo(x: Number): Number = Abs(x);", + "Add(10, Foo(-10))", + false, + 20.0)] + [InlineData( + "Add(x: Number, y:Number): Number = x + y; Foo(x: Number): Number = Abs(x);", + "Add(1 , Add(1 , Add(1 , Add(1 , Add(1 , Foo(-1))))))", + false, + 6.0)] + [InlineData( + "TriplePowerSum(x: Number, y:Number, z:Number): Number = Power(2,x) + Power(2,y) + Power(2,z);", + "TriplePowerSum(1 , 2, 3)", + false, + 14.0)] + + // Recursive calls are not allowed + [InlineData( + "hailstone(x:Number):Number = If(Not(x = 1), If(Mod(x, 2)=0, hailstone(x/2), hailstone(3*x+1)), x);", + "hailstone(Float(192))", + true)] + [InlineData( + "odd(number:Number):Boolean = If(number = 0, false, even(Abs(number)-1)); even(number:Number):Boolean = If(number = 0, true, odd(Abs(number)-1));", + "odd(17)", + true)] + [InlineData( + "odd(number:Number):Boolean = If(number = 0, false, even(Abs(number)-1)); even(number:Number):Boolean = If(number = 0, true, odd(Abs(number)-1));", + "even(17)", + true)] + [InlineData( + "odd(number:Number):Boolean = If(number = 0, false, even(If(number<0,-number,number)-1)); even(number:Decimal):Boolean = If(number = 0, true, odd(If(number<0,-number,number)-1));", + "odd(17)", + true)] + [InlineData( + "odd(number:Number):Boolean = If(number = 0, false, even(If(number<0,-number,number)-1)); even(number:Decimal):Boolean = If(number = 0, true, odd(If(number<0,-number,number)-1));", + "even(17)", + true)] + + // Redefinition is not allowed + [InlineData( + "foo():Blank = foo(); foo():Number = x + 1;", + null, + true)] + + // Syntax error + [InlineData( + "foo():Blank = x[", + null, + true)] + + // Incorrect parameters + [InlineData( + "foo(x:Number):Number = x + 1;", + "foo(False)", + true)] + [InlineData( + "foo(x:Number):Number = x + 1;", + "foo(Table( { Value: \"Strawberry\" }, { Value: \"Vanilla\" } ))", + true)] + [InlineData( + "foo(x:Number):Number = x + 1;", + "foo(Float(1))", + false, + 2.0)] + + public void UserDefinedFunctionTest(string udfExpression, string expression, bool expectedError, double expected = 0) + { + var config = new PowerFxConfig() + { + MaxCallDepth = 100 + }; + var recalcEngine = new RecalcEngine(config); + + try + { + recalcEngine.AddUserDefinedFunction(udfExpression, CultureInfo.InvariantCulture); + + var check = recalcEngine.Check(expression); + + Assert.Equal(check.IsSuccess, !expectedError); + + var result = recalcEngine.Eval(expression); + var fvExpected = FormulaValue.New(expected); + + Assert.Equal(fvExpected.AsDecimal(), result.AsDecimal()); + } + catch (Exception ex) + { + Assert.True(expectedError, ex.Message); + } + } + + [Theory] + [InlineData("foo(x: Number, y:Number):Number = x + y;", "foo(1,2)", 3.0)] + [InlineData("foo(x: Number, y:Number):Number = x - Abs(y);", "foo(myArg,1)", 9.0)] + public void UserDefinedFunctionSymbolTableTest(string script, string expression, double expected) + { + var engine = new RecalcEngine(); + var symbolTable = new SymbolTable(); + + engine.UpdateVariable("myArg", FormulaValue.New(10)); + + symbolTable.AddUserDefinedFunction(script, CultureInfo.InvariantCulture, engine.SupportedFunctions, engine.PrimitiveTypes); + + var check = engine.Check(expression, symbolTable: symbolTable); + var result = check.GetEvaluator().Eval(); + var fvExpected = FormulaValue.New(expected); + + Assert.Equal(fvExpected.AsDecimal(), result.AsDecimal()); + } + + [Theory] + [InlineData("foo(x:Number):Number = x + missingArg1 - missingArg2;")] + [InlineData("foo(x:Number):Number = x + ;")] + public void DefinedFunctionsErrorsTest(string script) + { + var engine = new RecalcEngine(); + + Assert.Throws(() => engine.AddUserDefinedFunction(script, CultureInfo.InvariantCulture)); + } + + // Overloads and conflict + [Theory] + [InlineData("foo(Text:Number):Number = Text;", 1)] // param name conflicts with type name + [InlineData("foo(K1:Number):Number = K1;", 1)] // param takes precedence + [InlineData("foo(param:Number):Number = K1;", 9999)] // param takes precedence + public void FunctionPrecedenceTest(string script, double expected) + { + SymbolTable st = new SymbolTable { DebugName = "Extras" }; + st.AddConstant("K1", FormulaValue.New(9999)); + + var engine = new RecalcEngine(); + engine.AddUserDefinedFunction(script, symbolTable: st); + + var check = engine.Check("foo(1)"); + Assert.True(check.IsSuccess); + Assert.Equal(FormulaType.Number, check.ReturnType); + + var result = check.GetEvaluator().Eval(); + Assert.Equal(expected, result.AsDouble()); + } + + [Theory] + + // Return value with side effectful UDF + [InlineData( + "F1(x:Number) : Number = { Set(a, x); a+1; };", + "F1(123)", + false, + null, + 124)] + + // Mismatch return value with side effectful UDF + [InlineData( + "F1(x:Number) : Boolean = { Set(a, x); Today(); };", + null, + true, + "AddUserDefinedFunction", + 0)] + + public void ImperativeUserDefinedFunctionTest(string udfExpression, string expression, bool expectedError, string expectedMethodFailure, double expected) + { + var config = new PowerFxConfig(); + config.EnableSetFunction(); + var recalcEngine = new RecalcEngine(config); + recalcEngine.UpdateVariable("a", 1m); + + try + { + recalcEngine.AddUserDefinedFunction(udfExpression, CultureInfo.InvariantCulture, symbolTable: recalcEngine.EngineSymbols, allowSideEffects: true); + + var result = recalcEngine.Eval(expression, options: _opts); + var fvExpected = FormulaValue.New(expected); + + Assert.Equal(fvExpected.AsDecimal(), result.AsDecimal()); + Assert.False(expectedError); + } + catch (Exception ex) + { + Assert.True(expectedError, ex.Message); + Assert.Contains(expectedMethodFailure, ex.StackTrace); + } + } + + // Binding to inner functions does not impact outer functions. + [Fact] + public void FunctionInner() + { + // Inner table + SymbolTable stInner = SymbolTable.WithPrimitiveTypes(); + stInner.AddUserDefinedFunction("Func1() : Text = \"inner\";"); + + SymbolTable st = SymbolTable.WithPrimitiveTypes(); + st.AddUserDefinedFunction("Func2() : Text = Func1() & \"2\";", symbolTable: stInner); + + var engine = new RecalcEngine(); + engine.AddUserDefinedFunction("Func1() : Text = \"Outer\";", symbolTable: st); + + // Func1() here should bind to the top-level "outer" one, not the "inner" one. + var result = engine.EvalAsync("Func1() & Func2()", default, symbolTable: st).Result; + var str = ((StringValue)result).Value; + Assert.Equal("Outerinner2", str); + } + + [Fact] + public void PropagateNull() + { + var engine = new RecalcEngine(); + engine.SetFormula("A", expr: "Blank()", OnUpdate); + engine.SetFormula("B", "A", OnUpdate); + + var b = engine.GetValue("B"); + Assert.True(b is BlankValue); + } + + // Record with null values. + [Fact] + public void ChangeRecord() + { + var engine = new RecalcEngine(); + + engine.UpdateVariable("R", FormulaValue.NewRecordFromFields( + new NamedValue("F1", FormulaValue.NewBlank(FormulaType.Number)), + new NamedValue("F2", FormulaValue.New(6.0)))); + + engine.SetFormula("A", "R.F2 + 3 + R.F1", OnUpdate); + AssertUpdate("A-->9;"); + + engine.UpdateVariable("R", FormulaValue.NewRecordFromFields( + new NamedValue("F1", FormulaValue.New(2.0)), + new NamedValue("F2", FormulaValue.New(7.0)))); + AssertUpdate("A-->12;"); + } + + [Fact] + public void ChangeRecord_Decimal() + { + var engine = new RecalcEngine(); + + engine.UpdateVariable("R", FormulaValue.NewRecordFromFields( + new NamedValue("F1", FormulaValue.NewBlank(FormulaType.Decimal)), + new NamedValue("F2", FormulaValue.New(6)))); + + engine.SetFormula("A", "R.F2 + 3 + R.F1", OnUpdate); + AssertUpdate("A-->9;"); + + engine.UpdateVariable("R", FormulaValue.NewRecordFromFields( + new NamedValue("F1", FormulaValue.New(2)), + new NamedValue("F2", FormulaValue.New(7)))); + AssertUpdate("A-->12;"); + } + + [Fact] + public void CheckFunctionCounts() + { + var config = new PowerFxConfig(); + config.EnableJsonFunctions(); + + var engine1 = new Engine(config); + + // Pick a function in core but not implemented in interpreter. + var nyiFunc = BuiltinFunctionsCore.ISOWeekNum; + +#pragma warning disable CS0618 // Type or member is obsolete + Assert.Contains(nyiFunc, engine1.Functions.Functions); +#pragma warning restore CS0618 // Type or member is obsolete + + // RecalcEngine will add the interpreter's functions. + var engine2 = new RecalcEngine(config); + +#pragma warning disable CS0618 // Type or member is obsolete + Assert.DoesNotContain(nyiFunc, engine2.Functions.Functions); +#pragma warning restore CS0618 // Type or member is obsolete + + Assert.True(engine2.FunctionCount > 100); + + // Spot check some known functions + Assert.NotEmpty(engine2.Functions.WithName("Cos")); + Assert.NotEmpty(engine2.Functions.WithName("ParseJSON")); + } + + [Fact] + public void CheckSuccess() + { + var engine = new RecalcEngine(); + var result = engine.Check( + "3*2+x", + RecordType.Empty().Add( + new NamedFormulaType("x", FormulaType.Number))); + + Assert.True(result.IsSuccess); + Assert.True(result.ReturnType is NumberType); + Assert.Single(result.TopLevelIdentifiers); + Assert.Equal("x", result.TopLevelIdentifiers.First()); + } + + [Fact] + public void CanRunWithWarnings() + { + var config = new PowerFxConfig(Features.None); + var engine = new RecalcEngine(config); + + var result = engine.Check("T.Var = 23", RecordType.Empty() + .Add(new NamedFormulaType("T", RecordType.Empty().Add(new NamedFormulaType("Var", FormulaType.String))))); + + Assert.True(result.IsSuccess); + Assert.Equal(1, result.Errors.Count(x => x.IsWarning)); + } + + [Fact] + public void CheckSuccessWarning() + { + var engine = new RecalcEngine(); + + // issues a warning, verify it's still successful. + var result = engine.Check("Filter([1,2,3],true)"); + + Assert.True(result.IsSuccess); + Assert.Equal(1, result.Errors.Count(x => x.Severity == ErrorSeverity.Warning)); + } + + [Fact] + public void CheckParseError() + { + var engine = new RecalcEngine(); + var result = engine.Check("3*1+"); + + Assert.False(result.IsSuccess); + Assert.StartsWith("Error 4-4: Expected an operand", result.Errors.First().ToString()); + } + + [Fact] + public void CheckBindError() + { + var engine = new RecalcEngine(); + var result = engine.Check("3+foo+2"); // foo is undefined + + Assert.False(result.IsSuccess); + Assert.Single(result.Errors); + Assert.StartsWith("Error 2-5: Name isn't valid. 'foo' isn't recognized", result.Errors.First().ToString()); + } + + [Fact] + public void CheckLambdaBindError() + { + var engine = new RecalcEngine(); + var result = engine.Check("Filter([1,2,3] As X, X.Value > foo)"); + + Assert.False(result.IsSuccess); + Assert.Single(result.Errors); + Assert.StartsWith("Error 31-34: Name isn't valid. 'foo' isn't recognized", result.Errors.First().ToString()); + } + + [Fact] + public void CheckDottedBindError() + { + var engine = new RecalcEngine(); + var result = engine.Check("First([1,2,3]).foo"); + + Assert.False(result.IsSuccess); + Assert.Single(result.Errors); + Assert.StartsWith("Error 14-18: Name isn't valid. 'foo' isn't recognized", result.Errors.First().ToString()); + } + + [Fact] + public void CheckDottedBindErrorForSingleColumnAccess() + { + var config = new PowerFxConfig(); + var engine = new Engine(config); + var result = engine.Check("[1,2,3].foo"); + Assert.False(result.IsSuccess); + Assert.Single(result.Errors); + Assert.StartsWith("Error 7-11: Deprecated use of '.'. Please use the 'ShowColumns' function instead.", result.Errors.First().ToString()); + } + + [Fact] + public void CheckDottedBindError2() + { + var engine = new RecalcEngine(); + var result = engine.Check("First([]).Value"); + + Assert.False(result.IsSuccess); + Assert.Single(result.Errors); + Assert.StartsWith("Error 9-15: Name isn't valid. 'Value' isn't recognized", result.Errors.First().ToString()); + } + + [Fact] + public void CheckBindEnum() + { + var engine = new RecalcEngine(new PowerFxConfig(Features.None)); + var result = engine.Check("TimeUnit.Hours"); + + Assert.True(result.IsSuccess); + + // The resultant type will be the underlying type of the enum provided to + // check. In the case of TimeUnit, this is StringType + Assert.True(result.ReturnType is StringType); + Assert.Empty(result.TopLevelIdentifiers); + } + + [Fact] + public void CheckBindErrorWithParseExpression() + { + var engine = new RecalcEngine(); + var result = engine.Check("3+foo+2", RecordType.Empty()); // foo is undefined + + Assert.False(result.IsSuccess); + Assert.Single(result.Errors); + Assert.StartsWith("Error 2-5: Name isn't valid. 'foo' isn't recognized", result.Errors.First().ToString()); + } + + [Fact] + public void CheckSuccessWithParsedExpression() + { + var engine = new RecalcEngine(); + var result = engine.Check( + "3*2+x", + RecordType.Empty().Add( + new NamedFormulaType("x", FormulaType.Number))); + + // Test that parsing worked + Assert.True(result.IsSuccess); + Assert.True(result.ReturnType is NumberType); + Assert.Single(result.TopLevelIdentifiers); + Assert.Equal("x", result.TopLevelIdentifiers.First()); + + // Test evaluation of parsed expression + var recordValue = FormulaValue.NewRecordFromFields( + new NamedValue("x", FormulaValue.New(5.0))); + var formulaValue = result.GetEvaluator().Eval(recordValue); + Assert.Equal(11.0, (double)formulaValue.ToObject()); + } + + // Test Globals + Locals + GetValuator() + [Fact] + public void CheckGlobalAndLocal() + { + var engine = new RecalcEngine(); + engine.UpdateVariable("y", FormulaValue.New(10)); + + var result = engine.Check( + "x+y", + RecordType.Empty().Add( + new NamedFormulaType("x", FormulaType.Number))); + + // Test that parsing worked + Assert.True(result.IsSuccess); + Assert.True(result.ReturnType is NumberType); + + // Test evaluation of parsed expression + var recordValue = FormulaValue.NewRecordFromFields( + new NamedValue("x", FormulaValue.New(5.0))); + + var formulaValue = result.GetEvaluator().Eval(recordValue); + + Assert.Equal(15.0, (double)formulaValue.ToObject()); + } + + [Fact] + public void RecalcEngineMutateConfig() + { + var config = new PowerFxConfig(); + config.SymbolTable.AddFunction(BuiltinFunctionsCore.Blank); + + var recalcEngine = new Engine(config) + { + SupportedFunctions = new SymbolTable() // clear builtins + }; + + var func = BuiltinFunctionsCore.AsType; // Function not already in engine +#pragma warning disable CS0618 // Type or member is obsolete + Assert.DoesNotContain(func, recalcEngine.Functions.Functions); // didn't get auto-added by engine. +#pragma warning restore CS0618 // Type or member is obsolete + + // We can mutate config after engine is created. + var optionSet = new OptionSet("foo", DisplayNameUtility.MakeUnique(new Dictionary() { { "one key", "one value" } })); + config.SymbolTable.AddFunction(func); + config.SymbolTable.AddEntity(optionSet); + + Assert.True(config.TryGetVariable(new DName("foo"), out _)); +#pragma warning disable CS0618 // Type or member is obsolete + Assert.Contains(func, recalcEngine.Functions.Functions); // function was added to the config. +#pragma warning restore CS0618 // Type or member is obsolete + +#pragma warning disable CS0618 // Type or member is obsolete + Assert.DoesNotContain(BuiltinFunctionsCore.Abs, recalcEngine.Functions.Functions); +#pragma warning restore CS0618 // Type or member is obsolete + } + + [Fact] + public void RecalcEngine_AddFunction_Twice() + { + var config = new PowerFxConfig(); + config.AddFunction(BuiltinFunctionsCore.Blank); + + Assert.Throws(() => config.AddFunction(BuiltinFunctionsCore.Blank)); + } + + [Fact] + public void RecalcEngine_FunctionOrdering1() + { + var config = new PowerFxConfig(Features.PowerFxV1); + config.AddFunction(new TestFunctionMultiply()); + config.AddFunction(new TestFunctionSubstract()); + + var engine = new RecalcEngine(config); + var result = engine.Eval("Func(7, 11)"); + + Assert.IsType(result); + + // Multiply function is first and a valid overload so that's the one we use as coercion is valid for this one + Assert.Equal(77.0, (result as NumberValue).Value); + } + + [Fact] + public void RecalcEngine_FunctionOrdering2() + { + var config = new PowerFxConfig(Features.PowerFxV1); + config.AddFunction(new TestFunctionSubstract()); + config.AddFunction(new TestFunctionMultiply()); + + var engine = new RecalcEngine(config); + var result = engine.Eval("Func(7, 11)"); + + Assert.IsType(result); + + // Substract function is first and a valid overload so that's the one we use as coercion is valid for this one + Assert.Equal(-4.0, (result as NumberValue).Value); + } + + private class TestFunctionMultiply : CustomTexlFunction + { + public override bool IsSelfContained => true; + + public TestFunctionMultiply() + : base(DPath.Root, "Func", FunctionCategories.MathAndStat, DType.Number, null, DType.Number, DType.String) + { + } + + public override IEnumerable GetSignatures() + { + yield return new[] { TexlStrings.IsBlankArg1 }; + } + + public override Task InvokeAsync(IServiceProvider serviceProvider, FormulaValue[] args, CancellationToken cancellationToken) + { + var arg0 = args[0] as NumberValue; + var arg1 = args[1] as StringValue; + + return Task.FromResult(NumberValue.New(arg0.Value * double.Parse(arg1.Value))); + } + } + + private class TestFunctionSubstract : CustomTexlFunction + { + public override bool IsSelfContained => true; + + public TestFunctionSubstract() + : base(DPath.Root, "Func", FunctionCategories.MathAndStat, DType.Number, null, DType.String, DType.Number) + { + } + + public override IEnumerable GetSignatures() + { + yield return new[] { TexlStrings.IsBlankArg1 }; + } + + public override Task InvokeAsync(IServiceProvider serviceProvider, FormulaValue[] args, CancellationToken cancellationToken) + { + var arg0 = args[0] as StringValue; + var arg1 = args[1] as NumberValue; + + return Task.FromResult(NumberValue.New(double.Parse(arg0.Value) - arg1.Value)); + } + } + + [Fact] + public void OptionSetChecks() + { + var config = new PowerFxConfig(); + + var optionSet = new OptionSet("OptionSet", DisplayNameUtility.MakeUnique(new Dictionary() + { + { "option_1", "Option1" }, + { "option_2", "Option2" } + })); + + config.AddOptionSet(optionSet); + var recalcEngine = new RecalcEngine(config); + + var checkResult = recalcEngine.Check("OptionSet.Option1 <> OptionSet.Option2"); + Assert.True(checkResult.IsSuccess); + } + + [Theory] + + // Text() returns the display name of the input option set value + [InlineData("Text(OptionSet.option_1)", "Option1")] + [InlineData("Text(OptionSet.Option1)", "Option1")] + [InlineData("Text(Option1)", "Option1")] + [InlineData("Text(If(1<0, Option1))", null)] + + // OptionSetInfo() returns the logical name of the input option set value + [InlineData("OptionSetInfo(OptionSet.option_1)", "option_1")] + [InlineData("OptionSetInfo(OptionSet.Option1)", "option_1")] + [InlineData("OptionSetInfo(Option1)", "option_1")] + [InlineData("OptionSetInfo(If(1<0, Option1))", "")] + public async void OptionSetInfoTests(string expression, string expected) + { + var optionSet = new OptionSet("OptionSet", DisplayNameUtility.MakeUnique(new Dictionary() + { + { "option_1", "Option1" }, + { "option_2", "Option2" } + })); + + optionSet.TryGetValue(new DName("option_1"), out var option1); + + var symbol = new SymbolTable(); + var option1Solt = symbol.AddVariable("Option1", FormulaType.OptionSetValue, null); + var symValues = new SymbolValues(symbol); + symValues.Set(option1Solt, option1); + + var config = new PowerFxConfig() { SymbolTable = symbol }; +#pragma warning disable CS0618 // Type or member is obsolete + config.EnableOptionSetInfo(); +#pragma warning restore CS0618 // Type or member is obsolete + config.AddOptionSet(optionSet); + var recalcEngine = new RecalcEngine(config); + + var result = await recalcEngine.EvalAsync(expression, CancellationToken.None, symValues).ConfigureAwait(false); + Assert.Equal(expected, result.ToObject()); + } + + [Theory] + [InlineData("Text(OptionSet)")] + + [InlineData("OptionSetInfo(OptionSet)")] + [InlineData("OptionSetInfo(\"test\")")] + [InlineData("OptionSetInfo(1)")] + [InlineData("OptionSetInfo(true)")] + [InlineData("OptionSetInfo(Color.Red)")] + public async Task OptionSetInfoNegativeTest(string expression) + { + var optionSet = new OptionSet("OptionSet", DisplayNameUtility.MakeUnique(new Dictionary() + { + { "option_1", "Option1" }, + { "option_2", "Option2" } + })); + + var config = new PowerFxConfig(Features.None); + config.AddOptionSet(optionSet); + var recalcEngine = new RecalcEngine(config); + var checkResult = recalcEngine.Check(expression, RecordType.Empty()); + Assert.False(checkResult.IsSuccess); + } + + [Fact] + public void OptionSetResultType() + { + var config = new PowerFxConfig(); + + var optionSet = new OptionSet("FooOs", DisplayNameUtility.MakeUnique(new Dictionary() + { + { "option_1", "Option1" }, + { "option_2", "Option2" } + })); + + config.AddOptionSet(optionSet); + var recalcEngine = new RecalcEngine(config); + + var checkResult = recalcEngine.Check("FooOs.Option1"); + Assert.True(checkResult.IsSuccess); + var osvaluetype = Assert.IsType(checkResult.ReturnType); + Assert.Equal("FooOs", osvaluetype.OptionSetName); + } + + [Fact] + public void OptionSetChecksWithMakeUniqueCollision() + { + var config = new PowerFxConfig(); + + var optionSet = new OptionSet("OptionSet", DisplayNameUtility.MakeUnique(new Dictionary() + { + { "foo", "Option1" }, + { "bar", "Option2" }, + { "baz", "foo" } + })); + + config.AddEntity(optionSet, new DName("SomeDisplayName")); + var recalcEngine = new RecalcEngine(config); + + var checkResult = recalcEngine.Check("SomeDisplayName.Option1 <> SomeDisplayName.'foo (baz)'"); + Assert.True(checkResult.IsSuccess); + } + + [Fact] + public void EmptyEnumStoreTest() + { + var config = PowerFxConfig.BuildWithEnumStore(new EnumStoreBuilder()); + + var recalcEngine = new RecalcEngine(config); + + var checkResult = recalcEngine.Check("SortOrder.Ascending"); + Assert.True(checkResult.IsSuccess); + Assert.IsType(checkResult.ReturnType); + } + + [Theory] + [InlineData("Text(TestEnum.Choice1)", true)] + [InlineData("\"Label: \" & TestEnum.Choice1", true)] + [InlineData("Value(TestEnum.Choice1)", false)] + [InlineData("TestEnum.Choice1 + 1", false)] + [InlineData("Decimal(TestEnum.Choice1)", false)] + [InlineData("Float(TestEnum.Choice1)", false)] + [InlineData("Boolean(TestEnum.Choice1)", false)] + [InlineData("Boolean([TestEnum.Choice1,TestEnum.Choice2])", false)] + [InlineData("TestEnum.Choice1 And true", false)] + [InlineData("ColorFade(TestEnum.Choice1,10%)", false)] + [InlineData("ColorFade([TestEnum.Choice1,TestEnum.Choice2],10%)", false)] + public void OptionSetBackingTextTests(string expression, bool valid) + { + var enumStoreBuilder = new EnumStoreBuilder(); + enumStoreBuilder.TestOnly_WithCustomEnum(new EnumSymbol( + new DName("TestEnum"), + DType.String, + new Dictionary() + { + { "Choice1", "Choice_1" }, + { "Choice2", "Choice_2" }, + })); + var config = PowerFxConfig.BuildWithEnumStore(enumStoreBuilder, features: Features.PowerFxV1); + var recalcEngine = new RecalcEngine(config); + + var checkResult = recalcEngine.Check(expression, RecordType.Empty()); + Assert.Equal(valid, checkResult.IsSuccess); + } + + [Theory] + [InlineData("Text(TestEnum.Choice1)", true)] + [InlineData("\"Label: \" & TestEnum.Choice1", true)] + [InlineData("Value(TestEnum.Choice1)", true)] + [InlineData("TestEnum.Choice1 + 1", false)] // see https://github.com/microsoft/Power-Fx/issues/2229 + [InlineData("Decimal(TestEnum.Choice1)", true)] + [InlineData("Float(TestEnum.Choice1)", true)] + [InlineData("Boolean(TestEnum.Choice1)", false)] + [InlineData("Boolean([TestEnum.Choice1,TestEnum.Choice2])", false)] + [InlineData("TestEnum.Choice1 And true", false)] + [InlineData("ColorFade(TestEnum.Choice1,10%)", false)] + [InlineData("ColorFade([TestEnum.Choice1,TestEnum.Choice2],10%)", false)] + public void OptionSetBackingNumberTests(string expression, bool valid) + { + var enumStoreBuilder = new EnumStoreBuilder(); + enumStoreBuilder.TestOnly_WithCustomEnum(new EnumSymbol( + new DName("TestEnum"), + DType.Number, + new Dictionary() + { + { "Choice1", 1 }, + { "Choice2", 2 }, + })); + var config = PowerFxConfig.BuildWithEnumStore(enumStoreBuilder, features: Features.PowerFxV1); + var recalcEngine = new RecalcEngine(config); + + var checkResult = recalcEngine.Check(expression, RecordType.Empty()); + Assert.Equal(valid, checkResult.IsSuccess); + } + + [Theory] + [InlineData("Text(TestEnum.Choice1)", true)] + [InlineData("\"Label: \" & TestEnum.Choice1", true)] + [InlineData("Value(TestEnum.Choice1)", false)] + [InlineData("TestEnum.Choice1 + 1", false)] + [InlineData("Decimal(TestEnum.Choice1)", false)] + [InlineData("Float(TestEnum.Choice1)", false)] + [InlineData("Boolean(TestEnum.Choice1)", true)] + [InlineData("Boolean([TestEnum.Choice1,TestEnum.Choice2])", true)] + [InlineData("TestEnum.Choice1 And true", true)] + [InlineData("ColorFade(TestEnum.Choice1,10%)", false)] + [InlineData("ColorFade([TestEnum.Choice1,TestEnum.Choice2],10%)", false)] + public void OptionSetBackingBooleanTests(string expression, bool valid) + { + var enumStoreBuilder = new EnumStoreBuilder(); + enumStoreBuilder.TestOnly_WithCustomEnum(new EnumSymbol( + new DName("TestEnum"), + DType.Boolean, + new Dictionary() + { + { "Choice1", true }, + { "Choice2", false }, + }, + canCoerceToBackingKind: true)); + var config = PowerFxConfig.BuildWithEnumStore(enumStoreBuilder, features: Features.PowerFxV1); + var recalcEngine = new RecalcEngine(config); + + var checkResult = recalcEngine.Check(expression, RecordType.Empty()); + Assert.Equal(valid, checkResult.IsSuccess); + } + + [Theory] + [InlineData("Text(TestEnum.Choice1)", true)] + [InlineData("\"Label: \" & TestEnum.Choice1", true)] + [InlineData("Value(TestEnum.Choice1)", false)] + [InlineData("TestEnum.Choice1 + 1", false)] + [InlineData("Decimal(TestEnum.Choice1)", false)] + [InlineData("Float(TestEnum.Choice1)", false)] + [InlineData("Boolean(TestEnum.Choice1)", false)] + [InlineData("Boolean([TestEnum.Choice1,TestEnum.Choice2])", false)] + [InlineData("TestEnum.Choice1 And true", false)] + [InlineData("ColorFade(TestEnum.Choice1,10%)", false)] + [InlineData("ColorFade([TestEnum.Choice1,TestEnum.Choice2],10%)", false)] + public void OptionSetBackingColorTests(string expression, bool valid) + { + var enumStoreBuilder = new EnumStoreBuilder(); + enumStoreBuilder.TestOnly_WithCustomEnum(new EnumSymbol( + new DName("TestEnum"), + DType.Color, + new Dictionary() + { + { "Choice1", 0 }, + { "Choice2", 255 }, + })); + var config = PowerFxConfig.BuildWithEnumStore(enumStoreBuilder, features: Features.PowerFxV1); + var recalcEngine = new RecalcEngine(config); + + var checkResult = recalcEngine.Check(expression, RecordType.Empty()); + Assert.Equal(valid, checkResult.IsSuccess); + } + + [Fact] + public void TestWithTimeZoneInfo() + { + // CultureInfo not set in PowerFxConfig as we use Symbols + var pfxConfig = new PowerFxConfig(Features.None); + var recalcEngine = new RecalcEngine(pfxConfig); + var symbols = new RuntimeConfig(); + + // 10/30/22 is the date where DST applies in France (https://www.timeanddate.com/time/change/france/paris) + // So adding 2 hours to 1:34am will result in 2:34am + var frTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Romance Standard Time"); + symbols.SetTimeZone(frTimeZone); + + var jaCulture = new CultureInfo("ja-JP"); + symbols.SetCulture(jaCulture); + + Assert.Same(frTimeZone, symbols.GetService()); + Assert.Same(jaCulture, symbols.GetService()); + + var fv = recalcEngine.EvalAsync( + @"Text(DateAdd(DateTimeValue(""dimanche 30 octobre 2022 01:34:03"", ""fr-FR""), ""2"", ""hours""), ""dddd, MMMM dd, yyyy hh:mm:ss"")", + CancellationToken.None, + runtimeConfig: symbols).Result; + + Assert.NotNull(fv); + Assert.IsType(fv); + + // Then we convert the result to Japanese date/time format (English equivalent: "Sunday, October 30, 2022 02:34:03") + Assert.Equal("日曜日, 10月 30, 2022 02:34:03", fv.ToObject()); + } + + [Fact] + public void FunctionServices() + { + var engine = new RecalcEngine(); + var values = new RuntimeConfig(); + values.SetRandom(new TestRandService()); + + // Rand + var result = engine.EvalAsync("Rand()", CancellationToken.None, runtimeConfig: values).Result; + Assert.Equal(0.5, result.ToObject()); + + // 1 service can impact multiple functions. + // It also doesn't replace the function, so existing function logic (errors, range checks, etc) still is used. + // RandBetween maps 0.5 to 6. + result = engine.EvalAsync("RandBetween(1,10)", CancellationToken.None, runtimeConfig: values).Result; + Assert.Equal(6.0m, result.ToObject()); + } + + [Fact] + public async Task FunctionServicesHostBug() + { + // Need to protect against bogus values from a poorly implemented service. + // These are exceptions, not ErrorValues, since it's a host bug. + var engine = new RecalcEngine(); + var values = new RuntimeConfig(); + + // Host bug, service should be 0...1, this is out of range. + var buggyService = new TestRandService { _value = 9999 }; + + values.AddService(buggyService); + + try + { + await engine.EvalAsync("Rand()", CancellationToken.None, runtimeConfig: values).ConfigureAwait(false); + Assert.False(true); // should have thrown on illegal IRandomService service. + } + catch (InvalidOperationException e) + { + var name = typeof(TestRandService).FullName; + Assert.Equal($"IRandomService ({name}) returned an illegal value 9999. Must be between 0 and 1", e.Message); + } + } + + [Fact] + public async Task ExecutingWithRemovedVarFails() + { + var symTable = new SymbolTable(); + var slot = symTable.AddVariable("x", FormulaType.Number, null); + + var engine = new RecalcEngine(); + var result = engine.Check("x+1", symbolTable: symTable); + Assert.True(result.IsSuccess); + + var eval = result.GetEvaluator(); + var symValues = symTable.CreateValues(); + symValues.Set(slot, FormulaValue.New(10.0)); + + var result1 = await eval.EvalAsync(CancellationToken.None, symValues).ConfigureAwait(false); + Assert.Equal(11.0, result1.ToObject()); + + // Adding a variable is ok. + var slotY = symTable.AddVariable("y", FormulaType.Number, null); + result1 = await eval.EvalAsync(CancellationToken.None, symValues).ConfigureAwait(false); + Assert.Equal(11.0, result1.ToObject()); + + // Executing an existing IR fails if it uses a deleted variable. + symTable.RemoveVariable("x"); + await Assert.ThrowsAsync(async () => await eval.EvalAsync(CancellationToken.None, symValues).ConfigureAwait(false)).ConfigureAwait(false); + + // Even re-adding with same type still fails. + // (somebody could have re-added with a different type) + var slot2 = symTable.AddVariable("x", FormulaType.Number, null); + symValues.Set(slot2, FormulaValue.New(20.0)); + + await Assert.ThrowsAsync(async () => await eval.EvalAsync(CancellationToken.None, symValues).ConfigureAwait(false)).ConfigureAwait(false); + } + + // execute w/ missing var (never adding to SymValues) + [Fact] + public async Task ExecutingWithMissingVar() + { + var engine = new Engine(new PowerFxConfig()); + + var recordType = RecordType.Empty() + .Add("x", FormulaType.Number) + .Add("y", FormulaType.Number); + + var result = engine.Check("x+y", recordType); + var eval = result.GetEvaluator(); + + var recordXY = RecordValue.NewRecordFromFields( + new NamedValue("x", FormulaValue.New(10.0)), + new NamedValue("y", FormulaValue.New(100.0))); + + var result2 = eval.Eval(recordXY); + Assert.Equal(110.0, result2.ToObject()); + + // Missing y , treated as blank (0) + var recordX = RecordValue.NewRecordFromFields( + new NamedValue("x", FormulaValue.New(10.0))); + result2 = eval.Eval(recordX); + Assert.Equal(10.0, result2.ToObject()); + } + + [Theory] + [InlineData("ThisRecord.Field2", "_field2")] // row scope, no conflcit + [InlineData("Task", "_fieldTask")] // row scope wins the conflict, it's closer. + [InlineData("[@Task]", "_globalTask")] // global scope + [InlineData("[@Task] & Task", "_globalTask_fieldTask")] // both in same expression + [InlineData("[@Task] & ThisRecord.Task", "_globalTask_fieldTask")] // both, fully unambiguous. + [InlineData("With({Task : true}, Task)", true)] // With() wins, shadows rowscope. + [InlineData("With({Task : true}, ThisRecord.Task)", true)] // With() also has ThisRecord, shadows previous rowscope. + [InlineData("With({Task : true}, [@Task])", "_globalTask")] // Globals. + [InlineData("With({Task : true} As T2, Task)", "_fieldTask")] // As avoids the conflict. + [InlineData("With({Task : true} As T2, ThisRecord.Task)", "_fieldTask")] // As avoids the conflict. + [InlineData("With({Task : true} As T2, ThisRecord.Field2)", "_field2")] // As avoids the conflict. + + // Errors + [InlineData("[@Field2]")] // error, doesn't exist in global scope. + [InlineData("With({Task : true}, ThisRecord.Field2)")] // Error. ThisRecord doesn't union, it refers exclusively to With(). + public void DisambiguationTest(string expr, object expected = null) + { + var engine = new RecalcEngine(); + + // Setup Global "Task", and RowScope with "Task" field. + var record = FormulaValue.NewRecordFromFields( + new NamedValue("Task", FormulaValue.New("_fieldTask")), + new NamedValue("Field2", FormulaValue.New("_field2"))); + + var globals = new SymbolTable(); + var slot = globals.AddVariable("Task", FormulaType.String, null); + + var rowScope = ReadOnlySymbolTable.NewFromRecord(record.Type, allowThisRecord: true); + + // ensure rowScope is listed first since that should get higher priority + var symbols = ReadOnlySymbolTable.Compose(rowScope, globals); + + // Values + var rowValues = ReadOnlySymbolValues.NewFromRecord(rowScope, record); + var globalValues = globals.CreateValues(); + globalValues.Set(slot, FormulaValue.New("_globalTask")); + + var runtimeConfig = new RuntimeConfig + { + Values = symbols.CreateValues(globalValues, rowValues) + }; + + var check = engine.Check(expr, symbolTable: symbols); // never throws + + if (expected == null) + { + Assert.False(check.IsSuccess); + return; + } + + var run = check.GetEvaluator(); + var result = run.Eval(runtimeConfig); + + Assert.Equal(expected, result.ToObject()); + } + + [Fact] + public void GetVariableRecalcEngine() + { + var config = new PowerFxConfig(); + + var engine = new RecalcEngine(config); + engine.UpdateVariable("A", FormulaValue.New(0.0)); + + Assert.True(engine.TryGetVariableType("A", out var type)); + Assert.Equal(FormulaType.Number, type); + + Assert.False(engine.TryGetVariableType("Invalid", out type)); + Assert.Equal(default, type); + + engine.DeleteFormula("A"); + Assert.False(engine.TryGetVariableType("A", out type)); + Assert.Equal(default, type); + } + + [Fact] + public void ComparisonWithMismatchedTypes() + { + foreach ((Features f, ErrorSeverity es) in new[] + { + (Features.PowerFxV1, ErrorSeverity.Severe), + (Features.None, ErrorSeverity.Warning) + }) + { + var config = new PowerFxConfig(f); + var engine = new RecalcEngine(config); + + CheckResult cr = engine.Check(@"If(2 = ""2"", 3, 4 )"); + ExpressionError firstError = cr.Errors.First(); + + Assert.Equal(es, firstError.Severity); + Assert.Equal("Incompatible types for comparison. These types can't be compared: Decimal, Text.", firstError.Message); + } + } + + [Fact] + public void TryGetValueShouldNotThrowOnNonExistingValue() + { + var config = new PowerFxConfig(); + var engine = new RecalcEngine(config); + + var success = engine.TryGetValue("Invalid", out var shouldBeNull); + + Assert.False(success); + Assert.Null(shouldBeNull); + } + + private class TestRandService : IRandomService + { + public double _value = 0.5; + + // Returns between 0 and 1. + public double NextDouble() + { + return _value; + } + } + + [Fact] + public void LookupBuiltinOptionSets() + { + var config = new PowerFxConfig(); + var engine = new RecalcEngine(config); + + // Builtin enums are on engine.SupportedFunctionm + var ok = engine.SupportedFunctions.TryGetSymbolType("Color", out var type); + Assert.True(ok); + + ok = engine.GetCombinedEngineSymbols().TryGetSymbolType("Color", out type); + Assert.True(ok); + + // Wrong type: https://github.com/microsoft/Power-Fx/issues/2342 + } + + [Theory] + [InlineData( + "Point = Type({x : Number, y : Number}); distance(a: Point, b: Point): Number = Sqrt(Power(b.x-a.x, 2) + Power(b.y-a.y, 2));", + "distance({x: 0, y: 0}, {x: 0, y: 5})", + true, + 5.0)] + + // Table types are accepted + [InlineData( + "People = Type([{Id:Number, Age: Number}]); countMinors(p: People): Number = CountRows(Filter(p, Age < 18));", + "countMinors([{Id: 1, Age: 17}, {Id: 2, Age: 21}])", + true, + 1.0)] + [InlineData( + "Numbers = Type([Number]); countEven(nums: Numbers): Number = CountRows(Filter(nums, Mod(Value, 2) = 0));", + "countEven([1,2,3,4,5,6,7,8,9,10])", + true, + 5.0)] + + // Type Aliases are allowed + [InlineData( + "CarYear = Type(Number); Car = Type({Model: Text, ModelYear: CarYear}); createCar(model:Number, year: Number): Car = {Model:model, ModelYear: year};", + "createCar(\"Model Y\", 2024).ModelYear", + true, + 2024.0)] + + // Type definitions order shouldn't matter + [InlineData( + "Person = Type({Id: IdType, Age: Number}); IdType = Type(Number); createUser(id:Number, a: Number): Person = {Id:id, Age: a};", + "createUser(1, 42).Age", + true, + 42.0)] + + // Functions accept record with more/less fields + [InlineData( + "People = Type([{Name: Text, Age: Number}]); countMinors(p: People): Number = CountRows(Filter(p, Age < 18));", + "countMinors([{Name: \"Bob\", Age: 21, Title: \"Engineer\"}, {Name: \"Alice\", Age: 25, Title: \"Manager\"}])", + true, + 0.0)] + [InlineData( + "Employee = Type({Name: Text, Age: Number, Title: Text}); getAge(e: Employee): Number = e.Age;", + "getAge({Name: \"Bob\", Age: 21})", + true, + 21.0)] + [InlineData( + @"Employee = Type({Name: Text, Age: Number, Title: Text}); Employees = Type([Employee]); EmployeeNames = Type([{Name: Text}]); + getNames(e: Employees):EmployeeNames = ShowColumns(e, Name); + getNamesCount(e: EmployeeNames):Number = CountRows(getNames(e));", + "getNamesCount([{Name: \"Jim\", Age:25}, {Name: \"Tony\", Age:42}])", + true, + 2.0)] + [InlineData( + @"Employee = Type({Name: Text, Age: Number, Title: Text}); + getAge(e: Employee): Number = e.Age; + hasNoAge(e: Employee): Number = IsBlank(getAge(e));", + "hasNoAge({Name: \"Bob\", Title: \"CEO\"})", + true, + 1.0)] + + // Types with UDF restricted primitive types resolve successfully + [InlineData( + @"Patient = Type({DOB: DateTimeTZInd, Weight: Decimal, Dummy: None}); + Patients = Type([Patient]); + Dummy():Number = CountRows([]);", + "Dummy()", + true, + 0.0)] + + // Aggregate types with restricted types are not allowed in UDF + [InlineData( + @"Patient = Type({DOB: DateTimeTZInd, Weight: Decimal, Dummy: None}); + Patients = Type([Patient]); + getAnomaly(p: Patients): Patients = Filter(p, Weight < 0);", + "", + false)] + + [InlineData( + @"Patient = Type({Name: Text, Details: {h: Number, w:Decimal}}); + getPatient(): Patient = {Name:""Alice"", Details: {h: 1, w: 2}};", + "", + false)] + + // Cycles not allowed + [InlineData( + "Z = Type([{a: {b: Z}}]);", + "", + false)] + [InlineData( + "X = Type(Y); Y = Type(X);", + "", + false)] + [InlineData( + "C = Type({x: Boolean, y: Date, f: B});B = Type({ x: A }); A = Type([C]);", + "", + false)] + + // Redeclaration not allowed + [InlineData( + "Number = Type(Text);", + "", + false)] + [InlineData( + "Point = Type({x : Number, y : Number}); Point = Type({x : Number, y : Number, z: Number})", + "", + false)] + + // UDFs with body errors should fail + [InlineData( + "S = Type({x:Text}); f():S = ({);", + "", + false)] + + public void UserDefinedTypeTest(string userDefinitions, string evalExpression, bool isValid, double expectedResult = 0) + { + var config = new PowerFxConfig(); + var recalcEngine = new RecalcEngine(config); + var parserOptions = new ParserOptions() + { + AllowsSideEffects = false, + AllowParseAsTypeLiteral = true + }; + + if (isValid) + { + recalcEngine.AddUserDefinitions(userDefinitions, CultureInfo.InvariantCulture); + Assert.Equal(expectedResult, recalcEngine.Eval(evalExpression, options: parserOptions).ToObject()); + } + else + { + Assert.Throws(() => recalcEngine.AddUserDefinitions(userDefinitions, CultureInfo.InvariantCulture)); + } + } + + #region Test + + private readonly StringBuilder _updates = new StringBuilder(); + + private void AssertUpdate(string expected) + { + Assert.Equal(expected, _updates.ToString()); + _updates.Clear(); + } + + private void OnUpdate(string name, FormulaValue newValue) + { + var str = newValue.ToObject()?.ToString(); + + _updates.Append($"{name}-->{str};"); + } + #endregion + } +} diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/RefreshTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/RefreshTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/RefreshTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/RefreshTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/RegExTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/RegExTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/RegExTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/RegExTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/RemoveFunctionTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/RemoveFunctionTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/RemoveFunctionTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/RemoveFunctionTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/SandboxTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/SandboxTests.cs similarity index 99% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/SandboxTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/SandboxTests.cs index ac0e28727..37d5da09a 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/SandboxTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/SandboxTests.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -14,7 +14,7 @@ using Microsoft.PowerFx.Core.Types; using Microsoft.PowerFx.Core.Utils; using Microsoft.PowerFx.Types; using Xunit; -using static Microsoft.PowerFx.Core.Localization.TexlStrings; +using static Microsoft.PowerFx.Core.Localization.TexlStrings; namespace Microsoft.PowerFx.Tests { @@ -32,7 +32,7 @@ namespace Microsoft.PowerFx.Tests var config = new PowerFxConfig() { MaxCallDepth = 10 - }; + }; var opts = new ParserOptions() { NumberIsFloat = true }; var recalcEngine = new RecalcEngine(config); Assert.IsType(recalcEngine.Eval("Abs(Abs(Abs(Abs(Abs(Abs(1))))))", options: opts)); @@ -74,68 +74,68 @@ namespace Microsoft.PowerFx.Tests // Pick a "small" memory size that's large enough to execute basic expressions but will // fail on intentionally large expressions. - private const int DefaultMemorySizeBytes = 100 * 1024; - - // Verify memory limits. - [Theory] - [InlineData(10, 15)] - [InlineData(100, 4)] - [InlineData(50, 20)] - public async Task MemoryLimit(int nWidth, int nDepth) - { - var expr = CreateMemoryExpression(nWidth, nDepth); - await RunExpressionWithMemoryLimit(expr, DefaultMemorySizeBytes, hasGovernorException: true).ConfigureAwait(false); - } - - [Theory] - [InlineData("Len(With({one: \"aaaaaaaaaaaaaaaaaa\"}, Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(one, \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one)))")] - [InlineData("Len(With({one: \"aaaaaaaaaaaaaaaaaabbbbbbbaaaaaaaa\"}, Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(one, \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one)))")] - [InlineData("Len(With({one: \"bcabcabcabcabcaaaaaaaaaaaaaaabbbbbbbaaaacccccccccccaaaa\"}, Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(one, \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one)))")] - [InlineData("Len(With({one: \"aaaaaaaaaaaaaaaaaa\"}, Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(one, \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one)))")] - public async Task SubstituteMemoryLimit(string expr) - { - long maxLength = long.MaxValue; - await RunExpressionWithMemoryLimit(expr, maxLength, hasGovernorException: false).ConfigureAwait(false); - } - - private async Task RunExpressionWithMemoryLimit(string expression, long memorySize, bool hasGovernorException) - { - var config = new PowerFxConfig - { - MaximumExpressionLength = 2000 - }; - - var engine = new RecalcEngine(config); - - var mem = new SingleThreadedGovernor(memorySize); - - var runtimeConfig = new RuntimeConfig(); - runtimeConfig.AddService(mem); - - // Ensure governor traps excessive memory usage. - var eval = engine.EvalAsync(expression, CancellationToken.None, runtimeConfig: runtimeConfig); - if (hasGovernorException) - { - await Assert.ThrowsAsync(async () => await eval.ConfigureAwait(false)).ConfigureAwait(false); - } - else - { - var result = await eval.ConfigureAwait(false); - Assert.IsType(result); - ErrorValue ev = (ErrorValue)result; - Assert.Equal("Overflow", ev.Errors[0].Message); - } - -#if false // https://github.com/microsoft/Power-Fx/issues/971 - // Still traps without the pre-poll. - // The pre-poll may fail the expression before it even executes. - // So skipping pre-poll tests the execution can be aborted. - var gov2 = new TestIgnorePrePollGovernor(DefaultMemorySizeBytes); - runtimeConfig.AddService(gov2); - - await Assert.ThrowsAsync(async () => await engine.EvalAsync(expr, CancellationToken.None, runtimeConfig: runtimeConfig)); -#endif - } + private const int DefaultMemorySizeBytes = 100 * 1024; + + // Verify memory limits. + [Theory] + [InlineData(10, 15)] + [InlineData(100, 4)] + [InlineData(50, 20)] + public async Task MemoryLimit(int nWidth, int nDepth) + { + var expr = CreateMemoryExpression(nWidth, nDepth); + await RunExpressionWithMemoryLimit(expr, DefaultMemorySizeBytes, hasGovernorException: true).ConfigureAwait(false); + } + + [Theory] + [InlineData("Len(With({one: \"aaaaaaaaaaaaaaaaaa\"}, Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(one, \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one)))")] + [InlineData("Len(With({one: \"aaaaaaaaaaaaaaaaaabbbbbbbaaaaaaaa\"}, Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(one, \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one)))")] + [InlineData("Len(With({one: \"bcabcabcabcabcaaaaaaaaaaaaaaabbbbbbbaaaacccccccccccaaaa\"}, Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(one, \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one)))")] + [InlineData("Len(With({one: \"aaaaaaaaaaaaaaaaaa\"}, Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(Substitute(one, \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one), \"a\", one)))")] + public async Task SubstituteMemoryLimit(string expr) + { + long maxLength = long.MaxValue; + await RunExpressionWithMemoryLimit(expr, maxLength, hasGovernorException: false).ConfigureAwait(false); + } + + private async Task RunExpressionWithMemoryLimit(string expression, long memorySize, bool hasGovernorException) + { + var config = new PowerFxConfig + { + MaximumExpressionLength = 2000 + }; + + var engine = new RecalcEngine(config); + + var mem = new SingleThreadedGovernor(memorySize); + + var runtimeConfig = new RuntimeConfig(); + runtimeConfig.AddService(mem); + + // Ensure governor traps excessive memory usage. + var eval = engine.EvalAsync(expression, CancellationToken.None, runtimeConfig: runtimeConfig); + if (hasGovernorException) + { + await Assert.ThrowsAsync(async () => await eval.ConfigureAwait(false)).ConfigureAwait(false); + } + else + { + var result = await eval.ConfigureAwait(false); + Assert.IsType(result); + ErrorValue ev = (ErrorValue)result; + Assert.Equal("Overflow", ev.Errors[0].Message); + } + +#if false // https://github.com/microsoft/Power-Fx/issues/971 + // Still traps without the pre-poll. + // The pre-poll may fail the expression before it even executes. + // So skipping pre-poll tests the execution can be aborted. + var gov2 = new TestIgnorePrePollGovernor(DefaultMemorySizeBytes); + runtimeConfig.AddService(gov2); + + await Assert.ThrowsAsync(async () => await engine.EvalAsync(expr, CancellationToken.None, runtimeConfig: runtimeConfig)); +#endif + } // We can recover after a oom expression [Fact] @@ -207,42 +207,42 @@ namespace Microsoft.PowerFx.Tests [InlineData(5, 5, 2, true, 5, true)] // 1 match, if not found, original length [InlineData(5, 5, 9, true, 9, true)] // 1 match, if found, replacement length [InlineData(5, 3, 6, true, 12, true)] // rounds up. - [InlineData(5, 3, 1, true, 5, true)] - [InlineData(612220032, 1, 18, true, 0, false)] - [InlineData(int.MaxValue, 1, 100, true, 0, false)] - [InlineData(100, 10, int.MaxValue, true, 0, false)] - [InlineData(612220032, 0, int.MaxValue, true, 612220032, true)] - [InlineData(612220032, int.MaxValue, 100, true, 612220032, true)] - [InlineData(int.MaxValue, 2000, 0, true, int.MaxValue, true)] - [InlineData(0, 3, int.MaxValue, true, 0, true)] - [InlineData(0, 0, int.MaxValue, true, 0, true)] - [InlineData(5, 3, 0, true, 5, true)] + [InlineData(5, 3, 1, true, 5, true)] + [InlineData(612220032, 1, 18, true, 0, false)] + [InlineData(int.MaxValue, 1, 100, true, 0, false)] + [InlineData(100, 10, int.MaxValue, true, 0, false)] + [InlineData(612220032, 0, int.MaxValue, true, 612220032, true)] + [InlineData(612220032, int.MaxValue, 100, true, 612220032, true)] + [InlineData(int.MaxValue, 2000, 0, true, int.MaxValue, true)] + [InlineData(0, 3, int.MaxValue, true, 0, true)] + [InlineData(0, 0, int.MaxValue, true, 0, true)] + [InlineData(5, 3, 0, true, 5, true)] [InlineData(4, 1, 5, false, 8, true)] // just first, (4-1+5) [InlineData(4, 5, 3, false, 4, true)] - [InlineData(int.MaxValue, 2, 10, false, 0, false)] - [InlineData(5, 2, int.MaxValue, false, 0, false)] - [InlineData(612220032, 0, int.MaxValue, false, 612220032, true)] - [InlineData(612220032, int.MaxValue, 100, false, 612220032, true)] - [InlineData(int.MaxValue, 2000, 0, false, int.MaxValue, true)] - [InlineData(0, 2, int.MaxValue, false, 0, true)] + [InlineData(int.MaxValue, 2, 10, false, 0, false)] + [InlineData(5, 2, int.MaxValue, false, 0, false)] + [InlineData(612220032, 0, int.MaxValue, false, 612220032, true)] + [InlineData(612220032, int.MaxValue, 100, false, 612220032, true)] + [InlineData(int.MaxValue, 2000, 0, false, int.MaxValue, true)] + [InlineData(0, 2, int.MaxValue, false, 0, true)] [InlineData(0, 0, int.MaxValue, false, 0, true)] public void TestSubstitutePrePoll(int sourceLen, int matchLen, int replacementLen, bool replaceAll, int expectedMaxLenChars, bool isSuccess) { - int maxLenChars = int.MinValue; - - try - { - maxLenChars = Functions.Library.SubstituteGetResultLength(sourceLen, matchLen, replacementLen, replaceAll); - } - catch (Exception e) - { - Assert.False(isSuccess); - Assert.Contains("Arithmetic operation resulted in an overflow.", e.Message); - } - - if (isSuccess) - { - Assert.Equal(expectedMaxLenChars, maxLenChars); + int maxLenChars = int.MinValue; + + try + { + maxLenChars = Functions.Library.SubstituteGetResultLength(sourceLen, matchLen, replacementLen, replaceAll); + } + catch (Exception e) + { + Assert.False(isSuccess); + Assert.Contains("Arithmetic operation resulted in an overflow.", e.Message); + } + + if (isSuccess) + { + Assert.Equal(expectedMaxLenChars, maxLenChars); } } } diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/Scenarios/CustomRecordTypeWithUndefinedFields.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Scenarios/CustomRecordTypeWithUndefinedFields.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/Scenarios/CustomRecordTypeWithUndefinedFields.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Scenarios/CustomRecordTypeWithUndefinedFields.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/Scenarios/DataTableMarshallerProvider.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Scenarios/DataTableMarshallerProvider.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/Scenarios/DataTableMarshallerProvider.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Scenarios/DataTableMarshallerProvider.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/Scenarios/ObjectListMarshallerProvider.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Scenarios/ObjectListMarshallerProvider.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/Scenarios/ObjectListMarshallerProvider.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Scenarios/ObjectListMarshallerProvider.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/Scenarios/PrimitiveWrapperAsUnknownObject.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Scenarios/PrimitiveWrapperAsUnknownObject.cs similarity index 99% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/Scenarios/PrimitiveWrapperAsUnknownObject.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Scenarios/PrimitiveWrapperAsUnknownObject.cs index 97fa48799..b03ec4eb5 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/Scenarios/PrimitiveWrapperAsUnknownObject.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Scenarios/PrimitiveWrapperAsUnknownObject.cs @@ -2,7 +2,7 @@ // Licensed under the MIT license. using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using Microsoft.PowerFx.Core.Tests; @@ -35,8 +35,8 @@ namespace Microsoft.PowerFx.Tests if (_source is int || _source is double || _source is uint) { return FormulaType.Number; - } - + } + if (_source is bool) { return FormulaType.Boolean; @@ -50,11 +50,11 @@ namespace Microsoft.PowerFx.Tests if (_source.GetType().IsArray) { return ExternalType.ArrayType; - } - - if (_source is decimal || _source is long || _source is ulong) - { - return FormulaType.Decimal; + } + + if (_source is decimal || _source is long || _source is ulong) + { + return FormulaType.Decimal; } return ExternalType.ObjectType; @@ -107,46 +107,46 @@ namespace Microsoft.PowerFx.Tests if (_source is double valDouble) { return valDouble; - } + } if (_source is int valInt) { return valInt; - } - + } + if (_source is uint valUInt) { return valUInt; - } + } throw new InvalidOperationException($"Not a number type"); - } - + } + public decimal GetDecimal() - { + { // Fx will only call this helper for decimals. - Assert.True(Type == FormulaType.Decimal); - - if (_source is decimal valDecimal) - { - return valDecimal; - } - + Assert.True(Type == FormulaType.Decimal); + + if (_source is decimal valDecimal) + { + return valDecimal; + } + if (_source is long valLong) { return valLong; - } - + } + if (_source is ulong valULong) { return valULong; } throw new InvalidOperationException($"Not a decimal type"); - } - + } + public string GetUntypedNumber() - { + { throw new NotImplementedException(); } @@ -189,12 +189,12 @@ namespace Microsoft.PowerFx.Tests } return true; - } - - public bool TryGetPropertyNames(out IEnumerable result) - { - result = null; - return false; - } + } + + public bool TryGetPropertyNames(out IEnumerable result) + { + result = null; + return false; + } } } diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/Scenarios/ScenarioCustomMarshaler.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Scenarios/ScenarioCustomMarshaler.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/Scenarios/ScenarioCustomMarshaler.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Scenarios/ScenarioCustomMarshaler.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/Scenarios/ScenarioDotNetObjectWrapper.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Scenarios/ScenarioDotNetObjectWrapper.cs similarity index 99% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/Scenarios/ScenarioDotNetObjectWrapper.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Scenarios/ScenarioDotNetObjectWrapper.cs index f1d02fa75..a50ed5650 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/Scenarios/ScenarioDotNetObjectWrapper.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Scenarios/ScenarioDotNetObjectWrapper.cs @@ -23,18 +23,18 @@ namespace Microsoft.PowerFx.Interpreter.Tests public bool Flag { get; set; } - public string Msg { get; set; } - - public long BigInt { get; set; } - - public decimal Decimal { get; set; } - - public decimal Decimal2 { get; set; } - - public double Double { get; set; } - - public uint UInt { get; set; } - + public string Msg { get; set; } + + public long BigInt { get; set; } + + public decimal Decimal { get; set; } + + public decimal Decimal2 { get; set; } + + public double Double { get; set; } + + public uint UInt { get; set; } + public ulong ULong { get; set; } // Verify we don't eagerly touch all properties @@ -46,30 +46,30 @@ namespace Microsoft.PowerFx.Interpreter.Tests var objFx = PrimitiveWrapperAsUnknownObject.New(obj); engine.UpdateVariable(name, objFx); } - + // inline data does not support decimal, which is why the max/min decimal tests aren't here [Theory] - [InlineData("Float(obj.Next.Value)", 20.0)] - [InlineData("Float(obj.Next.Double)", 2.28515625)] - [InlineData("Float(obj.UInt)", (double)0xffffffffU)] - [InlineData("Float(obj.Next.UInt)", (double)0xfffffff0U)] - [InlineData("Decimal(obj.ULong)", 0xffffffffffffffffUL)] - [InlineData("Decimal(obj.Next.ULong)", 0xfffffffffffffff0UL)] - [InlineData("Decimal(obj.BigInt)", -9223372036854775808L)] - [InlineData("Decimal(obj.Next.BigInt)", 9223372036854775807L)] - [InlineData("Decimal(obj.Decimal2)", 9882075136L)] - [InlineData("Decimal(obj.Next.Decimal2)", 158113202176L)] - [InlineData("Text(obj.Value)", "10")] - [InlineData("Text(obj.BigInt)", "-9223372036854775808")] - [InlineData("Text(obj.Decimal)", "-79228162514264337593543950335")] - [InlineData("Text(obj.Double)", "0.5712890625")] + [InlineData("Float(obj.Next.Value)", 20.0)] + [InlineData("Float(obj.Next.Double)", 2.28515625)] + [InlineData("Float(obj.UInt)", (double)0xffffffffU)] + [InlineData("Float(obj.Next.UInt)", (double)0xfffffff0U)] + [InlineData("Decimal(obj.ULong)", 0xffffffffffffffffUL)] + [InlineData("Decimal(obj.Next.ULong)", 0xfffffffffffffff0UL)] + [InlineData("Decimal(obj.BigInt)", -9223372036854775808L)] + [InlineData("Decimal(obj.Next.BigInt)", 9223372036854775807L)] + [InlineData("Decimal(obj.Decimal2)", 9882075136L)] + [InlineData("Decimal(obj.Next.Decimal2)", 158113202176L)] + [InlineData("Text(obj.Value)", "10")] + [InlineData("Text(obj.BigInt)", "-9223372036854775808")] + [InlineData("Text(obj.Decimal)", "-79228162514264337593543950335")] + [InlineData("Text(obj.Double)", "0.5712890625")] [InlineData("Text(obj.Next.Value)", "20")] - [InlineData("Text(obj.Next.BigInt)", "9223372036854775807")] - [InlineData("Text(obj.Next.Decimal)", "79228162514264337593543950335")] - [InlineData("Text(obj.Next.Double)", "2.28515625")] - [InlineData("Text(obj.UInt)", "4294967295")] - [InlineData("Text(obj.Next.UInt)", "4294967280")] - [InlineData("Text(obj.ULong)", "18446744073709551615")] + [InlineData("Text(obj.Next.BigInt)", "9223372036854775807")] + [InlineData("Text(obj.Next.Decimal)", "79228162514264337593543950335")] + [InlineData("Text(obj.Next.Double)", "2.28515625")] + [InlineData("Text(obj.UInt)", "4294967295")] + [InlineData("Text(obj.Next.UInt)", "4294967280")] + [InlineData("Text(obj.ULong)", "18446744073709551615")] [InlineData("Text(obj.Next.ULong)", "18446744073709551600")] [InlineData("obj.missing", null)] // missing fields are blank [InlineData("IsBlank(obj.Next.Next)", true)] @@ -81,7 +81,7 @@ namespace Microsoft.PowerFx.Interpreter.Tests [InlineData("Text(Index(array, 2))", "two")] [InlineData("Index(array, 0)", "#error")] // Out of bounds, low [InlineData("Index(array, -1)", "#error")] // Out of bounds, low - [InlineData("Index(array, 100)", "#error")] // Out of bounds, high + [InlineData("Index(array, 100)", "#error")] // Out of bounds, high public void Test(string expr, object expected) { @@ -89,21 +89,21 @@ namespace Microsoft.PowerFx.Interpreter.Tests var obj = new TestObj { - Value = 10, - Double = 0.5712890625, - BigInt = -9223372036854775808L, - Decimal = -79228162514264337593543950335m, - Decimal2 = 9882075136, - UInt = 0xFFFFFFFF, + Value = 10, + Double = 0.5712890625, + BigInt = -9223372036854775808L, + Decimal = -79228162514264337593543950335m, + Decimal2 = 9882075136, + UInt = 0xFFFFFFFF, ULong = 0xFFFFFFFFFFFFFFFFUL, Next = new TestObj { - Value = 20, - Double = 2.28515625, - BigInt = 9223372036854775807L, - Decimal = 79228162514264337593543950335m, - Decimal2 = 158113202176, - UInt = 0xFFFFFFF0, + Value = 20, + Double = 2.28515625, + BigInt = 9223372036854775807L, + Decimal = 79228162514264337593543950335m, + Decimal2 = 158113202176, + UInt = 0xFFFFFFF0, ULong = 0xFFFFFFFFFFFFFFF0UL }, Flag = true, @@ -121,10 +121,10 @@ namespace Microsoft.PowerFx.Interpreter.Tests { Assert.IsType(result); } - else if (expected is long || expected is ulong) - { - Assert.Equal(Convert.ToDecimal(expected), result.ToObject()); - } + else if (expected is long || expected is ulong) + { + Assert.Equal(Convert.ToDecimal(expected), result.ToObject()); + } else { Assert.Equal(expected, result.ToObject()); diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/Scenarios/ScenarioMutation.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Scenarios/ScenarioMutation.cs similarity index 99% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/Scenarios/ScenarioMutation.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Scenarios/ScenarioMutation.cs index a4ffdc600..dfd0a7b32 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/Scenarios/ScenarioMutation.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/Scenarios/ScenarioMutation.cs @@ -164,16 +164,16 @@ namespace Microsoft.PowerFx.Interpreter.Tests public double GetDouble() { return ((NumberValue)_value).Value; - } - + } + public decimal GetDecimal() { return ((DecimalValue)_value).Value; - } - - public string GetUntypedNumber() - { - return ((StringValue)_value).Value; + } + + public string GetUntypedNumber() + { + return ((StringValue)_value).Value; } public string GetString() @@ -184,13 +184,13 @@ namespace Microsoft.PowerFx.Interpreter.Tests public bool TryGetProperty(string value, out IUntypedObject result) { throw new NotImplementedException(); - } - - public bool TryGetPropertyNames(out IEnumerable result) - { - result = null; - return false; - } + } + + public bool TryGetPropertyNames(out IEnumerable result) + { + result = null; + return false; + } } private class MutableObject : IUntypedObject @@ -229,17 +229,17 @@ namespace Microsoft.PowerFx.Interpreter.Tests public double GetDouble() { throw new NotImplementedException(); - } - + } + public decimal GetDecimal() { throw new NotImplementedException(); - } - - public string GetUntypedNumber() - { - throw new NotImplementedException(); - } + } + + public string GetUntypedNumber() + { + throw new NotImplementedException(); + } public string GetString() { @@ -256,13 +256,13 @@ namespace Microsoft.PowerFx.Interpreter.Tests result = null; return false; - } - - public bool TryGetPropertyNames(out IEnumerable result) - { - result = null; - return false; - } + } + + public bool TryGetPropertyNames(out IEnumerable result) + { + result = null; + return false; + } } } } diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/SerializationTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/SerializationTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/SerializationTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/SerializationTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/ServiceProviderTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/ServiceProviderTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/ServiceProviderTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/ServiceProviderTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/SetFunctionTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/SetFunctionTests.cs similarity index 98% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/SetFunctionTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/SetFunctionTests.cs index 7962a1165..27cd04830 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/SetFunctionTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/SetFunctionTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -using System; -using System.Data; +using System; +using System.Data; using System.Threading; -using Microsoft.PowerFx.Core.IR; +using Microsoft.PowerFx.Core.IR; using Microsoft.PowerFx.Core.Tests; using Microsoft.PowerFx.Types; using Xunit; @@ -13,7 +13,7 @@ namespace Microsoft.PowerFx.Interpreter.Tests { public class SetFunctionTests : PowerFxTest { - private readonly ParserOptions _opts = new ParserOptions { AllowsSideEffects = true }; + private readonly ParserOptions _opts = new ParserOptions { AllowsSideEffects = true }; [Fact] public void SetVar() @@ -27,57 +27,57 @@ namespace Microsoft.PowerFx.Interpreter.Tests var r1 = engine.Eval("x", null, _opts); // 12 Assert.Equal(12m, r1.ToObject()); - var r2 = engine.Eval("Set(x, 15)", null, _opts); - - Assert.IsType(r2); + var r2 = engine.Eval("Set(x, 15)", null, _opts); + + Assert.IsType(r2); r1 = engine.Eval("x"); // 15 Assert.Equal(15m, r1.ToObject()); r1 = engine.GetValue("x"); Assert.Equal(15m, r1.ToObject()); - } - - [Theory] - [InlineData("Set(x, Float(15))", true, true)] - [InlineData("Set(x, Decimal(15))", true, true)] - [InlineData("Set(x, 15)", true, true)] - - [InlineData("Set(x, Float(15))", true, false)] - [InlineData("Set(x, Decimal(15))", true, false)] - [InlineData("Set(x, 15)", true, false)] - - // Set() only coerces Numeric values. - [InlineData("Set(x, \"15\")", false, true)] - [InlineData("Set(x, \"15\")", false, false)] - public void SetVarNumberCoercion(string expression, bool isCheckSuccess, bool isFloat) - { - var option = new ParserOptions { NumberIsFloat = isFloat, AllowsSideEffects = true }; - FormulaValue value = isFloat ? FormulaValue.New(12.0) : FormulaValue.New(12m); - var expectedValue = isFloat ? (object)15.0 : (object)15m; - - var config = new PowerFxConfig(); - config.EnableSetFunction(); - var engine = new RecalcEngine(config); - engine.UpdateVariable("x", value); - - var x = engine.Eval("x", null, option); - Assert.Equal(value.ToObject(), x.ToObject()); - - var check = engine.Check(expression, option); - Assert.Equal(isCheckSuccess, check.IsSuccess); - - if (isCheckSuccess) - { - var isSuccess = check.GetEvaluator().Eval(); - - Assert.IsType(isSuccess); - - var result = engine.Eval("x"); - Assert.Equal(expectedValue, result.ToObject()); - } } - + + [Theory] + [InlineData("Set(x, Float(15))", true, true)] + [InlineData("Set(x, Decimal(15))", true, true)] + [InlineData("Set(x, 15)", true, true)] + + [InlineData("Set(x, Float(15))", true, false)] + [InlineData("Set(x, Decimal(15))", true, false)] + [InlineData("Set(x, 15)", true, false)] + + // Set() only coerces Numeric values. + [InlineData("Set(x, \"15\")", false, true)] + [InlineData("Set(x, \"15\")", false, false)] + public void SetVarNumberCoercion(string expression, bool isCheckSuccess, bool isFloat) + { + var option = new ParserOptions { NumberIsFloat = isFloat, AllowsSideEffects = true }; + FormulaValue value = isFloat ? FormulaValue.New(12.0) : FormulaValue.New(12m); + var expectedValue = isFloat ? (object)15.0 : (object)15m; + + var config = new PowerFxConfig(); + config.EnableSetFunction(); + var engine = new RecalcEngine(config); + engine.UpdateVariable("x", value); + + var x = engine.Eval("x", null, option); + Assert.Equal(value.ToObject(), x.ToObject()); + + var check = engine.Check(expression, option); + Assert.Equal(isCheckSuccess, check.IsSuccess); + + if (isCheckSuccess) + { + var isSuccess = check.GetEvaluator().Eval(); + + Assert.IsType(isSuccess); + + var result = engine.Eval("x"); + Assert.Equal(expectedValue, result.ToObject()); + } + } + [Fact] public void Circular() { @@ -90,8 +90,8 @@ namespace Microsoft.PowerFx.Interpreter.Tests // Circular reference ok var r3 = engine.Eval("Set(x, 1);Set(x,x+1);x", null, _opts); Assert.Equal(2.0m, r3.ToObject()); - } - + } + [Fact] public void SetVar2() { @@ -126,8 +126,8 @@ namespace Microsoft.PowerFx.Interpreter.Tests // But SetField fails var r2 = engine.Check("Set(obj.X, 31); obj.X", null, _opts); Assert.False(r2.IsSuccess); - } - + } + [Fact] public void SetRecordFloat() { @@ -138,9 +138,9 @@ namespace Microsoft.PowerFx.Interpreter.Tests var cache = new TypeMarshallerCache(); var obj = cache.Marshal(new { X = 10f, Y = 20f }); - engine.UpdateVariable("obj", obj); - - // Can update record + engine.UpdateVariable("obj", obj); + + // Can update record var r1 = engine.Eval("Set(obj, {X: Float(11), Y: Float(21)}); obj.X", null, _opts); Assert.Equal(11.0, r1.ToObject()); @@ -187,13 +187,13 @@ namespace Microsoft.PowerFx.Interpreter.Tests var result = engine.Check("Set(x, 15)", null, _opts); Assert.False(result.IsSuccess); - } + } [Fact] public void UpdateSimple() - { - var symTable = new SymbolTable(); - var slotX = symTable.AddVariable("x", FormulaType.Number, mutable: true); + { + var symTable = new SymbolTable(); + var slotX = symTable.AddVariable("x", FormulaType.Number, mutable: true); var sym = symTable.CreateValues(); sym.Set(slotX, FormulaValue.New(12.0)); @@ -202,200 +202,200 @@ namespace Microsoft.PowerFx.Interpreter.Tests config.EnableSetFunction(); var engine = new RecalcEngine(config); - var expr = "Set(x, x+1);x"; - + var expr = "Set(x, x+1);x"; + var runtimeConfig = new RuntimeConfig(sym); var result = engine.EvalAsync(expr, CancellationToken.None, options: _opts, runtimeConfig: runtimeConfig).Result; Assert.Equal(13.0, result.ToObject()); - - result = sym.Get(slotX); - Assert.Equal(13.0, result.ToObject()); + + result = sym.Get(slotX); + Assert.Equal(13.0, result.ToObject()); var found = sym.TryGetValue("x", out var result2); Assert.True(found); Assert.Equal(13.0, result2.ToObject()); - } - + } + [Fact] public void UpdateSimple_Decimal() - { - var symTable = new SymbolTable(); - var slotX = symTable.AddVariable("x", FormulaType.Decimal, mutable: true); + { + var symTable = new SymbolTable(); + var slotX = symTable.AddVariable("x", FormulaType.Decimal, mutable: true); - var sym = symTable.CreateValues(); - sym.Set(slotX, FormulaValue.New(12)); - - var config = new PowerFxConfig(); - config.EnableSetFunction(); - var engine = new RecalcEngine(config); - - var expr = "Set(x, x+1);x"; - - var runtimeConfig = new RuntimeConfig(sym); - var result = engine.EvalAsync(expr, CancellationToken.None, options: _opts, runtimeConfig: runtimeConfig).Result; - Assert.Equal(13m, result.ToObject()); - - result = sym.Get(slotX); - Assert.Equal(13m, result.ToObject()); - - var found = sym.TryGetValue("x", out var result2); - Assert.True(found); - Assert.Equal(13m, result2.ToObject()); - } - - [Fact] - public void MutableByDefault() - { - var sym = new SymbolValues(); - sym.Add("x", FormulaValue.New(12m)); // mutable by default - - var config = new PowerFxConfig(); - config.EnableSetFunction(); - var engine = new RecalcEngine(config); - - // ok, mutable - var expr = "Set(x,5)"; - - var result = engine.Check(expr, options: _opts, symbolTable: sym.SymbolTable); - Assert.True(result.IsSuccess); - } - - // Attempting to Set() a readonly variable should be a binding error. - [Fact] - public void UpdateFailsOnReadOnlyValue() - { - var symTable = new SymbolTable(); - var slotX = symTable.AddVariable("x", FormulaType.Decimal, mutable: true); - var sym = symTable.CreateValues(); sym.Set(slotX, FormulaValue.New(12)); var config = new PowerFxConfig(); config.EnableSetFunction(); var engine = new RecalcEngine(config); - - // fails on readonly failure. - var expr = "With({y : x},Set(y,5))"; - - var result = engine.Check(expr, options: _opts, symbolTable: symTable); - Assert.False(result.IsSuccess); - - // but ok to set X since it's mutable. - expr = "With({y : x},Set(x,y*2))"; - - //result = engine.Check(expr, symbolTable: sym.GetSymbolTableSnapshot()); - result = engine.Check(expr, options: _opts, symbolTable: symTable); - + + var expr = "Set(x, x+1);x"; + + var runtimeConfig = new RuntimeConfig(sym); + var result = engine.EvalAsync(expr, CancellationToken.None, options: _opts, runtimeConfig: runtimeConfig).Result; + Assert.Equal(13m, result.ToObject()); + + result = sym.Get(slotX); + Assert.Equal(13m, result.ToObject()); + + var found = sym.TryGetValue("x", out var result2); + Assert.True(found); + Assert.Equal(13m, result2.ToObject()); + } + + [Fact] + public void MutableByDefault() + { + var sym = new SymbolValues(); + sym.Add("x", FormulaValue.New(12m)); // mutable by default + + var config = new PowerFxConfig(); + config.EnableSetFunction(); + var engine = new RecalcEngine(config); + + // ok, mutable + var expr = "Set(x,5)"; + + var result = engine.Check(expr, options: _opts, symbolTable: sym.SymbolTable); Assert.True(result.IsSuccess); - } - + } + + // Attempting to Set() a readonly variable should be a binding error. + [Fact] + public void UpdateFailsOnReadOnlyValue() + { + var symTable = new SymbolTable(); + var slotX = symTable.AddVariable("x", FormulaType.Decimal, mutable: true); + + var sym = symTable.CreateValues(); + sym.Set(slotX, FormulaValue.New(12)); + + var config = new PowerFxConfig(); + config.EnableSetFunction(); + var engine = new RecalcEngine(config); + + // fails on readonly failure. + var expr = "With({y : x},Set(y,5))"; + + var result = engine.Check(expr, options: _opts, symbolTable: symTable); + Assert.False(result.IsSuccess); + + // but ok to set X since it's mutable. + expr = "With({y : x},Set(x,y*2))"; + + //result = engine.Check(expr, symbolTable: sym.GetSymbolTableSnapshot()); + result = engine.Check(expr, options: _opts, symbolTable: symTable); + + Assert.True(result.IsSuccess); + } + [Fact] public void UpdateFailsOnReadOnlyValue2() - { - var symTable = new SymbolTable(); - var slotX = symTable.AddVariable("x", FormulaType.Decimal, mutable: false); - + { + var symTable = new SymbolTable(); + var slotX = symTable.AddVariable("x", FormulaType.Decimal, mutable: false); + var sym = symTable.CreateValues(); sym.Set(slotX, FormulaValue.New(12)); // Ok var config = new PowerFxConfig(); config.EnableSetFunction(); var engine = new RecalcEngine(config); - + // fails on readonly failure. - var expr = "Set(x,5)"; - - var result = engine.Check(expr, options: _opts, symbolTable: symTable); + var expr = "Set(x,5)"; + + var result = engine.Check(expr, options: _opts, symbolTable: symTable); Assert.False(result.IsSuccess); - } - + } + [Fact] public void UpdateFailsOnReadOnlyValue3() - { - var symTable = new SymbolTable(); - symTable.AddConstant("x", FormulaValue.New(12)); - - var found = symTable.TryLookupSlot("x", out var slot); - Assert.False(found); // no slots for constants. - Assert.Null(slot); + { + var symTable = new SymbolTable(); + symTable.AddConstant("x", FormulaValue.New(12)); + + var found = symTable.TryLookupSlot("x", out var slot); + Assert.False(found); // no slots for constants. + Assert.Null(slot); var config = new PowerFxConfig(); config.EnableSetFunction(); - var engine = new RecalcEngine(config); - - // fails on readonly failure. - var expr = "Set(x,5)"; - - var result = engine.Check(expr, options: _opts, symbolTable: symTable); + var engine = new RecalcEngine(config); + + // fails on readonly failure. + var expr = "Set(x,5)"; + + var result = engine.Check(expr, options: _opts, symbolTable: symTable); Assert.False(result.IsSuccess); - } - - // Demonstrate we can have a single Check w/ SymbolTable used against multiple SymbolValues. - [Fact] - public void Update3() - { - var symTable = new SymbolTable(); - var slot = symTable.AddVariable("num", FormulaType.Number, mutable: true); - + } + + // Demonstrate we can have a single Check w/ SymbolTable used against multiple SymbolValues. + [Fact] + public void Update3() + { + var symTable = new SymbolTable(); + var slot = symTable.AddVariable("num", FormulaType.Number, mutable: true); + var config = new PowerFxConfig(); config.EnableSetFunction(); - var engine = new RecalcEngine(config); - - // Create multiple values that share a symbol table. - var symValues1 = symTable.CreateValues(); - symValues1.Set(slot, FormulaValue.New(10.0)); - - var symValues2 = symTable.CreateValues(); - symValues2.Set(slot, FormulaValue.New(20.0)); - - var check = engine.Check("Set(num, num+5)", options: _opts, symbolTable: symTable); - check.ThrowOnErrors(); - var eval = check.GetEvaluator(); - - foreach (var symValue in new[] { symValues1, symValues2 }) - { - var runtimeConfig = new RuntimeConfig(symValue); - var result = eval.EvalAsync(CancellationToken.None, runtimeConfig: runtimeConfig); - } - - AssertValue(symValues1, "num", 15.0); - AssertValue(symValues2, "num", 25.0); - } - - [Fact] - public void Update3_Decimal() - { - var symTable = new SymbolTable(); - var slot = symTable.AddVariable("num", FormulaType.Decimal, mutable: true); - + var engine = new RecalcEngine(config); + + // Create multiple values that share a symbol table. + var symValues1 = symTable.CreateValues(); + symValues1.Set(slot, FormulaValue.New(10.0)); + + var symValues2 = symTable.CreateValues(); + symValues2.Set(slot, FormulaValue.New(20.0)); + + var check = engine.Check("Set(num, num+5)", options: _opts, symbolTable: symTable); + check.ThrowOnErrors(); + var eval = check.GetEvaluator(); + + foreach (var symValue in new[] { symValues1, symValues2 }) + { + var runtimeConfig = new RuntimeConfig(symValue); + var result = eval.EvalAsync(CancellationToken.None, runtimeConfig: runtimeConfig); + } + + AssertValue(symValues1, "num", 15.0); + AssertValue(symValues2, "num", 25.0); + } + + [Fact] + public void Update3_Decimal() + { + var symTable = new SymbolTable(); + var slot = symTable.AddVariable("num", FormulaType.Decimal, mutable: true); + var config = new PowerFxConfig(); config.EnableSetFunction(); - var engine = new RecalcEngine(config); - - // Create multiple values that share a symbol table. - var symValues1 = symTable.CreateValues(); - symValues1.Set(slot, FormulaValue.New(10)); - - var symValues2 = symTable.CreateValues(); - symValues2.Set(slot, FormulaValue.New(20)); - - var check = engine.Check("Set(num, num+5)", options: _opts, symbolTable: symTable); - check.ThrowOnErrors(); - var eval = check.GetEvaluator(); - - foreach (var symValue in new[] { symValues1, symValues2 }) - { - var runtimeConfig = new RuntimeConfig(symValue); - var result = eval.EvalAsync(CancellationToken.None, runtimeConfig: runtimeConfig); - } - - AssertValue(symValues1, "num", 15m); - AssertValue(symValues2, "num", 25m); - } - + var engine = new RecalcEngine(config); + + // Create multiple values that share a symbol table. + var symValues1 = symTable.CreateValues(); + symValues1.Set(slot, FormulaValue.New(10)); + + var symValues2 = symTable.CreateValues(); + symValues2.Set(slot, FormulaValue.New(20)); + + var check = engine.Check("Set(num, num+5)", options: _opts, symbolTable: symTable); + check.ThrowOnErrors(); + var eval = check.GetEvaluator(); + + foreach (var symValue in new[] { symValues1, symValues2 }) + { + var runtimeConfig = new RuntimeConfig(symValue); + var result = eval.EvalAsync(CancellationToken.None, runtimeConfig: runtimeConfig); + } + + AssertValue(symValues1, "num", 15m); + AssertValue(symValues2, "num", 25m); + } + [Fact] public void UpdateRowScope() - { + { var recordType = RecordType.Empty() .Add(new NamedFormulaType("num", FormulaType.Number, "displayNum")) .Add(new NamedFormulaType("str", FormulaType.String, "displayStr")); @@ -406,29 +406,29 @@ namespace Microsoft.PowerFx.Interpreter.Tests new NamedValue("str", FormulaValue.New("abc"))); var expr = "Set(displayNum, Float(12)); displayNum"; - + var sym = NewMutableFromRecord(record); var config = new PowerFxConfig(); config.EnableSetFunction(); var engine = new RecalcEngine(config); - + var runtimeConfig = new RuntimeConfig(sym); var result = engine.EvalAsync(expr, CancellationToken.None, options: _opts, runtimeConfig: runtimeConfig).Result; Assert.Equal(12.0, result.ToObject()); - + AssertValue(sym, "num", 12.0); // Can get invariant form. var invariant = engine.GetInvariantExpression(expr, recordType); Assert.Equal("Set(num, Float(12)); num", invariant); - } - - // Expression from 2 row scopes! + } + + // Expression from 2 row scopes! [Fact] public void UpdateRowScope2() - { + { var recordType1 = RecordType.Empty() .Add(new NamedFormulaType("num", FormulaType.Number, "displayNum")) .Add(new NamedFormulaType("str", FormulaType.String, "displayStr")); @@ -436,78 +436,78 @@ namespace Microsoft.PowerFx.Interpreter.Tests var record1 = FormulaValue.NewRecordFromFields( recordType1, new NamedValue("num", FormulaValue.New(10.0)), - new NamedValue("str", FormulaValue.New("abc"))); - - var recordType2 = RecordType.Empty() - .Add(new NamedFormulaType("num2", FormulaType.Number, "displayNum2")); - + new NamedValue("str", FormulaValue.New("abc"))); + + var recordType2 = RecordType.Empty() + .Add(new NamedFormulaType("num2", FormulaType.Number, "displayNum2")); + var record2 = FormulaValue.NewRecordFromFields( recordType2, new NamedValue("num2", FormulaValue.New(20.0))); var expr = "Set(displayNum, displayNum2+5); displayNum"; - - var sym1 = NewMutableFromRecord(record1); - var sym2 = NewMutableFromRecord(record2); - - var sym = ReadOnlySymbolValues.Compose(sym1, sym2); - + + var sym1 = NewMutableFromRecord(record1); + var sym2 = NewMutableFromRecord(record2); + + var sym = ReadOnlySymbolValues.Compose(sym1, sym2); + var config = new PowerFxConfig(); config.EnableSetFunction(); var engine = new RecalcEngine(config); - - // Verify Check, creating symbol table several ways - var check1 = engine.Check(expr, _opts, sym.SymbolTable); - Assert.True(check1.IsSuccess); - - var symTable1 = NewMutableFromRecord(recordType1); - var symTable2 = NewMutableFromRecord(recordType2); - var symTable = ReadOnlySymbolTable.Compose(symTable1, symTable2); - - var check2 = engine.Check(expr, _opts, symTable); - Assert.True(check1.IsSuccess); - - // Verify evaluation. + + // Verify Check, creating symbol table several ways + var check1 = engine.Check(expr, _opts, sym.SymbolTable); + Assert.True(check1.IsSuccess); + + var symTable1 = NewMutableFromRecord(recordType1); + var symTable2 = NewMutableFromRecord(recordType2); + var symTable = ReadOnlySymbolTable.Compose(symTable1, symTable2); + + var check2 = engine.Check(expr, _opts, symTable); + Assert.True(check1.IsSuccess); + + // Verify evaluation. var runtimeConfig = new RuntimeConfig(sym); var result = engine.EvalAsync(expr, CancellationToken.None, options: _opts, runtimeConfig: runtimeConfig).Result; Assert.Equal(25.0, result.ToObject()); - + AssertValue(sym, "num", 25.0); - // Can get invariant form. - // Can't pass in symbol table here... + // Can get invariant form. + // Can't pass in symbol table here... // https://github.com/microsoft/Power-Fx/issues/767 //var invariant = engine.GetInvariantExpression(expr, recordType); //Assert.Equal("Set(num, 12); num", invariant); - } - - private static void AssertMissing(ReadOnlySymbolValues symValues, string name) - { + } + + private static void AssertMissing(ReadOnlySymbolValues symValues, string name) + { var found = symValues.TryGetValue(name, out var result); - Assert.False(found); - Assert.Null(result); - } - - private static void AssertValue(ReadOnlySymbolValues symValues, string name, object expected) - { + Assert.False(found); + Assert.Null(result); + } + + private static void AssertValue(ReadOnlySymbolValues symValues, string name, object expected) + { var found = symValues.TryGetValue(name, out var result); - Assert.True(found); - Assert.Equal(expected, result.ToObject()); - } - + Assert.True(found); + Assert.Equal(expected, result.ToObject()); + } + public static ReadOnlySymbolValues NewMutableFromRecord(RecordValue parameters) - { - var symTable = NewMutableFromRecord(parameters.Type); - return ReadOnlySymbolValues.NewFromRecord(symTable, parameters); - } - + { + var symTable = NewMutableFromRecord(parameters.Type); + return ReadOnlySymbolValues.NewFromRecord(symTable, parameters); + } + public static ReadOnlySymbolTable NewMutableFromRecord(RecordType type) - { - var symTable = ReadOnlySymbolTable.NewFromRecord(type, allowMutable: true); - return symTable; - } - + { + var symTable = ReadOnlySymbolTable.NewFromRecord(type, allowMutable: true); + return symTable; + } + [Fact] public void NewErrorMessageWithTypes() { @@ -523,5 +523,5 @@ namespace Microsoft.PowerFx.Interpreter.Tests Assert.False(check.IsSuccess); Assert.Contains(check.Errors, d => d.Message.Contains("Invalid argument type (Boolean). Expecting a Number value instead.")); } - } + } } diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/SymbolValueTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/SymbolValueTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/SymbolValueTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/SymbolValueTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/TableTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/TableTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/TableTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/TableTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/TexlTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/TexlTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/TexlTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/TexlTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/TextDateTimeToUTCTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/TextDateTimeToUTCTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/TextDateTimeToUTCTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/TextDateTimeToUTCTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/TextFirstTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/TextFirstTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/TextFirstTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/TextFirstTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/TextTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/TextTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/TextTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/TextTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/ThreadingTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/ThreadingTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/ThreadingTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/ThreadingTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/TimeTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/TimeTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/TimeTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/TimeTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/TimeZoneExtensions.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/TimeZoneExtensions.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/TimeZoneExtensions.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/TimeZoneExtensions.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/TypeCoercionTest.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/TypeCoercionTest.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/TypeCoercionTest.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/TypeCoercionTest.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/UserDefinedTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/UserDefinedTests.cs similarity index 99% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/UserDefinedTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/UserDefinedTests.cs index b57b20911..5c9be5c2e 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests/UserDefinedTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/UserDefinedTests.cs @@ -12,8 +12,8 @@ using Xunit; namespace Microsoft.PowerFx.Interpreter.Tests { public class UserDefinedTests - { - private static readonly ReadOnlySymbolTable _primitiveTypes = ReadOnlySymbolTable.PrimitiveTypesTableInstance; + { + private static readonly ReadOnlySymbolTable _primitiveTypes = ReadOnlySymbolTable.PrimitiveTypesTableInstance; [Theory] [InlineData("x=1;y=2;z=x+y;", "Float(Abs(-(x+y+z)))", 6d)] @@ -35,7 +35,7 @@ namespace Microsoft.PowerFx.Interpreter.Tests [Theory] [InlineData("test2(b: Boolean): Boolean = { Set(a, b); };")] [InlineData("test2(b: Boolean): Boolean = { Set(a, b); Collect(abc, { bcd: 1 }) };")] - [InlineData("test2(b: Boolean): Boolean = { Set(a, b); Collect(abc, { bcd: 1 }) }; num = 3;")] + [InlineData("test2(b: Boolean): Boolean = { Set(a, b); Collect(abc, { bcd: 1 }) }; num = 3;")] public void ValidUDFBodyTest(string script) { var options = new ParserOptions() @@ -43,15 +43,15 @@ namespace Microsoft.PowerFx.Interpreter.Tests AllowsSideEffects = true, Culture = CultureInfo.InvariantCulture }; - var parseResult = UserDefinitions.Parse(script, options); - var udfs = UserDefinedFunction.CreateFunctions(parseResult.UDFs.Where(udf => udf.IsParseValid), _primitiveTypes, out var errors); + var parseResult = UserDefinitions.Parse(script, options); + var udfs = UserDefinedFunction.CreateFunctions(parseResult.UDFs.Where(udf => udf.IsParseValid), _primitiveTypes, out var errors); errors.AddRange(parseResult.Errors ?? Enumerable.Empty()); Assert.False(errors.Any()); - } - - [Theory] - [InlineData("F1(x:Number) : Number = { Set(a, x); x+1; };")] + } + + [Theory] + [InlineData("F1(x:Number) : Number = { Set(a, x); x+1; };")] [InlineData("F1(x:Number) : Boolean = { Set(a, x); Today(); };")] public void ValidUDFBodyTest2(string script) { @@ -60,8 +60,8 @@ namespace Microsoft.PowerFx.Interpreter.Tests AllowsSideEffects = true, Culture = CultureInfo.InvariantCulture }; - var parseResult = UserDefinitions.Parse(script, options); - var udfs = UserDefinedFunction.CreateFunctions(parseResult.UDFs.Where(udf => udf.IsParseValid), _primitiveTypes, out var errors); + var parseResult = UserDefinitions.Parse(script, options); + var udfs = UserDefinedFunction.CreateFunctions(parseResult.UDFs.Where(udf => udf.IsParseValid), _primitiveTypes, out var errors); errors.AddRange(parseResult.Errors ?? Enumerable.Empty()); Assert.False(errors.Any()); @@ -76,8 +76,8 @@ namespace Microsoft.PowerFx.Interpreter.Tests AllowsSideEffects = true, Culture = CultureInfo.InvariantCulture }; - var parseResult = UserDefinitions.Parse(script, options); - var udfs = UserDefinedFunction.CreateFunctions(parseResult.UDFs.Where(udf => udf.IsParseValid), _primitiveTypes, out var errors); + var parseResult = UserDefinitions.Parse(script, options); + var udfs = UserDefinedFunction.CreateFunctions(parseResult.UDFs.Where(udf => udf.IsParseValid), _primitiveTypes, out var errors); errors.AddRange(parseResult.Errors ?? Enumerable.Empty()); Assert.True(errors.Any()); @@ -93,16 +93,16 @@ namespace Microsoft.PowerFx.Interpreter.Tests AllowsSideEffects = true, Culture = CultureInfo.InvariantCulture }; - var parseResult = UserDefinitions.Parse(script, options); - var udfs = UserDefinedFunction.CreateFunctions(parseResult.UDFs.Where(udf => udf.IsParseValid), _primitiveTypes, out var errors); + var parseResult = UserDefinitions.Parse(script, options); + var udfs = UserDefinedFunction.CreateFunctions(parseResult.UDFs.Where(udf => udf.IsParseValid), _primitiveTypes, out var errors); errors.AddRange(parseResult.Errors ?? Enumerable.Empty()); Assert.True(errors.Any()); - Assert.Equal(udfCount, parseResult.UDFs.Count()); + Assert.Equal(udfCount, parseResult.UDFs.Count()); Assert.Equal(validUdfCount, udfs.Count()); Assert.Equal(nfCount, parseResult.NamedFormulas.Count()); - } - + } + [Theory] [InlineData("test2(b: Boolean): Boolean = { Set(a, b); };")] public void ImperativeUdfsEnabledOrDisabled(string script) @@ -111,23 +111,23 @@ namespace Microsoft.PowerFx.Interpreter.Tests { AllowsSideEffects = false, Culture = CultureInfo.InvariantCulture - }; + }; - var parseResult = UserDefinitions.Parse(script, options); - var udfs = UserDefinedFunction.CreateFunctions(parseResult.UDFs.Where(udf => udf.IsParseValid), _primitiveTypes, out var errors); + var parseResult = UserDefinitions.Parse(script, options); + var udfs = UserDefinedFunction.CreateFunctions(parseResult.UDFs.Where(udf => udf.IsParseValid), _primitiveTypes, out var errors); errors.AddRange(parseResult.Errors ?? Enumerable.Empty()); - Assert.True(errors.Any()); - + Assert.True(errors.Any()); + options = new ParserOptions() { AllowsSideEffects = true, Culture = CultureInfo.InvariantCulture }; - parseResult = UserDefinitions.Parse(script, options); - udfs = UserDefinedFunction.CreateFunctions(parseResult.UDFs.Where(udf => udf.IsParseValid), _primitiveTypes, out errors); - errors.AddRange(parseResult.Errors ?? Enumerable.Empty()); - + parseResult = UserDefinitions.Parse(script, options); + udfs = UserDefinedFunction.CreateFunctions(parseResult.UDFs.Where(udf => udf.IsParseValid), _primitiveTypes, out errors); + errors.AddRange(parseResult.Errors ?? Enumerable.Empty()); + Assert.False(errors.Any()); } } diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/UserInfoTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/UserInfoTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/UserInfoTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/UserInfoTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/ValueTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/ValueTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/ValueTests.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/ValueTests.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/xUnitExtensions/InterpreterTestCase.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/xUnitExtensions/InterpreterTestCase.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/xUnitExtensions/InterpreterTestCase.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/xUnitExtensions/InterpreterTestCase.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/xUnitExtensions/InterpreterTheoryAttribute.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/xUnitExtensions/InterpreterTheoryAttribute.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/xUnitExtensions/InterpreterTheoryAttribute.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/xUnitExtensions/InterpreterTheoryAttribute.cs diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests/xUnitExtensions/InterpreterTheoryDiscoverer.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/xUnitExtensions/InterpreterTheoryDiscoverer.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Interpreter.Tests/xUnitExtensions/InterpreterTheoryDiscoverer.cs rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/xUnitExtensions/InterpreterTheoryDiscoverer.cs diff --git a/src/tests/Microsoft.PowerFx.Json.Tests/JSONTests.cs b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/JSONTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Json.Tests/JSONTests.cs rename to src/tests/Microsoft.PowerFx.Json.Tests.Shared/JSONTests.cs diff --git a/src/tests/Microsoft.PowerFx.Json.Tests.Shared/Microsoft.PowerFx.Json.Tests.Shared.projitems b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/Microsoft.PowerFx.Json.Tests.Shared.projitems new file mode 100644 index 000000000..2e74634b4 --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/Microsoft.PowerFx.Json.Tests.Shared.projitems @@ -0,0 +1,19 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + b23198d8-6652-4ce5-aee3-8646202e6bbd + + + Microsoft.PowerFx.Json.Tests.Shared + + + + + + + PreserveNewest + + + \ No newline at end of file diff --git a/src/tests/Microsoft.PowerFx.Json.Tests.Shared/Microsoft.PowerFx.Json.Tests.Shared.shproj b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/Microsoft.PowerFx.Json.Tests.Shared.shproj new file mode 100644 index 000000000..db5367c88 --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/Microsoft.PowerFx.Json.Tests.Shared.shproj @@ -0,0 +1,13 @@ + + + + b23198d8-6652-4ce5-aee3-8646202e6bbd + 14.0 + + + + + + + + diff --git a/src/tests/Microsoft.PowerFx.Json.Tests/ParseJSONTests.cs b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/ParseJSONTests.cs similarity index 99% rename from src/tests/Microsoft.PowerFx.Json.Tests/ParseJSONTests.cs rename to src/tests/Microsoft.PowerFx.Json.Tests.Shared/ParseJSONTests.cs index 9a68755b5..7db71fb02 100644 --- a/src/tests/Microsoft.PowerFx.Json.Tests/ParseJSONTests.cs +++ b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/ParseJSONTests.cs @@ -22,25 +22,25 @@ namespace Microsoft.PowerFx.Json.Tests var engine = new RecalcEngine(config); var result = engine.Eval("Value(ParseJSON(\"5\"))"); Assert.Equal(5m, result.ToObject()); - } - + } + [Fact] public void SingleColumnTableFromJson() - { - var tableType = RecordType.Empty().Add("Value", FormulaType.String).ToTable(); - var res = FormulaValueJSON.FromJson("[ { \"Value\": \"Seattle\"}, { \"Value\": \"Redmond\"} ]", tableType); - - var symbol = new SymbolTable(); - var slot = symbol.AddVariable("table", res.Type); - var symValue = new SymbolValues(symbol); - symValue.Set(slot, res); - + { + var tableType = RecordType.Empty().Add("Value", FormulaType.String).ToTable(); + var res = FormulaValueJSON.FromJson("[ { \"Value\": \"Seattle\"}, { \"Value\": \"Redmond\"} ]", tableType); + + var symbol = new SymbolTable(); + var slot = symbol.AddVariable("table", res.Type); + var symValue = new SymbolValues(symbol); + symValue.Set(slot, res); + var config = new PowerFxConfig(); - config.EnableJsonFunctions(); + config.EnableJsonFunctions(); var engine = new RecalcEngine(config); - var result = engine.EvalAsync("First(table).Value", CancellationToken.None, symValue).ConfigureAwait(false).GetAwaiter().GetResult(); - Assert.Equal("Seattle", result.ToObject()); + var result = engine.EvalAsync("First(table).Value", CancellationToken.None, symValue).ConfigureAwait(false).GetAwaiter().GetResult(); + Assert.Equal("Seattle", result.ToObject()); } [Fact] @@ -53,19 +53,19 @@ namespace Microsoft.PowerFx.Json.Tests DecimalValue wv = (DecimalValue)fv; Assert.Equal(17m, wv.Value); - Assert.Equal("w", wv.Type.ToStringWithDisplayNames()); - + Assert.Equal("w", wv.Type.ToStringWithDisplayNames()); + FormulaValue fv2 = FormulaValueJSON.FromJson(expr, FormulaType.Number); Assert.NotNull(fv2); - Assert.True(fv2 is NumberValue); + Assert.True(fv2 is NumberValue); NumberValue nv = (NumberValue)fv2; Assert.Equal(17, nv.Value); Assert.Equal("n", nv.Type.ToStringWithDisplayNames()); - + FormulaValue wv2 = FormulaValueJSON.FromJson(expr, FormulaType.Decimal); Assert.NotNull(wv2); Assert.True(wv2 is DecimalValue); - Assert.Equal(17m, ((DecimalValue)wv2).Value); + Assert.Equal(17m, ((DecimalValue)wv2).Value); Assert.Equal("w", wv2.Type.ToStringWithDisplayNames()); Assert.Throws(() => FormulaValueJSON.FromJson(expr, TableType.Empty())); @@ -80,40 +80,40 @@ namespace Microsoft.PowerFx.Json.Tests Assert.True(fv3 is UntypedObjectValue); Assert.NotNull(((UntypedObjectValue)fv3).Impl); - Assert.Equal(17d, ((UntypedObjectValue)fv3).Impl.GetDouble()); + Assert.Equal(17d, ((UntypedObjectValue)fv3).Impl.GetDouble()); Assert.Equal(17m, ((UntypedObjectValue)fv3).Impl.GetDecimal()); FormulaValue fv4 = FormulaValueJSON.FromJson(expr, new BlankType()); Assert.NotNull(fv4); Assert.True(fv4 is DecimalValue); Assert.Equal(17m, ((DecimalValue)fv4).Value); - } - + } + [Fact] public void ParseJsonNumber_NumberIsFloat() { string expr = "17"; FormulaValue fv = FormulaValueJSON.FromJson(expr, numberIsFloat: true); Assert.NotNull(fv); - Assert.True(fv is NumberValue); - + Assert.True(fv is NumberValue); + NumberValue wv = (NumberValue)fv; Assert.Equal(17, wv.Value); - Assert.Equal("n", wv.Type.ToStringWithDisplayNames()); - - // for the rest of these tests with explicit typing, numberIsFloat should have no effect, - // the results should be the same as above + Assert.Equal("n", wv.Type.ToStringWithDisplayNames()); + + // for the rest of these tests with explicit typing, numberIsFloat should have no effect, + // the results should be the same as above FormulaValue fv2 = FormulaValueJSON.FromJson(expr, FormulaType.Number, numberIsFloat: true); Assert.NotNull(fv2); - Assert.True(fv2 is NumberValue); + Assert.True(fv2 is NumberValue); NumberValue nv = (NumberValue)fv2; Assert.Equal(17, nv.Value); - Assert.Equal("n", nv.Type.ToStringWithDisplayNames()); - + Assert.Equal("n", nv.Type.ToStringWithDisplayNames()); + FormulaValue wv2 = FormulaValueJSON.FromJson(expr, FormulaType.Decimal, numberIsFloat: true); Assert.NotNull(wv2); Assert.True(wv2 is DecimalValue); - Assert.Equal(17m, ((DecimalValue)wv2).Value); + Assert.Equal(17m, ((DecimalValue)wv2).Value); Assert.Equal("w", wv2.Type.ToStringWithDisplayNames()); Assert.Throws(() => FormulaValueJSON.FromJson(expr, TableType.Empty(), numberIsFloat: true)); @@ -128,7 +128,7 @@ namespace Microsoft.PowerFx.Json.Tests Assert.True(fv3 is UntypedObjectValue); Assert.NotNull(((UntypedObjectValue)fv3).Impl); - Assert.Equal(17d, ((UntypedObjectValue)fv3).Impl.GetDouble()); + Assert.Equal(17d, ((UntypedObjectValue)fv3).Impl.GetDouble()); Assert.Equal(17m, ((UntypedObjectValue)fv3).Impl.GetDecimal()); FormulaValue fv4 = FormulaValueJSON.FromJson(expr, new BlankType(), numberIsFloat: true); @@ -156,7 +156,7 @@ namespace Microsoft.PowerFx.Json.Tests Assert.Throws(() => FormulaValueJSON.FromJson(expr, TableType.Empty())); Assert.Throws(() => FormulaValueJSON.FromJson(expr, RecordType.Empty())); - Assert.Throws(() => FormulaValueJSON.FromJson(expr, FormulaType.Number)); + Assert.Throws(() => FormulaValueJSON.FromJson(expr, FormulaType.Number)); Assert.Throws(() => FormulaValueJSON.FromJson(expr, FormulaType.Decimal)); Assert.Throws(() => FormulaValueJSON.FromJson(expr, FormulaType.Boolean)); Assert.Throws(() => FormulaValueJSON.FromJson(expr, FormulaType.Guid)); @@ -226,7 +226,7 @@ namespace Microsoft.PowerFx.Json.Tests Assert.Throws(() => FormulaValueJSON.FromJson(expr, TableType.Empty())); Assert.Throws(() => FormulaValueJSON.FromJson(expr, RecordType.Empty())); - Assert.Throws(() => FormulaValueJSON.FromJson(expr, FormulaType.Number)); + Assert.Throws(() => FormulaValueJSON.FromJson(expr, FormulaType.Number)); Assert.Throws(() => FormulaValueJSON.FromJson(expr, FormulaType.Decimal)); Assert.Throws(() => FormulaValueJSON.FromJson(expr, FormulaType.String)); Assert.Throws(() => FormulaValueJSON.FromJson(expr, FormulaType.Guid)); @@ -262,7 +262,7 @@ namespace Microsoft.PowerFx.Json.Tests Assert.True(fv2 is RecordValue); Assert.Throws(() => FormulaValueJSON.FromJson(expr, TableType.Empty())); - Assert.Throws(() => FormulaValueJSON.FromJson(expr, FormulaType.Number)); + Assert.Throws(() => FormulaValueJSON.FromJson(expr, FormulaType.Number)); Assert.Throws(() => FormulaValueJSON.FromJson(expr, FormulaType.Decimal)); Assert.Throws(() => FormulaValueJSON.FromJson(expr, FormulaType.String)); Assert.Throws(() => FormulaValueJSON.FromJson(expr, FormulaType.Boolean)); @@ -312,7 +312,7 @@ namespace Microsoft.PowerFx.Json.Tests Assert.True(fv2 is RecordValue); Assert.Throws(() => FormulaValueJSON.FromJson(expr, TableType.Empty())); - Assert.Throws(() => FormulaValueJSON.FromJson(expr, FormulaType.Number)); + Assert.Throws(() => FormulaValueJSON.FromJson(expr, FormulaType.Number)); Assert.Throws(() => FormulaValueJSON.FromJson(expr, FormulaType.Decimal)); Assert.Throws(() => FormulaValueJSON.FromJson(expr, FormulaType.String)); Assert.Throws(() => FormulaValueJSON.FromJson(expr, FormulaType.Boolean)); @@ -330,19 +330,19 @@ namespace Microsoft.PowerFx.Json.Tests Assert.True(uo.Impl.TryGetProperty("b", out IUntypedObject b)); Assert.Equal("str", b.GetString()); Assert.True(uo.Impl.TryGetProperty("c", out IUntypedObject c)); - Assert.Equal(17.5d, c.GetDouble()); + Assert.Equal(17.5d, c.GetDouble()); Assert.Equal(17.5m, c.GetDecimal()); Assert.True(uo.Impl.TryGetProperty("d", out IUntypedObject d)); Assert.Null(d.GetString()); Assert.True(uo.Impl.TryGetProperty("e", out IUntypedObject e)); Assert.Equal(2, e.GetArrayLength()); Assert.Equal(1d, e[0].GetDouble()); - Assert.Equal(2d, e[1].GetDouble()); + Assert.Equal(2d, e[1].GetDouble()); Assert.Equal(1m, e[0].GetDecimal()); Assert.Equal(2m, e[1].GetDecimal()); Assert.True(uo.Impl.TryGetProperty("f", out IUntypedObject f)); Assert.True(f.TryGetProperty("g", out IUntypedObject g)); - Assert.Equal(7, g.GetDouble()); + Assert.Equal(7, g.GetDouble()); Assert.Equal(7m, g.GetDecimal()); FormulaValue fv4 = FormulaValueJSON.FromJson(expr, new BlankType()); @@ -352,8 +352,8 @@ namespace Microsoft.PowerFx.Json.Tests rv = (RecordValue)fv4; Assert.Equal(6, rv.Fields.Count()); Assert.Equal("![a:b, b:s, c:w, d:N, e:*[Value:w], f:![g:w]]", rv.Type.ToStringWithDisplayNames()); - } - + } + [Fact] public void ParseJsonRecord_NumberIsFloat() { @@ -365,7 +365,7 @@ namespace Microsoft.PowerFx.Json.Tests RecordValue rv = (RecordValue)fv; Assert.Equal(6, rv.Fields.Count()); Assert.Equal("![a:b, b:s, c:n, d:N, e:*[Value:n], f:![g:n]]", rv.Type.ToStringWithDisplayNames()); - Assert.True(rv.GetField("a") is BooleanValue); + Assert.True(rv.GetField("a") is BooleanValue); Assert.True(((BooleanValue)rv.GetField("a")).Value); Assert.True(rv.GetField("b") is StringValue); Assert.Equal("str", ((StringValue)rv.GetField("b")).Value); @@ -381,15 +381,15 @@ namespace Microsoft.PowerFx.Json.Tests RecordValue innerRecord = (RecordValue)rv.GetField("f"); Assert.True(innerRecord.GetField("g") is NumberValue); Assert.Equal(7, ((NumberValue)innerRecord.GetField("g")).Value); - - // for the rest of these tests with explicit typing, numberIsFloat should have no effect, + + // for the rest of these tests with explicit typing, numberIsFloat should have no effect, // the results should be the same as above FormulaValue fv2 = FormulaValueJSON.FromJson(expr, rv.Type, numberIsFloat: true); Assert.NotNull(fv2); Assert.True(fv2 is RecordValue); Assert.Throws(() => FormulaValueJSON.FromJson(expr, TableType.Empty(), numberIsFloat: true)); - Assert.Throws(() => FormulaValueJSON.FromJson(expr, FormulaType.Number, numberIsFloat: true)); + Assert.Throws(() => FormulaValueJSON.FromJson(expr, FormulaType.Number, numberIsFloat: true)); Assert.Throws(() => FormulaValueJSON.FromJson(expr, FormulaType.Decimal, numberIsFloat: true)); Assert.Throws(() => FormulaValueJSON.FromJson(expr, FormulaType.String, numberIsFloat: true)); Assert.Throws(() => FormulaValueJSON.FromJson(expr, FormulaType.Boolean, numberIsFloat: true)); @@ -407,28 +407,28 @@ namespace Microsoft.PowerFx.Json.Tests Assert.True(uo.Impl.TryGetProperty("b", out IUntypedObject b)); Assert.Equal("str", b.GetString()); Assert.True(uo.Impl.TryGetProperty("c", out IUntypedObject c)); - Assert.Equal(17.5d, c.GetDouble()); + Assert.Equal(17.5d, c.GetDouble()); Assert.Equal(17.5m, c.GetDecimal()); Assert.True(uo.Impl.TryGetProperty("d", out IUntypedObject d)); Assert.Null(d.GetString()); Assert.True(uo.Impl.TryGetProperty("e", out IUntypedObject e)); Assert.Equal(2, e.GetArrayLength()); Assert.Equal(1d, e[0].GetDouble()); - Assert.Equal(2d, e[1].GetDouble()); + Assert.Equal(2d, e[1].GetDouble()); Assert.Equal(1m, e[0].GetDecimal()); Assert.Equal(2m, e[1].GetDecimal()); Assert.True(uo.Impl.TryGetProperty("f", out IUntypedObject f)); Assert.True(f.TryGetProperty("g", out IUntypedObject g)); - Assert.Equal(7, g.GetDouble()); + Assert.Equal(7, g.GetDouble()); Assert.Equal(7m, g.GetDecimal()); FormulaValue fv4 = FormulaValueJSON.FromJson(expr, new BlankType(), numberIsFloat: true); Assert.NotNull(fv4); - Assert.True(fv4 is RecordValue); - + Assert.True(fv4 is RecordValue); + rv = (RecordValue)fv4; Assert.Equal(6, rv.Fields.Count()); - Assert.Equal("![a:b, b:s, c:n, d:N, e:*[Value:n], f:![g:n]]", rv.Type.ToStringWithDisplayNames()); + Assert.Equal("![a:b, b:s, c:n, d:N, e:*[Value:n], f:![g:n]]", rv.Type.ToStringWithDisplayNames()); } [Fact] @@ -449,7 +449,7 @@ namespace Microsoft.PowerFx.Json.Tests Assert.True(fv2 is TableValue); Assert.Throws(() => FormulaValueJSON.FromJson(expr, RecordType.Empty())); - Assert.Throws(() => FormulaValueJSON.FromJson(expr, FormulaType.Number)); + Assert.Throws(() => FormulaValueJSON.FromJson(expr, FormulaType.Number)); Assert.Throws(() => FormulaValueJSON.FromJson(expr, FormulaType.Decimal)); Assert.Throws(() => FormulaValueJSON.FromJson(expr, FormulaType.String)); Assert.Throws(() => FormulaValueJSON.FromJson(expr, FormulaType.Boolean)); @@ -480,14 +480,14 @@ namespace Microsoft.PowerFx.Json.Tests int i = 0; foreach (var row in res.Rows) - { - if (expected[i] is int) - { - Assert.Equal(expected[i++].ToString(), row.Value.GetField("Value").ToObject().ToString()); - } - else - { - Assert.Equal(expected[i++], row.Value.GetField("Value").ToObject()); + { + if (expected[i] is int) + { + Assert.Equal(expected[i++].ToString(), row.Value.GetField("Value").ToObject().ToString()); + } + else + { + Assert.Equal(expected[i++], row.Value.GetField("Value").ToObject()); } } } @@ -537,8 +537,8 @@ namespace Microsoft.PowerFx.Json.Tests switch (expected[i]) { case double: - Assert.Equal(expected[i], array[i].GetDouble()); - Assert.Equal(expected[i], (double)array[i].GetDecimal()); + Assert.Equal(expected[i], array[i].GetDouble()); + Assert.Equal(expected[i], (double)array[i].GetDecimal()); break; case string: Assert.Equal(expected[i], array[i].GetString()); @@ -548,8 +548,8 @@ namespace Microsoft.PowerFx.Json.Tests break; } } - } - + } + [Theory] [InlineData("[{\"Name\": \"Peter\"}]", "[]")] [InlineData("[1,2,3]", "[]")] @@ -571,8 +571,8 @@ namespace Microsoft.PowerFx.Json.Tests FormulaValue prototypeValue = FormulaValueJSON.FromJson(prototypeJson); FormulaValue value = FormulaValueJSON.FromJson(valueJson, prototypeValue.Type); Assert.NotEqual(prototypeValue.Type, value.Type); - } - + } + [Theory] [InlineData("[{\"Name\": \"Peter\"}]", "[1,2,3]")] public async Task FromArrayWithIncompatibleType2(string prototypeJson, string valueJson) diff --git a/src/tests/Microsoft.PowerFx.Json.Tests/PublicSurfaceTests.cs b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/PublicSurfaceTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Json.Tests/PublicSurfaceTests.cs rename to src/tests/Microsoft.PowerFx.Json.Tests.Shared/PublicSurfaceTests.cs diff --git a/src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/FormulaTypeSerializerSnapshotTests.cs b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/FormulaTypeSerializerSnapshotTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/FormulaTypeSerializerSnapshotTests.cs rename to src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/FormulaTypeSerializerSnapshotTests.cs diff --git a/src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/Recursive.json b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/Recursive.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/Recursive.json rename to src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/Recursive.json diff --git a/src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimpleEmptyRecord.json b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimpleEmptyRecord.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimpleEmptyRecord.json rename to src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimpleEmptyRecord.json diff --git a/src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimpleEmptyTable.json b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimpleEmptyTable.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimpleEmptyTable.json rename to src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimpleEmptyTable.json diff --git a/src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveBlank.json b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveBlank.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveBlank.json rename to src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveBlank.json diff --git a/src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveBoolean.json b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveBoolean.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveBoolean.json rename to src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveBoolean.json diff --git a/src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveDTNTZ.json b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveDTNTZ.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveDTNTZ.json rename to src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveDTNTZ.json diff --git a/src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveDate.json b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveDate.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveDate.json rename to src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveDate.json diff --git a/src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveDateTime.json b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveDateTime.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveDateTime.json rename to src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveDateTime.json diff --git a/src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveError.json b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveError.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveError.json rename to src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveError.json diff --git a/src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveGuid.json b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveGuid.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveGuid.json rename to src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveGuid.json diff --git a/src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveHyperlink.json b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveHyperlink.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveHyperlink.json rename to src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveHyperlink.json diff --git a/src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveNumber.json b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveNumber.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveNumber.json rename to src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveNumber.json diff --git a/src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveString.json b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveString.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveString.json rename to src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveString.json diff --git a/src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveTime.json b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveTime.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveTime.json rename to src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveTime.json diff --git a/src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveUntyped.json b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveUntyped.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveUntyped.json rename to src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimplePrimitiveUntyped.json diff --git a/src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimpleRecord.json b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimpleRecord.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimpleRecord.json rename to src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimpleRecord.json diff --git a/src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimpleRecordNested.json b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimpleRecordNested.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimpleRecordNested.json rename to src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimpleRecordNested.json diff --git a/src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimpleRecordTableNested.json b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimpleRecordTableNested.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimpleRecordTableNested.json rename to src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimpleRecordTableNested.json diff --git a/src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimpleTableSingleColumn.json b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimpleTableSingleColumn.json similarity index 100% rename from src/tests/Microsoft.PowerFx.Json.Tests/TypeSystemTests/JsonTypeSnapshots/SimpleTableSingleColumn.json rename to src/tests/Microsoft.PowerFx.Json.Tests.Shared/TypeSystemTests/JsonTypeSnapshots/SimpleTableSingleColumn.json diff --git a/src/tests/Microsoft.PowerFx.Performance.Tests/BasicPerformance.cs b/src/tests/Microsoft.PowerFx.Performance.Tests.Shared/BasicPerformance.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Performance.Tests/BasicPerformance.cs rename to src/tests/Microsoft.PowerFx.Performance.Tests.Shared/BasicPerformance.cs diff --git a/src/tests/Microsoft.PowerFx.Performance.Tests.Shared/Microsoft.PowerFx.Performance.Tests.Shared.projitems b/src/tests/Microsoft.PowerFx.Performance.Tests.Shared/Microsoft.PowerFx.Performance.Tests.Shared.projitems new file mode 100644 index 000000000..aa843b19e --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Performance.Tests.Shared/Microsoft.PowerFx.Performance.Tests.Shared.projitems @@ -0,0 +1,14 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 21e2744a-4c69-4862-894a-6922c2b8e2f8 + + + Microsoft.PowerFx.Performance.Tests.Shared + + + + + \ No newline at end of file diff --git a/src/tests/Microsoft.PowerFx.Performance.Tests.Shared/Microsoft.PowerFx.Performance.Tests.Shared.shproj b/src/tests/Microsoft.PowerFx.Performance.Tests.Shared/Microsoft.PowerFx.Performance.Tests.Shared.shproj new file mode 100644 index 000000000..65da87498 --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Performance.Tests.Shared/Microsoft.PowerFx.Performance.Tests.Shared.shproj @@ -0,0 +1,13 @@ + + + + 21e2744a-4c69-4862-894a-6922c2b8e2f8 + 14.0 + + + + + + + + diff --git a/src/tests/Microsoft.PowerFx.Performance.Tests/Program.cs b/src/tests/Microsoft.PowerFx.Performance.Tests.Shared/Program.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Performance.Tests/Program.cs rename to src/tests/Microsoft.PowerFx.Performance.Tests.Shared/Program.cs diff --git a/src/tests/Microsoft.PowerFx.Performance.Tests/PvaPerformance.cs b/src/tests/Microsoft.PowerFx.Performance.Tests.Shared/PvaPerformance.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Performance.Tests/PvaPerformance.cs rename to src/tests/Microsoft.PowerFx.Performance.Tests.Shared/PvaPerformance.cs diff --git a/src/tests/Microsoft.PowerFx.Performance.Tests/Reference.cs b/src/tests/Microsoft.PowerFx.Performance.Tests.Shared/Reference.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Performance.Tests/Reference.cs rename to src/tests/Microsoft.PowerFx.Performance.Tests.Shared/Reference.cs diff --git a/src/tests/Microsoft.PowerFx.Performance.Tests/UnitTests.cs b/src/tests/Microsoft.PowerFx.Performance.Tests.Shared/UnitTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Performance.Tests/UnitTests.cs rename to src/tests/Microsoft.PowerFx.Performance.Tests.Shared/UnitTests.cs diff --git a/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Microsoft.PowerFx.Repl.Tests.Shared.projitems b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Microsoft.PowerFx.Repl.Tests.Shared.projitems new file mode 100644 index 000000000..23db53f13 --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Microsoft.PowerFx.Repl.Tests.Shared.projitems @@ -0,0 +1,15 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + f065ba6a-db76-409b-9516-1f027753c2f4 + + + Microsoft.PowerFx.Repl.Tests.Shared + + + + + + \ No newline at end of file diff --git a/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Microsoft.PowerFx.Repl.Tests.Shared.shproj b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Microsoft.PowerFx.Repl.Tests.Shared.shproj new file mode 100644 index 000000000..8aa6644cb --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Microsoft.PowerFx.Repl.Tests.Shared.shproj @@ -0,0 +1,13 @@ + + + + f065ba6a-db76-409b-9516-1f027753c2f4 + 14.0 + + + + + + + + diff --git a/src/tests/Microsoft.PowerFx.Repl.Tests/Mocks/TestReplOutput.cs b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Mocks/TestReplOutput.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Repl.Tests/Mocks/TestReplOutput.cs rename to src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Mocks/TestReplOutput.cs diff --git a/src/tests/Microsoft.PowerFx.Repl.Tests/MultilineProcessorTests.cs b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/MultilineProcessorTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Repl.Tests/MultilineProcessorTests.cs rename to src/tests/Microsoft.PowerFx.Repl.Tests.Shared/MultilineProcessorTests.cs diff --git a/src/tests/Microsoft.PowerFx.Repl.Tests/ReplTests.cs b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/ReplTests.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.Repl.Tests/ReplTests.cs rename to src/tests/Microsoft.PowerFx.Repl.Tests.Shared/ReplTests.cs diff --git a/src/tests/Microsoft.PowerFx.TexlFunctionExporter/AssemblyProperties.cs b/src/tests/Microsoft.PowerFx.TexlFunctionExporter.Shared/AssemblyProperties.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.TexlFunctionExporter/AssemblyProperties.cs rename to src/tests/Microsoft.PowerFx.TexlFunctionExporter.Shared/AssemblyProperties.cs diff --git a/src/tests/Microsoft.PowerFx.TexlFunctionExporter/ConnectorStat.cs b/src/tests/Microsoft.PowerFx.TexlFunctionExporter.Shared/ConnectorStat.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.TexlFunctionExporter/ConnectorStat.cs rename to src/tests/Microsoft.PowerFx.TexlFunctionExporter.Shared/ConnectorStat.cs diff --git a/src/tests/Microsoft.PowerFx.TexlFunctionExporter/FunctionStat.cs b/src/tests/Microsoft.PowerFx.TexlFunctionExporter.Shared/FunctionStat.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.TexlFunctionExporter/FunctionStat.cs rename to src/tests/Microsoft.PowerFx.TexlFunctionExporter.Shared/FunctionStat.cs diff --git a/src/tests/Microsoft.PowerFx.TexlFunctionExporter/IYamlFunction.cs b/src/tests/Microsoft.PowerFx.TexlFunctionExporter.Shared/IYamlFunction.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.TexlFunctionExporter/IYamlFunction.cs rename to src/tests/Microsoft.PowerFx.TexlFunctionExporter.Shared/IYamlFunction.cs diff --git a/src/tests/Microsoft.PowerFx.TexlFunctionExporter.Shared/Microsoft.PowerFx.TexlFunctionExporter.Shared.projitems b/src/tests/Microsoft.PowerFx.TexlFunctionExporter.Shared/Microsoft.PowerFx.TexlFunctionExporter.Shared.projitems new file mode 100644 index 000000000..7b092bc43 --- /dev/null +++ b/src/tests/Microsoft.PowerFx.TexlFunctionExporter.Shared/Microsoft.PowerFx.TexlFunctionExporter.Shared.projitems @@ -0,0 +1,15 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 6bb3fa08-791f-4418-ad1c-fab5ee67d88c + + + Microsoft.PowerFx.TexlFunctionExporter.Shared + + + + + + \ No newline at end of file diff --git a/src/tests/Microsoft.PowerFx.TexlFunctionExporter.Shared/Microsoft.PowerFx.TexlFunctionExporter.Shared.shproj b/src/tests/Microsoft.PowerFx.TexlFunctionExporter.Shared/Microsoft.PowerFx.TexlFunctionExporter.Shared.shproj new file mode 100644 index 000000000..49e0f18ce --- /dev/null +++ b/src/tests/Microsoft.PowerFx.TexlFunctionExporter.Shared/Microsoft.PowerFx.TexlFunctionExporter.Shared.shproj @@ -0,0 +1,13 @@ + + + + 6bb3fa08-791f-4418-ad1c-fab5ee67d88c + 14.0 + + + + + + + + diff --git a/src/tests/Microsoft.PowerFx.TexlFunctionExporter/SwaggerFileIdentification.cs b/src/tests/Microsoft.PowerFx.TexlFunctionExporter.Shared/SwaggerFileIdentification.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.TexlFunctionExporter/SwaggerFileIdentification.cs rename to src/tests/Microsoft.PowerFx.TexlFunctionExporter.Shared/SwaggerFileIdentification.cs diff --git a/src/tests/Microsoft.PowerFx.TexlFunctionExporter/SwaggerLocatorSettings.cs b/src/tests/Microsoft.PowerFx.TexlFunctionExporter.Shared/SwaggerLocatorSettings.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.TexlFunctionExporter/SwaggerLocatorSettings.cs rename to src/tests/Microsoft.PowerFx.TexlFunctionExporter.Shared/SwaggerLocatorSettings.cs diff --git a/src/tests/Microsoft.PowerFx.TexlFunctionExporter/TexlYamlComparer.cs b/src/tests/Microsoft.PowerFx.TexlFunctionExporter.Shared/TexlYamlComparer.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.TexlFunctionExporter/TexlYamlComparer.cs rename to src/tests/Microsoft.PowerFx.TexlFunctionExporter.Shared/TexlYamlComparer.cs diff --git a/src/tests/Microsoft.PowerFx.TexlFunctionExporter/YamlComparer.cs b/src/tests/Microsoft.PowerFx.TexlFunctionExporter.Shared/YamlComparer.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.TexlFunctionExporter/YamlComparer.cs rename to src/tests/Microsoft.PowerFx.TexlFunctionExporter.Shared/YamlComparer.cs diff --git a/src/tests/Microsoft.PowerFx.TexlFunctionExporter/YamlExporter.cs b/src/tests/Microsoft.PowerFx.TexlFunctionExporter.Shared/YamlExporter.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.TexlFunctionExporter/YamlExporter.cs rename to src/tests/Microsoft.PowerFx.TexlFunctionExporter.Shared/YamlExporter.cs diff --git a/src/tests/Microsoft.PowerFx.TexlFunctionExporter/YamlReaderWriter.cs b/src/tests/Microsoft.PowerFx.TexlFunctionExporter.Shared/YamlReaderWriter.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.TexlFunctionExporter/YamlReaderWriter.cs rename to src/tests/Microsoft.PowerFx.TexlFunctionExporter.Shared/YamlReaderWriter.cs diff --git a/src/tests/Microsoft.PowerFx.TexlFunctionExporter/YamlTexlFunction.cs b/src/tests/Microsoft.PowerFx.TexlFunctionExporter.Shared/YamlTexlFunction.cs similarity index 100% rename from src/tests/Microsoft.PowerFx.TexlFunctionExporter/YamlTexlFunction.cs rename to src/tests/Microsoft.PowerFx.TexlFunctionExporter.Shared/YamlTexlFunction.cs diff --git a/src/tests/Microsoft.PowerFx.TexlFunctionExporter/icon.png b/src/tests/Microsoft.PowerFx.TexlFunctionExporter.Shared/icon.png similarity index 100% rename from src/tests/Microsoft.PowerFx.TexlFunctionExporter/icon.png rename to src/tests/Microsoft.PowerFx.TexlFunctionExporter.Shared/icon.png