-
Notifications
You must be signed in to change notification settings - Fork 46
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
Create a check "nonblank" #73
Comments
I think this is a good idea, but deciding what "non-blank" is will need some discussion. First issue is the
In general, implicitly excluding objects or references from checks will lead to confusing results and pierce the encapsulation of overloaded objects. It could be argued that non-blessed references should be excluded, or even blessed references which are not string or numeric overloaded, but not simply all references. I say to keep it simple and to let the caller decide whether they want to exclude references. |
Next is the term "blank". What does it mean? The code says it's something that has a length. What about It would be clearer to call this These match
These do not:
|
I agree with @schwern here, if the blank/non-blank were to be a core part of Test2::Suite it would need to look like this to be properly generic, and meet expectations. That said, I am guessing this does not help you with your use case as much as you would like. However you could then wrap this with a couple other exisitng tools to create your_not_blank() to suppliment it in your own test suite's tools. |
The The |
I'm also thinking that it should be |
Something with string in the name should stringify the argument it is given, so if URI->new("") stringifies to "" then it should pass as an empty string. |
I think empty_string is a better name than string_empty. The other thingsd you mention may come to exist, but seem to specialized for core. |
I take it back, I think this is confusing. |
@petdance As for whether
I say "works as designed". It keeps it simple. The check does one thing, it checks if a thing will stringify as an empty string or not. This simple definition avoids having to make a bunch of judgement calls and edge cases and impose a philosophy about objects (ie. treating refs and objects as special things). The check only cares about what it stringifies to. If you want to also check that it's not a reference, make that explicit. |
&& is not overloaded, but it easily could be using the set checks which let you check that all checks within them match, or that only one does, or that any one does... @schwern can you make a ticket for fleshing out that feature? |
I think calling it |
Also, I think it's incredibly uncommon to check that something is an empty string, whereas checking for non-emptiness is extremely common. I use that MX |
The whole point is to not have to make it explicit. The point is to have a string that is not a reference. Let me describe the use case. I know it probably doesn't fit the model you have in mind. Right now I might have code like this:
These are so common I have wrapped them up into
For me, the promise of Test2 is the ability to do this:
|
Exactly the problem. Not just with my model, but with the probably half a dozen different ways people think about refs and objects in Perl floating around out there. Which is to say that your use case is totally valid; the question is how to solve it without invalidating the other common use cases. Adding extra implicit caveats to checks restricts how they can be used. If This is why I advocate simplicity, meaning few moving parts. A check that does one thing has maximum utility for the maximum number of situations and more specialized and complex/compound checks can be built on it. I think #75 resolves this problem between flexible simplicity and specific complexity by making multiple checks easy to combine without having to write a plethora of new combined checks. Hopefully we can write your example as...
|
As for |
Agreed 100%. Components are good.
This goes back to what I said above: |
@petdance Good one, the problem is much clearer with Ok, here's my summary of the approaches. Invert the check to
|
Oh 💩, another edge case. What do we do about this?
Should a blessed hash reference be treated like a hash? As much as I'd like to say "no, don't peek inside objects!" I realize a large bulk of the Perl 5 community is against me on this one. So I'll concede that should pass. If you don't want it to pass, write Whew! Thoughts? |
My thought is that if the name includes the structure type, 'array', 'hash', 'object', etc. That the check should verify that the thing being checked is of that type, or can pretend to be that type via overloading (or is that type but blessed). This includes the negated ones. so !populated_array still implies that it must be an array. Negation negates the state of the structure, but not the type. This is consistent with how I have been trying to do all the checks. populated_array and !populated_array should both fail if the thing they are checking is not an array. This is why checks have both verify() and delta() verify() checks that the thing is the expected type (array), delta() then returns the differences of the contents. populated_array should have a verify() that returns true/false based on the type of the thing being checked. It's delta() should be run only when verify() passes (that is baked in already) and return differences between got and want knowing they are at least the same type. |
If an object is a hash, then populated_hash and !populated_hash should both have their verify step pass, cause the thing they got can be used as a hash. |
Fact is there is ambiguity, we cannot get rid of ambiguity completely, but what we can do is always treat the ambiguous cases consistently, and that means a check against a structure must verify it got something of that structure, even if it's checks inside the structure have been negated via !. This should be documented as the "consistent" thing to do, and anything we have already that does nto conform to this should be fixed (I am pretty sure everything conforms) |
@exodist You said "type" and "is" a lot, not "behaves". This is important. Do you mean "behaves like a hash/array/string/number" or do you mean "is a hash/array/string/number"? |
if it can sanely be treated like an array (is an array, is tied as an array, overloaded to be an array, etc) then verify() should pass on anything that implies it works on an array, even when negated. |
to be clear "behaves like an array" |
Thinking about it, the array and hash checks as they stand will fail on anything that is not |
I note that Ref::Util provides Also, to go back to overloading a bit ... this is a huge mess with string & number overloading, since all Perl types are capable of acting like strings or numbers, but only in the most useless way possible. To actually check whether something acts like a number in Perl is really painful and requires you to complete violate overloading encapsulation. See http://blog.urth.org/2010/10/16/whats-wrong-with-perl-5s-overloading/ for a blog post I wrote about this years ago, as well as Specio's type check's for these things for more painful details. |
The main difference here is we are not "guessing" in any way. The check on the right hand side dictates what should be in the left hand side (the got structure). So if you do number(5) on the right hand side it will only pass if the left is In neither case does it inspect internals, or try to guess if it is a number vs string vs ref, it just treats it as what it expects, and fails if that produces no match, passes if it produces a match. We must not go down the smartmatch road to hell by trying to guess these things. Carrying this forward, array { ... } should pass when the left hand side can be treated as an array (ie, using $rhs->[0] or @{$rhs} works) and all the indexes match their checks. If the check contains the word array it implies that the check must be used against an array, or something that behaves as an array. IF the thing it gets does not behave as an array it should fail, and not get to the nested checks, negated or not. |
I know, I'm talking high-level here. What if I did it as
That's really the more likely use anyway, that users will set up their own meta-checks, like maybe
|
That will still have the wrong line numbers, it will report to being inside your sub. I anticipated this in the todo list above, I will write a tool to make it easy. Probably do not have time to write the tool today though. |
to be clear, here is what I have in mind:
Perhapse also this:
so that people can define them at BEGIN time easily also, I think compose_check should define the sub in void context, but return an anonymous sub in non-void context. |
So long as I can call I just want to be able to
|
You will be able to. |
oops, did not intend to close. |
I'm cool with the policy list exodist posted, with one major exception. _Treating default reference numification/stringification as a number/string is useless and error prone._ I'm with @autarch and @petdance on that one now. Any "string" and "number" type checks should reject default reference numificaiton and stringification (ie. So long as we do it consistently I don't think anyone will even notice because it Just Works. Go ahead and look through your existing tests and mentally count which ones really want Exceptional cases should be exceptional. If someone really wants to test default reference numification and stringification, they can explicitly numify or stringify it. Or hey, checks are functions. Add an option. |
Did this get addressed and I missed it? It sounds like the current plan is:
and
Which means that every test is defining the semantics of its own negation, and that these two lines are not equivalent: not( empty_string->matches( $x ));
( ! empty_string)->matches( $x ); This makes me a lot more unhappy than "double negation." If we have two clearly-defined tests, each of which can be negated through a generic mechanism that means "does not pass this," then it's clear. If every assertion has two meanings, I am sure I will end up pretty confused in short order. |
@rjbs Does #73 (comment) clear up why the type checks are necessary and why they don't also negate? Looking at it another way, we're negating the "empty" part, not the "type check" part because that would be nonsense. And yes, this is different from negating the result. We can get away with this because it's a DSL and not intended to be used as code with explicit method calls like you have. This is one of those things that only becomes a problem when you think about it too much. :) |
@rjbs To put it another way, we intend Maybe what we need is a more generic
I'm liking that! The bare reference string/number problem is easier to resolve. We can offer This also avoids a proliferation of OH! Maybe take a page from
I think that's it! Then there's no ambiguity about what this means.
|
@schwern, I agree with what you say. we can make default ref string/numberising to be an exception. |
Plus, if someone wants to check stringification of their object, they can explicitly put the object in quotes to get the stringification.
|
I strongly object to this suggestion. If my input is: $input = { a => { b => [ ..., $c ] } }; I want to be able to write: is($input, $complicated_test); and not is("$input->{a}{b}[9]", $test1);
is("$input->{a}{z}{w}", $test2); and so on. It is the test specification that should say what it means. |
I think the real problem we have here is the classic no-one understands what overloaded negation actually means - at least not consistently with what other people think. This is why I think the magical aspect of it is just a really bad idea and we should avoid it entirely. No matter what we decide it means half the people will think it means the other thing and that's how we end up with lots of buggy tests. The less magic in tests the better. I'm not entirely sure why Maybe we could add some other syntactic sugar meaning 'something that doesn't match this condition' and stay away from |
I feel like this thread is getting crazy. @rjbs I don't think anyone is proposing what you are against, though the wording @petdance used does seem to imply that way. The only thing being proposed is that plain refs without string overloading, ie 'hash(1244)' is almost never what you want. By that a string check on the left that only cares that it is given a string (but does not care what is in it) should not pass if it gets a ref that has no string overloading. in this case you can still do the uncommon case of actually wanting 'hash(1234)' by quoting thr thing on the left. |
I think @rjbs and I are talking about two different things, and I don't disagree with the things he's talking about at all. To clarify, Rik, what I was saying is that we want this:
to verify that Right now, in Test::More, if you say
the test will pass because
And if we do want to explicitly check for overloaded stringiness, we can do so like so:
|
I think the following:
I think we need to move to the less is more approach. I think the dsl should have only a handful of subs: is(
$structure,
st Hash => sub {
my $st = shift;
# or
my $st = st_this;
stc key1 => 'val1';
stc key2 => 'val2';
stc nested => st Array => { sta empty => 1 };
stc non_empty_string => st String => { sta empty => 0 };
stc price => st Number => 12;
sta 'end'; # Or to be more explicit:
sta end => 1; # or stp etc => 1,
},
"name"
); st - define a structure stc - structure component; stcs - structure values sta - structure attributes stp - structure property Other subs can be added as shortcuts for some common cases, in fact all current dsl subs can be implemented with these. This interface still allows for builders (the kind that take subs) while allowing for those that do not (like string() and number()) It is a bit more verbose, but it leads to more consistency, and less need to know the differences between similar things. Negation of a structure check (all checks are now structure checks, just that string and number are not deep structures) will still enforce an instance of the structure, but negate the contents of the structure. We cannot negate something that isn't assigned. Schwern wanted
This solves the st Array => \@array;
st Array => sub { ... };
st String => "...";
st String => sub { ... }; consistent! no prototypes, no magic! Final point to address, petdance implied we would also change isnt(). That needs to be discussed. Currently isn't is a full fledged negation, it passes if any of the checks fail. I have been thinking isnt() is it is currently defined is useless for all but shallow checks |
I am going to create a milestone for all the various issues here, and split the thing from this ticket into several tickets inside the milestone. I think there are too many discussions here. |
Sorry, I made a mistake up above. I didn't mean to say that we would change anything about Here's corrected version (and I corrected above, too)
|
I think I captured all the issues fairly well in the milestone. A combination of things mean I probably cannot work much on it this week, hopefully the tickets make a few things clear. It should be possible for others to work many of them if anyone feels like it. |
I am going to close this ticket in favor of the tickets that came from it. Many of them still reference this ticket making the history easy to find. |
In the old system, I have many tests that are of the form
which is effectively
And we should be able to do
The text was updated successfully, but these errors were encountered: