shoulda 3.5.0 → 3.6.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 (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