delta_test 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +32 -0
  3. data/README.md +63 -14
  4. data/Rakefile +16 -3
  5. data/circle.yml +8 -1
  6. data/delta_test.gemspec +4 -3
  7. data/ext/delta_test/delta_test_native.c +154 -0
  8. data/ext/delta_test/delta_test_native.h +15 -0
  9. data/ext/delta_test/extconf.rb +12 -0
  10. data/lib/delta_test.rb +8 -2
  11. data/lib/delta_test/cli.rb +108 -21
  12. data/lib/delta_test/configuration.rb +91 -35
  13. data/lib/delta_test/dependencies_table.rb +15 -1
  14. data/lib/delta_test/generator.rb +42 -29
  15. data/lib/delta_test/profiler.rb +5 -0
  16. data/lib/delta_test/related_spec_list.rb +67 -8
  17. data/lib/delta_test/spec_helpers.rb +9 -7
  18. data/lib/delta_test/version.rb +1 -1
  19. data/spec/lib/delta_test/cli_spec.rb +26 -5
  20. data/spec/lib/delta_test/configuration_spec.rb +12 -0
  21. data/spec/lib/delta_test/dependencies_table_spec.rb +35 -0
  22. data/spec/lib/delta_test/generator_spec.rb +34 -17
  23. data/spec/lib/delta_test/profiler_spec.rb +121 -0
  24. data/spec/lib/delta_test/related_spec_list_spec.rb +150 -34
  25. data/spec/lib/delta_test/spec_helpers_spec.rb +11 -5
  26. data/spec/rails/Gemfile +8 -2
  27. data/spec/rails/Gemfile.lock +37 -3
  28. data/spec/rails/app/models/category.rb +14 -0
  29. data/spec/rails/app/models/comment.rb +20 -0
  30. data/spec/rails/app/models/post.rb +23 -0
  31. data/spec/rails/app/models/post_categorizing.rb +14 -0
  32. data/spec/rails/app/models/user.rb +15 -0
  33. data/spec/rails/db/migrate/20150518052022_create_users.rb +9 -0
  34. data/spec/rails/db/migrate/20150518052057_create_posts.rb +11 -0
  35. data/spec/rails/db/migrate/20150518052332_create_comments.rb +11 -0
  36. data/spec/rails/db/migrate/20150518052523_create_categories.rb +9 -0
  37. data/spec/rails/db/migrate/20150518052604_create_post_categorizings.rb +10 -0
  38. data/spec/rails/db/schema.rb +59 -0
  39. data/spec/rails/spec/factories/categories.rb +5 -0
  40. data/spec/rails/spec/factories/comments.rb +8 -0
  41. data/spec/rails/spec/factories/post_categorizings.rb +6 -0
  42. data/spec/rails/spec/factories/posts.rb +7 -0
  43. data/spec/rails/spec/factories/users.rb +5 -0
  44. data/spec/rails/spec/models/category_spec.rb +3 -0
  45. data/spec/rails/spec/models/comment_spec.rb +3 -0
  46. data/spec/rails/spec/models/post_categorizing_spec.rb +3 -0
  47. data/spec/rails/spec/models/post_spec.rb +3 -0
  48. data/spec/rails/spec/models/user_spec.rb +20 -0
  49. data/spec/rails/spec/spec_helper.rb +53 -9
  50. data/spec/spec_helper.rb +2 -0
  51. metadata +79 -19
  52. data/lib/delta_test/analyzer.rb +0 -47
  53. data/spec/lib/delta_test/analyzer_spec.rb +0 -126
@@ -1,5 +1,7 @@
1
1
  require 'open3'
2
2
  require 'shellwords'
3
+ require 'thread'
4
+ require 'thwait'
3
5
 
4
6
  require_relative 'related_spec_list'
5
7
 
@@ -12,6 +14,10 @@ module DeltaTest
12
14
  'verbose' => false,
13
15
  }.freeze
14
16
 
17
+ BUNDLE_EXEC = ['bundle', 'exec'].freeze
18
+
19
+ SPLITTER = '--'
20
+
15
21
  attr_reader *%i[
16
22
  args
17
23
  command
@@ -54,6 +60,8 @@ module DeltaTest
54
60
  do_table
55
61
  when 'exec'
56
62
  do_exec
63
+ when 'clear'
64
+ do_clear
57
65
  when '-v', '--version'
58
66
  do_version
59
67
  else
@@ -111,8 +119,8 @@ module DeltaTest
111
119
  #
112
120
  # @return {Boolean}
113
121
  ###
114
- def run_full_tests?
115
- Git.same_commit?(@options['base'], @options['head'])
122
+ def profile_mode?
123
+ @profile_mode ||= Git.same_commit?(@options['base'], @options['head'])
116
124
  end
117
125
 
118
126
  ###
@@ -148,24 +156,53 @@ module DeltaTest
148
156
  spec_files = nil
149
157
  args = []
150
158
 
151
- if run_full_tests?
152
- args << ('%s=%s' % [VERBOSE_FLAG, true]) if DeltaTest.verbose?
153
- args << ('%s=%s' % [ACTIVE_FLAG, true])
154
- else
155
- args << 'cat'
156
- args << '|'
157
- args << 'xargs'
159
+ begin
160
+ unless profile_mode?
161
+ @list.load_table!
162
+ @list.retrive_changed_files!(@options['base'], @options['head'])
163
+
164
+ spec_files = @list.related_spec_files.to_a
165
+
166
+ if spec_files.empty?
167
+ exit_with_message(0, 'Nothing to test')
168
+ end
169
+ end
170
+ rescue TableNotFoundError
171
+ # force profile mode cuz we don't have a table
172
+ @profile_mode = true
173
+ end
158
174
 
159
- @list.load_table!
160
- @list.retrive_changed_files!(@options['base'], @options['head'])
175
+ @args.map! { |arg| Shellwords.escape(arg) }
161
176
 
162
- spec_files = @list.related_spec_files.to_a
177
+ if (splitter = @args.index(SPLITTER))
178
+ files = @args.drop(splitter + 1)
179
+ @args = @args.take(splitter)
163
180
 
164
- if spec_files.empty?
165
- exit_with_message(0, 'Nothing to test')
181
+ if files && files.any?
182
+ if spec_files
183
+ pattern = files.map { |file| Regexp.escape(file) }
184
+ pattern = '^(%s)' % pattern.join('|')
185
+ spec_files = spec_files.grep(pattern)
186
+ else
187
+ spec_files = files
188
+ end
166
189
  end
167
190
  end
168
191
 
192
+ if profile_mode?
193
+ args << ('%s=%s' % [VERBOSE_FLAG, true]) if DeltaTest.verbose?
194
+ args << ('%s=%s' % [ACTIVE_FLAG, true])
195
+ end
196
+
197
+ if spec_files
198
+ args.unshift('cat', '|')
199
+ args << 'xargs'
200
+ end
201
+
202
+ if bundler_enabled? && BUNDLE_EXEC != @args.take(2)
203
+ args += BUNDLE_EXEC
204
+ end
205
+
169
206
  args += @args
170
207
  args = args.join(' ')
171
208
 
@@ -174,11 +211,32 @@ module DeltaTest
174
211
  Open3.popen3(args) do |i, o, e, w|
175
212
  i.write(spec_files.join("\n")) if spec_files
176
213
  i.close
177
- o.each { |l| puts l }
178
- e.each { |l| $stderr.puts l }
214
+
215
+ threads = []
216
+ threads << Thread.new { o.each { |l| puts l } }
217
+ threads << Thread.new { e.each { |l| $stderr.puts l } }
218
+
219
+ ThreadsWait.all_waits(*threads)
220
+ exit w.value.exitstatus
179
221
  end
180
222
  end
181
223
 
224
+ ###
225
+ # Clean up tables and caches
226
+ ###
227
+ def do_clear
228
+ table_file_path = DeltaTest.config.table_file_path('')
229
+
230
+ return unless table_file_path
231
+
232
+ args = [
233
+ 'rm',
234
+ '%s*' % Shellwords.escape(table_file_path),
235
+ ]
236
+
237
+ Open3.capture3(args.join(' ')) rescue nil
238
+ end
239
+
182
240
  ###
183
241
  # Show version
184
242
  ###
@@ -194,9 +252,9 @@ module DeltaTest
194
252
  puts "Command not found: #{@command}"
195
253
  end
196
254
 
197
- puts <<-HELP
255
+ puts <<HELP
198
256
  usage: delta_test <command> [--base=<base>] [--head=<head>] [--verbose] [<args>]
199
- [-v]
257
+ [-v|--version]
200
258
 
201
259
  options:
202
260
  --base=<base> A branch or a commit id to diff from.
@@ -215,9 +273,38 @@ commands:
215
273
 
216
274
  table Show dependencies table.
217
275
 
218
- exec <script> Rxecute test script using delta_test.
219
- Run command something like `delta_test list | xargs script'.
220
- HELP
276
+ exec <script> [-- <files>]
277
+ Execute test script using delta_test.
278
+ if <base> and <head> is the same commit or no dependencies table is found,
279
+ it'll run full test cases with a profile mode to create a table.
280
+ Otherwise, it'll run test script with only related spec files
281
+ passed by its arguments, like `delta_test list | xargs script'.
282
+
283
+ clear Clean up tables and caches.
284
+ HELP
285
+ end
286
+
287
+
288
+ private
289
+
290
+ ###
291
+ # Check bundler existance
292
+ #
293
+ # @see http://github.com/carlhuda/bundler Bundler::SharedHelpers#find_gemfile
294
+ ###
295
+ def bundler_enabled?
296
+ return true if Object.const_defined?(:Bundler)
297
+
298
+ previous = nil
299
+ current = File.expand_path(Dir.pwd)
300
+
301
+ until !File.directory?(current) || current == previous
302
+ filename = File.join(current, 'Gemfile')
303
+ return true if File.exist?(filename)
304
+ current, previous = File.expand_path('..', current), current
305
+ end
306
+
307
+ false
221
308
  end
222
309
 
223
310
  end
@@ -8,11 +8,46 @@ require_relative 'utils'
8
8
  module DeltaTest
9
9
  class Configuration
10
10
 
11
+ module Validator
12
+
13
+ def self.included(base)
14
+ base.include(InstanceMethods)
15
+ base.extend(ClassMethods)
16
+ end
17
+
18
+ module ClassMethods
19
+
20
+ def _validators
21
+ @_validators ||= []
22
+ end
23
+
24
+ def validate(attr, message, &block)
25
+ _validators << [attr, message, block]
26
+ end
27
+
28
+ end
29
+
30
+ module InstanceMethods
31
+
32
+ def validate!
33
+ self.class._validators.each do |attr, message, block|
34
+ raise ValidationError.new(attr, message) unless self.instance_eval(&block)
35
+ end
36
+ end
37
+
38
+ end
39
+
40
+ end
41
+
42
+ include Validator
43
+
11
44
  CONFIG_FILES = [
12
45
  'delta_test.yml',
13
46
  'delta_test.yaml',
14
47
  ].freeze
15
48
 
49
+ PART_FILE_EXT = '.part-%s'
50
+
16
51
  attr_accessor *%i[
17
52
  base_path
18
53
  files
@@ -20,6 +55,7 @@ module DeltaTest
20
55
  table_file
21
56
  patterns
22
57
  exclude_patterns
58
+ full_test_patterns
23
59
  custom_mappings
24
60
  ]
25
61
 
@@ -29,14 +65,43 @@ module DeltaTest
29
65
  table_file_path
30
66
  ]
31
67
 
68
+ validate :base_path, 'need to be an absolute path' do
69
+ self.base_path.absolute?
70
+ end
71
+
72
+ validate :files, 'need to be an array' do
73
+ self.files.is_a?(Array)
74
+ end
75
+
76
+ validate :patterns, 'need to be an array' do
77
+ self.patterns.is_a?(Array)
78
+ end
79
+
80
+ validate :exclude_patterns, 'need to be an array' do
81
+ self.exclude_patterns.is_a?(Array)
82
+ end
83
+
84
+ validate :full_test_patterns, 'need to be an array' do
85
+ self.full_test_patterns.is_a?(Array)
86
+ end
87
+
88
+ validate :custom_mappings, 'need to be a hash' do
89
+ self.custom_mappings.is_a?(Hash)
90
+ end
91
+
92
+ validate :custom_mappings, 'need to have an array in the contents' do
93
+ self.custom_mappings.values.all? { |v| v.is_a?(Array) }
94
+ end
95
+
32
96
  def initialize
33
97
  update do |c|
34
- c.base_path = File.expand_path('.')
35
- c.table_file = 'tmp/.delta_test_dt'
36
- c.files = []
37
- c.patterns = []
38
- c.exclude_patterns = []
39
- c.custom_mappings = {}
98
+ c.base_path = File.expand_path('.')
99
+ c.table_file = 'tmp/.delta_test_dt'
100
+ c.files = []
101
+ c.patterns = []
102
+ c.exclude_patterns = []
103
+ c.full_test_patterns = []
104
+ c.custom_mappings = {}
40
105
  end
41
106
  end
42
107
 
@@ -64,6 +129,26 @@ module DeltaTest
64
129
  end
65
130
 
66
131
 
132
+ # Override getters
133
+ #-----------------------------------------------
134
+ ###
135
+ # Returns file path for the table
136
+ #
137
+ # @params {String} part
138
+ #
139
+ # @return {Pathname}
140
+ ###
141
+ def table_file_path(part = nil)
142
+ return unless @table_file_path
143
+
144
+ if part
145
+ @table_file_path.sub_ext(PART_FILE_EXT % part)
146
+ else
147
+ @table_file_path
148
+ end
149
+ end
150
+
151
+
67
152
  # Update
68
153
  #-----------------------------------------------
69
154
  ###
@@ -77,35 +162,6 @@ module DeltaTest
77
162
  precalculate!
78
163
  end
79
164
 
80
- ###
81
- # Validate option values
82
- ###
83
- def validate!
84
- if self.base_path.relative?
85
- raise ValidationError.new(:base_path, 'need to be an absolute path')
86
- end
87
-
88
- unless self.files.is_a?(Array)
89
- raise ValidationError.new(:files, 'need to be an array')
90
- end
91
-
92
- unless self.patterns.is_a?(Array)
93
- raise ValidationError.new(:patterns, 'need to be an array')
94
- end
95
-
96
- unless self.exclude_patterns.is_a?(Array)
97
- raise ValidationError.new(:exclude_patterns, 'need to be an array')
98
- end
99
-
100
- unless self.custom_mappings.is_a?(Hash)
101
- raise ValidationError.new(:custom_mappings, 'need to be a hash')
102
-
103
- unless self.custom_mappings.values.all? { |v| v.is_a?(Array) }
104
- raise ValidationError.new(:custom_mappings, 'need to have an array in the contents')
105
- end
106
- end
107
- end
108
-
109
165
  ###
110
166
  # Precalculate some values
111
167
  ###
@@ -41,6 +41,21 @@ module DeltaTest
41
41
  self[spec_file] << source_file if DeltaTest.config.filtered_files.include?(source_file)
42
42
  end
43
43
 
44
+ ###
45
+ # Reverse merge other table
46
+ #
47
+ # @params {DependenciesTable} other
48
+ ###
49
+ def reverse_merge!(other)
50
+ raise TypeError unless other.is_a?(self.class)
51
+
52
+ other.each do |spec_file, source_files|
53
+ self[spec_file] |= source_files
54
+ end
55
+
56
+ nil
57
+ end
58
+
44
59
  ###
45
60
  # Temporary disable default_proc
46
61
  # Because Marshal can't dump Hash with default_proc
@@ -70,7 +85,6 @@ module DeltaTest
70
85
  # @params {String|Pathname} file
71
86
  ###
72
87
  def dump(file)
73
- # Marshal can't dump hash with default proc
74
88
  without_default_proc do
75
89
  cleanup!
76
90
  data = Marshal.dump(self)
@@ -1,4 +1,6 @@
1
- require_relative 'analyzer'
1
+ require 'singleton'
2
+
3
+ require_relative 'profiler'
2
4
  require_relative 'dependencies_table'
3
5
 
4
6
  require_relative 'utils'
@@ -12,11 +14,9 @@ module DeltaTest
12
14
  ]
13
15
 
14
16
  ###
15
- # Setup analyzer and table
16
- #
17
- # @params {Boolean} _auto_teardown
17
+ # Setup table
18
18
  ###
19
- def setup!(_auto_teardown = true)
19
+ def setup!
20
20
  return unless DeltaTest.active?
21
21
 
22
22
  return if @_setup
@@ -24,16 +24,13 @@ module DeltaTest
24
24
 
25
25
  DeltaTest.log('--- setup!')
26
26
 
27
- @analyzer = Analyzer.new
28
- @table = DependenciesTable.load(DeltaTest.config.table_file_path)
27
+ @table = DependenciesTable.load(DeltaTest.config.table_file_path)
29
28
 
30
29
  @current_spec_file = nil
31
-
32
- hook_on_exit { teardown! } if _auto_teardown
33
30
  end
34
31
 
35
32
  ###
36
- # Start analyzer for the spec file
33
+ # Start profiler for the spec file
37
34
  #
38
35
  # @params {String} spec_file
39
36
  ###
@@ -43,27 +40,30 @@ module DeltaTest
43
40
  DeltaTest.log('--- start!(%s)' % spec_file)
44
41
 
45
42
  @current_spec_file = Utils.regulate_filepath(spec_file, DeltaTest.config.base_path).to_s
46
- @analyzer.start
43
+
44
+ Profiler.start!
47
45
  end
48
46
 
49
47
  ###
50
- # Stop analyzer and update table
48
+ # Stop profiler and update table
51
49
  ###
52
50
  def stop!
53
51
  return unless DeltaTest.active?
54
52
 
53
+ Profiler.stop!
54
+
55
55
  DeltaTest.log('--- stop!')
56
56
 
57
57
  spec_file = @current_spec_file
58
58
  @current_spec_file = nil
59
59
 
60
- @analyzer.stop
61
-
62
60
  if spec_file
63
- @analyzer.related_source_files.each do |file|
61
+ Profiler.last_result.each do |file|
64
62
  @table.add(spec_file, file)
65
63
  end
66
64
  end
65
+
66
+ DeltaTest::Profiler.clean!
67
67
  end
68
68
 
69
69
  ###
@@ -76,26 +76,39 @@ module DeltaTest
76
76
 
77
77
  DeltaTest.log('--- teardown!')
78
78
 
79
- @analyzer.stop
80
- @table.dump(DeltaTest.config.table_file_path)
81
- end
79
+ Profiler.clean!
82
80
 
81
+ if defined?(ParallelTests)
82
+ if ParallelTests.first_process?
83
+ ParallelTests.wait_for_other_processes_to_finish
83
84
 
84
- private
85
+ table_file_path = DeltaTest.config.table_file_path('*')
85
86
 
86
- ###
87
- # Handle exit event
88
- ###
89
- def hook_on_exit(&block)
90
- at_exit do
91
- if defined?(ParallelTests)
92
- break unless ParallelTests.first_process?
93
- ParallelTests.wait_for_other_processes_to_finish
94
- end
87
+ Dir.glob(table_file_path).each do |part_file|
88
+ part_table = DependenciesTable.load(part_file)
89
+ @table.reverse_merge!(part_table)
90
+ end
91
+ else
92
+ table_file_path = DeltaTest.config.table_file_path(ENV['TEST_ENV_NUMBER'])
95
93
 
96
- block.call
94
+ @table.dump(table_file_path)
95
+ return
96
+ end
97
97
  end
98
+
99
+ @table.dump(DeltaTest.config.table_file_path)
98
100
  end
99
101
 
102
+ ###
103
+ # Hook teardown! on exit
104
+ ###
105
+ def hook_on_exit
106
+ at_exit { teardown! }
107
+ end
108
+
109
+ end
110
+
111
+ class GeneratorSingleton < Generator
112
+ include Singleton
100
113
  end
101
114
  end