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
@@ -2,87 +2,168 @@ require 'open3'
2
2
  require 'shellwords'
3
3
 
4
4
  module DeltaTest
5
- module Git
6
- class << self
7
-
8
- ###
9
- # Check if in git managed directory
10
- #
11
- # @return {Boolean}
12
- ###
13
- def git_repo?
14
- o, e, s = exec(%q{git rev-parse --is-inside-work-tree}) rescue []
15
- !!s && s.success?
16
- end
17
-
18
- ###
19
- # Get root directory of git
20
- #
21
- # @return {String}
22
- ###
23
- def root_dir
24
- o, e, s = exec(%q{git rev-parse --show-toplevel})
25
- s.success? ? o.strip : nil
26
- end
27
-
28
- ###
29
- # Get commit id from rev name
30
- #
31
- # @params {String} rev - e.g., branch name
32
- #
33
- # @return {String}
34
- ###
35
- def rev_parse(rev)
36
- o, e, s = exec(%q{git rev-parse %s}, rev)
37
- s.success? ? o.strip : nil
38
- end
39
-
40
- ###
41
- # Compare two rev names by their commit ids
42
- #
43
- # @params {String} r1
44
- # @params {String} r2
45
- #
46
- # @return {Boolean}
47
- ###
48
- def same_commit?(r1, r2)
49
- rev_parse(r1) == rev_parse(r2)
50
- end
51
-
52
- ###
53
- # Get file list from git index
54
- #
55
- # @return {Array<String>}
56
- ###
57
- def ls_files
58
- o, e, s = exec(%q{git ls-files -z})
59
- s.success? ? o.split("\x0") : []
60
- end
61
-
62
- ###
63
- # Get list of modified files in diff
64
- #
65
- # @params {String} base
66
- # @params {String} head
67
- #
68
- # @return {Array<String>}
69
- ###
70
- def changed_files(base = 'master', head = 'HEAD')
71
- o, e, s = exec(%q{git --no-pager diff --name-only -z %s %s}, base, head)
72
- s.success? ? o.split("\x0") : []
73
- end
74
-
75
-
76
- private
77
-
78
- ###
79
- # Util for executing command
80
- ###
81
- def exec(command, *args)
82
- args = args.map { |a| Shellwords.escape(a) }
83
- Open3.capture3(command % args, chdir: DeltaTest.config.base_path)
84
- end
5
+ class Git
85
6
 
7
+ attr_reader :dir
8
+
9
+ def initialize(dir)
10
+ @dir = Pathname.new(dir)
11
+ end
12
+
13
+ ###
14
+ # Check if in git managed directory
15
+ #
16
+ # @return {Boolean}
17
+ ###
18
+ def git_repo?
19
+ _, _, s = exec(%q{rev-parse --is-inside-work-tree}) rescue []
20
+ !!s && s.success?
21
+ end
22
+
23
+ ###
24
+ # Get root directory of git
25
+ #
26
+ # @return {String}
27
+ ###
28
+ def root_dir
29
+ o, _, s = exec(%q{rev-parse --show-toplevel})
30
+ s.success? ? o.strip : nil
31
+ end
32
+
33
+ ###
34
+ # Get commit id from rev name
35
+ #
36
+ # @params {String} rev - e.g., branch name
37
+ #
38
+ # @return {String}
39
+ ###
40
+ def rev_parse(rev)
41
+ o, _, s = exec(%q{rev-parse %s}, rev)
42
+ s.success? ? o.strip : nil
43
+ end
44
+
45
+ ###
46
+ # Compare two rev names by their commit ids
47
+ #
48
+ # @params {String} r1
49
+ # @params {String} r2
50
+ #
51
+ # @return {Boolean}
52
+ ###
53
+ def same_commit?(r1, r2)
54
+ rev_parse(r1) == rev_parse(r2)
55
+ end
56
+
57
+ ###
58
+ # Get file list from git index
59
+ #
60
+ # @return {Array<String>}
61
+ ###
62
+ def ls_files(path: '.')
63
+ o, _, s = exec(%q{ls-files -z %s}, path)
64
+ s.success? ? o.split("\x0") : []
65
+ end
66
+
67
+ ###
68
+ # Get list of modified files in diff
69
+ #
70
+ # @params {String} base
71
+ # @params {String} head
72
+ #
73
+ # @return {Array<String>}
74
+ ###
75
+ def changed_files(base = 'master', head = 'HEAD', path: '.')
76
+ o, _, s = exec(%q{diff --name-only -z %s %s %s}, base, head, path)
77
+ s.success? ? o.split("\x0") : []
78
+ end
79
+
80
+ ###
81
+ # Get list of hashes for the last N commits
82
+ #
83
+ # @params {Integer} n
84
+ #
85
+ # @return {Array<String>}
86
+ ###
87
+ def ls_hashes(n)
88
+ o, _, s = exec(%q{log -z -n %d --format='%%H'}, n.to_i)
89
+ s.success? ? o.split("\x0") : []
90
+ end
91
+
92
+ ###
93
+ # Get url for the remote origin
94
+ #
95
+ # @return {String}
96
+ ###
97
+ def remote_url
98
+ o, _, s = exec(%q{config --get remote.origin.url})
99
+ s.success? ? o.strip : nil
100
+ end
101
+
102
+ ###
103
+ # Check if it has a remote origin
104
+ #
105
+ # @return {String}
106
+ ###
107
+ def has_remote?
108
+ !!remote_url rescue false
109
+ end
110
+
111
+ ###
112
+ # Pull from the origin master
113
+ #
114
+ # @return {Boolean}
115
+ ###
116
+ def pull
117
+ _, _, s = exec(%q{pull origin master})
118
+ s.success?
119
+ end
120
+
121
+ ###
122
+ # Push to the origin master
123
+ #
124
+ # @return {Boolean}
125
+ ###
126
+ def push
127
+ _, _, s = exec(%q{push origin master})
128
+ s.success?
129
+ end
130
+
131
+ ###
132
+ # Add a path to the index
133
+ #
134
+ # @return {Boolean}
135
+ ###
136
+ def add(path)
137
+ _, _, s = exec(%q{add %s}, path.to_s)
138
+ s.success?
139
+ end
140
+
141
+ ###
142
+ # Create commit
143
+ #
144
+ # @return {Boolean}
145
+ ###
146
+ def commit(message)
147
+ _, _, s = exec(%q{commit -m %s}, message.to_s)
148
+ s.success?
149
+ end
150
+
151
+ ###
152
+ # Util for executing command
153
+ ###
154
+ def exec(subcommand, *args)
155
+ command = [
156
+ 'git',
157
+ '--git-dir=%s',
158
+ '--no-pager',
159
+ subcommand,
160
+ ].join(' ')
161
+
162
+ args.unshift(@dir.join('.git'))
163
+ args.map! { |a| a.is_a?(String) ? Shellwords.escape(a) : a }
164
+
165
+ Open3.capture3(command % args, chdir: @dir)
86
166
  end
167
+
87
168
  end
88
169
  end
@@ -1,5 +1,13 @@
1
1
  module DeltaTest
2
2
  module Profiler
3
3
 
4
+ # These methods from native ext:
5
+ #
6
+ # .clean!
7
+ # .start!
8
+ # .stop!
9
+ # .running?
10
+ # .last_result
11
+
4
12
  end
5
13
  end
@@ -6,20 +6,25 @@ require_relative 'dependencies_table'
6
6
  module DeltaTest
7
7
  class RelatedSpecList
8
8
 
9
- attr_reader *%i[
9
+ attr_reader(*%i[
10
+ git
10
11
  table
11
12
  changed_files
12
- ]
13
+ ])
14
+
15
+ def initialize
16
+ @git = Git.new(DeltaTest.config.base_path)
17
+ end
13
18
 
14
19
  ###
15
20
  # Load table from the file
16
21
  ###
17
- def load_table!
18
- unless File.exist?(DeltaTest.config.table_file_path)
19
- raise TableNotFoundError.new(DeltaTest.config.table_file_path)
22
+ def load_table!(table_file_path)
23
+ unless File.exist?(table_file_path)
24
+ raise TableNotFoundError.new(table_file_path)
20
25
  end
21
26
 
22
- @table = DependenciesTable.load(DeltaTest.config.table_file_path)
27
+ @table = DependenciesTable.load(table_file_path)
23
28
  end
24
29
 
25
30
  ###
@@ -28,12 +33,12 @@ module DeltaTest
28
33
  # @params {String} base
29
34
  # @params {String} head
30
35
  ###
31
- def retrive_changed_files!(base, head)
32
- unless Git.git_repo?
36
+ def retrive_changed_files!(commit)
37
+ unless @git.git_repo?
33
38
  raise NotInGitRepositoryError
34
39
  end
35
40
 
36
- @changed_files = Git.changed_files(base, head)
41
+ @changed_files = @git.changed_files(commit)
37
42
  end
38
43
 
39
44
  ###
@@ -0,0 +1,41 @@
1
+ require_relative 'git'
2
+
3
+ module DeltaTest
4
+ class Stats
5
+
6
+ attr_reader :base_git
7
+ attr_reader :stats_git
8
+
9
+ TABLE_FILENAME = 'table.marshal'
10
+
11
+ def initialize(head: false)
12
+ @base_git = Git.new(DeltaTest.config.base_path)
13
+ @stats_git = Git.new(DeltaTest.config.stats_path)
14
+
15
+ @base_commit = @base_git.rev_parse('HEAD') if head
16
+ end
17
+
18
+ def base_commit
19
+ return @base_commit if defined?(@base_commit)
20
+
21
+ indexes = @stats_git.ls_files
22
+ .map { |f| f.split('/').take(2).join('') }
23
+ .to_set
24
+
25
+ @base_commit = @base_git.ls_hashes(DeltaTest.config.stats_life)
26
+ .find { |h| indexes.include?(h) }
27
+ end
28
+
29
+ def commit_dir
30
+ return unless base_commit
31
+ return @commit_dir if defined?(@commit_dir)
32
+
33
+ @commit_dir = DeltaTest.config.stats_path.join(*base_commit.unpack('A2A*'))
34
+ end
35
+
36
+ def table_file_path
37
+ commit_dir && commit_dir.join(TABLE_FILENAME)
38
+ end
39
+
40
+ end
41
+ end
@@ -1,7 +1,7 @@
1
1
  module DeltaTest
2
2
 
3
- MAJOR = 0
4
- MINOR = 2
3
+ MAJOR = 1
4
+ MINOR = 0
5
5
  REVISION = 0
6
6
 
7
7
  VERSION = [MAJOR, MINOR, REVISION].compact.join('.')
data/lib/delta_test.rb CHANGED
@@ -19,18 +19,19 @@ module DeltaTest
19
19
 
20
20
  attr_reader :config
21
21
 
22
- attr_writer *%i[
22
+ attr_writer(*%i[
23
23
  active
24
24
  verbose
25
- ]
25
+ ])
26
26
 
27
- def setup
28
- @config = Configuration.new
29
- @config.auto_configure! if active?
27
+ def config
28
+ @config ||= Configuration.new.tap do |c|
29
+ c.auto_configure! if active?
30
+ end
30
31
  end
31
32
 
32
33
  def configure(&block)
33
- @config.update(&block)
34
+ config.update(&block)
34
35
  end
35
36
 
36
37
  def active?
@@ -44,10 +45,14 @@ module DeltaTest
44
45
  end
45
46
 
46
47
  def log(*args)
47
- puts *args if verbose?
48
+ puts(*args) if verbose?
49
+ end
50
+
51
+ def tester_id
52
+ return @tester_id if @tester_id
53
+ t = Time.now
54
+ @tester_id = 'p%dt%dn%d' % [$$, t.to_i, t.nsec]
48
55
  end
49
56
 
50
57
  end
51
58
  end
52
-
53
- DeltaTest.setup
@@ -0,0 +1,164 @@
1
+ require 'delta_test/cli/command_base'
2
+
3
+ describe DeltaTest::CLI::CommandBase do
4
+
5
+ let(:command_base) { DeltaTest::CLI::CommandBase.new([]) }
6
+
7
+ describe '#invoke!' do
8
+
9
+ it 'should raise a not implemented error' do
10
+ expect {
11
+ command_base.invoke!
12
+ }.to raise_error
13
+ end
14
+
15
+ end
16
+
17
+ describe '#invoke' do
18
+
19
+ context 'verbose=false' do
20
+
21
+ it 'should call `invoke!`' do
22
+ allow(DeltaTest).to receive(:verbose?).and_return(false)
23
+ expect(command_base).to receive(:invoke!).and_return(nil)
24
+ command_base.invoke
25
+ end
26
+
27
+ it 'should exit with an error message' do
28
+ expect(command_base).to receive(:exit_with_message)
29
+ command_base.invoke
30
+ end
31
+
32
+ end
33
+
34
+ context 'verbose=true' do
35
+
36
+ it 'should call `invoke!`' do
37
+ allow(DeltaTest).to receive(:verbose?).and_return(false)
38
+ expect(command_base).to receive(:invoke!).and_return(nil)
39
+ command_base.invoke!
40
+ end
41
+
42
+ it 'should raise an error' do
43
+ expect {
44
+ command_base.invoke
45
+ }.to raise_error
46
+ end
47
+
48
+ end
49
+
50
+ end
51
+
52
+ describe '#parse_options!' do
53
+
54
+ it 'should parse short options' do
55
+ args = ['-a', '-b']
56
+ options = command_base.parse_options!(args)
57
+
58
+ expect(args).to be_empty
59
+ expect(options).to be_a(Hash)
60
+ expect(options['a']).to be(true)
61
+ expect(options['b']).to be(true)
62
+ end
63
+
64
+ it 'should parse long options' do
65
+ args = ['--long-a', '--long-b']
66
+ options = command_base.parse_options!(args)
67
+
68
+ expect(args).to be_empty
69
+ expect(options).to be_a(Hash)
70
+ expect(options['long-a']).to be(true)
71
+ expect(options['long-b']).to be(true)
72
+ end
73
+
74
+ it 'should parse long options with value' do
75
+ args = ['--long-a=value-of-a', '--long-b=value-of-b']
76
+ options = command_base.parse_options!(args)
77
+
78
+ expect(args).to be_empty
79
+ expect(options).to be_a(Hash)
80
+ expect(options['long-a']).to eq('value-of-a')
81
+ expect(options['long-b']).to eq('value-of-b')
82
+ end
83
+
84
+ it 'should not parse options after once non-option args appears' do
85
+ args = ['-a', '--long-a', 'non-option', '--long-b=value-of-b']
86
+ options = command_base.parse_options!(args)
87
+
88
+ expect(args).to eq(['non-option', '--long-b=value-of-b'])
89
+ expect(options).to be_a(Hash)
90
+ expect(options['a']).to be(true)
91
+ expect(options['long-a']).to be(true)
92
+ expect(options['long-b']).to be_nil
93
+ end
94
+
95
+ describe 'Defaults' do
96
+
97
+ it 'should set default options' do
98
+ args = []
99
+ options = command_base.parse_options!(args)
100
+
101
+ expect(options).to be_a(Hash)
102
+
103
+ expect(options['force-run']).to eq(false)
104
+ expect(options['verbose']).to eq(false)
105
+ end
106
+
107
+ it 'should be able to overwrite default options' do
108
+ args = ['--force-run', '--verbose']
109
+ options = command_base.parse_options!(args)
110
+
111
+ expect(options).to be_a(Hash)
112
+
113
+ expect(options['force-run']).to eq(true)
114
+ expect(options['verbose']).to eq(true)
115
+ end
116
+
117
+ end
118
+
119
+ end
120
+
121
+ describe '#exit_with_message' do
122
+
123
+ let(:message) { 'a message' }
124
+ let(:message_regexp) { /a message/ }
125
+
126
+ context 'With status code of zero' do
127
+
128
+ let(:status) { 0 }
129
+
130
+ it 'should print a message to stdout and exit' do
131
+ expect {
132
+ begin
133
+ command_base.exit_with_message(status, message)
134
+ rescue SystemExit => e
135
+ expect(e.status).to eq(status)
136
+ end
137
+ }.to output(message_regexp).to_stdout
138
+ end
139
+
140
+ end
141
+
142
+ context 'With status code of non-zero' do
143
+
144
+ let(:status) { 1 }
145
+
146
+ it 'should print a message to stderr and exit' do
147
+ expect {
148
+ begin
149
+ command_base.exit_with_message(status, message)
150
+ rescue SystemExit => e
151
+ expect(e.status).to eq(status)
152
+ end
153
+ }.to output(message_regexp).to_stderr
154
+ end
155
+
156
+ end
157
+
158
+ end
159
+
160
+ describe '#bundler_enabled?' do
161
+
162
+ end
163
+
164
+ end
@@ -0,0 +1,128 @@
1
+ require 'delta_test/cli/exec_command'
2
+
3
+ describe DeltaTest::CLI::ExecCommand do
4
+
5
+ let(:args) { ['bundle', 'exec', 'rspec'] }
6
+ let(:command) { DeltaTest::CLI::ExecCommand.new(args) }
7
+
8
+ let(:table) do
9
+ {
10
+ 'spec/foo_spec.rb' => ['lib/foo.rb']
11
+ }
12
+ end
13
+
14
+ let(:related_spec_files) do
15
+ [
16
+ 'spec/foo_spec.rb',
17
+ ]
18
+ end
19
+
20
+ let(:base_commit) { '1111111111111111111111111111111111111111' }
21
+
22
+ before do
23
+ allow(command.list).to receive(:load_table!).and_return(nil)
24
+ allow(command.list).to receive(:table).and_return(table)
25
+ allow(command.list).to receive(:retrive_changed_files!).and_return(nil)
26
+ allow(command.list).to receive(:related_spec_files).and_return(related_spec_files)
27
+
28
+ allow(command.stats).to receive(:base_commit).and_return(base_commit)
29
+ end
30
+
31
+ describe '#profile_mode?' do
32
+
33
+ it 'should return false if a base commit exists' do
34
+ expect(command.profile_mode?).to be(false)
35
+ end
36
+
37
+ it 'should return true if a base commit does not exist' do
38
+ allow(command.stats).to receive(:base_commit).and_return(nil)
39
+ expect(command.profile_mode?).to be(true)
40
+ end
41
+
42
+ it 'should be able to override by an instance variable' do
43
+ expect(command.profile_mode?).to be(false)
44
+ command.instance_variable_set(:@profile_mode, true)
45
+ expect(command.profile_mode?).to be(true)
46
+ end
47
+
48
+ context 'with --force-run' do
49
+
50
+ let(:args) { ['--force-run', 'bundle', 'exec', 'rspec'] }
51
+
52
+ it 'should always return false' do
53
+ allow(command.stats).to receive(:base_commit).and_return(nil)
54
+ expect(command.profile_mode?).to be(false)
55
+ end
56
+
57
+ end
58
+
59
+ end
60
+
61
+ describe '#invoke!' do
62
+
63
+ before do
64
+ allow(command).to receive(:exec_with_data).and_return(nil)
65
+ end
66
+
67
+ context 'Full tests' do
68
+
69
+ before do
70
+ allow(command).to receive(:profile_mode?).and_return(true)
71
+ end
72
+
73
+ it 'should run script with a flag' do
74
+ expect(command.list).not_to receive(:related_spec_files)
75
+
76
+ _args = ['%s=%s' % [DeltaTest::ACTIVE_FLAG, true], *args].join(' ')
77
+ expect(command).to receive(:exec_with_data).with(_args, nil)
78
+
79
+ expect {
80
+ command.invoke!
81
+ }.not_to raise_error
82
+ end
83
+
84
+ end
85
+
86
+ context 'Partial tests' do
87
+
88
+ before do
89
+ allow(command).to receive(:profile_mode?).and_return(false)
90
+ end
91
+
92
+ context 'Any related files' do
93
+
94
+ it 'should run script with related spec files' do
95
+ expect(command.list).to receive(:related_spec_files)
96
+
97
+ _args = ['cat', '|', 'xargs', *args].join(' ')
98
+ expect(command).to receive(:exec_with_data).with(_args, related_spec_files)
99
+
100
+ expect {
101
+ command.invoke!
102
+ }.not_to raise_error
103
+ end
104
+
105
+ end
106
+
107
+ context 'No related files' do
108
+
109
+ let(:related_spec_files) { [] }
110
+
111
+ it 'should not run script and exit with a message' do
112
+ expect(command.list).to receive(:related_spec_files)
113
+
114
+ expect {
115
+ begin
116
+ command.invoke!
117
+ rescue SystemExit => e
118
+ expect(e.status).to eq(0)
119
+ end
120
+ }.to output(/Nothing/).to_stdout
121
+ end
122
+
123
+ end
124
+
125
+ end
126
+
127
+ end
128
+ end
@@ -0,0 +1,17 @@
1
+ require 'delta_test/cli/help_command'
2
+
3
+ describe DeltaTest::CLI::HelpCommand do
4
+
5
+ let(:command) { DeltaTest::CLI::HelpCommand.new([]) }
6
+
7
+ describe '#invoke!' do
8
+
9
+ it 'should print gem version' do
10
+ expect {
11
+ command.invoke!
12
+ }.to output(/usage/).to_stdout
13
+ end
14
+
15
+ end
16
+
17
+ end