suture 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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