Support None values
Support for None is very spotty. I think IntSubject supports it, maybe string, but not all of them. None doesn't happen much in rule code per-se, more so in utility functions. It's common enough that not having this makes using rules_testing a bit of a pain.
So implement allowing a None as the actual value and having None passed to equals() and not_equals(). Basic support should be pretty easy (i.e. just asserting None on a None value). Handling a None actual value for all the other assertions might be more involved -- almost all of them assume they are working with a non-None value. We can start with basic and go from there, though.
Subjects to update:
- [ ] Bool
- [ ] Collection
- [ ] DefaultInfo
- [ ] DepsetFile
- [ ] Dict
- [ ] File
- [ ] Int
- [ ] Label
- [ ] Runfiles
- [ ] Str
- [ ] Struct
- [ ] Target
Another way to do this would be to support a NoneSubject because quite often None is a special value from the business logic point of view and it is definitely not an int or str. Maybe doing env.expect.that_<foo>(actual).is_none() would be also good. If we were not using fluent APIs we could just do env.expect.is_none(actual).
That said supporting None in the equals method is important as well from API ergonomics point of view.
See #93 for more details, but why not just solve it with a generic Optional type?
IMO it should be an error to have a none value for something you've declared as a string. If you want that to be possible, it should be declared as a subjects.optional(subjects.str)
def optional(factory):
def new_factory(value, *, meta):
# Optimisations could be done here to just pull all the methods from the parent and add the method is_none()
def some():
if value == None:
meta.add_failure("Wanted a value, but got None", None)
return factory(value, meta = meta)
def is_none():
if value != None:
meta.add_failure("Wanted None, but got a value", value)
return struct(some = some, is_none = is_none)