delta_test 0.2.0 → 1.0.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.
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
  ###