rules_testing icon indicating copy to clipboard operation
rules_testing copied to clipboard

Support None values

Open rickeylev opened this issue 2 years ago • 2 comments

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

rickeylev avatar Jul 11 '23 20:07 rickeylev

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.

aignas avatar Jul 11 '23 23:07 aignas

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)

matts1 avatar Feb 21 '24 03:02 matts1