-
Notifications
You must be signed in to change notification settings - Fork 7.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement Get-Random -Count without specifying an InputObject list #9111
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, been wanting to get this done, so thanks! 😄
int randomNumber = this.Generator.Next(min, max); | ||
Debug.Assert(min <= randomNumber, "lower bound <= random number"); | ||
Debug.Assert(randomNumber < max, "random number < upper bound"); | ||
for (int i = 0; i < Math.Max(1, this.Count); i++) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than need a call to Math.Max()
you could simply initialize the value of this parameter to 1 by default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@@ -281,7 +281,7 @@ private double ConvertToDouble(object o, double defaultIfNull) | |||
/// <summary> | |||
/// Number of items to output (number of list items or of numbers). | |||
/// </summary> | |||
[Parameter(ParameterSetName = GetRandomCommand.RandomListItemParameterSet)] | |||
[Parameter] | |||
[ValidateRange(1, int.MaxValue)] | |||
public int Count { get; set; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public int Count { get; set; } | |
public int Count { get; set; } = 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
for (int i = 0; i < Math.Max(1, this.Count); i++) | ||
{ | ||
int randomNumber = this.Generator.Next(min, max); | ||
Debug.Assert(min <= randomNumber, "lower bound <= random number"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we generally use Debug.Assert()
in cmdlets. This seems like something that should only fail if the Random
class has a fatal regression for its Next()
code path(s), which is outside the purview of our debug code in general, I would think.
I think if we want to test this at all, we should implement this as a Pester test so that we can see it in CI rather than waiting for someone to stumble across it. 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm not too sure I agree that Debug.Assert isn't used since I have found it in quite a few cmdlets throughout the codebase.
I do agree that ideally this type of thing should surface as a result of a failing CI test but I don't know how important the consequence of removing it would be (scripts not crashing and using potentially invalid random values). 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I remember rightly @PaulHigin, aren't Debug.Assert() calls ignored / no-op in release
builds anyway? 🤔 Or am I misremembering? 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that this is the wrong place for debug asserts and the correct place is in the random number generator adapter. Debug asserts are only evaluated in debug builds and are intended to verify expected (and assumed) behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done :)
Debug.Assert(randomNumber < max, "random number < upper bound"); | ||
for (int i = 0; i < Math.Max(1, this.Count); i++) | ||
{ | ||
int randomNumber = this.Generator.Next(min, max); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this.
is usually needed here. Is there a naming conflict that requires this? If not, we don't generally use it where it's not explicitly required. 🙂
There are a couple of these, just tagging this one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. I've removed all this.
invocations from this commandlet. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cheers! 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
for (int i = 0; i < Math.Max(1, this.Count); i++) | ||
{ | ||
int randomNumber = this.Generator.Next(min, max); | ||
Debug.Assert(min <= randomNumber, "lower bound <= random number"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that this is the wrong place for debug asserts and the correct place is in the random number generator adapter. Debug asserts are only evaluated in debug builds and are intended to verify expected (and assumed) behavior.
@pougetat Please move all style changes in new PR - we should not mix style and function changes. Also please open new issue in PowerShell-Docs repo and add reference to the PR description (current link points to PowerShell repo issue that is wrong). |
ec13067
to
3fd48e6
Compare
Hey @iSazonov , |
|
||
$result = Get-Random -Maximum $maximum -Minimum $minimum -Count 1 | ||
$result | Should -BeGreaterThan $greaterThan | ||
$result | Should -BeLessThan $lessThan |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking asserts above it seems it should be BeLessOrEqual in all tests. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with you. I've made the modifications.
@PaulHigin @adityapatwardhan Please confirm latest changes in tests. |
@{ Name = 'max is double with plus sign and enclosed in quote'; Maximum = '+100.0'; Minimum = 0; GreaterThan = -1.0; LessThan = 100.0; Type = 'System.Double' } | ||
@{ Name = 'both set to the special numbers as 1.0e+xx '; Maximum = $null; Minimum = 1.0e+100; GreaterThan = 1.0e+99; LessThan = ([double]::MaxValue); Type = 'System.Double' } | ||
@{ Name = 'max is Double.MaxValue, min is Double.MinValue'; Maximum = ([double]::MaxValue); Minimum = ([double]::MinValue); GreaterThan = ([double]::MinValue); LessThan = ([double]::MaxValue); Type = 'System.Double' } | ||
@{ Name = 'no params'; Maximum = $null; Minimum = $null; GreaterThan = -1; LessOrEqual = ([int32]::MaxValue); Type = 'System.Int32' } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why was LessThan
changed to LessOrEqual
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The debug.assert statements in the code a exlusive for lower bounds and inclusive for higher bounds thus we have changed LessThan into LessOrEqual to make it consistent with the current implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, our tests do not match the code. One of the two must be corrected.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wait sorry i mixed things up. The assert statements are exclusive for higher bounds and inclusive for lower bounds. In any case I think I should just revert the changes made to the tests for the following reason :
- The debug.assert statements should be moved into the actual random number generators but for that it also means looking at what numbers can be generated (inclusive bounds VS exclusive bounds) since some of the bounding conditions will be incorrect as they stand today.
I think that discussion can be had in another PR and this one can simply remain very pointed in scope :)
I have no issue opening the extra PR myself btw.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pougetat Let's wait @adityapatwardhan conclusion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pougetat Sorry I reverted the commit when I wrote my previous comment. Having said that I still have it on a local branch so I can put it back easily depending on his conclusion. I should have simply reverted with an actual commit instead of dropping the commit altogether. In any case yes I agree we can wait for his conclusion :).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with having a separate PR for that. I just signed off.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pougetat for future reference, I do believe the random generators are inclusive on the lower bound, but exclusive on the higher bound. Don't recall the exact reason for it, but it's a pretty common pattern.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ae1372d
to
3fd48e6
Compare
test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Random.Tests.ps1
Show resolved
Hide resolved
@mklement0 Does this meet your expectations? |
@@ -163,6 +178,7 @@ Describe "Get-Random" -Tags "CI" { | |||
$secondRandomNumber = Get-Random 34359738367 -SetSeed 20 | |||
$firstRandomNumber | Should -Be @secondRandomNumber | |||
} | |||
|
|||
It "Should throw an error because the hexadecimal number is to large " { | |||
{ Get-Random 0x07FFFFFFFFFFFFFFFF } | Should -Throw "Value was either too large or too small for a UInt32" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't dug deeper, but the full error message is self-contradictory (Int32 vs. UInt32): Cannot convert value "0x07FFFFFFFFFFFFFFFF" to type "System.Int32". Error: "Value was either too large or too small for a UInt32."
Where does the UInt come from?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I actually fixed this back when I was toying with the tokenizer, and then it was requested I re-break this for consistency with prior implementations. This can be fixed, but the fix would be in the tokenizer and how hex numbers get parsed.
This number is an extra digit beyond the widest permitted for uint64, and a hex number with a leading zero is forced to be an unsigned numeral when using the conversion paths we have in the tokenizer (which are still done with Convert.ToInt32()
I think?). I think the default path goes to (u)int32 because it checks for (u)int64 lengths and then defaults to (u)int32 for all other cases iirc.
I remember I originally did some fun things in my tokenizer PRs where I would automatically upcast between uint/int64 if needed, but it differed too much from the previous implementation I think and we decided to keep the behaviour consistent. I think my solution was to have it upcast up to decimal (or even biginteger) if needed, which worked well.
I think due to the refactoring in #7993 this error message might actually change, but it's been a little while and I don't quite recall.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see: if the pre-parameter-binding typing can't convert a "number-looking" argument to an actual number, it passes a string, and line baseObject = System.Management.Automation.Language.Parser.ScanNumber((string)baseObject, typeof(int));
then re-attempts conversion, which fails.
Again, not in the scope of this PR, but I wonder if the error message could be made friendlier (and it's still curious that UInt32 is referenced, given that hex. literals seem to top out at Int64 [see previous comment]).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, @vexx32 - wires crossed - reading your comment now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The following is unrelated to this specific PR, but it's worth pointing out:
Tests such as the following are flawed, because they always pass:
$randomNumber[0] | Should -Be (1 -or 2 -or 3 -or 5 -or 8 -or 13)
The above should be:
$randomNumber[0] | Should -BeIn 1, 2, 3, 5, 8, 13
@pougetat Thanks for your contribution! |
@iSazonov Thanks for the review |
PR Summary
The purpose of this PR is to implement the following feature for the Get-Random commandlet :
Get-Random -Maximum 10 -Count 3
5
1
7
PR Context
This PR is tied to the following issue :
fix #8986
PR Checklist
.h
,.cpp
,.cs
,.ps1
and.psm1
files have the correct copyright headerWIP:
or[ WIP ]
to the beginning of the title (theWIP
bot will keep its status check atPending
while the prefix is present) and remove the prefix when the PR is ready.[feature]
to your commit messages if the change is significant or affects feature tests