Alpha Chen 2 years ago
parent fb7c012f2c
commit 7bc04b20e2
Signed by: alpha
SSH Key Fingerprint: SHA256:3fOT8fiYQG/aK9ntivV3Bqtg8AYQ7q4nV6ZgihOA20g

@ -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.

@ -31,10 +31,10 @@ class Minitest::ThesisTest < Minitest::Thesis
# 1001]``, and then lowering the length by two, turning it into ``[1001]`` as # 1001]``, and then lowering the length by two, turning it into ``[1001]`` as
# desired. # desired.
def test_finds_small_list_even_with_bad_lists def test_finds_small_list_even_with_bad_lists
bad_list = Possibility.new( bad_list = Possibility.new("bad_list") {|tc|
->(tc) { n = tc.choice(10); Array.new(n) { tc.choice(10_000) }}, n = tc.choice(10)
name: "bad_list", Array.new(n) { tc.choice(10_000) }
) }
(0...10).each do |seed| (0...10).each do |seed|
out, _ = capture_io do out, _ = capture_io do
@ -321,7 +321,10 @@ class Minitest::ThesisTest < Minitest::Thesis
def test_selected_possibility def test_selected_possibility
run_test("selected_possibility", database: {}) do |tc| run_test("selected_possibility", database: {}) do |tc|
n = tc.any(integers(0, 5).satisfying(&:even?)) n = tc.any(
integers(0, 5)
.satisfying(&:even?)
)
assert n.even? assert n.even?
end end
end end

Loading…
Cancel
Save