delta_test 0.2.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/README.md +22 -34
  4. data/Rakefile +5 -2
  5. data/bin/delta_test +1 -1
  6. data/circle.yml +5 -1
  7. data/delta_test.gemspec +1 -1
  8. data/lib/delta_test/cli/command_base.rb +114 -0
  9. data/lib/delta_test/cli/exec_command.rb +95 -0
  10. data/lib/delta_test/cli/help_command.rb +38 -0
  11. data/lib/delta_test/cli/specs_command.rb +43 -0
  12. data/lib/delta_test/cli/stats_clean_command.rb +20 -0
  13. data/lib/delta_test/cli/stats_save_command.rb +67 -0
  14. data/lib/delta_test/cli/stats_show_command.rb +46 -0
  15. data/lib/delta_test/cli/version_command.rb +13 -0
  16. data/lib/delta_test/cli.rb +22 -296
  17. data/lib/delta_test/configuration.rb +57 -42
  18. data/lib/delta_test/errors.rb +24 -0
  19. data/lib/delta_test/generator.rb +4 -24
  20. data/lib/delta_test/git.rb +161 -80
  21. data/lib/delta_test/profiler.rb +8 -0
  22. data/lib/delta_test/related_spec_list.rb +14 -9
  23. data/lib/delta_test/stats.rb +41 -0
  24. data/lib/delta_test/version.rb +2 -2
  25. data/lib/delta_test.rb +14 -9
  26. data/spec/lib/delta_test/cli/command_base_spec.rb +164 -0
  27. data/spec/lib/delta_test/cli/exec_command_spec.rb +128 -0
  28. data/spec/lib/delta_test/cli/help_command_spec.rb +17 -0
  29. data/spec/lib/delta_test/cli/specs_command_spec.rb +54 -0
  30. data/spec/lib/delta_test/cli/stats_clean_command_spec.rb +39 -0
  31. data/spec/lib/delta_test/cli/stats_save_command_spec.rb +207 -0
  32. data/spec/lib/delta_test/cli/stats_show_command_spec.rb +52 -0
  33. data/spec/lib/delta_test/cli/version_command_spec.rb +17 -0
  34. data/spec/lib/delta_test/cli_spec.rb +47 -386
  35. data/spec/lib/delta_test/configuration_spec.rb +99 -47
  36. data/spec/lib/delta_test/dependencies_table_spec.rb +1 -1
  37. data/spec/lib/delta_test/generator_spec.rb +3 -3
  38. data/spec/lib/delta_test/git_spec.rb +291 -50
  39. data/spec/lib/delta_test/profiler_spec.rb +3 -3
  40. data/spec/lib/delta_test/related_spec_list_spec.rb +12 -14
  41. data/spec/lib/delta_test/stats_spec.rb +89 -0
  42. data/spec/lib/delta_test/utils_spec.rb +4 -4
  43. data/spec/lib/delta_test_spec.rb +13 -4
  44. data/spec/rails/Gemfile.lock +5 -2
  45. data/spec/rails/app/models/category.rb +4 -0
  46. data/spec/rails/delta_test.yml +4 -3
  47. data/spec/rails/spec/models/category_spec.rb +4 -0
  48. data/spec/spec_helper.rb +9 -2
  49. data/spec/supports/create_table_file.rb +11 -1
  50. data/visual.jpg +0 -0
  51. metadata +32 -4
@@ -1,310 +1,36 @@
1
- require 'open3'
2
- require 'shellwords'
3
- require 'thread'
4
- require 'thwait'
1
+ require_relative 'cli/exec_command'
2
+ require_relative 'cli/specs_command'
3
+ require_relative 'cli/stats_clean_command'
4
+ require_relative 'cli/stats_show_command'
5
+ require_relative 'cli/stats_save_command'
6
+ require_relative 'cli/version_command'
7
+ require_relative 'cli/help_command'
5
8
 
6
- require_relative 'related_spec_list'
7
9
 
8
10
  module DeltaTest
9
11
  class CLI
10
12
 
11
- DEFAULTS = {
12
- 'base' => 'master',
13
- 'head' => 'HEAD',
14
- 'verbose' => false,
15
- }.freeze
16
-
17
- BUNDLE_EXEC = ['bundle', 'exec'].freeze
18
-
19
- SPLITTER = '--'
20
-
21
- attr_reader *%i[
22
- args
23
- command
24
- options
25
- ]
26
-
27
- def initialize
28
- @args = []
29
- @command = nil
30
- @options = {}
31
- end
32
-
33
- ###
34
- # Run cli
35
- #
36
- # @params {Array} args
37
- ###
38
- def run(args)
39
- @args = args.dup
40
-
13
+ def initialize(args)
14
+ @args = args.dup
41
15
  @command = @args.shift
42
- @options = parse_options!
43
-
44
- @list = RelatedSpecList.new
45
-
46
- DeltaTest.verbose = !!@options['verbose']
47
-
48
- invoke
49
16
  end
50
17
 
51
- ###
52
- # Invoke action method
53
- ###
54
- def invoke
55
- begin
56
- case @command
57
- when 'list'
58
- do_list
59
- when 'table'
60
- do_table
61
- when 'exec'
62
- do_exec
63
- when 'clear'
64
- do_clear
65
- when '-v', '--version'
66
- do_version
67
- else
68
- do_help
69
- end
70
- rescue => e
71
- if DeltaTest.verbose?
72
- raise e
73
- else
74
- exit_with_message(1, '[%s] %s' % [e.class.name, e.message])
75
- end
76
- end
77
- end
18
+ COMMANDS = {
19
+ 'exec' => ExecCommand,
20
+ 'specs' => SpecsCommand,
21
+ 'stats:clean' => StatsCleanCommand,
22
+ 'stats:show' => StatsShowCommand,
23
+ 'stats:save' => StatsSaveCommand,
24
+ 'version' => VersionCommand,
25
+ 'help' => HelpCommand,
26
+ }
78
27
 
79
28
  ###
80
- # Parse option arguments
81
- #
82
- # @return {Hash<String, Boolean|String>}
83
- ###
84
- def parse_options!
85
- options = {}
86
-
87
- @args.reject! do |arg|
88
- case arg
89
- when /^-([a-z0-9])$/i, /^--([a-z0-9][a-z0-9-]*)$/i
90
- options[$1] = true
91
- when /^--([a-z0-9][a-z0-9-]*)=(.+)$/i
92
- options[$1] = $2
93
- else
94
- break
95
- end
96
- end
97
-
98
- DEFAULTS.merge(options)
99
- end
100
-
101
- ###
102
- # Print message and exit with a status
103
- #
104
- # @params {Integer} status - exit code
105
- # @params {Object} *args
106
- ###
107
- def exit_with_message(status, *args)
108
- if status.zero?
109
- puts(*args)
110
- else
111
- $stderr.puts(*args)
112
- end
113
-
114
- exit status
115
- end
116
-
117
- ###
118
- # Whether run full test or not
119
- #
120
- # @return {Boolean}
121
- ###
122
- def profile_mode?
123
- @profile_mode ||= Git.same_commit?(@options['base'], @options['head'])
124
- end
125
-
126
- ###
127
- # Show table contents
128
- ###
129
- def do_table
130
- @list.load_table!
131
-
132
- @list.table.each do |spec_file, dependencies|
133
- puts spec_file
134
- puts
135
- dependencies.each do |dependency|
136
- puts "\t#{dependency}"
137
- end
138
- puts
139
- end
140
- end
141
-
142
- ###
143
- # Show related spec files
144
- ###
145
- def do_list
146
- @list.load_table!
147
- @list.retrive_changed_files!(@options['base'], @options['head'])
148
-
149
- puts @list.related_spec_files
150
- end
151
-
152
- ###
153
- # Execute test script with delta_test
154
- ###
155
- def do_exec
156
- spec_files = nil
157
- args = []
158
-
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
174
-
175
- @args.map! { |arg| Shellwords.escape(arg) }
176
-
177
- if (splitter = @args.index(SPLITTER))
178
- files = @args.drop(splitter + 1)
179
- @args = @args.take(splitter)
180
-
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
189
- end
190
- end
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
-
206
- args += @args
207
- args = args.join(' ')
208
-
209
- $stdout.sync = true
210
-
211
- Open3.popen3(args) do |i, o, e, w|
212
- i.write(spec_files.join("\n")) if spec_files
213
- i.close
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
221
- end
222
- end
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
-
240
- ###
241
- # Show version
242
- ###
243
- def do_version
244
- puts 'DeltaTest v%s' % VERSION
245
- end
246
-
247
- ###
248
- # Show help
249
- ###
250
- def do_help
251
- if !@command.nil? && '-' != @command[0]
252
- puts "Command not found: #{@command}"
253
- end
254
-
255
- puts <<HELP
256
- usage: delta_test <command> [--base=<base>] [--head=<head>] [--verbose] [<args>]
257
- [-v|--version]
258
-
259
- options:
260
- --base=<base> A branch or a commit id to diff from.
261
- <head> is default to master.
262
-
263
- --head=<head> A branch or a commit id to diff to.
264
- <head> is default to HEAD. (current branch you're on)
265
-
266
- --verbose Print more output.
267
-
268
- -v, --version Show version.
269
-
270
- commands:
271
- list List related spec files for changes between base and head.
272
- head is default to master; base is to the current branch.
273
-
274
- table Show dependencies table.
275
-
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
29
+ # Run cli
294
30
  ###
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
31
+ def run
32
+ command_class = COMMANDS[@command] || COMMANDS['help']
33
+ command_class.new(@args).invoke
308
34
  end
309
35
 
310
36
  end
@@ -48,25 +48,30 @@ module DeltaTest
48
48
 
49
49
  PART_FILE_EXT = '.part-%s'
50
50
 
51
- attr_accessor *%i[
51
+ attr_accessor(*%i[
52
52
  base_path
53
53
  files
54
54
 
55
- table_file
55
+ stats_path
56
+ stats_life
57
+
56
58
  patterns
57
59
  exclude_patterns
58
60
  full_test_patterns
59
61
  custom_mappings
60
- ]
62
+ ])
61
63
 
62
64
  # for precalculated values
63
- attr_reader *%i[
65
+ attr_reader(*%i[
64
66
  filtered_files
65
- table_file_path
66
- ]
67
+ ])
67
68
 
68
69
  validate :base_path, 'need to be an absolute path' do
69
- self.base_path.absolute?
70
+ self.base_path.absolute? rescue false
71
+ end
72
+
73
+ validate :base_path, 'need to be managed by git' do
74
+ Git.new(self.base_path).git_repo?
70
75
  end
71
76
 
72
77
  validate :files, 'need to be an array' do
@@ -77,6 +82,18 @@ module DeltaTest
77
82
  self.patterns.is_a?(Array)
78
83
  end
79
84
 
85
+ validate :stats_path, 'need to be an absolute path' do
86
+ self.stats_path.absolute? rescue false
87
+ end
88
+
89
+ validate :stats_path, 'need to be managed by git' do
90
+ Git.new(self.stats_path).git_repo?
91
+ end
92
+
93
+ validate :stats_life, 'need to be a real number' do
94
+ self.stats_life.is_a?(Integer) && self.stats_life > 0
95
+ end
96
+
80
97
  validate :exclude_patterns, 'need to be an array' do
81
98
  self.exclude_patterns.is_a?(Array)
82
99
  end
@@ -94,10 +111,13 @@ module DeltaTest
94
111
  end
95
112
 
96
113
  def initialize
97
- update do |c|
98
- c.base_path = File.expand_path('.')
99
- c.table_file = 'tmp/.delta_test_dt'
100
- c.files = []
114
+ update(validate: false) do |c|
115
+ c.base_path = File.expand_path('.')
116
+ c.files = []
117
+
118
+ c.stats_path = File.expand_path('tmp/delta_test_stats')
119
+ c.stats_life = 1000 # commits
120
+
101
121
  c.patterns = []
102
122
  c.exclude_patterns = []
103
123
  c.full_test_patterns = []
@@ -115,37 +135,19 @@ module DeltaTest
115
135
  # @return {Pathname}
116
136
  ###
117
137
  def base_path=(path)
138
+ return unless path
118
139
  @base_path = Pathname.new(path)
119
140
  end
120
141
 
121
142
  ###
122
- # Store table_file as Pathname
143
+ # Store stats_path as Pathname
123
144
  #
124
145
  # @params {String|Pathname} path
125
146
  # @return {Pathname}
126
147
  ###
127
- def table_file=(path)
128
- @table_file = Pathname.new(path)
129
- end
130
-
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
148
+ def stats_path=(path)
149
+ return unless path
150
+ @stats_path = Pathname.new(path)
149
151
  end
150
152
 
151
153
 
@@ -156,9 +158,9 @@ module DeltaTest
156
158
  #
157
159
  # @block
158
160
  ###
159
- def update
161
+ def update(validate: true)
160
162
  yield self if block_given?
161
- validate!
163
+ validate! if validate
162
164
  precalculate!
163
165
  end
164
166
 
@@ -174,7 +176,7 @@ module DeltaTest
174
176
 
175
177
  @filtered_files = Set.new(filtered_files)
176
178
 
177
- @table_file_path = Pathname.new(File.absolute_path(self.table_file, self.base_path))
179
+ @stats_path = Pathname.new(File.absolute_path(self.stats_path, self.base_path))
178
180
  end
179
181
 
180
182
 
@@ -201,8 +203,13 @@ module DeltaTest
201
203
  end
202
204
 
203
205
  yaml = YAML.load_file(config_file)
206
+ yaml_dir = File.dirname(config_file)
204
207
 
205
- self.base_path = File.dirname(config_file)
208
+ _base_path = yaml.delete('base_path')
209
+ self.base_path = _base_path ? File.absolute_path(_base_path, yaml_dir) : yaml_dir
210
+
211
+ _stats_path = yaml.delete('stats_path')
212
+ self.stats_path = File.absolute_path(_stats_path, yaml_dir) if _stats_path
206
213
 
207
214
  yaml.each do |k, v|
208
215
  if self.respond_to?("#{k}=")
@@ -218,11 +225,19 @@ module DeltaTest
218
225
  # And update `files`
219
226
  ###
220
227
  def retrive_files_from_git_index!
221
- unless Git.git_repo?
222
- raise NotInGitRepositoryError
223
- end
228
+ self.files = Git.new(self.base_path).ls_files
229
+ end
230
+
224
231
 
225
- self.files = Git.ls_files
232
+ # Getters
233
+ #-----------------------------------------------
234
+ ###
235
+ # Temporary table file path
236
+ #
237
+ # @return {Pathname}
238
+ ###
239
+ def tmp_table_file
240
+ self.stats_path.join('tmp', DeltaTest.tester_id)
226
241
  end
227
242
 
228
243
  end
@@ -20,6 +20,30 @@ module DeltaTest
20
20
 
21
21
  end
22
22
 
23
+ class StatsNotFoundError < StandardError
24
+
25
+ def message
26
+ 'no stats data found'
27
+ end
28
+
29
+ end
30
+
31
+ class StatsRepositorySyncError < StandardError
32
+
33
+ def message
34
+ 'failed to sync the stats repository'
35
+ end
36
+
37
+ end
38
+
39
+ class TableFileStageError < StandardError
40
+
41
+ def message
42
+ 'failed to stage a table file'
43
+ end
44
+
45
+ end
46
+
23
47
  class NoConfigurationFileFoundError < IOError
24
48
 
25
49
  def message
@@ -8,10 +8,10 @@ require_relative 'utils'
8
8
  module DeltaTest
9
9
  class Generator
10
10
 
11
- attr_reader *%i[
11
+ attr_reader(*%i[
12
12
  current_spec_file
13
13
  table
14
- ]
14
+ ])
15
15
 
16
16
  ###
17
17
  # Setup table
@@ -24,7 +24,7 @@ module DeltaTest
24
24
 
25
25
  DeltaTest.log('--- setup!')
26
26
 
27
- @table = DependenciesTable.load(DeltaTest.config.table_file_path)
27
+ @table = DependenciesTable.new
28
28
 
29
29
  @current_spec_file = nil
30
30
  end
@@ -75,28 +75,8 @@ module DeltaTest
75
75
  @_teardown = true
76
76
 
77
77
  DeltaTest.log('--- teardown!')
78
-
79
78
  Profiler.clean!
80
-
81
- if defined?(ParallelTests)
82
- if ParallelTests.first_process?
83
- ParallelTests.wait_for_other_processes_to_finish
84
-
85
- table_file_path = DeltaTest.config.table_file_path('*')
86
-
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'])
93
-
94
- @table.dump(table_file_path)
95
- return
96
- end
97
- end
98
-
99
- @table.dump(DeltaTest.config.table_file_path)
79
+ @table.dump(DeltaTest.config.tmp_table_file)
100
80
  end
101
81
 
102
82
  ###