|
|
@ -193,9 +193,9 @@ module Minitest
|
|
|
|
class Possibility
|
|
|
|
class Possibility
|
|
|
|
attr_reader :produce, :name
|
|
|
|
attr_reader :produce, :name
|
|
|
|
|
|
|
|
|
|
|
|
def initialize(produce, name: "TODO")
|
|
|
|
def initialize(name = "TODO", &produce)
|
|
|
|
@produce = produce
|
|
|
|
|
|
|
|
@name = name
|
|
|
|
@name = name
|
|
|
|
|
|
|
|
@produce = produce
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
def inspect = name
|
|
|
|
def inspect = name
|
|
|
@ -203,41 +203,34 @@ module Minitest
|
|
|
|
|
|
|
|
|
|
|
|
# "Returns a `Possibility` where values come from applying `f` to some possible value for `self`."
|
|
|
|
# "Returns a `Possibility` where values come from applying `f` to some possible value for `self`."
|
|
|
|
def map(&f)
|
|
|
|
def map(&f)
|
|
|
|
self.class.new(
|
|
|
|
self.class.new("#{name}.map(TODO)") {|tc| f.call(tc.any(self)) }
|
|
|
|
->(test_case) { f.(test_case.any(self)) },
|
|
|
|
|
|
|
|
name: "#{name}.map(TODO)",
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
# Returns a `Possibility` where values come from applying `f` (which
|
|
|
|
# Returns a `Possibility` where values come from applying `f` (which
|
|
|
|
# should return a new `Possibility` to some possible value for `self`
|
|
|
|
# should return a new `Possibility` to some possible value for `self`
|
|
|
|
# then returning a possible value from that.
|
|
|
|
# then returning a possible value from that.
|
|
|
|
def bind(&f)
|
|
|
|
def bind(&f)
|
|
|
|
produce = ->(test_case) { test_case.any(f.(test_case.any(self))) }
|
|
|
|
self.class.new("#{name}.bind(TODO)") {|tc| tc.any(f.(tc.any(self))) }
|
|
|
|
self.class.new(produce, name: "#{name}.bind(TODO)")
|
|
|
|
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
# Returns a `Possibility` whose values are any possible value of `self`
|
|
|
|
# Returns a `Possibility` whose values are any possible value of `self`
|
|
|
|
# for which `f` returns True.
|
|
|
|
# for which `f` returns True.
|
|
|
|
def satisfying(&f)
|
|
|
|
def satisfying(&f)
|
|
|
|
produce = ->(test_case) {
|
|
|
|
self.class.new("#{name}.select(TODO)") {|test_case|
|
|
|
|
3.times do
|
|
|
|
3.times.first {
|
|
|
|
candidate = test_case.any(self)
|
|
|
|
candidate = test_case.any(self)
|
|
|
|
return candidate if f.(candidate)
|
|
|
|
candidate if f.(candidate)
|
|
|
|
end
|
|
|
|
} || test_case.reject
|
|
|
|
test_case.reject
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
self.class.new(produce, name: "#{name}.select(TODO)")
|
|
|
|
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
# Any integer in the range [m, n] is possible
|
|
|
|
# Any integer in the range [m, n] is possible
|
|
|
|
def integers(m, n) = Possibility.new(->(tc) { m + tc.choice(n - m) }, name: "integers(#{m}, #{n})")
|
|
|
|
def integers(m, n) = Possibility.new("integers(#{m}, #{n})") {|tc| m + tc.choice(n - m) }
|
|
|
|
|
|
|
|
|
|
|
|
# Any lists whose elements are possible values from `elements` are possible.
|
|
|
|
# Any lists whose elements are possible values from `elements` are possible.
|
|
|
|
def lists(elements, min_size: 0, max_size: Float::INFINITY)
|
|
|
|
def lists(elements, min_size: 0, max_size: Float::INFINITY)
|
|
|
|
produce = ->(test_case) {
|
|
|
|
Possibility.new("lists(#{elements.name})") {|test_case|
|
|
|
|
result = []
|
|
|
|
result = []
|
|
|
|
loop do
|
|
|
|
loop do
|
|
|
|
if result.length < min_size
|
|
|
|
if result.length < min_size
|
|
|
@ -252,33 +245,29 @@ module Minitest
|
|
|
|
end
|
|
|
|
end
|
|
|
|
result
|
|
|
|
result
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Possibility.new(produce, name: "lists(#{elements.name})")
|
|
|
|
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
# Only `value` is possible.
|
|
|
|
# Only `value` is possible.
|
|
|
|
def just(value) = Possibility.new(->(_) { value }, name: "just(#{value})")
|
|
|
|
def just(value) = Possibility.new("just(#{value})") { value }
|
|
|
|
|
|
|
|
|
|
|
|
# No possible values. i.e. Any call to `any` will reject the test case.
|
|
|
|
# No possible values. i.e. Any call to `any` will reject the test case.
|
|
|
|
def nothing = Possibility.new(->(tc) { tc.reject })
|
|
|
|
def nothing = Possibility.new {|tc| tc.reject }
|
|
|
|
|
|
|
|
|
|
|
|
# Possible values can be any value possible for one of `possibilities`.
|
|
|
|
# Possible values can be any value possible for one of `possibilities`.
|
|
|
|
def mix_of(*possibilities)
|
|
|
|
def mix_of(*possibilities)
|
|
|
|
return nothing if possibilities.empty?
|
|
|
|
return nothing if possibilities.empty?
|
|
|
|
|
|
|
|
|
|
|
|
Possibility.new(
|
|
|
|
Possibility.new("mix_of(#{possibilities.map(&:name).join(", ")})") {|tc|
|
|
|
|
->(tc) { tc.any(possibilities[tc.choice(possibilities.length - 1)]) },
|
|
|
|
tc.any(possibilities[tc.choice(possibilities.length - 1)])
|
|
|
|
name: "mix_of(#{possibilities.map(&:name).join(", ")})",
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
# Any tuple t of of length len(possibilities) such that t[i] is possible
|
|
|
|
# Any tuple t of of length len(possibilities) such that t[i] is possible
|
|
|
|
# for possibilities[i] is possible.
|
|
|
|
# for possibilities[i] is possible.
|
|
|
|
def tuples(*possibilities)
|
|
|
|
def tuples(*possibilities)
|
|
|
|
Possibility.new(
|
|
|
|
Possibility.new( "tuples(#{possibilities.map(&:name).join(", ")})") {|tc|
|
|
|
|
->(tc) { possibilities.map {|p| tc.any(p) } },
|
|
|
|
possibilities.map {|p| tc.any(p) }
|
|
|
|
name: "tuples(#{possibilities.map(&:name).join(", ")})",
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
# We cap the maximum amount of entropy a test case can use.
|
|
|
|
# We cap the maximum amount of entropy a test case can use.
|
|
|
|