shoulda 3.5.0 → 3.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +6 -14
  2. data/.gitignore +7 -7
  3. data/.hound.yml +3 -0
  4. data/.hound/ruby.yml +1042 -0
  5. data/.rubocop.yml +13 -0
  6. data/.travis.yml +11 -7
  7. data/Appraisals +13 -10
  8. data/CONTRIBUTING.md +6 -1
  9. data/Gemfile +8 -1
  10. data/README.md +58 -73
  11. data/Rakefile +21 -12
  12. data/gemfiles/4.2.gemfile +17 -0
  13. data/gemfiles/4.2.gemfile.lock +174 -0
  14. data/gemfiles/5.0.gemfile +17 -0
  15. data/gemfiles/5.0.gemfile.lock +179 -0
  16. data/lib/shoulda/version.rb +1 -1
  17. data/shoulda.gemspec +23 -23
  18. data/test/acceptance/rails_integration_test.rb +76 -0
  19. data/test/acceptance_test_helper.rb +17 -0
  20. data/test/report_warnings.rb +7 -0
  21. data/test/support/acceptance/add_shoulda_to_project.rb +78 -0
  22. data/test/support/acceptance/helpers.rb +19 -0
  23. data/test/support/acceptance/helpers/active_model_helpers.rb +11 -0
  24. data/test/support/acceptance/helpers/array_helpers.rb +13 -0
  25. data/test/support/acceptance/helpers/base_helpers.rb +14 -0
  26. data/test/support/acceptance/helpers/command_helpers.rb +54 -0
  27. data/test/support/acceptance/helpers/file_helpers.rb +19 -0
  28. data/test/support/acceptance/helpers/gem_helpers.rb +31 -0
  29. data/test/support/acceptance/helpers/pluralization_helpers.rb +13 -0
  30. data/test/support/acceptance/helpers/step_helpers.rb +69 -0
  31. data/test/support/acceptance/matchers/have_output.rb +31 -0
  32. data/test/support/acceptance/matchers/indicate_number_of_tests_was_run_matcher.rb +54 -0
  33. data/test/support/acceptance/matchers/indicate_that_tests_were_run_matcher.rb +75 -0
  34. data/test/support/tests/bundle.rb +94 -0
  35. data/test/support/tests/command_runner.rb +230 -0
  36. data/test/support/tests/current_bundle.rb +61 -0
  37. data/test/support/tests/filesystem.rb +100 -0
  38. data/test/support/tests/version.rb +45 -0
  39. data/test/test_helper.rb +18 -0
  40. data/test/warnings_spy.rb +62 -0
  41. data/test/warnings_spy/filesystem.rb +45 -0
  42. data/test/warnings_spy/partitioner.rb +36 -0
  43. data/test/warnings_spy/reader.rb +53 -0
  44. data/test/warnings_spy/reporter.rb +88 -0
  45. metadata +80 -121
  46. data/features/rails_integration.feature +0 -87
  47. data/features/step_definitions/rails_steps.rb +0 -77
  48. data/features/support/env.rb +0 -14
  49. data/gemfiles/3.0.gemfile +0 -7
  50. data/gemfiles/3.0.gemfile.lock +0 -127
  51. data/gemfiles/3.1.gemfile +0 -9
  52. data/gemfiles/3.1.gemfile.lock +0 -148
  53. data/gemfiles/3.2.gemfile +0 -9
  54. data/gemfiles/3.2.gemfile.lock +0 -146
@@ -0,0 +1,230 @@
1
+ require 'timeout'
2
+ require 'shellwords'
3
+
4
+ module Tests
5
+ class CommandRunner
6
+ TimeoutError = Class.new(StandardError)
7
+
8
+ def self.run(*args)
9
+ new(*args).tap do |runner|
10
+ yield runner
11
+ runner.call
12
+ end
13
+ end
14
+
15
+ def self.run!(*args)
16
+ run(*args) do |runner|
17
+ runner.run_successfully = true
18
+ yield runner if block_given?
19
+ end
20
+ end
21
+
22
+ attr_reader :status, :options, :env
23
+ attr_accessor :command_prefix, :run_quickly, :run_successfully, :retries,
24
+ :timeout
25
+
26
+ def initialize(*args)
27
+ @reader, @writer = IO.pipe
28
+ options = (args.last.is_a?(Hash) ? args.pop : {})
29
+ @args = args
30
+ @options = options.merge(
31
+ err: [:child, :out],
32
+ out: writer,
33
+ )
34
+ @env = extract_env_from(@options)
35
+
36
+ @wrapper = ->(block) { block.call }
37
+ @command_prefix = ''
38
+ self.directory = Dir.pwd
39
+ @run_quickly = false
40
+ @run_successfully = false
41
+ @retries = 1
42
+ @num_times_run = 0
43
+ @timeout = 20
44
+ end
45
+
46
+ def around_command(&block)
47
+ @wrapper = block
48
+ end
49
+
50
+ def directory
51
+ @options[:chdir]
52
+ end
53
+
54
+ def directory=(directory)
55
+ @options[:chdir] = directory || Dir.pwd
56
+ end
57
+
58
+ def formatted_command
59
+ [formatted_env, Shellwords.join(command)].
60
+ select { |value| !value.empty? }.
61
+ join(' ')
62
+ end
63
+
64
+ def call
65
+ possibly_retrying do
66
+ possibly_running_quickly do
67
+ run_with_debugging
68
+
69
+ if run_successfully && !success?
70
+ fail!
71
+ end
72
+ end
73
+ end
74
+
75
+ self
76
+ end
77
+
78
+ def stop
79
+ unless writer.closed?
80
+ writer.close
81
+ end
82
+ end
83
+
84
+ def output
85
+ @_output ||= begin
86
+ stop
87
+ without_colors(reader.read)
88
+ end
89
+ end
90
+
91
+ def elided_output
92
+ lines = output.split(/\n/)
93
+ new_lines = lines[0..4]
94
+
95
+ if lines.size > 10
96
+ new_lines << "(...#{lines.size - 10} more lines...)"
97
+ end
98
+
99
+ new_lines << lines[-5..-1]
100
+ new_lines.join("\n")
101
+ end
102
+
103
+ def success?
104
+ status.success?
105
+ end
106
+
107
+ def exit_status
108
+ status.exitstatus
109
+ end
110
+
111
+ def fail!
112
+ raise <<-MESSAGE
113
+ Command #{formatted_command.inspect} exited with status #{exit_status}.
114
+ Output:
115
+ #{divider('START') + output + divider('END')}
116
+ MESSAGE
117
+ end
118
+
119
+ def has_output?(expected_output)
120
+ if expected_output.is_a?(Regexp)
121
+ output =~ expected_output
122
+ else
123
+ output.include?(expected_output)
124
+ end
125
+ end
126
+
127
+ protected
128
+
129
+ attr_reader :args, :reader, :writer, :wrapper
130
+
131
+ private
132
+
133
+ def extract_env_from(options)
134
+ options.delete(:env) { {} }.inject({}) do |hash, (key, value)|
135
+ hash[key.to_s] = value
136
+ hash
137
+ end
138
+ end
139
+
140
+ def command
141
+ ([command_prefix] + args).flatten.flat_map do |word|
142
+ Shellwords.split(word)
143
+ end
144
+ end
145
+
146
+ def formatted_env
147
+ env.map { |key, value| "#{key}=#{value.inspect}" }.join(' ')
148
+ end
149
+
150
+ def run
151
+ pid = spawn(env, *command, options)
152
+ Process.waitpid(pid)
153
+ @status = $?
154
+ end
155
+
156
+ def run_with_wrapper
157
+ wrapper.call(method(:run))
158
+ end
159
+
160
+ def run_with_debugging
161
+ debug { "\n\e[33mChanging to directory:\e[0m #{directory}" }
162
+ debug { "\e[32mRunning command:\e[0m #{formatted_command}" }
163
+
164
+ run_with_wrapper
165
+
166
+ debug { "\n" + divider('START') + output + divider('END') }
167
+ end
168
+
169
+ def possibly_running_quickly(&block)
170
+ if run_quickly
171
+ begin
172
+ Timeout.timeout(timeout, &block)
173
+ rescue Timeout::Error
174
+ stop
175
+
176
+ message =
177
+ "Command timed out after #{timeout} seconds: #{formatted_command}\n" +
178
+ "Output:\n" +
179
+ output
180
+
181
+ raise TimeoutError, message
182
+ end
183
+ else
184
+ yield
185
+ end
186
+ end
187
+
188
+ def possibly_retrying
189
+ begin
190
+ @num_times_run += 1
191
+ yield
192
+ rescue => error
193
+ debug { "#{error.class}: #{error.message}" }
194
+
195
+ if @num_times_run < @retries
196
+ sleep @num_times_run
197
+ retry
198
+ else
199
+ raise error
200
+ end
201
+ end
202
+ end
203
+
204
+ def divider(title = '')
205
+ total_length = 72
206
+ start_length = 3
207
+
208
+ string = ''
209
+ string << ('-' * start_length)
210
+ string << title
211
+ string << '-' * (total_length - start_length - title.length)
212
+ string << "\n"
213
+ string
214
+ end
215
+
216
+ def without_colors(string)
217
+ string.gsub(/\e\[\d+(?:;\d+)?m(.+?)\e\[0m/, '\1')
218
+ end
219
+
220
+ def debugging_enabled?
221
+ ENV['DEBUG_COMMANDS'] == '1'
222
+ end
223
+
224
+ def debug
225
+ if debugging_enabled?
226
+ puts yield
227
+ end
228
+ end
229
+ end
230
+ end
@@ -0,0 +1,61 @@
1
+ require 'bundler'
2
+ require 'appraisal'
3
+
4
+ module Tests
5
+ class CurrentBundle
6
+ AppraisalNotSpecified = Class.new(ArgumentError)
7
+
8
+ include Singleton
9
+
10
+ def assert_appraisal!
11
+ unless appraisal_in_use?
12
+ message = <<EOT
13
+
14
+
15
+ Please run tests starting with `appraisal <appraisal_name>`.
16
+ Possible appraisals are: #{available_appraisals}
17
+
18
+ EOT
19
+ raise AppraisalNotSpecified, message
20
+ end
21
+ end
22
+
23
+ def appraisal_in_use?
24
+ path.dirname == root.join('gemfiles')
25
+ end
26
+
27
+ def current_or_latest_appraisal
28
+ current_appraisal || latest_appraisal
29
+ end
30
+
31
+ def latest_appraisal
32
+ available_appraisals.sort.last
33
+ end
34
+
35
+ private
36
+
37
+ def available_appraisals
38
+ appraisals = []
39
+
40
+ Appraisal::AppraisalFile.each do |appraisal|
41
+ appraisals << appraisal.name
42
+ end
43
+
44
+ appraisals
45
+ end
46
+
47
+ def current_appraisal
48
+ if appraisal_in_use?
49
+ File.basename(path, '.gemfile')
50
+ end
51
+ end
52
+
53
+ def path
54
+ Bundler.default_gemfile
55
+ end
56
+
57
+ def root
58
+ Pathname.new('../../../..').expand_path(__FILE__)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,100 @@
1
+ require 'fileutils'
2
+
3
+ module Tests
4
+ class Filesystem
5
+ PROJECT_NAME = 'test-project'.freeze
6
+ ROOT_DIRECTORY = Pathname.new('../../../..').expand_path(__FILE__)
7
+ TEMP_DIRECTORY = ROOT_DIRECTORY.join('tmp/acceptance')
8
+ PROJECT_DIRECTORY = TEMP_DIRECTORY.join(PROJECT_NAME)
9
+
10
+ def root_directory
11
+ ROOT_DIRECTORY
12
+ end
13
+
14
+ def temp_directory
15
+ TEMP_DIRECTORY
16
+ end
17
+
18
+ def project_directory
19
+ PROJECT_DIRECTORY
20
+ end
21
+
22
+ def wrap(path)
23
+ if path.is_a?(Pathname)
24
+ path
25
+ else
26
+ find_in_project(path)
27
+ end
28
+ end
29
+
30
+ def within_project(&block)
31
+ Dir.chdir(project_directory, &block)
32
+ end
33
+
34
+ def clean
35
+ if temp_directory.exist?
36
+ temp_directory.rmtree
37
+ end
38
+ end
39
+
40
+ def create
41
+ project_directory.mkpath
42
+ end
43
+
44
+ def find_in_project(path)
45
+ project_directory.join(path)
46
+ end
47
+
48
+ def open(path, *args, &block)
49
+ find_in_project(path).open(*args, &block)
50
+ end
51
+
52
+ def read(path)
53
+ find_in_project(path).read
54
+ end
55
+
56
+ def write(path, content)
57
+ pathname = wrap(path)
58
+ create_parents_of(pathname)
59
+ pathname.open('w') { |f| f.write(content) }
60
+ end
61
+
62
+ def create_parents_of(path)
63
+ wrap(path).dirname.mkpath
64
+ end
65
+
66
+ def append_to_file(path, content, _options = {})
67
+ create_parents_of(path)
68
+ open(path, 'a') { |f| f.puts(content + "\n") }
69
+ end
70
+
71
+ def remove_from_file(path, pattern)
72
+ unless pattern.is_a?(Regexp)
73
+ pattern = Regexp.new('^' + Regexp.escape(pattern) + '$')
74
+ end
75
+
76
+ transform(path) do |lines|
77
+ lines.reject { |line| line =~ pattern }
78
+ end
79
+ end
80
+
81
+ def comment_lines_matching(path, pattern)
82
+ transform(path) do |lines|
83
+ lines.map do |line|
84
+ if line =~ pattern
85
+ "###{line}"
86
+ else
87
+ line
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ def transform(path)
94
+ content = read(path)
95
+ lines = content.split(/\n/)
96
+ transformed_lines = yield lines
97
+ write(path, transformed_lines.join("\n") + "\n")
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,45 @@
1
+ module Tests
2
+ class Version
3
+ def initialize(version)
4
+ @version = Gem::Version.new(version.to_s + '')
5
+ end
6
+
7
+ def <(other_version)
8
+ compare?(:<, other_version)
9
+ end
10
+
11
+ def <=(other_version)
12
+ compare?(:<=, other_version)
13
+ end
14
+
15
+ def ==(other_version)
16
+ compare?(:==, other_version)
17
+ end
18
+
19
+ def >=(other_version)
20
+ compare?(:>=, other_version)
21
+ end
22
+
23
+ def >(other_version)
24
+ compare?(:>, other_version)
25
+ end
26
+
27
+ def =~(other_version)
28
+ Gem::Requirement.new(other_version).satisfied_by?(version)
29
+ end
30
+
31
+ def to_s
32
+ version.to_s
33
+ end
34
+
35
+ protected
36
+
37
+ attr_reader :version
38
+
39
+ private
40
+
41
+ def compare?(op, other_version)
42
+ Gem::Requirement.new("#{op} #{other_version}").satisfied_by?(version)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,18 @@
1
+ PROJECT_ROOT = Pathname.new('../..').expand_path(__FILE__).freeze
2
+ $LOAD_PATH << File.join(PROJECT_ROOT, 'lib')
3
+
4
+ require 'pry'
5
+ require 'pry-byebug'
6
+ require 'minitest/autorun'
7
+ require 'minitest/reporters'
8
+ require 'shoulda'
9
+
10
+ Minitest::Reporters.use!(Minitest::Reporters::SpecReporter.new)
11
+
12
+ Shoulda::Matchers.configure do |config|
13
+ config.integrate do |with|
14
+ with.test_framework :minitest
15
+ end
16
+ end
17
+
18
+ $VERBOSE = true