blood_contracts 0.2.1 → 1.0.0
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/README.md +63 -119
- data/Rakefile +30 -0
- data/bin/console +3 -2
- data/blood_contracts.gemspec +10 -27
- data/lib/blood_contracts.rb +3 -38
- data/spec/blood_contracts_spec.rb +36 -0
- data/spec/spec_helper.rb +16 -0
- metadata +20 -91
- data/.travis.yml +0 -5
- data/lib/blood_contracts/base_contract.rb +0 -42
- data/lib/blood_contracts/contracts/description.rb +0 -25
- data/lib/blood_contracts/contracts/iterator.rb +0 -34
- data/lib/blood_contracts/contracts/matcher.rb +0 -38
- data/lib/blood_contracts/contracts/statistics.rb +0 -34
- data/lib/blood_contracts/contracts/validator.rb +0 -47
- data/lib/blood_contracts/debugger.rb +0 -39
- data/lib/blood_contracts/runner.rb +0 -92
- data/lib/blood_contracts/storage.rb +0 -79
- data/lib/blood_contracts/storages/base_backend.rb +0 -59
- data/lib/blood_contracts/storages/file_backend.rb +0 -108
- data/lib/blood_contracts/storages/serializer.rb +0 -38
- data/lib/blood_contracts/suite.rb +0 -41
- data/lib/blood_contracts/version.rb +0 -3
- data/lib/extensions/string.rb +0 -31
- data/lib/rspec/meet_contract_matcher.rb +0 -106
@@ -1,59 +0,0 @@
|
|
1
|
-
require 'nanoid'
|
2
|
-
|
3
|
-
module BloodContracts
|
4
|
-
module Storages
|
5
|
-
class BaseBackend
|
6
|
-
extend Dry::Initializer
|
7
|
-
extend Forwardable
|
8
|
-
|
9
|
-
param :storage
|
10
|
-
param :example_name
|
11
|
-
option :name, default: -> do
|
12
|
-
BloodContracts.run_name || ::Nanoid.generate(size: 10)
|
13
|
-
end
|
14
|
-
def_delegators :@storage, :input_writer, :output_writer,
|
15
|
-
:input_serializer, :output_serializer, :meta_serializer
|
16
|
-
|
17
|
-
|
18
|
-
def sample_exists?(sample_name)
|
19
|
-
raise NotImplementedError
|
20
|
-
end
|
21
|
-
|
22
|
-
def find_all_samples(run, tag, sample)
|
23
|
-
raise NotImplementedError
|
24
|
-
end
|
25
|
-
|
26
|
-
def load_sample(_sample_name)
|
27
|
-
%i(input output meta).map do |type|
|
28
|
-
load_sample_chunk(type, _sample_name)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def load_sample_chunk(_dump_type, _sample_name)
|
33
|
-
raise NotImplementedError
|
34
|
-
end
|
35
|
-
|
36
|
-
def describe_sample(_tag, _options, _context)
|
37
|
-
raise NotImplementedError
|
38
|
-
end
|
39
|
-
|
40
|
-
def serialize_sample(tag, options, context)
|
41
|
-
%i(input output meta).each do |type|
|
42
|
-
serialize_sample_chunk(type, tag, options, context)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
def serialize_sample_chunk(_type, _tag, _option, _context)
|
47
|
-
raise NotImplementedError
|
48
|
-
end
|
49
|
-
|
50
|
-
def suggestion
|
51
|
-
raise NotImplementedError
|
52
|
-
end
|
53
|
-
|
54
|
-
def unexpected_suggestion
|
55
|
-
raise NotImplementedError
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
@@ -1,108 +0,0 @@
|
|
1
|
-
module BloodContracts
|
2
|
-
module Storages
|
3
|
-
class FileBackend < BaseBackend
|
4
|
-
option :root, default: -> { Rails.root.join(path) }
|
5
|
-
|
6
|
-
def suggestion
|
7
|
-
"#{path}/*/*"
|
8
|
-
end
|
9
|
-
|
10
|
-
def unexpected_suggestion
|
11
|
-
"#{path}/#{Storage::UNDEFINED_RULE}/*"
|
12
|
-
end
|
13
|
-
|
14
|
-
def default_path
|
15
|
-
"./tmp/contract_tests/"
|
16
|
-
end
|
17
|
-
|
18
|
-
def timestamp
|
19
|
-
@timestamp ||= Time.current.to_s(:usec)[8..-3]
|
20
|
-
end
|
21
|
-
|
22
|
-
def reset_timestamp!
|
23
|
-
@timestamp = nil
|
24
|
-
end
|
25
|
-
|
26
|
-
def parse_sample_name(sample_name)
|
27
|
-
path_items = sample_name.split("/")
|
28
|
-
sample = path_items.pop
|
29
|
-
tag = path_items.pop
|
30
|
-
path_str = path_items.join("/")
|
31
|
-
run_n_example_str = path_str.sub(default_path, '')
|
32
|
-
if run_n_example_str.end_with?('*')
|
33
|
-
[
|
34
|
-
run_n_example_str.chomp("*"),
|
35
|
-
tag,
|
36
|
-
sample
|
37
|
-
]
|
38
|
-
elsif run_n_example_str.end_with?(example_name)
|
39
|
-
[
|
40
|
-
run_n_example_str.chomp(example_name),
|
41
|
-
tag,
|
42
|
-
sample
|
43
|
-
]
|
44
|
-
else
|
45
|
-
%w(__no_match__ __no_match__ __no_match__)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def find_all_samples(sample_name)
|
50
|
-
run, tag, sample = parse_sample_name(sample_name)
|
51
|
-
run_path = path(run_name: run)
|
52
|
-
files = Dir.glob("#{run_path}/#{tag.to_s}/#{sample}*")
|
53
|
-
files.select { |f| f.end_with?(".output") }
|
54
|
-
.map { |f| f.chomp(".output") }
|
55
|
-
end
|
56
|
-
|
57
|
-
def path(run_name: name)
|
58
|
-
File.join(default_path, run_name, example_name.to_s)
|
59
|
-
end
|
60
|
-
|
61
|
-
def sample_name(tag, run_path: root, sample: timestamp)
|
62
|
-
File.join(run_path, tag.to_s, sample)
|
63
|
-
end
|
64
|
-
|
65
|
-
def sample_exists?(sample_name)
|
66
|
-
run, tag, sample = parse_sample_name(sample_name)
|
67
|
-
name = sample_name(tag, run_path: path(run_name: run), sample: sample)
|
68
|
-
File.exist?("#{name}.input")
|
69
|
-
end
|
70
|
-
|
71
|
-
def load_sample_chunk(dump_type, sample_name)
|
72
|
-
run, tag, sample = parse_sample_name(sample_name)
|
73
|
-
name = sample_name(tag, run_path: path(run_name: run), sample: sample)
|
74
|
-
send("#{dump_type}_serializer")[:load].call(
|
75
|
-
File.read("#{name}.#{dump_type}.dump")
|
76
|
-
)
|
77
|
-
end
|
78
|
-
|
79
|
-
def write(writer, cntxt, options)
|
80
|
-
writer = cntxt.method(writer) if cntxt && writer.respond_to?(:to_sym)
|
81
|
-
writer.call(options).encode(
|
82
|
-
"UTF-8", invalid: :replace, undef: :replace, replace: "?",
|
83
|
-
)
|
84
|
-
end
|
85
|
-
|
86
|
-
def describe_sample(tag, options, context)
|
87
|
-
FileUtils.mkdir_p File.join(root, tag.to_s)
|
88
|
-
|
89
|
-
reset_timestamp!
|
90
|
-
name = sample_name(tag)
|
91
|
-
File.open("#{name}.input", "w+") do |f|
|
92
|
-
f << write(input_writer, context, options)
|
93
|
-
end
|
94
|
-
File.open("#{name}.output", "w+") do |f|
|
95
|
-
f << write(output_writer, context, options)
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
def serialize_sample_chunk(type, tag, options, context)
|
100
|
-
return unless (dump_proc = send("#{type}_serializer")[:dump])
|
101
|
-
name, data = sample_name(tag), options.send(type)
|
102
|
-
File.open("#{name}.#{type}.dump", "w+") do |f|
|
103
|
-
f << write(dump_proc, context, data)
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
@@ -1,38 +0,0 @@
|
|
1
|
-
module BloodContracts
|
2
|
-
module Storages
|
3
|
-
class Serializer
|
4
|
-
extend Dry::Initializer
|
5
|
-
|
6
|
-
param :serializer
|
7
|
-
|
8
|
-
def self.call(*args)
|
9
|
-
new(*args).call
|
10
|
-
end
|
11
|
-
|
12
|
-
def call
|
13
|
-
return object_serializer_to_hash if object_serializer?
|
14
|
-
return serializer.to_hash if hash_serializer?
|
15
|
-
|
16
|
-
raise "Both #dump and #load methods should be defined for serialization"
|
17
|
-
end
|
18
|
-
|
19
|
-
private
|
20
|
-
|
21
|
-
def object_serializer?
|
22
|
-
serializer.respond_to?(:dump) && serializer.respond_to?(:load)
|
23
|
-
end
|
24
|
-
|
25
|
-
def object_serializer_to_hash
|
26
|
-
{
|
27
|
-
load: serializer.method(:load),
|
28
|
-
dump: serializer.method(:dump),
|
29
|
-
}
|
30
|
-
end
|
31
|
-
|
32
|
-
def hash_serializer?
|
33
|
-
return unless serializer.respond_to?(:to_hash)
|
34
|
-
(%i[dump load] - serializer.to_hash.keys).empty?
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
@@ -1,41 +0,0 @@
|
|
1
|
-
module BloodContracts
|
2
|
-
class Suite
|
3
|
-
extend Dry::Initializer
|
4
|
-
|
5
|
-
option :contract, ->(v) { Hashie::Mash.new(v) }
|
6
|
-
option :data_generator, optional: true
|
7
|
-
|
8
|
-
option :input_writer, optional: true
|
9
|
-
option :output_writer, optional: true
|
10
|
-
|
11
|
-
option :input_serializer, optional: true
|
12
|
-
option :output_serializer, optional: true
|
13
|
-
option :meta_serializer, optional: true
|
14
|
-
|
15
|
-
option :storage_backend, optional: true
|
16
|
-
option :storage, default: -> { default_storage }
|
17
|
-
|
18
|
-
def data_generator=(generator)
|
19
|
-
raise ArgumentError unless generator.respond_to?(:call)
|
20
|
-
@data_generator = generator
|
21
|
-
end
|
22
|
-
|
23
|
-
def input_writer=(writer)
|
24
|
-
storage.input_writer = writer
|
25
|
-
end
|
26
|
-
|
27
|
-
def output_writer=(writer)
|
28
|
-
storage.output_writer = writer
|
29
|
-
end
|
30
|
-
|
31
|
-
def default_storage
|
32
|
-
Storage.new(
|
33
|
-
input_writer: input_writer,
|
34
|
-
output_writer: output_writer,
|
35
|
-
input_serializer: input_serializer,
|
36
|
-
output_serializer: output_serializer,
|
37
|
-
meta_serializer: meta_serializer,
|
38
|
-
)
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
data/lib/extensions/string.rb
DELETED
@@ -1,31 +0,0 @@
|
|
1
|
-
class String
|
2
|
-
# See gem Facets String#pathize
|
3
|
-
|
4
|
-
# Transforms a namespace, i.e. a class or module name, into a viable
|
5
|
-
# file path.
|
6
|
-
#
|
7
|
-
# "ExamplePathize".pathize #=> "example_pathize"
|
8
|
-
# "ExamplePathize::Example".pathize #=> "example_pathize/example"
|
9
|
-
#
|
10
|
-
# Compare this method to {String#modulize) and {String#methodize).
|
11
|
-
#
|
12
|
-
def pathize
|
13
|
-
gsub(/([A-Z]+)([A-Z])/,'\1_\2').
|
14
|
-
gsub(/([a-z])([A-Z])/,'\1_\2').
|
15
|
-
gsub('__','/').
|
16
|
-
gsub('::','/').
|
17
|
-
gsub(/\s+/, ''). # spaces are bad form
|
18
|
-
gsub(/[?%*:|"<>.]+/, ''). # reserved characters
|
19
|
-
downcase
|
20
|
-
end
|
21
|
-
|
22
|
-
# Compare to Rails definition:
|
23
|
-
#
|
24
|
-
# gsub(/__/, '/').
|
25
|
-
# gsub(/::/, '/').
|
26
|
-
# gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
27
|
-
# gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
28
|
-
# tr("-", "_").
|
29
|
-
# downcase
|
30
|
-
#
|
31
|
-
end
|
@@ -1,106 +0,0 @@
|
|
1
|
-
module RSpec
|
2
|
-
module MeetContractMatcher
|
3
|
-
extend RSpec::Matchers::DSL
|
4
|
-
Runner = ::BloodContracts::Runner
|
5
|
-
Debugger = ::BloodContracts::Debugger
|
6
|
-
|
7
|
-
matcher :meet_contract_rules do |args|
|
8
|
-
match do |subject|
|
9
|
-
runner = ENV["debug"] ? Debugger : Runner
|
10
|
-
|
11
|
-
@_contract_runner = runner.new(
|
12
|
-
subject,
|
13
|
-
context: self,
|
14
|
-
suite: build_suite(args || subject),
|
15
|
-
iterations: @_iterations,
|
16
|
-
time_to_run: @_time_to_run,
|
17
|
-
stop_on_unexpected: @_halt_on_unexpected,
|
18
|
-
)
|
19
|
-
@_contract_runner.call
|
20
|
-
end
|
21
|
-
|
22
|
-
def build_suite(args)
|
23
|
-
suite = nil
|
24
|
-
if args.respond_to?(:to_contract_suite)
|
25
|
-
suite = args.to_contract_suite(name: _example_name_to_path)
|
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
|
-
)
|
31
|
-
else
|
32
|
-
raise "Matcher arguments is not a Blood Contract"
|
33
|
-
end
|
34
|
-
suite.data_generator = @_generator if @_generator
|
35
|
-
suite
|
36
|
-
end
|
37
|
-
|
38
|
-
def new_storage
|
39
|
-
storage = Storage.new(contract_name: _example_name_to_path)
|
40
|
-
storage.input_writer = _input_writer if _input_writer
|
41
|
-
storage.output_writer = _output_writer if _output_writer
|
42
|
-
if @_input_serializer
|
43
|
-
storage.input_serializer = @_input_serializer
|
44
|
-
end
|
45
|
-
if @_output_serializer
|
46
|
-
storage.output_serializer = @_output_serializer
|
47
|
-
end
|
48
|
-
storage
|
49
|
-
end
|
50
|
-
|
51
|
-
def _example_name_to_path
|
52
|
-
method_missing(:class)
|
53
|
-
.name
|
54
|
-
.sub("RSpec::ExampleGroups::", "")
|
55
|
-
.pathize
|
56
|
-
end
|
57
|
-
|
58
|
-
def _input_writer
|
59
|
-
input_writer = @_writers.to_h[:input]
|
60
|
-
input_writer ||= :input_writer if defined? self.input_writer
|
61
|
-
input_writer
|
62
|
-
end
|
63
|
-
|
64
|
-
def _output_writer
|
65
|
-
output_writer = @_writers.to_h[:output]
|
66
|
-
output_writer ||= :output_writer if defined? self.output_writer
|
67
|
-
output_writer
|
68
|
-
end
|
69
|
-
|
70
|
-
supports_block_expectations
|
71
|
-
|
72
|
-
failure_message { @_contract_runner.failure_message }
|
73
|
-
|
74
|
-
description { @_contract_runner.description }
|
75
|
-
|
76
|
-
chain :using_generator do |generator|
|
77
|
-
if generator.respond_to?(:to_sym)
|
78
|
-
@_generator = method(generator.to_sym)
|
79
|
-
else
|
80
|
-
raise ArgumentError unless generator.respond_to?(:call)
|
81
|
-
@_generator = generator
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
chain :during_n_iterations_run do |iterations|
|
86
|
-
@_iterations = Integer(iterations)
|
87
|
-
end
|
88
|
-
|
89
|
-
chain :during_n_seconds_run do |time_to_run|
|
90
|
-
@_time_to_run = Float(time_to_run)
|
91
|
-
end
|
92
|
-
|
93
|
-
chain :halt_on_unexpected do
|
94
|
-
@_halt_on_unexpected = true
|
95
|
-
end
|
96
|
-
|
97
|
-
chain :serialize_input do |serializer|
|
98
|
-
@_input_serializer = serializer
|
99
|
-
end
|
100
|
-
|
101
|
-
chain :serialize_output do |serializer|
|
102
|
-
@_output_serializer = serializer
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|