From 2d37016c0fedd067031ce70c3a21c82303f92121 Mon Sep 17 00:00:00 2001 From: "Christoph Bergmeister [MVP]" Date: Wed, 13 May 2020 21:47:53 +0100 Subject: [PATCH 001/219] Make .Net Core version in Docker image of .devcontainer match the one in global.json (#1494) --- .devcontainer/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 0b85b17b1..571a36cf7 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -3,7 +3,7 @@ # Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. #------------------------------------------------------------------------------------------------------------- -FROM mcr.microsoft.com/dotnet/core/sdk:3.1 +FROM mcr.microsoft.com/dotnet/core/sdk:3.1.101 # This Dockerfile adds a non-root user with sudo access. Use the "remoteUser" # property in devcontainer.json to use it. On Linux, the container user's GID/UIDs From aa66551ee6739327355e92dd3a0da5ade0b2bb6a Mon Sep 17 00:00:00 2001 From: "Christoph Bergmeister [MVP]" Date: Mon, 18 May 2020 22:07:02 +0100 Subject: [PATCH 002/219] UseConsistentWhitespace: Fix CheckParameter bug when using interpolated string (#1498) --- Rules/UseConsistentWhitespace.cs | 3 ++- Tests/Rules/UseConsistentWhitespace.tests.ps1 | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Rules/UseConsistentWhitespace.cs b/Rules/UseConsistentWhitespace.cs index f589d9e45..86795d705 100644 --- a/Rules/UseConsistentWhitespace.cs +++ b/Rules/UseConsistentWhitespace.cs @@ -390,7 +390,8 @@ private IEnumerable FindParameterViolations(Ast ast) testAst => testAst is CommandAst, true); foreach (CommandAst commandAst in commandAsts) { - List commandParameterAstElements = commandAst.FindAll(testAst => true, searchNestedScriptBlocks: false).ToList(); + List commandParameterAstElements = commandAst.FindAll( + testAst => testAst.Parent == commandAst, searchNestedScriptBlocks: false).ToList(); for (int i = 0; i < commandParameterAstElements.Count - 1; i++) { IScriptExtent leftExtent = commandParameterAstElements[i].Extent; diff --git a/Tests/Rules/UseConsistentWhitespace.tests.ps1 b/Tests/Rules/UseConsistentWhitespace.tests.ps1 index 23bf18350..38437076e 100644 --- a/Tests/Rules/UseConsistentWhitespace.tests.ps1 +++ b/Tests/Rules/UseConsistentWhitespace.tests.ps1 @@ -474,11 +474,11 @@ bar -h i ` } It "Should fix script to always have 1 space between parameters except when using colon syntax but not by default" { - $def = 'foo -bar $baz -ParameterName: $ParameterValue' + $def = 'foo -bar $baz -ParameterName: $ParameterValue "$PSScriptRoot\module.psd1"' Invoke-Formatter -ScriptDefinition $def | Should -BeExactly $def -Because 'CheckParameter configuration is not turned on by default (yet) as the setting is new' Invoke-Formatter -ScriptDefinition $def -Settings $settings | - Should -BeExactly 'foo -bar $baz -ParameterName: $ParameterValue' + Should -BeExactly 'foo -bar $baz -ParameterName: $ParameterValue "$PSScriptRoot\module.psd1"' } It "Should fix script when newlines are involved" { From c265f0373555f5293d1a8e7654855464ccaf8d18 Mon Sep 17 00:00:00 2001 From: "Christoph Bergmeister [MVP]" Date: Mon, 18 May 2020 22:07:59 +0100 Subject: [PATCH 003/219] UseUsingScopeModifierInNewRunspaces: Fix ArgumentException when the same variable name is used in 2 different sessions. (#1493) * UseUsingScopeModifierInNewRunspaces: Fix ArgumentException when the same variable name is used in 2 different sessions. * use .add method to fix full .net build Co-authored-by: Christoph Bergmeister --- Rules/UseUsingScopeModifierInNewRunspaces.cs | 33 ++++++++++--------- ...UsingScopeModifierInNewRunspaces.tests.ps1 | 21 ++++++++++++ 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/Rules/UseUsingScopeModifierInNewRunspaces.cs b/Rules/UseUsingScopeModifierInNewRunspaces.cs index 1db914276..6d665e989 100644 --- a/Rules/UseUsingScopeModifierInNewRunspaces.cs +++ b/Rules/UseUsingScopeModifierInNewRunspaces.cs @@ -116,7 +116,7 @@ private class SyntaxCompatibilityVisitor : AstVisitor private static readonly IEnumerable s_invokeCommandCmdletNamesAndAliases = Helper.Instance.CmdletNameAndAliases("Invoke-Command"); - private readonly Dictionary> _varsDeclaredPerSession; + private readonly Dictionary> _varsDeclaredPerSession; private readonly List _diagnosticAccumulator; @@ -127,7 +127,7 @@ private class SyntaxCompatibilityVisitor : AstVisitor public SyntaxCompatibilityVisitor(UseUsingScopeModifierInNewRunspaces rule, string analyzedScriptPath) { _diagnosticAccumulator = new List(); - _varsDeclaredPerSession = new Dictionary>(); + _varsDeclaredPerSession = new Dictionary>(); _rule = rule; _analyzedFilePath = analyzedScriptPath; } @@ -183,7 +183,7 @@ public override AstVisitAction VisitScriptBlockExpression(ScriptBlockExpressionA return AstVisitAction.Continue; } - IReadOnlyDictionary varsInLocalAssignments = FindVarsInAssignmentAsts(scriptBlockExpressionAst); + HashSet varsInLocalAssignments = FindVarsInAssignmentAsts(scriptBlockExpressionAst); if (varsInLocalAssignments != null) { AddAssignedVarsToSession(sessionName, varsInLocalAssignments); @@ -191,7 +191,7 @@ public override AstVisitAction VisitScriptBlockExpression(ScriptBlockExpressionA GenerateDiagnosticRecords( FindNonAssignedNonUsingVarAsts( - scriptBlockExpressionAst, + scriptBlockExpressionAst, GetAssignedVarsInSession(sessionName))); return AstVisitAction.SkipChildren; @@ -205,10 +205,10 @@ public override AstVisitAction VisitScriptBlockExpression(ScriptBlockExpressionA /// Example: `$foo = "foo"` ==> the VariableExpressionAst for $foo is returned /// /// - private static IReadOnlyDictionary FindVarsInAssignmentAsts(Ast ast) + private static HashSet FindVarsInAssignmentAsts(Ast ast) { - Dictionary variableDictionary = - new Dictionary(); + HashSet variableDictionary = + new HashSet(); // Find all variables that are assigned within this ast foreach (AssignmentStatementAst statementAst in ast.FindAll(IsAssignmentStatementAst, true)) @@ -217,11 +217,11 @@ private static IReadOnlyDictionary FindVarsInAssi { string variableName = string.Format(variable.VariablePath.UserPath, StringComparer.OrdinalIgnoreCase); - variableDictionary.Add(variableName, variable); + variableDictionary.Add(variableName); } }; - return new ReadOnlyDictionary(variableDictionary); + return variableDictionary; } /// @@ -264,14 +264,14 @@ private static bool IsAssignmentStatementAst(Ast ast) /// /// private static IEnumerable FindNonAssignedNonUsingVarAsts( - Ast ast, IReadOnlyDictionary varsInAssignments) + Ast ast, HashSet varsInAssignments) { // Find all variables that are not locally assigned, and don't have $using: scope modifier foreach (VariableExpressionAst variable in ast.FindAll(IsNonUsingNonSpecialVariableExpressionAst, true)) { var varName = string.Format(variable.VariablePath.UserPath, StringComparer.OrdinalIgnoreCase); - if (varsInAssignments.ContainsKey(varName)) + if (varsInAssignments.Contains(varName)) { yield break; } @@ -368,7 +368,7 @@ private static bool TryGetSessionNameFromInvokeCommand(CommandAst invokeCommandA /// GetAssignedVarsInSession: Retrieves all previously declared vars for a given session (as in Invoke-Command -Session $session). /// /// - private IReadOnlyDictionary GetAssignedVarsInSession(string sessionName) + private HashSet GetAssignedVarsInSession(string sessionName) { return _varsDeclaredPerSession[sessionName]; } @@ -378,16 +378,19 @@ private IReadOnlyDictionary GetAssignedVarsInSessi /// /// /// - private void AddAssignedVarsToSession(string sessionName, IReadOnlyDictionary variablesToAdd) + private void AddAssignedVarsToSession(string sessionName, HashSet variablesToAdd) { if (!_varsDeclaredPerSession.ContainsKey(sessionName)) { - _varsDeclaredPerSession.Add(sessionName, new Dictionary()); + _varsDeclaredPerSession.Add(sessionName, new HashSet()); } foreach (var item in variablesToAdd) { - _varsDeclaredPerSession[sessionName].Add(item.Key, item.Value); + if (!_varsDeclaredPerSession[sessionName].Contains(item)) + { + _varsDeclaredPerSession[sessionName].Add(item); + } } } diff --git a/Tests/Rules/UseUsingScopeModifierInNewRunspaces.tests.ps1 b/Tests/Rules/UseUsingScopeModifierInNewRunspaces.tests.ps1 index 66452baf6..87dce8e90 100644 --- a/Tests/Rules/UseUsingScopeModifierInNewRunspaces.tests.ps1 +++ b/Tests/Rules/UseUsingScopeModifierInNewRunspaces.tests.ps1 @@ -274,6 +274,27 @@ Describe "UseUsingScopeModifierInNewRunspaces" { } }' } + # Issue 1492: https://github.com/PowerShell/PSScriptAnalyzer/issues/1492 + @{ + Description = 'Does not throw when the same variable name is used in two different sessions' + ScriptBlock = @' +function Get-One{ + + Invoke-Command -Session $sourceRemoteSession { + $a = $sccmModule + foo $a + } +} + +function Get-Two{ + + Invoke-Command -Session $sourceRemoteSession { + $a = $sccmModule + foo $a + } +} +'@ + } ) } From f9661f972883cf0def556ee2afd17fe327d269c2 Mon Sep 17 00:00:00 2001 From: Frederik Hjorslev Date: Fri, 22 May 2020 18:59:04 +0200 Subject: [PATCH 004/219] Remove references to Windows in Introduction (#1509) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 158904dac..c47ba4ac6 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Table of Contents Introduction ============ -PSScriptAnalyzer is a static code checker for Windows PowerShell modules and scripts. PSScriptAnalyzer checks the quality of Windows PowerShell code by running a set of rules. +PSScriptAnalyzer is a static code checker for PowerShell modules and scripts. PSScriptAnalyzer checks the quality of PowerShell code by running a set of rules. The rules are based on PowerShell best practices identified by PowerShell Team and the community. It generates DiagnosticResults (errors and warnings) to inform users about potential code defects and suggests possible solutions for improvements. From 67805a157daf3f1f3f6e72e180cc9dfeacfec3bd Mon Sep 17 00:00:00 2001 From: "Christoph Bergmeister [MVP]" Date: Tue, 26 May 2020 21:35:33 +0100 Subject: [PATCH 005/219] Add more useful launch configurations (#1499) * Add more useful launch configurations * Update .vscode/launch.json --- .vscode/launch.json | 46 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 9beb418df..2f8776e19 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,18 +1,44 @@ { "version": "0.2.0", "configurations": [ - { - "name": ".NET Core Launch (console)", - "type": "coreclr", + "type": "PowerShell", + "request": "launch", + "name": "Build", + "script": "./build.ps1", + "args": [ + "" + ], + "cwd": "${workspaceFolder}", + "createTemporaryIntegratedConsole": true + }, + { + "type": "PowerShell", + "request": "launch", + "name": "Build & Run all tests", + "script": "./build.ps1; ./build.ps1 -Test", + "args": [ + "" + ], + "cwd": "${workspaceFolder}", + "createTemporaryIntegratedConsole": true + }, + { + "type": "PowerShell", "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceRoot}/bin/Debug//", - "args": [], - "cwd": "${workspaceRoot}", - "externalConsole": false, - "stopAtEntry": false, - "internalConsoleOptions": "openOnSessionStart" + "name": "Run current Pester file with PSScriptAnalyzer development build", + "script": "Import-Module ./out/PSScriptAnalyzer/*/PSScriptAnalyzer.psd1; Invoke-Pester -Path '${file}'; $PID", + "args": [ + "" + ], + "cwd": "${workspaceFolder}", + "createTemporaryIntegratedConsole": true + }, + { + "name": "PowerShell Attach to Host Process", + "type": "PowerShell", + "request": "attach", + "runspaceId": 1 }, { "name": ".NET Core Attach", From 735489217fe315430aac9c71381b237e3e9a2200 Mon Sep 17 00:00:00 2001 From: "Christoph Bergmeister [MVP]" Date: Thu, 11 Jun 2020 20:46:10 +0100 Subject: [PATCH 006/219] Pester v5 (#1444) * Replace usage of $MyInvocation.MyCommand.Pat with $PSScriptRoot and cleanup tests * Fix 2 small mistakes, test should now be green again * Adapt build.psm1 * Engine tests migration to Pester v5 * use pester v5 in ci and always upload test results * Upgrade Rule tests to Pester v5 using BeforeAll block and addressing some -Skip problems already * -AllowPrerelease * try bootstrap in wmf4 * fix invoke-pester call in CI * fix CorrectionExtent.tests.ps1 * fix Helper.tests.ps1 * Fix remaining tests with BeforeAll/AfterAll (discovery works now) * fix UseUsingScopeModifierInNewRunspaces.tests.ps1 * make test cases more readable * cleanup UseUsingScopeModifierInNewRunspaces.tests.ps1 * Fix UseShouldProcessForStateChangingFunctions.tests.ps1 * cleanup UseShouldProcessForStateChangingFunctions.tests.ps1 * Fix PSCompatibilityCollector/Tests/UtilityApi.Tests.ps1 * Fix InvokeScriptAnalyzer.tests.ps1 * Format InvokeScriptAnalyzer.tests.ps1 * Fix ModuleHelp.Tests.ps1 and cleanup * Fix RuleSuppression.tests.ps1 * Fix Settings.tests.ps1 * format Settings.tests.ps1 * Fix Tests/Engine/TextEdit.tests.ps1 and format * Fix AllCompatibilityRules.Tests.ps1 * Fix AvoidUnloadableModuleOrMissingRequiredFieldInManifest.tests.ps1 * Format AvoidUnloadableModuleOrMissingRequiredFieldInManifest.tests.ps1 to fix indentation * Fix AvoidUsingPlainTextForPassword.tests.ps1 * fix place*brace tests * Format brace tests and fix PSCredentialType.tests.ps1 * Fix UseCompatibleCommands.Tests.ps1 * Fix UseCompatibleSyntax.Tests.ps1 * Fix UseCompatibleTypes.Tests.ps1 * Fix UseCompatibleCmdlets.tests.ps1 * Fix variable clash between compatibility tests that were not in BeforeEach block and remove unused variables * Fix LibraryUsage.tests.ps1 teardown syntax * Fix LibraryUsage.tests.ps1 * rc3 for wmf4 * Properly execute library tests and cleanup CustomizedRule.tests.ps1 * tidy up * cleanup module imports and only import once at the root * Fix 5 of 6 v4 tests and add logging to better understand modulehelp failure * remove test-psversion functions and fix v3 or v4 checks * Fix ModuleHelp.Tests.ps1 for v4 and cleanup * Really fix ModuleHelp.Tests.ps1 on v4 and cleanup invoke-pester calls * add debugging as to why there is no TestResults.xml on Ubuntu * fix yaml * fix test file upload on Ubuntu (casing) and ensure test file always gets uploaded on appveyor * move appveyor upload back * move appveyor into finally * fix merge error to make install pester v5 again * Pester rc4 * Update to rc5 * cleanup ci run * rc6 * rc7 * rc8 * Update appveyor.psm1 * don't use -CI switch for local build and disable code coverage in CI * import pester module before running tests to import types * fix property name * actually use configuration object (doh) * config object * fix output verbosity * fix type casting of [string[]] of pester paths * pester 5 GA * Pester 5.0.2 * move testcases declaration before test * remove left-over comments * remove unnecessary [bool] in if condition * simplify version check to not require [version] type and patch version * fix missing whitespace in test that made test invalid * empty commit since Azure Pipelines did not trigger * empty commit since Azure Pipelines did not trigger again * empty commit since Azure Pipelines did not trigger again Co-authored-by: Christoph Bergmeister --- .../templates/test-powershell.yaml | 2 +- .../Tests/UtilityApi.Tests.ps1 | 237 ++++---- README.md | 2 +- .../UseTypeAtVariableAssignment.tests.ps1 | 3 +- Tests/Engine/CorrectionExtent.tests.ps1 | 6 +- Tests/Engine/CustomizedRule.tests.ps1 | 93 +-- Tests/Engine/EditableText.tests.ps1 | 13 +- Tests/Engine/Extensions.tests.ps1 | 54 +- Tests/Engine/GetScriptAnalyzerRule.tests.ps1 | 42 +- Tests/Engine/GlobalSuppression.test.ps1 | 19 +- Tests/Engine/Helper.tests.ps1 | 23 +- Tests/Engine/InvokeFormatter.tests.ps1 | 9 +- Tests/Engine/InvokeScriptAnalyzer.tests.ps1 | 474 ++++++++-------- Tests/Engine/LibraryUsage.tests.ps1 | 407 ++++++------- .../Engine/ModuleDependencyHandler.tests.ps1 | 51 +- Tests/Engine/ModuleHelp.Tests.ps1 | 397 ++++++------- Tests/Engine/RuleSuppression.tests.ps1 | 107 ++-- Tests/Engine/RuleSuppressionClass.tests.ps1 | 17 +- Tests/Engine/Settings.tests.ps1 | 224 ++++---- Tests/Engine/TextEdit.tests.ps1 | 12 +- Tests/PSScriptAnalyzerTestHelper.psm1 | 18 - .../Rules/AlignAssignmentStatement.tests.ps1 | 24 +- Tests/Rules/AllCompatibilityRules.Tests.ps1 | 145 +++-- ...oidAssignmentToAutomaticVariable.tests.ps1 | 7 +- ...nvertToSecureStringWithPlainText.tests.ps1 | 15 +- ...dDefaultTrueValueSwitchParameter.tests.ps1 | 13 +- ...efaultValueForMandatoryParameter.tests.ps1 | 7 +- Tests/Rules/AvoidEmptyCatchBlock.tests.ps1 | 13 +- Tests/Rules/AvoidGlobalAliases.tests.ps1 | 17 +- Tests/Rules/AvoidGlobalFunctions.tests.ps1 | 12 +- Tests/Rules/AvoidGlobalVars.tests.ps1 | 19 +- .../Rules/AvoidInvokingEmptyMembers.tests.ps1 | 13 +- Tests/Rules/AvoidLongLines.tests.ps1 | 19 +- ...dNullOrEmptyHelpMessageAttribute.tests.ps1 | 13 +- .../AvoidOverwritingBuiltInCmdlets.tests.ps1 | 63 ++- .../Rules/AvoidPositionalParameters.tests.ps1 | 15 +- Tests/Rules/AvoidReservedParams.tests.ps1 | 13 +- .../AvoidShouldContinueWithoutForce.tests.ps1 | 13 +- Tests/Rules/AvoidTrailingWhitespace.tests.ps1 | 15 +- ...OrMissingRequiredFieldInManifest.tests.ps1 | 41 +- .../AvoidUserNameAndPasswordParams.tests.ps1 | 14 +- Tests/Rules/AvoidUsingAlias.tests.ps1 | 20 +- .../AvoidUsingComputerNameHardcoded.tests.ps1 | 13 +- ...oidUsingDeprecatedManifestFields.tests.ps1 | 13 +- .../AvoidUsingInvokeExpression.tests.ps1 | 13 +- .../AvoidUsingPlainTextForPassword.tests.ps1 | 42 +- .../AvoidUsingReservedCharNames.tests.ps1 | 13 +- Tests/Rules/AvoidUsingWMICmdlet.tests.ps1 | 13 +- Tests/Rules/AvoidUsingWriteHost.tests.ps1 | 15 +- Tests/Rules/DscExamplesPresent.tests.ps1 | 64 ++- Tests/Rules/DscTestsPresent.tests.ps1 | 64 ++- Tests/Rules/MisleadingBacktick.tests.ps1 | 15 +- Tests/Rules/PSCredentialType.tests.ps1 | 24 +- Tests/Rules/PlaceCloseBrace.tests.ps1 | 45 +- Tests/Rules/PlaceOpenBrace.tests.ps1 | 26 +- ...sibleIncorrectComparisonWithNull.tests.ps1 | 13 +- ...correctUsageOfAssignmentOperator.tests.ps1 | 7 +- ...orrectUsageOfRedirectionOperator.tests.ps1 | 8 +- Tests/Rules/ProvideCommentHelp.tests.ps1 | 81 +-- ...eturnCorrectTypesForDSCFunctions.tests.ps1 | 29 +- Tests/Rules/ReviewUnusedParameter.tests.ps1 | 3 + .../UseBOMForUnicodeEncodedFile.tests.ps1 | 19 +- Tests/Rules/UseCmdletCorrectly.tests.ps1 | 14 +- Tests/Rules/UseCompatibleCmdlets.tests.ps1 | 135 +++-- Tests/Rules/UseCompatibleCommands.Tests.ps1 | 7 +- Tests/Rules/UseCompatibleSyntax.Tests.ps1 | 118 ++-- Tests/Rules/UseCompatibleTypes.Tests.ps1 | 5 +- .../Rules/UseConsistentIndentation.tests.ps1 | 8 +- Tests/Rules/UseConsistentWhitespace.tests.ps1 | 42 +- Tests/Rules/UseCorrectCasing.tests.ps1 | 3 + Tests/Rules/UseDSCResourceFunctions.tests.ps1 | 31 +- ...eDeclaredVarsMoreThanAssignments.tests.ps1 | 17 +- ...enticalMandatoryParametersForDSC.tests.ps1 | 20 +- .../Rules/UseIdenticalParametersDSC.tests.ps1 | 22 +- ...seLiteralInitializerForHashtable.tests.ps1 | 7 +- Tests/Rules/UseOutputTypeCorrectly.tests.ps1 | 18 +- ...seProcessBlockForPipelineCommand.tests.ps1 | 5 +- .../Rules/UseShouldProcessCorrectly.tests.ps1 | 18 +- ...ProcessForStateChangingFunctions.tests.ps1 | 65 ++- .../UseSingularNounsReservedVerbs.tests.ps1 | 76 +-- .../Rules/UseSupportsShouldProcess.tests.ps1 | 19 +- .../UseToExportFieldsInManifest.tests.ps1 | 58 +- .../UseUTF8EncodingForHelpFile.tests.ps1 | 5 +- ...UsingScopeModifierInNewRunspaces.tests.ps1 | 533 +++++++++--------- .../UseVerboseMessageInDSCResource.Tests.ps1 | 15 +- build.psm1 | 14 +- tools/appveyor.psm1 | 41 +- 87 files changed, 2403 insertions(+), 2134 deletions(-) diff --git a/.azure-pipelines-ci/templates/test-powershell.yaml b/.azure-pipelines-ci/templates/test-powershell.yaml index e832f04e4..306dab473 100644 --- a/.azure-pipelines-ci/templates/test-powershell.yaml +++ b/.azure-pipelines-ci/templates/test-powershell.yaml @@ -15,5 +15,5 @@ steps: - task: PublishTestResults@2 inputs: testRunner: NUnit - testResultsFiles: 'TestResults.xml' + testResultsFiles: 'testResults.xml' condition: succeededOrFailed() diff --git a/PSCompatibilityCollector/Tests/UtilityApi.Tests.ps1 b/PSCompatibilityCollector/Tests/UtilityApi.Tests.ps1 index 3d0108b50..444adffc5 100644 --- a/PSCompatibilityCollector/Tests/UtilityApi.Tests.ps1 +++ b/PSCompatibilityCollector/Tests/UtilityApi.Tests.ps1 @@ -1,52 +1,43 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function Get-TypeNameAstFromScript -{ - param([string]$Script) +BeforeAll { + function Get-TypeNameAstFromScript + { + param([string]$Script) - $ast = [System.Management.Automation.Language.Parser]::ParseInput($Script, [ref]$null, [ref]$null) - $typeExpAst = $ast.Find({ - $args[0] -is [System.Management.Automation.Language.TypeExpressionAst] - }, $true) + $ast = [System.Management.Automation.Language.Parser]::ParseInput($Script, [ref]$null, [ref]$null) + $typeExpAst = $ast.Find({ + $args[0] -is [System.Management.Automation.Language.TypeExpressionAst] + }, $true) - return $typeExpAst.TypeName -} + return $typeExpAst.TypeName + } -function Get-TypeAccelerators -{ - [psobject].Assembly.GetType('System.Management.Automation.TypeAccelerators', 'nonpublic')::Get.GetEnumerator() + function Get-TypeAccelerators + { + [psobject].Assembly.GetType('System.Management.Automation.TypeAccelerators', 'nonpublic')::Get.GetEnumerator() + } } -Describe "Type name serialization" { - BeforeAll { - $typeNameTestCases = @( - @{ InputType = [System.Reflection.Assembly]; ExpectedName = "System.Reflection.Assembly" } - @{ InputType = [string]; ExpectedName = "System.String" } - @{ InputType = [datetime]; ExpectedName = "System.DateTime" } - @{ InputType = [string[]]; ExpectedName = "System.String[]" } - @{ InputType = [System.TimeZoneInfo+AdjustmentRule]; ExpectedName = "System.TimeZoneInfo+AdjustmentRule" } - @{ InputType = [System.Func`1]; ExpectedName = "System.Func``1" } - @{ InputType = [System.Collections.Generic.Dictionary`2]; ExpectedName = "System.Collections.Generic.Dictionary``2" } - @{ InputType = [System.Collections.Generic.Dictionary`2+Enumerator]; ExpectedName = "System.Collections.Generic.Dictionary``2+Enumerator" } - @{ InputType = [System.Collections.Generic.Dictionary[string,object]].GetNestedType('Enumerator'); ExpectedName = "System.Collections.Generic.Dictionary``2+Enumerator" } - @{ InputType = [System.Collections.Generic.List[object]]; ExpectedName = "System.Collections.Generic.List``1[System.Object]" } - @{ InputType = [System.Collections.Generic.Dictionary[string, object]]; ExpectedName = "System.Collections.Generic.Dictionary``2[System.String,System.Object]" } - @{ InputType = [System.Collections.Generic.Dictionary`2+Enumerator[string,object]]; ExpectedName = "System.Collections.Generic.Dictionary``2+Enumerator[System.String,System.Object]" } - @{ InputType = [System.Collections.Concurrent.ConcurrentDictionary`2].GetMethod('ToArray').ReturnType; ExpectedName = "System.Collections.Generic.KeyValuePair``2[]"} - ) - - $genericStrippingTests = @( - @{ RawTypeName = "String"; StrippedTypeName = "String" } - @{ RawTypeName = "Dictionary``2"; StrippedTypeName = "Dictionary" } - @{ RawTypeName = "Dictionary``2"; StrippedTypeName = "Dictionary" } - @{ RawTypeName = "Dictionary``2+Enumerator"; StrippedTypeName = "Dictionary+Enumerator" } - ) - } - It "Serializes the name of type to " -TestCases $typeNameTestCases { - param([type]$InputType, [string]$ExpectedName) +Describe "Type name serialization" { + It "Serializes the name of type to " -TestCases @( + @{ InputType = [System.Reflection.Assembly]; ExpectedName = "System.Reflection.Assembly" } + @{ InputType = [string]; ExpectedName = "System.String" } + @{ InputType = [datetime]; ExpectedName = "System.DateTime" } + @{ InputType = [string[]]; ExpectedName = "System.String[]" } + @{ InputType = [System.TimeZoneInfo+AdjustmentRule]; ExpectedName = "System.TimeZoneInfo+AdjustmentRule" } + @{ InputType = [System.Func`1]; ExpectedName = "System.Func``1" } + @{ InputType = [System.Collections.Generic.Dictionary`2]; ExpectedName = "System.Collections.Generic.Dictionary``2" } + @{ InputType = [System.Collections.Generic.Dictionary`2+Enumerator]; ExpectedName = "System.Collections.Generic.Dictionary``2+Enumerator" } + @{ InputType = [System.Collections.Generic.Dictionary[string, object]].GetNestedType('Enumerator'); ExpectedName = "System.Collections.Generic.Dictionary``2+Enumerator" } + @{ InputType = [System.Collections.Generic.List[object]]; ExpectedName = "System.Collections.Generic.List``1[System.Object]" } + @{ InputType = [System.Collections.Generic.Dictionary[string, object]]; ExpectedName = "System.Collections.Generic.Dictionary``2[System.String,System.Object]" } + @{ InputType = [System.Collections.Generic.Dictionary`2+Enumerator[string, object]]; ExpectedName = "System.Collections.Generic.Dictionary``2+Enumerator[System.String,System.Object]" } + @{ InputType = [System.Collections.Concurrent.ConcurrentDictionary`2].GetMethod('ToArray').ReturnType; ExpectedName = "System.Collections.Generic.KeyValuePair``2[]" } + ) { $name = [Microsoft.PowerShell.CrossCompatibility.TypeNaming]::GetFullTypeName($InputType) $name | Should -BeExactly $ExpectedName } @@ -57,86 +48,79 @@ Describe "Type name serialization" { } | Should -Throw -ErrorId "ArgumentNullException" } - It "Strips generic quantifiers from '' to return ''" -TestCases $genericStrippingTests { + It "Strips generic quantifiers from '' to return ''" { param([string]$RawTypeName, [string]$StrippedTypeName) $stripped = [Microsoft.PowerShell.CrossCompatibility.TypeNaming]::StripGenericQuantifiers($RawTypeName) $stripped | Should -BeExactly $StrippedTypeName - } + } -TestCases @( + @{ RawTypeName = "String"; StrippedTypeName = "String" } + @{ RawTypeName = "Dictionary``2"; StrippedTypeName = "Dictionary" } + @{ RawTypeName = "Dictionary``2"; StrippedTypeName = "Dictionary" } + @{ RawTypeName = "Dictionary``2+Enumerator"; StrippedTypeName = "Dictionary+Enumerator" } + ) } Describe "Type accelerator expansion" { BeforeAll { $typeAccelerators = Get-TypeAccelerators | ForEach-Object { $d = New-Object 'System.Collections.Generic.Dictionary[string,string]' } { $d.Add($_.Key, $_.Value.FullName) } { $d } - - $typeAcceleratorTestCases = @( - @{ Raw = "[System.Exception]"; Expanded = "System.Exception" } - @{ Raw = "[string]"; Expanded = "System.String" } - @{ Raw = "[psmoduleinfo]"; Expanded = "System.Management.Automation.PSModuleInfo" } - @{ Raw = "[System.Collections.Generic.List[int]]"; Expanded = "System.Collections.Generic.List``1[System.Int32]" } - @{ Raw = "[System.Collections.Generic.Dictionary[string,psmoduleinfo]]"; Expanded = "System.Collections.Generic.Dictionary``2[System.String,System.Management.Automation.PSModuleInfo]" } - @{ Raw = "[System.Collections.Generic.Dictionary[string, psmoduleinfo]]"; Expanded = "System.Collections.Generic.Dictionary``2[System.String,System.Management.Automation.PSModuleInfo]" } - @{ Raw = "[System.Collections.Generic.Dictionary [string, psmoduleinfo]]"; Expanded = "System.Collections.Generic.Dictionary``2[System.String,System.Management.Automation.PSModuleInfo]" } - @{ Raw = "[System.Collections.Generic.List``1[uri]]"; Expanded = "System.Collections.Generic.List``1[System.Uri]" } - @{ Raw = "[System.Collections.Generic.Dictionary``2[string,psmoduleinfo]]"; Expanded = "System.Collections.Generic.Dictionary``2[System.String,System.Management.Automation.PSModuleInfo]" } - @{ Raw = "[System.Collections.Generic.Dictionary``2 [string, psmoduleinfo]]"; Expanded = "System.Collections.Generic.Dictionary``2[System.String,System.Management.Automation.PSModuleInfo]" } - @{ Raw = "[object]"; Expanded = "System.Object" } - ) } - It "Expands the typename in to " -TestCases $typeAcceleratorTestCases { - param([string]$Raw, [string]$Expanded) + It "Expands the typename in to " -TestCases @( + @{ Raw = "[System.Exception]"; Expanded = "System.Exception" } + @{ Raw = "[string]"; Expanded = "System.String" } + @{ Raw = "[psmoduleinfo]"; Expanded = "System.Management.Automation.PSModuleInfo" } + @{ Raw = "[System.Collections.Generic.List[int]]"; Expanded = "System.Collections.Generic.List``1[System.Int32]" } + @{ Raw = "[System.Collections.Generic.Dictionary[string,psmoduleinfo]]"; Expanded = "System.Collections.Generic.Dictionary``2[System.String,System.Management.Automation.PSModuleInfo]" } + @{ Raw = "[System.Collections.Generic.Dictionary[string, psmoduleinfo]]"; Expanded = "System.Collections.Generic.Dictionary``2[System.String,System.Management.Automation.PSModuleInfo]" } + @{ Raw = "[System.Collections.Generic.Dictionary [string, psmoduleinfo]]"; Expanded = "System.Collections.Generic.Dictionary``2[System.String,System.Management.Automation.PSModuleInfo]" } + @{ Raw = "[System.Collections.Generic.List``1[uri]]"; Expanded = "System.Collections.Generic.List``1[System.Uri]" } + @{ Raw = "[System.Collections.Generic.Dictionary``2[string,psmoduleinfo]]"; Expanded = "System.Collections.Generic.Dictionary``2[System.String,System.Management.Automation.PSModuleInfo]" } + @{ Raw = "[System.Collections.Generic.Dictionary``2 [string, psmoduleinfo]]"; Expanded = "System.Collections.Generic.Dictionary``2[System.String,System.Management.Automation.PSModuleInfo]" } + @{ Raw = "[object]"; Expanded = "System.Object" } + ) -Test { $typeName = Get-TypeNameAstFromScript -Script $Raw $canonicalName = [Microsoft.PowerShell.CrossCompatibility.TypeNaming]::GetCanonicalTypeName($typeAccelerators, $typeName) $canonicalName | Should -BeExactly $Expanded - } } +$versionCreationTests = @( + @{ Version = '6.1'; Major = 6; Minor = 1; Patch = -1 } + @{ Version = '6.1.4'; Major = 6; Minor = 1; Patch = 4; } + @{ Version = '5.1.8-preview.2'; Major = 5; Minor = 1; Patch = 8; Label = 'preview.2' } + @{ Version = [version]'4.2'; Major = 4; Minor = 2; Patch = -1; Revision = -1 } + @{ Version = [version]'4.2.1'; Major = 4; Minor = 2; Patch = 1; Revision = -1 } + @{ Version = [version]'4.2.1.7'; Major = 4; Minor = 2; Patch = 1; Revision = 7 } +) + +if ($PSVersionTable.PSVersion.Major -ge 6) +{ + $versionCreationTests += @( + @{ Version = [semver]'6.1.2'; Major = 6; Minor = 1; Patch = 2; Label = $null } + @{ Version = [semver]'6.1.2-rc.1'; Major = 6; Minor = 1; Patch = 2; Label = 'rc.1' } + @{ Version = [semver]'6.1-rc.1'; Major = 6; Minor = 1; Patch = 0; Label = 'rc.1' } + @{ Version = [semver]'6-rc.1'; Major = 6; Minor = 0; Patch = 0; Label = 'rc.1' } + @{ Version = [semver]'6.1.2-rc.1+duck'; Major = 6; Minor = 1; Patch = 2; Label = 'rc.1'; BuildLabel = 'duck' } + @{ Version = [semver]'6.1-rc.1+duck'; Major = 6; Minor = 1; Patch = 0; Label = 'rc.1'; BuildLabel = 'duck' } + @{ Version = [semver]'6-rc.1+duck'; Major = 6; Minor = 0; Patch = 0; Label = 'rc.1'; BuildLabel = 'duck' } + ) +} + Describe "PowerShell version object" { Context "Version parsing" { - BeforeAll { - $genericVerCases = @( - @{ VerStr = '42'; Major = 42; Minor = -1; Patch = -1 } - @{ VerStr = '7'; Major = 7; Minor = -1; Patch = -1 } - @{ VerStr = '6.1'; Major = 6; Minor = 1; Patch = -1 } - @{ VerStr = '5.2.7'; Major = 5; Minor = 2; Patch = 7 } - @{ VerStr = '512.2124.71'; Major = 512; Minor = 2124; Patch = 71 } - ) - - $semVerCases = @( - @{ VerStr = '6.1.0-rc.1'; Major = 6; Minor = 1; Patch = 0; Label = 'rc.1' } - @{ VerStr = '6.2-preview.2'; Major = 6; Minor = 2; Patch = -1; Label = 'preview.2' } - @{ VerStr = '6-preview.2'; Major = 6; Minor = -1; Patch = -1; Label = 'preview.2' } - @{ VerStr = '6.1.0-rc.1+moo'; Major = 6; Minor = 1; Patch = 0; Label = 'rc.1'; BuildLabel = 'moo' } - @{ VerStr = '6.2-preview.2+horse'; Major = 6; Minor = 2; Patch = -1; Label = 'preview.2'; BuildLabel = 'horse' } - @{ VerStr = '6-preview.2+veryimportant'; Major = 6; Minor = -1; Patch = -1; Label = 'preview.2'; BuildLabel = 'veryimportant' } - ) - - $systemVerCases = @( - @{ VerStr = '5.2.1.12312'; Major = 5; Minor = 2; Patch = 1; Revision = 12312 } - ) - - $versionFailCases = @( - @{ VerStr = 'banana' } - @{ VerStr = '' } - @{ VerStr = '1.' } - @{ VerStr = '.6' } - @{ VerStr = '5.1.' } - @{ VerStr = '5.1.2.' } - @{ VerStr = '4.1.5.7.' } - @{ VerStr = '4.1.5.7.4' } - @{ VerStr = '4.1.5.7-rc.2' } - @{ VerStr = '4.1.5.-rc.2' } - ) - } - It "Parses version string '' as .." -TestCases $genericVerCases { - param([string]$VerStr, [int]$Major, [int]$Minor, [int]$Patch) + It "Parses version string '' as .." -TestCases @( + @{ VerStr = '42'; Major = 42; Minor = -1; Patch = -1 } + @{ VerStr = '7'; Major = 7; Minor = -1; Patch = -1 } + @{ VerStr = '6.1'; Major = 6; Minor = 1; Patch = -1 } + @{ VerStr = '5.2.7'; Major = 5; Minor = 2; Patch = 7 } + @{ VerStr = '512.2124.71'; Major = 512; Minor = 2124; Patch = 71 } + ) -Test { $v = [Microsoft.PowerShell.CrossCompatibility.PowerShellVersion]$VerStr @@ -145,7 +129,14 @@ Describe "PowerShell version object" { $v.Patch | Should -Be $Patch } - It "Parses version string '' as ..-