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
@@ -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