suture 0.2.0 → 0.3.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/.codeclimate.yml +28 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +1156 -0
- data/.travis.yml +4 -0
- data/README.md +143 -9
- data/Rakefile +22 -1
- data/lib/suture/adapter/dictaphone.rb +41 -23
- data/lib/suture/adapter/log.rb +31 -0
- data/lib/suture/builds_plan.rb +2 -2
- data/lib/suture/chooses_surgeon.rb +10 -0
- data/lib/suture/comparator.rb +7 -0
- data/lib/suture/error/observation_conflict.rb +26 -13
- data/lib/suture/error/verification_failed.rb +160 -5
- data/lib/suture/interprets_results.rb +2 -2
- data/lib/suture/prescribes_test_plan.rb +4 -4
- data/lib/suture/surgeon/observer.rb +11 -1
- data/lib/suture/tests_patient.rb +31 -7
- data/lib/suture/util/env.rb +9 -4
- data/lib/suture/util/shuffle.rb +13 -0
- data/lib/suture/util/timer.rb +14 -0
- data/lib/suture/value/plan.rb +2 -1
- data/lib/suture/value/test_plan.rb +35 -6
- data/lib/suture/value/test_results.rb +5 -1
- data/lib/suture/version.rb +1 -1
- data/lib/suture/wrap/logger.rb +40 -0
- data/lib/suture/wrap/sqlite.rb +8 -1
- data/lib/suture.rb +24 -2
- data/suture.gemspec +8 -1
- metadata +65 -2
@@ -4,10 +4,20 @@ module Suture::Surgeon
|
|
4
4
|
class Observer
|
5
5
|
def operate(plan)
|
6
6
|
dictaphone = Suture::Adapter::Dictaphone.new(plan)
|
7
|
-
|
7
|
+
invoke(plan).tap do |result|
|
8
8
|
dictaphone.record(result)
|
9
9
|
end
|
10
10
|
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def invoke(plan)
|
15
|
+
if plan.args
|
16
|
+
plan.old.call(*plan.args)
|
17
|
+
else
|
18
|
+
plan.old.call
|
19
|
+
end
|
20
|
+
end
|
11
21
|
end
|
12
22
|
end
|
13
23
|
|
data/lib/suture/tests_patient.rb
CHANGED
@@ -1,13 +1,19 @@
|
|
1
1
|
require "suture/adapter/dictaphone"
|
2
2
|
require "suture/value/test_results"
|
3
|
+
require "suture/util/timer"
|
4
|
+
require "suture/util/shuffle"
|
5
|
+
require "backports/1.9.2/random"
|
3
6
|
|
4
7
|
module Suture
|
5
8
|
class TestsPatient
|
6
9
|
def test(test_plan)
|
7
|
-
dictaphone = Suture::Adapter::Dictaphone.new(test_plan)
|
8
10
|
experienced_failure_in_life = false
|
9
|
-
|
10
|
-
|
11
|
+
timer = Suture::Util::Timer.new(test_plan.time_limit) unless test_plan.time_limit.nil?
|
12
|
+
test_cases = build_test_cases(test_plan)
|
13
|
+
Value::TestResults.new(test_cases.each_with_index.map { |observation, i|
|
14
|
+
if (test_plan.fail_fast && experienced_failure_in_life) ||
|
15
|
+
(test_plan.call_limit && i >= test_plan.call_limit) ||
|
16
|
+
(timer && timer.time_up?)
|
11
17
|
{
|
12
18
|
:observation => observation,
|
13
19
|
:ran => false
|
@@ -16,17 +22,35 @@ module Suture
|
|
16
22
|
invoke(test_plan, observation).merge({
|
17
23
|
:observation => observation,
|
18
24
|
:ran => true
|
19
|
-
}).tap { |r| experienced_failure_in_life = true unless r[:passed]}
|
25
|
+
}).tap { |r| experienced_failure_in_life = true unless r[:passed] }
|
20
26
|
end
|
21
27
|
})
|
22
28
|
end
|
23
29
|
|
30
|
+
private
|
31
|
+
|
32
|
+
def build_test_cases(test_plan)
|
33
|
+
dictaphone = Suture::Adapter::Dictaphone.new(test_plan)
|
34
|
+
shuffle(
|
35
|
+
dictaphone.play(test_plan.verify_only),
|
36
|
+
test_plan.random_seed
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
def shuffle(rows, random_seed)
|
41
|
+
return rows unless random_seed
|
42
|
+
Suture::Util::Shuffle.shuffle(Random.new(random_seed), rows)
|
43
|
+
end
|
44
|
+
|
24
45
|
def invoke(test_plan, observation)
|
25
46
|
{}.tap do |result|
|
26
47
|
begin
|
27
|
-
result[:new_result] =
|
28
|
-
|
29
|
-
|
48
|
+
result[:new_result] = if observation.args
|
49
|
+
test_plan.subject.call(*observation.args)
|
50
|
+
else
|
51
|
+
test_plan.subject.call
|
52
|
+
end
|
53
|
+
result[:passed] = test_plan.comparator.call(observation.result, result[:new_result])
|
30
54
|
rescue StandardError => e
|
31
55
|
result[:passed] = false
|
32
56
|
result[:error] = e
|
data/lib/suture/util/env.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
module Suture::Util
|
2
2
|
module Env
|
3
|
-
def self.to_map(excludes)
|
3
|
+
def self.to_map(excludes = {})
|
4
4
|
Hash[
|
5
5
|
ENV.keys.
|
6
6
|
select { |k| k.start_with?("SUTURE_") }.
|
7
7
|
map { |k| [to_sym(k), sanitize_value(ENV[k])] }
|
8
|
-
].reject { |(k,
|
8
|
+
].reject { |(k,_)| excludes.include?(k) }
|
9
9
|
end
|
10
10
|
|
11
11
|
# private
|
@@ -15,8 +15,13 @@ module Suture::Util
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def self.sanitize_value(value)
|
18
|
-
|
19
|
-
|
18
|
+
if value == "false"
|
19
|
+
false
|
20
|
+
elsif value == "true"
|
21
|
+
true
|
22
|
+
else
|
23
|
+
value
|
24
|
+
end
|
20
25
|
end
|
21
26
|
end
|
22
27
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Suture::Util
|
2
|
+
module Shuffle
|
3
|
+
def self.shuffle(random, unshuffled_array)
|
4
|
+
old_array = unshuffled_array.dup
|
5
|
+
new_array = []
|
6
|
+
while new_array.size < unshuffled_array.size
|
7
|
+
index = random.rand(old_array.size)
|
8
|
+
new_array << old_array.delete_at(index)
|
9
|
+
end
|
10
|
+
return new_array
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/suture/value/plan.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
module Suture::Value
|
2
2
|
class Plan
|
3
|
-
attr_reader :name, :old, :new, :args, :record_calls, :database_path
|
3
|
+
attr_reader :name, :old, :new, :args, :record_calls, :comparator, :database_path
|
4
4
|
def initialize(attrs = {})
|
5
5
|
@name = attrs[:name]
|
6
6
|
@old = attrs[:old]
|
7
7
|
@new = attrs[:new]
|
8
8
|
@args = attrs[:args]
|
9
9
|
@record_calls = !!attrs[:record_calls]
|
10
|
+
@comparator = attrs[:comparator]
|
10
11
|
@database_path = attrs[:database_path]
|
11
12
|
end
|
12
13
|
end
|
@@ -1,12 +1,41 @@
|
|
1
1
|
module Suture::Value
|
2
2
|
class TestPlan
|
3
|
-
attr_accessor :name, :subject,
|
3
|
+
attr_accessor :name, :subject,
|
4
|
+
:verify_only, :fail_fast, :call_limit, :time_limit,
|
5
|
+
:error_message_limit, :random_seed,
|
6
|
+
:comparator, :database_path
|
4
7
|
def initialize(attrs = {})
|
5
|
-
|
6
|
-
|
7
|
-
@
|
8
|
-
|
9
|
-
|
8
|
+
assign_simple_ivars!(attrs, :name, :subject, :fail_fast, :comparator, :database_path)
|
9
|
+
assign_integral_ivars(attrs, :verify_only, :call_limit, :time_limit, :error_message_limit)
|
10
|
+
@random_seed = determine_random_seed(attrs)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def assign_simple_ivars!(attrs, *names)
|
16
|
+
names.each do |name|
|
17
|
+
instance_variable_set("@#{name}", attrs[name])
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def assign_integral_ivars(attrs, *names)
|
22
|
+
assign_simple_ivars!(
|
23
|
+
Hash[attrs.select {|(k,_)| names.include?(k) }.
|
24
|
+
map { |(k,v)| [k, v.nil? ? nil : v.to_i]}],
|
25
|
+
*names
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
def determine_random_seed(attrs)
|
30
|
+
if attrs.has_key?(:random_seed)
|
31
|
+
if attrs[:random_seed].nil? || attrs[:random_seed] == "nil"
|
32
|
+
nil
|
33
|
+
else
|
34
|
+
attrs[:random_seed].to_i
|
35
|
+
end
|
36
|
+
else
|
37
|
+
rand(99999)
|
38
|
+
end
|
10
39
|
end
|
11
40
|
end
|
12
41
|
end
|
@@ -22,8 +22,12 @@ module Suture::Value
|
|
22
22
|
@results.count { |r| r[:passed] }
|
23
23
|
end
|
24
24
|
|
25
|
+
def failed
|
26
|
+
@results.select { |r| !r[:passed] && r[:ran] }
|
27
|
+
end
|
28
|
+
|
25
29
|
def failed_count
|
26
|
-
|
30
|
+
failed.size
|
27
31
|
end
|
28
32
|
|
29
33
|
def errored_count
|
data/lib/suture/version.rb
CHANGED
@@ -0,0 +1,40 @@
|
|
1
|
+
require "logger"
|
2
|
+
|
3
|
+
module Suture::Wrap
|
4
|
+
module Logger
|
5
|
+
def self.init(options)
|
6
|
+
if options[:log_file]
|
7
|
+
full_path = File.join(Dir.getwd, options[:log_file])
|
8
|
+
FileUtils.mkdir_p(File.dirname(full_path))
|
9
|
+
@logger = ::Logger.new(full_path)
|
10
|
+
elsif options[:log_stdout]
|
11
|
+
@logger = ::Logger.new(STDOUT)
|
12
|
+
else
|
13
|
+
@logger = NullLogger.new
|
14
|
+
end
|
15
|
+
|
16
|
+
@logger.level = if options[:log_level]
|
17
|
+
::Logger.const_get(options[:log_level])
|
18
|
+
else
|
19
|
+
::Logger::INFO
|
20
|
+
end
|
21
|
+
|
22
|
+
@logger.formatter = proc { |_, time , _, msg|
|
23
|
+
formatted_time = time.strftime("%Y-%m-%dT%H:%M:%S.%6N")
|
24
|
+
"[#{formatted_time}] Suture: #{msg}\n".tap { |out|
|
25
|
+
puts out if options[:log_file] && options[:log_stdout]
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
return @logger
|
30
|
+
end
|
31
|
+
|
32
|
+
class NullLogger
|
33
|
+
def formatter=; end
|
34
|
+
def level=; end
|
35
|
+
def debug; end
|
36
|
+
def info; end
|
37
|
+
def warn; end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/suture/wrap/sqlite.rb
CHANGED
@@ -43,7 +43,14 @@ module Suture::Wrap
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def self.select(db, table, where_clause, bind_params)
|
46
|
-
db.execute(
|
46
|
+
db.execute(
|
47
|
+
"select * from #{table} #{where_clause} order by id asc",
|
48
|
+
bind_params
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.delete(db, table, where_clause, bind_params)
|
53
|
+
db.execute("delete from #{table} #{where_clause}", bind_params)
|
47
54
|
end
|
48
55
|
end
|
49
56
|
end
|
data/lib/suture.rb
CHANGED
@@ -8,9 +8,16 @@ require "suture/prescribes_test_plan"
|
|
8
8
|
require "suture/tests_patient"
|
9
9
|
require "suture/interprets_results"
|
10
10
|
|
11
|
+
require "suture/adapter/log"
|
12
|
+
require "suture/comparator"
|
13
|
+
|
11
14
|
module Suture
|
12
15
|
DEFAULT_OPTIONS = {
|
13
|
-
:database_path => "db/suture.sqlite3"
|
16
|
+
:database_path => "db/suture.sqlite3",
|
17
|
+
:comparator => Comparator.new,
|
18
|
+
:log_level => "INFO",
|
19
|
+
:log_file => nil,
|
20
|
+
:log_stdout => true
|
14
21
|
}
|
15
22
|
|
16
23
|
def self.create(name, options)
|
@@ -23,7 +30,22 @@ module Suture
|
|
23
30
|
test_plan = Suture::PrescribesTestPlan.new.prescribe(name, options)
|
24
31
|
test_results = Suture::TestsPatient.new.test(test_plan)
|
25
32
|
if test_results.failed?
|
26
|
-
Suture::InterpretsResults.new.interpret(test_results)
|
33
|
+
Suture::InterpretsResults.new.interpret(test_plan, test_results)
|
27
34
|
end
|
28
35
|
end
|
36
|
+
|
37
|
+
def self.delete(id, options = {})
|
38
|
+
plan = BuildsPlan.new.build(name, options)
|
39
|
+
Suture::Adapter::Dictaphone.new(plan).delete(id)
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.config(config = {})
|
43
|
+
@config ||= DEFAULT_OPTIONS.dup
|
44
|
+
@config.merge!(config)
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.reset!
|
48
|
+
@config = nil
|
49
|
+
Adapter::Log.reset!
|
50
|
+
end
|
29
51
|
end
|
data/suture.gemspec
CHANGED
@@ -13,16 +13,23 @@ Gem::Specification.new do |spec|
|
|
13
13
|
spec.description = %q{Provides tools to record calls to legacy code and verify new implementations still work}
|
14
14
|
spec.homepage = "https://github.com/testdouble/suture"
|
15
15
|
|
16
|
-
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|db|safe|spec|features)/}) }
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|db|log|safe|spec|features)/}) }
|
17
17
|
spec.bindir = "exe"
|
18
18
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
21
|
spec.add_dependency "sqlite3"
|
22
|
+
spec.add_dependency "backports"
|
22
23
|
|
23
24
|
spec.add_development_dependency "bundler", "~> 1.12"
|
24
25
|
spec.add_development_dependency "rake", "~> 10.0"
|
25
26
|
spec.add_development_dependency "pry", "~> 0.9.12.6"
|
26
27
|
spec.add_development_dependency "minitest", "~> 5.9"
|
27
28
|
spec.add_development_dependency "gimme", "~> 0.5"
|
29
|
+
spec.add_development_dependency "github_changelog_generator"
|
30
|
+
|
31
|
+
if Gem.ruby_version >= Gem::Version.new("1.9.3")
|
32
|
+
spec.add_development_dependency "codeclimate-test-reporter"
|
33
|
+
spec.add_development_dependency "simplecov", "~> 0.11.2" #<--only here to lock
|
34
|
+
end
|
28
35
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: suture
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Searls
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-08-
|
11
|
+
date: 2016-08-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sqlite3
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: backports
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: bundler
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -94,6 +108,48 @@ dependencies:
|
|
94
108
|
- - "~>"
|
95
109
|
- !ruby/object:Gem::Version
|
96
110
|
version: '0.5'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: github_changelog_generator
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: codeclimate-test-reporter
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: simplecov
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: 0.11.2
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: 0.11.2
|
97
153
|
description: Provides tools to record calls to legacy code and verify new implementations
|
98
154
|
still work
|
99
155
|
email:
|
@@ -102,8 +158,10 @@ executables: []
|
|
102
158
|
extensions: []
|
103
159
|
extra_rdoc_files: []
|
104
160
|
files:
|
161
|
+
- ".codeclimate.yml"
|
105
162
|
- ".gitignore"
|
106
163
|
- ".projections.json"
|
164
|
+
- ".rubocop.yml"
|
107
165
|
- ".travis.yml"
|
108
166
|
- Gemfile
|
109
167
|
- LICENSE.txt
|
@@ -113,8 +171,10 @@ files:
|
|
113
171
|
- bin/setup
|
114
172
|
- lib/suture.rb
|
115
173
|
- lib/suture/adapter/dictaphone.rb
|
174
|
+
- lib/suture/adapter/log.rb
|
116
175
|
- lib/suture/builds_plan.rb
|
117
176
|
- lib/suture/chooses_surgeon.rb
|
177
|
+
- lib/suture/comparator.rb
|
118
178
|
- lib/suture/error/observation_conflict.rb
|
119
179
|
- lib/suture/error/schema_version.rb
|
120
180
|
- lib/suture/error/verification_failed.rb
|
@@ -125,11 +185,14 @@ files:
|
|
125
185
|
- lib/suture/surgeon/observer.rb
|
126
186
|
- lib/suture/tests_patient.rb
|
127
187
|
- lib/suture/util/env.rb
|
188
|
+
- lib/suture/util/shuffle.rb
|
189
|
+
- lib/suture/util/timer.rb
|
128
190
|
- lib/suture/value/observation.rb
|
129
191
|
- lib/suture/value/plan.rb
|
130
192
|
- lib/suture/value/test_plan.rb
|
131
193
|
- lib/suture/value/test_results.rb
|
132
194
|
- lib/suture/version.rb
|
195
|
+
- lib/suture/wrap/logger.rb
|
133
196
|
- lib/suture/wrap/sqlite.rb
|
134
197
|
- suture.gemspec
|
135
198
|
homepage: https://github.com/testdouble/suture
|