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.
@@ -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
- plan.old.call(*plan.args).tap do |result|
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
 
@@ -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
- Value::TestResults.new(dictaphone.play.map { |observation|
10
- if test_plan.fail_fast && experienced_failure_in_life
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] = test_plan.subject.call(*observation.args)
28
- # TODO: Comparators go here:
29
- result[:passed] = result[:new_result] == observation.result
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
@@ -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,v)| excludes.include?(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
- return false if value == "false"
19
- value
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
@@ -0,0 +1,14 @@
1
+ require "time"
2
+
3
+ module Suture::Util
4
+ class Timer
5
+ def initialize(limit_in_seconds)
6
+ @started_at = Time.new
7
+ @limit_in_seconds = limit_in_seconds
8
+ end
9
+
10
+ def time_up?
11
+ Time.new >= (@started_at + @limit_in_seconds)
12
+ end
13
+ end
14
+ end
@@ -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, :args, :fail_fast, :database_path
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
- @name = attrs[:name]
6
- @subject = attrs[:subject]
7
- @args = attrs[:args]
8
- @fail_fast = attrs[:fail_fast]
9
- @database_path = attrs[:database_path]
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
- @results.count { |r| !r[:passed] && r[:ran] }
30
+ failed.size
27
31
  end
28
32
 
29
33
  def errored_count
@@ -1,3 +1,3 @@
1
1
  module Suture
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -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
@@ -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("select * from #{table} #{where_clause}", bind_params)
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.2.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-21 00:00:00.000000000 Z
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