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.
- checksums.yaml +6 -14
- data/.gitignore +7 -7
- data/.hound.yml +3 -0
- data/.hound/ruby.yml +1042 -0
- data/.rubocop.yml +13 -0
- data/.travis.yml +11 -7
- data/Appraisals +13 -10
- data/CONTRIBUTING.md +6 -1
- data/Gemfile +8 -1
- data/README.md +58 -73
- data/Rakefile +21 -12
- data/gemfiles/4.2.gemfile +17 -0
- data/gemfiles/4.2.gemfile.lock +174 -0
- data/gemfiles/5.0.gemfile +17 -0
- data/gemfiles/5.0.gemfile.lock +179 -0
- data/lib/shoulda/version.rb +1 -1
- data/shoulda.gemspec +23 -23
- data/test/acceptance/rails_integration_test.rb +76 -0
- data/test/acceptance_test_helper.rb +17 -0
- data/test/report_warnings.rb +7 -0
- data/test/support/acceptance/add_shoulda_to_project.rb +78 -0
- data/test/support/acceptance/helpers.rb +19 -0
- data/test/support/acceptance/helpers/active_model_helpers.rb +11 -0
- data/test/support/acceptance/helpers/array_helpers.rb +13 -0
- data/test/support/acceptance/helpers/base_helpers.rb +14 -0
- data/test/support/acceptance/helpers/command_helpers.rb +54 -0
- data/test/support/acceptance/helpers/file_helpers.rb +19 -0
- data/test/support/acceptance/helpers/gem_helpers.rb +31 -0
- data/test/support/acceptance/helpers/pluralization_helpers.rb +13 -0
- data/test/support/acceptance/helpers/step_helpers.rb +69 -0
- data/test/support/acceptance/matchers/have_output.rb +31 -0
- data/test/support/acceptance/matchers/indicate_number_of_tests_was_run_matcher.rb +54 -0
- data/test/support/acceptance/matchers/indicate_that_tests_were_run_matcher.rb +75 -0
- data/test/support/tests/bundle.rb +94 -0
- data/test/support/tests/command_runner.rb +230 -0
- data/test/support/tests/current_bundle.rb +61 -0
- data/test/support/tests/filesystem.rb +100 -0
- data/test/support/tests/version.rb +45 -0
- data/test/test_helper.rb +18 -0
- data/test/warnings_spy.rb +62 -0
- data/test/warnings_spy/filesystem.rb +45 -0
- data/test/warnings_spy/partitioner.rb +36 -0
- data/test/warnings_spy/reader.rb +53 -0
- data/test/warnings_spy/reporter.rb +88 -0
- metadata +80 -121
- data/features/rails_integration.feature +0 -87
- data/features/step_definitions/rails_steps.rb +0 -77
- data/features/support/env.rb +0 -14
- data/gemfiles/3.0.gemfile +0 -7
- data/gemfiles/3.0.gemfile.lock +0 -127
- data/gemfiles/3.1.gemfile +0 -9
- data/gemfiles/3.1.gemfile.lock +0 -148
- data/gemfiles/3.2.gemfile +0 -9
- 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
|
data/test/test_helper.rb
ADDED
@@ -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
|