Skip to content

Improve ValidateLength error message consistency and refactor validation tests#25806

Merged
iSazonov merged 9 commits intoPowerShell:masterfrom
jorgeasaurus:master
Oct 13, 2025
Merged

Improve ValidateLength error message consistency and refactor validation tests#25806
iSazonov merged 9 commits intoPowerShell:masterfrom
jorgeasaurus:master

Conversation

@jorgeasaurus
Copy link
Copy Markdown
Contributor

@jorgeasaurus jorgeasaurus commented Aug 1, 2025

PR Summary

This PR improves the consistency of ValidateLength error messages and significantly enhances the quality of validation attribute tests. The changes improve both user experience (clearer error messages) and code maintainability (more reliable, readable tests).

Key improvements include:

Error Message Consistency

  1. Updated ValidateLengthMaxLengthFailure resource string in Metadata.resx to match the format of ValidateLengthMinLengthFailure
  2. Changed format from "The character length of the {1} argument is too long..." to "The character length ({1}) of the argument is too long..."
  3. Both min and max length errors now use consistent language and formatting with actual length in parentheses

Test Quality Improvements

  1. Replaced generic function names (foo) with descriptive PowerShell-compliant names (Test-ArrayCount, Test-NumericRange, Test-StringLength)
  2. Replaced generic parameter names (bar) with meaningful names ($Items, $Number, $InputString)
  3. Updated error assertions to use -PassThru with captured error variables instead of relying on $error[0]
  4. Added comprehensive ValidateLength test context with specific error message format validation

PR Context

The ValidateLength error messages were inconsistent in format:

  • MinLength: "The character length (3) of the argument is too short..."
  • MaxLength (old): "The character length of the 5 argument is too long..." ❌

The updated format makes both messages consistent:

  • MinLength: "The character length (3) of the argument is too short. Specify an argument with a length that is greater than or equal to "5"..."
  • MaxLength (new): "The character length (5) of the argument is too long. Specify an argument with a length that is shorter than or equal to "2"..." ✅

Additionally, the test refactoring addresses best practices by using -PassThru to capture exceptions directly rather than reading from the global $error array, making tests more reliable and less prone to interference from other test runs.

PR Checklist

  • PR has a meaningful title
    • Use the present tense and imperative mood when describing your changes
  • Summarized changes
  • Make sure all .h, .cpp, .cs, .ps1 and .psm1 files have the correct copyright header
  • This PR is ready to merge. If this PR is a work in progress, please open this as a Draft Pull Request and mark it as Ready to Review when it is ready to merge.

Breaking changes

  • None
    OR
  • Experimental feature(s) needed
    • Experimental feature name(s):

User-facing changes

  • Not Applicable
    OR
  • Documentation needed
    • Issue filed:

Testing - New and feature

  • N/A or can only be tested interactively
    OR
  • Make sure you've added a new test if existing tests do not effectively test the code changed

  (#25566)

  ## Problem
  The ValidateLengthMaxLengthFailure error message was
  displaying incorrectly,
  showing the length value where the parameter name
  should appear:
  - Actual: "The character length of the 5 argument is
  too long..."
  - Expected: "The character length of the "argument"
  argument is too long..."

  This occurred because the resource string was updated
   to expect three parameters
  (MaxLength, actual length, and parameter name) but
  the code was only passing two
  parameters, causing the format string to use the
  length value in place of the
  parameter name.

  ## Root Cause
  The resource string format was changed to include a
  parameter name placeholder:
  - Old format: "The character length of the {1}
  argument..."
  - New format: "The character length of the "{2}"
  argument is too long at {1} characters..."

  But the corresponding code change to pass the
  parameter name was not implemented.

  ## Solution
  1. Updated ValidateLengthAttribute.ValidateElement()
  to pass "argument" as the
     third parameter when throwing
  ValidationMetadataException for max length
     validation failures.

  2. Updated the ValidateLengthMaxLengthFailure
  resource string in Metadata.resx
     to properly format the error message with all
  three placeholders.

  While the current architecture doesn't support
  passing actual parameter names
  to validation attributes (which would require
  significant architectural changes),
  using "argument" as a generic placeholder prevents
  the confusing formatting
  error and provides a clear, consistent error message.

  ## Changes
  - Modified
  src/System.Management.Automation/engine/Attributes.cs
   to pass
    "argument" as third parameter in ValidateLength max
   length validation
  - Updated src/System.Management.Automation/resources/
  Metadata.resx to include
    proper formatting for parameter name placeholder
  - Added comprehensive tests in test/powershell/engine
  /Basic/ValidateAttributes.Tests.ps1
    to verify the fix

  ## Testing
  Added tests to verify:
  - Maximum length validation error formatting shows
  "argument" placeholder
  - Minimum length validation remains unchanged
  (different format)
  - Non-string parameter validation works correctly
  - Valid string lengths pass validation
  - Error message contains actual parameter name in
  outer exception

  The fix ensures error messages are properly formatted
   and readable while
  maintaining backward compatibility.

  Fixes #25566
@microsoft-github-policy-service microsoft-github-policy-service bot added the Review - Needed The PR is being reviewed label Aug 8, 2025
@iSazonov iSazonov added the CL-General Indicates that a PR should be marked as a general cmdlet change in the Change Log label Oct 6, 2025
@jorgeasaurus
Copy link
Copy Markdown
Contributor Author

Thank you @iSazonov for the review feedback. I've updated the PR to make ValidateLengthMaxLengthFailure consistent with ValidateLengthMinLengthFailure format.

Changes:

1. Error Message Format (Metadata.resx:178)

Changed from:

The character length of the "{2}" argument is too long at {1} characters. Shorten the character length so it is fewer than or equal to {0} characters.

To:

The character length ({1}) of the argument is too long. Specify an argument with a length that is shorter than or equal to "{0}", and then try the command again.

2. Code Update (Attributes.cs:872)

Removed the third parameter to match MinLength's two-parameter format:

// Before
throw new ValidationMetadataException(
    "ValidateLengthMaxLengthFailure",
    null,
    Metadata.ValidateLengthMaxLengthFailure,
    MaxLength, len, "argument");

// After
throw new ValidationMetadataException(
    "ValidateLengthMaxLengthFailure",
    null,
    Metadata.ValidateLengthMaxLengthFailure,
    MaxLength, len);

3. Test Update (ValidateAttributes.Tests.ps1:319-320)

Updated test to verify the new consistent format:

# Check the inner exception message is properly formatted and consistent with MinLength format
$error[0].Exception.InnerException.Message | Should -Match 'The character length \(5\) of the argument is too long\.'
$error[0].Exception.InnerException.Message | Should -Match 'shorter than or equal to "2"'

Result:

Both error messages now follow the same consistent pattern:

MinLength:

The character length (3) of the argument is too short. Specify an argument with a length that is greater than or equal to "5", and then try the command again.

MaxLength:

The character length (6) of the argument is too long. Specify an argument with a length that is shorter than or equal to "2", and then try the command again.

@jorgeasaurus jorgeasaurus requested a review from iSazonov October 8, 2025 05:39
BeforeAll {
$testCases = @(
@{
ScriptBlock = { function foo { param([ValidateLength(2, 5)] [string] $bar) }; foo "a" }
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please replace foo/bar with more informative names.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please address the comment.

Please replace foo/bar with more informative names.


{ foo "11111" } | Should -Throw -ErrorId "ParameterArgumentValidationError,foo"

# Check the inner exception message is properly formatted and consistent with MinLength format
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove obvious comment.
Below too.

{ foo "11111" } | Should -Throw -ErrorId "ParameterArgumentValidationError,foo"

# Check the inner exception message is properly formatted and consistent with MinLength format
$error[0].Exception.InnerException.Message | Should -Match 'The character length \(5\) of the argument is too long\.'
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We never use localized messages in tests. Please use more formal check like ".+($ExpectedRealLength).+($ExpectedMaxLength)". Also you can remove \n in the string before check.

@jorgeasaurus
Copy link
Copy Markdown
Contributor Author

Thank you @iSazonov

Changes:

Test Update (ValidateAttributes.Tests.ps1:313-323)

  1. Replaced foo/bar with descriptive names - Changed to Test-ValidateLength and Value
  2. Removed obvious comments - Cleaned up unnecessary comments
  3. Used formal regex pattern - Changed from checking localized message text to using pattern .+\($ExpectedRealLength\).+\"$ExpectedMaxLength`"`
It 'ValidateLength error message should be properly formatted' {
    function Test-ValidateLength { param([ValidateLength(0,2)] [string] $Value) $Value }

    $ExpectedMaxLength = 2
    $ExpectedRealLength = 5

    { Test-ValidateLength "11111" } | Should -Throw -ErrorId "ParameterArgumentValidationError,Test-ValidateLength"

    $error[0].Exception.InnerException.Message | Should -Match ".+\($ExpectedRealLength\).+\`"$ExpectedMaxLength\`""
    $error[0].Exception.Message | Should -Match 'Value'
}

This approach avoids relying on localized message text and provides a more robust test that verifies the error message contains the expected length values in the correct format.

@jorgeasaurus jorgeasaurus requested a review from iSazonov October 8, 2025 13:12

{ Test-ValidateLength "11111" } | Should -Throw -ErrorId "ParameterArgumentValidationError,Test-ValidateLength"

$error[0].Exception.InnerException.Message | Should -Match ".+\($ExpectedRealLength\).+\`"$ExpectedMaxLength\`""
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we check a message for max we should check a message for min too.

function Test-ValidateLength { param([ValidateLength(0,2)] [string] $Value) $Value }

$ExpectedMaxLength = 2
$ExpectedRealLength = 5
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it comes from "11111" it is better to evaluate the value from the string.

@jorgeasaurus
Copy link
Copy Markdown
Contributor Author

Thank you @iSazonov. I've made the following changes:

Changes:

  1. Added min length test - Now tests both max and min length error messages for consistency
  2. Evaluate length from test strings - Using $TestStringTooLong.Length and $TestStringTooShort.Length instead of hardcoded values
It 'ValidateLength error message should be properly formatted' {
    function Test-ValidateLengthMax { param([ValidateLength(0,2)] [string] $Value) $Value }
    function Test-ValidateLengthMin { param([ValidateLength(5,10)] [string] $Value) $Value }

    $TestStringTooLong = "11111"
    $TestStringTooShort = "123"
    $ExpectedMaxLength = 2
    $ExpectedMinLength = 5

    { Test-ValidateLengthMax $TestStringTooLong } | Should -Throw -ErrorId "ParameterArgumentValidationError,Test-ValidateLengthMax"
    $error[0].Exception.InnerException.Message | Should -Match ".+\($($TestStringTooLong.Length)\).+\`"$ExpectedMaxLength\`""
    $error[0].Exception.Message | Should -Match 'Value'

    { Test-ValidateLengthMin $TestStringTooShort } | Should -Throw -ErrorId "ParameterArgumentValidationError,Test-ValidateLengthMin"
    $error[0].Exception.InnerException.Message | Should -Match ".+\($($TestStringTooShort.Length)\).+\`"$ExpectedMinLength\`""
    $error[0].Exception.Message | Should -Match 'Value'
}

@jorgeasaurus jorgeasaurus requested a review from iSazonov October 9, 2025 12:27
@iSazonov
Copy link
Copy Markdown
Collaborator

/azp run PowerShell-Windows-Packaging-CI, PowerShell-CI-linux-packaging

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines could not run because the pipeline triggers exclude this branch/path.

@microsoft-github-policy-service microsoft-github-policy-service bot removed the Review - Needed The PR is being reviewed label Oct 10, 2025
@jorgeasaurus
Copy link
Copy Markdown
Contributor Author

/azp run PowerShell-Windows-Packaging-CI, PowerShell-CI-linux-packaging

@iSazonov this command appears to have failed.

Tagging for awareness.

It 'Exception: <FullyQualifiedErrorId>:<InnerErrorId>' -TestCases $testCases {
param($ScriptBlock, $FullyQualifiedErrorId, $InnerErrorId)

$ScriptBlock | Should -Throw -ErrorId $FullyQualifiedErrorId
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using $error[0] is not reliable. We can use:

            $err = $ScriptBlock | Should -Throw -ErrorId $FullyQualifiedErrorId -PassThru

BeforeAll {
$testCases = @(
@{
ScriptBlock = { function foo { param([ValidateLength(2, 5)] [string] $bar) }; foo "a" }
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please address the comment.

Please replace foo/bar with more informative names.


{ Test-ValidateLengthMax $TestStringTooLong } | Should -Throw -ErrorId "ParameterArgumentValidationError,Test-ValidateLengthMax"
$error[0].Exception.InnerException.Message | Should -Match ".+\($($TestStringTooLong.Length)\).+\`"$ExpectedMaxLength\`""
$error[0].Exception.Message | Should -Match 'Value'
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder why we check this.

@jorgeasaurus
Copy link
Copy Markdown
Contributor Author

Thank you @iSazonov. I've made the following changes:

Changes Made

1. Replaced $error[0] with -PassThru

Updated all test assertions throughout the file to use the -PassThru parameter for more reliable error handling:

Before:

$ScriptBlock | Should -Throw -ErrorId $FullyQualifiedErrorId
if ($InnerErrorId) {
    $error[0].exception.innerexception.errorrecord.FullyQualifiedErrorId | Should -Be $InnerErrorId
}

After:

$err = $ScriptBlock | Should -Throw -ErrorId $FullyQualifiedErrorId -PassThru
if ($InnerErrorId) {
    $err.exception.innerexception.errorrecord.FullyQualifiedErrorId | Should -Be $InnerErrorId
}

This change was applied to all contexts: ValidateCount, ValidateRange (ParameterConstructors, User Defined Range, Predefined Range), and ValidateLength.

2. Replaced foo/bar with Descriptive Names

Updated all test cases with more informative function and parameter names:

ValidateCount Context:

  • function foofunction Test-ArrayCount
  • $bar$Items

ValidateRange Contexts:

  • function foofunction Test-NumericRange
  • $bar$Number

ValidateLength Context:

  • function foofunction Test-StringLength
  • $bar$InputString

Example:

# Before
ScriptBlock = { function foo { param([ValidateCount(2, 3)] [string[]] $bar) }; foo 1,2,3,4 }

# After
ScriptBlock = { function Test-ArrayCount { param([ValidateCount(2, 3)] [string[]] $Items) }; Test-ArrayCount 1,2,3,4 }

3. Removed Outer Exception Message Checks

Removed the unnecessary outer exception message assertions from the ValidateLength test:

Before:

$err = { Test-ValidateLengthMax $TestStringTooLong } | Should -Throw -ErrorId "ParameterArgumentValidationError,Test-ValidateLengthMax" -PassThru
$err.Exception.InnerException.Message | Should -Match ".+\($($TestStringTooLong.Length)\).+\`"$ExpectedMaxLength\`""
$err.Exception.Message | Should -Match 'Value'

After:

$err = { Test-ValidateLengthMax $TestStringTooLong } | Should -Throw -ErrorId "ParameterArgumentValidationError,Test-ValidateLengthMax" -PassThru
$err.Exception.InnerException.Message | Should -Match ".+\($($TestStringTooLong.Length)\).+\`"$ExpectedMaxLength\`""

The outer exception message check was redundant since the ErrorId assertion already validates the correct error type, and the inner exception message check validates the actual validation error content.

Result

  • ✅ Explicit and reliable error capture using -PassThru
  • ✅ Descriptive test function and parameter names
  • ✅ Focused assertions without redundant checks

All changes are in test/powershell/engine/Basic/ValidateAttributes.Tests.ps1.

@iSazonov
Copy link
Copy Markdown
Collaborator

/azp run PowerShell-Windows-Packaging-CI, PowerShell-CI-linux-packaging

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines could not run because the pipeline triggers exclude this branch/path.

@iSazonov
Copy link
Copy Markdown
Collaborator

@jorgeasaurus Please update the PR title and description to reflect the PR changes.

@jorgeasaurus jorgeasaurus changed the title Fix ValidateLength error message formatting issue Improve ValidateLength error message consistency and refactor validation tests Oct 12, 2025
@jorgeasaurus
Copy link
Copy Markdown
Contributor Author

@iSazonov PR title and description have been updated.

@iSazonov iSazonov self-assigned this Oct 13, 2025
@iSazonov iSazonov added Hacktoberfest Potential candidate to participate in Hacktoberfest Hacktoberfest-Accepted Accepted to participate in Hacktoberfest labels Oct 13, 2025
@iSazonov iSazonov merged commit 26bb188 into PowerShell:master Oct 13, 2025
40 checks passed
@microsoft-github-policy-service
Copy link
Copy Markdown
Contributor

microsoft-github-policy-service bot commented Oct 13, 2025

📣 Hey @@jorgeasaurus, how did we do? We would love to hear your feedback with the link below! 🗣️

🔗 https://aka.ms/PSRepoFeedback

@iSazonov
Copy link
Copy Markdown
Collaborator

@jorgeasaurus Thanks for your contribution!

SIRMARGIN pushed a commit to SIRMARGIN/PowerShell that referenced this pull request Dec 12, 2025
kilasuit pushed a commit to kilasuit/PowerShell that referenced this pull request Jan 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CL-General Indicates that a PR should be marked as a general cmdlet change in the Change Log Hacktoberfest Potential candidate to participate in Hacktoberfest Hacktoberfest-Accepted Accepted to participate in Hacktoberfest

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants