blood_contracts 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/blood_contracts.rb +0 -1
- data/lib/blood_contracts/contracts/iterator.rb +34 -0
- data/lib/blood_contracts/contracts/statistics.rb +34 -0
- data/lib/blood_contracts/runner.rb +22 -45
- data/lib/blood_contracts/storage.rb +0 -1
- data/lib/blood_contracts/storages/file_backend.rb +0 -24
- data/lib/blood_contracts/suite.rb +7 -10
- data/lib/blood_contracts/version.rb +1 -1
- data/lib/rspec/meet_contract_matcher.rb +5 -3
- metadata +3 -2
- data/lib/blood_contracts/statistics.rb +0 -32
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 28ac2afbaaff105357ceefb2e96e4812f5689ceca47fecc4eeffd5c51c45d3a9
|
4
|
+
data.tar.gz: 3d14390aa3ea943b3ad53495b3855c673d2c87da925b3230071d22c555de506c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8850f7821e11d0b3ed3acf837569b7ada9b45f0844fb8491a9e212bda1e1599035635f849e28acb0f7966933fe5cb0e4d865266012ce0148a37a2d6afe3679e9
|
7
|
+
data.tar.gz: 8e7e070351a3dc36d63bf6f0817176489350c0ea361dc3deb8cc48f5e578909bb2b62379e421d82b3d27267c9892d5965f788648a64f8b0bb86db10421d37ffc
|
data/lib/blood_contracts.rb
CHANGED
@@ -6,7 +6,6 @@ require "hashie/mash"
|
|
6
6
|
|
7
7
|
require_relative "blood_contracts/suite"
|
8
8
|
require_relative "blood_contracts/storage"
|
9
|
-
require_relative "blood_contracts/statistics"
|
10
9
|
require_relative "blood_contracts/runner"
|
11
10
|
require_relative "blood_contracts/debugger"
|
12
11
|
require_relative "blood_contracts/base_contract"
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module BloodContracts
|
2
|
+
module Contracts
|
3
|
+
class Iterator
|
4
|
+
extend Dry::Initializer
|
5
|
+
|
6
|
+
param :iterations, ->(v) do
|
7
|
+
v = ENV["iterations"] if ENV["iterations"]
|
8
|
+
v.to_i.positive? ? v.to_i : 1
|
9
|
+
end
|
10
|
+
param :time_to_run, ->(v) do
|
11
|
+
v = ENV["duration"] if ENV["duration"]
|
12
|
+
v.to_f if v.to_f.positive?
|
13
|
+
end, optional: true
|
14
|
+
|
15
|
+
def next
|
16
|
+
return iterations.times { yield } unless time_to_run
|
17
|
+
|
18
|
+
@iterations = iterations_from_time_to_run { yield }
|
19
|
+
[iterations - 1, 0].max.times { yield }
|
20
|
+
end
|
21
|
+
|
22
|
+
def count
|
23
|
+
@iterations
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def iterations_from_time_to_run
|
29
|
+
time_per_action = Benchmark.measure { yield }
|
30
|
+
(time_to_run / time_per_action.real).ceil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module BloodContracts
|
2
|
+
module Contracts
|
3
|
+
class Statistics
|
4
|
+
extend Dry::Initializer
|
5
|
+
param :iterator
|
6
|
+
option :storage, default: -> { Hash.new(0) }
|
7
|
+
|
8
|
+
def store(rule)
|
9
|
+
storage[rule] += 1
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_h
|
13
|
+
Hash[storage.map { |rule_name, times| [rule_name, rule_stats(times)] }]
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
to_h.map do |name, occasions|
|
18
|
+
" - '#{name}' happened #{occasions.times} time(s) "\
|
19
|
+
"(#{(occasions.percent * 100).round(2)}% of the time)"
|
20
|
+
end.join("; \n")
|
21
|
+
end
|
22
|
+
|
23
|
+
def found_unexpected_behavior?
|
24
|
+
storage.key?(Storage::UNDEFINED_RULE)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def rule_stats(times)
|
30
|
+
Hashie::Mash.new(times: times, percent: (times.to_f / iterator.count))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
require_relative "contracts/validator"
|
2
2
|
require_relative "contracts/matcher"
|
3
3
|
require_relative "contracts/description"
|
4
|
+
require_relative "contracts/iterator"
|
5
|
+
require_relative "contracts/statistics"
|
4
6
|
|
5
7
|
module BloodContracts
|
6
8
|
class Runner
|
@@ -11,19 +13,16 @@ module BloodContracts
|
|
11
13
|
option :suite
|
12
14
|
option :storage, default: -> { suite.storage }
|
13
15
|
|
14
|
-
option :iterations, ->
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
v.to_f if v.to_f.positive?
|
21
|
-
end, optional: true
|
16
|
+
option :iterations, default: -> { 1 }
|
17
|
+
option :time_to_run, optional: true
|
18
|
+
option :stop_on_unexpected, default: -> { false }
|
19
|
+
option :iterator, default: -> do
|
20
|
+
Contracts::Iterator.new(iterations, time_to_run)
|
21
|
+
end
|
22
22
|
|
23
23
|
option :context, optional: true
|
24
|
-
option :stop_on_unexpected, default: -> { false }
|
25
24
|
|
26
|
-
option :statistics, default: -> { Statistics.new(
|
25
|
+
option :statistics, default: -> { Contracts::Statistics.new(iterator) }
|
27
26
|
option :matcher, default: -> { Contracts::Matcher.new(suite.contract) }
|
28
27
|
option :validator, default: -> { Contracts::Validator.new(suite.contract) }
|
29
28
|
option :contract_description, default: -> do
|
@@ -31,15 +30,15 @@ module BloodContracts
|
|
31
30
|
end
|
32
31
|
|
33
32
|
def call
|
34
|
-
|
35
|
-
next
|
36
|
-
|
37
|
-
|
33
|
+
return false if :halt == catch(:unexpected_behavior) do
|
34
|
+
iterator.next do
|
35
|
+
next if match_rules?(matches_storage: statistics) do
|
36
|
+
input = suite.data_generator.call
|
37
|
+
[input, checking_proc.call(input)]
|
38
|
+
end
|
39
|
+
throw :unexpected_behavior, :halt if stop_on_unexpected
|
38
40
|
end
|
39
|
-
throw :unexpected_behavior, :stop if stop_on_unexpected
|
40
41
|
end
|
41
|
-
return if stopped_by_unexpected_behavior?
|
42
|
-
|
43
42
|
validator.valid?(statistics)
|
44
43
|
end
|
45
44
|
|
@@ -47,21 +46,21 @@ module BloodContracts
|
|
47
46
|
def failure_message
|
48
47
|
intro = "expected that given Proc would meet the contract:"
|
49
48
|
|
50
|
-
if
|
49
|
+
if stats.unexpected_behavior?
|
51
50
|
"#{intro}\n#{contract_description}\n"\
|
52
|
-
|
53
|
-
|
51
|
+
" during #{iterator.count} run(s) but got unexpected behavior.\n\n"\
|
52
|
+
"For further investigations open: #{storage.unexpected_suggestion}"
|
54
53
|
else
|
55
54
|
"#{intro}\n#{contract_description}\n"\
|
56
|
-
|
57
|
-
|
55
|
+
" during #{iterator.count} run(s) but got:\n#{statistics}\n\n"\
|
56
|
+
"For further investigations open: #{storage.suggestion}"
|
58
57
|
end
|
59
58
|
end
|
60
59
|
|
61
60
|
# FIXME: Move to locales
|
62
61
|
def description
|
63
62
|
"meet the contract:\n#{contract_description} \n"\
|
64
|
-
" during #{
|
63
|
+
" during #{iterator.count} run(s). Stats:\n#{statistics}\n\n"\
|
65
64
|
"For further investigations open: #{storage.suggestion}\n"
|
66
65
|
end
|
67
66
|
|
@@ -78,28 +77,6 @@ module BloodContracts
|
|
78
77
|
raise
|
79
78
|
end
|
80
79
|
|
81
|
-
def stopped_by_unexpected_behavior?
|
82
|
-
@_stopped_by_unexpected_behavior == :stop
|
83
|
-
end
|
84
|
-
|
85
|
-
def iterate
|
86
|
-
run_iterations ||= iterations
|
87
|
-
|
88
|
-
if time_to_run
|
89
|
-
run_iterations = iterations_count_from_time_to_run { yield }
|
90
|
-
@iterations = run_iterations + 1
|
91
|
-
end
|
92
|
-
|
93
|
-
@_stopped_by_unexpected_behavior = catch(:unexpected_behavior) do
|
94
|
-
run_iterations.times { yield }
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
def iterations_count_from_time_to_run
|
99
|
-
time_per_action = Benchmark.measure { yield }
|
100
|
-
(time_to_run / time_per_action.real).ceil
|
101
|
-
end
|
102
|
-
|
103
80
|
def store_exception(error, input, output, context)
|
104
81
|
storage.store(
|
105
82
|
options: {
|
@@ -103,30 +103,6 @@ module BloodContracts
|
|
103
103
|
f << write(dump_proc, context, data)
|
104
104
|
end
|
105
105
|
end
|
106
|
-
#
|
107
|
-
# def serialize_input(tag, options, context)
|
108
|
-
# return unless (dump_proc = input_serializer[:dump])
|
109
|
-
# name = sample_name(tag)
|
110
|
-
# File.open("#{name}.input.dump", "w+") do |f|
|
111
|
-
# f << write(dump_proc, context, options.input)
|
112
|
-
# end
|
113
|
-
# end
|
114
|
-
#
|
115
|
-
# def serialize_output(tag, options, context)
|
116
|
-
# return unless (dump_proc = output_serializer[:dump])
|
117
|
-
# name = sample_name(tag)
|
118
|
-
# File.open("#{name}.output.dump", "w+") do |f|
|
119
|
-
# f << write(dump_proc, context, options.output)
|
120
|
-
# end
|
121
|
-
# end
|
122
|
-
#
|
123
|
-
# def serialize_meta(tag, options, context)
|
124
|
-
# return unless (dump_proc = meta_serializer[:dump])
|
125
|
-
# name = sample_name(tag)
|
126
|
-
# File.open("#{name}.meta.dump", "w+") do |f|
|
127
|
-
# f << write(dump_proc, context, options.meta)
|
128
|
-
# end
|
129
|
-
# end
|
130
106
|
end
|
131
107
|
end
|
132
108
|
end
|
@@ -2,14 +2,15 @@ module BloodContracts
|
|
2
2
|
class Suite
|
3
3
|
extend Dry::Initializer
|
4
4
|
|
5
|
+
option :contract, ->(v) { Hashie::Mash.new(v) }
|
5
6
|
option :data_generator, optional: true
|
6
|
-
option :contract, ->(v) { Hashie::Mash.new(v) }, default: -> { Hash.new }
|
7
7
|
|
8
8
|
option :input_writer, optional: true
|
9
9
|
option :output_writer, optional: true
|
10
10
|
|
11
|
-
option :input_serializer,
|
12
|
-
option :output_serializer,
|
11
|
+
option :input_serializer, optional: true
|
12
|
+
option :output_serializer, optional: true
|
13
|
+
option :meta_serializer, optional: true
|
13
14
|
|
14
15
|
option :storage_backend, optional: true
|
15
16
|
option :storage, default: -> { default_storage }
|
@@ -19,11 +20,6 @@ module BloodContracts
|
|
19
20
|
@data_generator = generator
|
20
21
|
end
|
21
22
|
|
22
|
-
def contract=(contract)
|
23
|
-
raise ArgumentError unless contract.respond_to?(:to_h)
|
24
|
-
@contract = Hashie::Mash.new(contract.to_h)
|
25
|
-
end
|
26
|
-
|
27
23
|
def input_writer=(writer)
|
28
24
|
storage.input_writer = writer
|
29
25
|
end
|
@@ -34,10 +30,11 @@ module BloodContracts
|
|
34
30
|
|
35
31
|
def default_storage
|
36
32
|
Storage.new(
|
37
|
-
input_writer:
|
33
|
+
input_writer: input_writer,
|
38
34
|
output_writer: output_writer,
|
39
|
-
input_serializer:
|
35
|
+
input_serializer: input_serializer,
|
40
36
|
output_serializer: output_serializer,
|
37
|
+
meta_serializer: meta_serializer,
|
41
38
|
)
|
42
39
|
end
|
43
40
|
end
|
@@ -23,9 +23,11 @@ module RSpec
|
|
23
23
|
suite = nil
|
24
24
|
if args.respond_to?(:to_contract_suite)
|
25
25
|
suite = args.to_contract_suite(name: _example_name_to_path)
|
26
|
-
elsif args.respond_to?(:
|
27
|
-
|
28
|
-
|
26
|
+
elsif args.respond_to?(:to_h) && args.to_h.fetch(:contract) { false }
|
27
|
+
::BloodContracts::Suite.new(
|
28
|
+
storage: new_storage,
|
29
|
+
contract: args[:contract]
|
30
|
+
)
|
29
31
|
else
|
30
32
|
raise "Matcher arguments is not a Blood Contract"
|
31
33
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: blood_contracts
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sergey Dolganov
|
@@ -128,11 +128,12 @@ files:
|
|
128
128
|
- lib/blood_contracts.rb
|
129
129
|
- lib/blood_contracts/base_contract.rb
|
130
130
|
- lib/blood_contracts/contracts/description.rb
|
131
|
+
- lib/blood_contracts/contracts/iterator.rb
|
131
132
|
- lib/blood_contracts/contracts/matcher.rb
|
133
|
+
- lib/blood_contracts/contracts/statistics.rb
|
132
134
|
- lib/blood_contracts/contracts/validator.rb
|
133
135
|
- lib/blood_contracts/debugger.rb
|
134
136
|
- lib/blood_contracts/runner.rb
|
135
|
-
- lib/blood_contracts/statistics.rb
|
136
137
|
- lib/blood_contracts/storage.rb
|
137
138
|
- lib/blood_contracts/storages/base_backend.rb
|
138
139
|
- lib/blood_contracts/storages/file_backend.rb
|
@@ -1,32 +0,0 @@
|
|
1
|
-
module BloodContracts
|
2
|
-
class Statistics
|
3
|
-
extend Dry::Initializer
|
4
|
-
param :iterations
|
5
|
-
option :storage, default: -> { Hash.new(0) }
|
6
|
-
|
7
|
-
def store(rule)
|
8
|
-
storage[rule] += 1
|
9
|
-
end
|
10
|
-
|
11
|
-
def to_h
|
12
|
-
Hash[storage.map { |rule_name, times| [rule_name, rule_stats(times)] }]
|
13
|
-
end
|
14
|
-
|
15
|
-
def to_s
|
16
|
-
to_h.map do |name, occasions|
|
17
|
-
" - '#{name}' happened #{occasions.times} time(s) "\
|
18
|
-
"(#{(occasions.percent * 100).round(2)}% of the time)"
|
19
|
-
end.join("; \n")
|
20
|
-
end
|
21
|
-
|
22
|
-
def found_unexpected_behavior?
|
23
|
-
storage.key?(Storage::UNDEFINED_RULE)
|
24
|
-
end
|
25
|
-
|
26
|
-
private
|
27
|
-
|
28
|
-
def rule_stats(times)
|
29
|
-
Hashie::Mash.new(times: times, percent: (times.to_f / iterations))
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|