spec_selector 0.1.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.
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_selector'
4
+ require 'stringio'
5
+ require 'factory_bot'
6
+ require 'timeout'
7
+ require 'shared'
8
+
9
+ RCN = RSpec::Core::Notifications
10
+ EXAMPLE_STUBS = { description: 'description',
11
+ execution_result: 'result',
12
+ full_description: 'full_description' }.freeze
13
+
14
+ alias ivar instance_variable_get
15
+ alias ivar_set instance_variable_set
16
+
17
+ RSpec.configure do |config|
18
+ config.expect_with :rspec do |expectations|
19
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
20
+ end
21
+
22
+ config.filter_run_when_matching :focus
23
+
24
+ config.mock_with :rspec do |mocks|
25
+ mocks.verify_partial_doubles = true
26
+ end
27
+
28
+ config.shared_context_metadata_behavior = :apply_to_host_groups
29
+
30
+ config.include_context 'shared', include_shared: true
31
+
32
+ config.include FactoryBot::Syntax::Methods
33
+
34
+ config.before(:suite) do
35
+ FactoryBot.find_definitions
36
+ end
37
+
38
+ config.around(:example, break_loop: true) do |example|
39
+ begin
40
+ Timeout.timeout(0.001) do
41
+ example.run
42
+ end
43
+ rescue Timeout::Error
44
+ nil
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe SpecSelector do
4
+ include_context 'shared'
5
+
6
+ let(:spec_selector) { described_class.new(StringIO.new) }
7
+
8
+ let(:output) { spec_selector.ivar(:@output).string }
9
+ let(:pass_result) { build(:execution_result) }
10
+ let(:pending_result) { build(:execution_result, status: :pending) }
11
+
12
+ let(:fail_result) do
13
+ build(:execution_result, status: :failed, exception: 'error')
14
+ end
15
+
16
+ describe '#message' do
17
+ let(:notification) { RCN::MessageNotification.new('message') }
18
+
19
+ it 'stores the message string in the @messages array' do
20
+ messages = spec_selector.ivar(:@messages)
21
+ spec_selector.message(notification)
22
+ expect(messages).to include('message')
23
+ end
24
+ end
25
+
26
+ describe '#example_group_started' do
27
+ let(:group) do
28
+ instance_double('ExampleGroup', metadata: { block: 'key' }, examples: [])
29
+ end
30
+
31
+ let(:notification) { RCN::GroupNotification.new(group) }
32
+
33
+ it 'passes the example group to SpecSelector#map_group' do
34
+ expect(spec_selector).to receive(:map_group).with(group)
35
+ spec_selector.example_group_started(notification)
36
+ end
37
+
38
+ it 'stores example group in the @groups hash' do
39
+ spec_selector.example_group_started(notification)
40
+ groups = spec_selector.ivar(:@groups)
41
+ expect(groups.values).to include(group)
42
+ end
43
+ end
44
+
45
+ describe '#example_passed' do
46
+ let(:example) { instance_double('Example', execution_result: pass_result) }
47
+ let(:notification) { RCN::ExampleNotification.send(:new, example) }
48
+
49
+ before do
50
+ allow(spec_selector).to receive(:map_example)
51
+ allow(spec_selector).to receive(:check_inclusion_status)
52
+ spec_selector.example_passed(notification)
53
+ end
54
+
55
+ it 'stores example in @passed array' do
56
+ passed = spec_selector.ivar(:@passed)
57
+ expect(passed).to include(example)
58
+ end
59
+
60
+ it 'increments @pass_count' do
61
+ pass_count = spec_selector.ivar(:@pass_count)
62
+ expect(pass_count).to eq(1)
63
+ end
64
+
65
+ it 'updates passing example status display' do
66
+ expect(output).to match(/PASS: \d+/)
67
+ end
68
+ end
69
+
70
+ describe '#example_pending' do
71
+ let(:example) { instance_double('Example', execution_result: pending_result) }
72
+ let(:notification) { RCN::ExampleNotification.send(:new, example) }
73
+
74
+ before do
75
+ allow(spec_selector).to receive(:map_example)
76
+ allow(spec_selector).to receive(:check_inclusion_status)
77
+ spec_selector.example_pending(notification)
78
+ end
79
+
80
+ it 'stores example in @pending array' do
81
+ pending = spec_selector.ivar(:@pending)
82
+ expect(pending).to include(notification.example)
83
+ end
84
+
85
+ it 'increments @pending_count' do
86
+ pending_count = spec_selector.ivar(:@pending_count)
87
+ expect(pending_count).to eq(1)
88
+ end
89
+
90
+ it 'updates pending status display' do
91
+ expect(output).to match(/PENDING: \d+/)
92
+ end
93
+ end
94
+
95
+ describe '#example_failed' do
96
+ let(:example) do
97
+ instance_double('Example',
98
+ full_description: 'full description',
99
+ execution_result: fail_result)
100
+ end
101
+
102
+ let(:notification) { RCN::FailedExampleNotification.send(:new, example) }
103
+
104
+ before do
105
+ allow(spec_selector).to receive(:map_example)
106
+ allow(spec_selector).to receive(:check_inclusion_status)
107
+ spec_selector.example_failed(notification)
108
+ end
109
+
110
+ it 'stores example in @failed array' do
111
+ failed = spec_selector.ivar(:@failed)
112
+ expect(failed).to include(notification.example)
113
+ end
114
+
115
+ it 'increments @fail_count' do
116
+ fail_count = spec_selector.ivar(:@fail_count)
117
+ expect(fail_count).to eq(1)
118
+ end
119
+
120
+ it 'calls #status_count' do
121
+ expect(output).to match(/FAIL: \d+/)
122
+ end
123
+ end
124
+
125
+ describe '#dump_summary', break_loop: true do
126
+ let(:notification) { build(:summary_notification) }
127
+
128
+ context 'when errors outside of examples have occurred' do
129
+ let(:notification) do
130
+ build(:summary_notification, errors_outside_of_examples_count: 2)
131
+ end
132
+
133
+ it 'passes notification to #print_errors' do
134
+ allow(spec_selector).to receive(:print_errors).and_call_original
135
+ spec_selector.dump_summary(notification)
136
+ expect(spec_selector).to have_received(:print_errors).with(notification)
137
+ end
138
+ end
139
+
140
+ context 'when errors outside of examples have not occured' do
141
+ it 'does not call #print_errors' do
142
+ allow(spec_selector).to receive(:print_errors).and_call_original
143
+ spec_selector.dump_summary(notification)
144
+ expect(spec_selector).not_to have_received(:print_errors)
145
+ end
146
+ end
147
+
148
+ context 'when non-error messages are present with no examples' do
149
+ it 'calls #messages only' do
150
+ spec_selector.ivar(:@messages) << 'some message'
151
+ spec_selector.dump_summary(notification)
152
+ end
153
+ end
154
+
155
+ context 'when examples successfully executed' do
156
+ it 'passes notification to #examples_summary' do
157
+ ivar_set(:@map, mixed_map)
158
+ allow(spec_selector).to receive(:examples_summary).and_call_original
159
+ spec_selector.dump_summary(notification)
160
+ expect(spec_selector).to have_received(:examples_summary)
161
+ .with(notification)
162
+ end
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,98 @@
1
+ describe 'SpecSelectorUtil::DataMap' do
2
+ subject(:spec_selector) { SpecSelector.new(StringIO.new) }
3
+
4
+ let(:example_group) { build(:example_group) }
5
+
6
+ let(:map) { spec_selector.ivar(:@map) }
7
+
8
+ describe '#top_level_push' do
9
+ before { spec_selector.top_level_push(example_group) }
10
+
11
+ it 'lazy-initializes @map[:top_level] to an array' do
12
+ expect(map[:top_level]).to be_an(Array)
13
+ end
14
+
15
+ it 'stores example group in @map[:top_level]' do
16
+ expect(map[:top_level]).to include(example_group)
17
+ end
18
+ end
19
+
20
+ # takes takes the metadata hash from an example group or from an
21
+ # example as its argument
22
+ describe '#parent_data' do
23
+ context 'when metadata hash is from an example' do
24
+ let(:example_metadata) { { example_group: example_group.metadata } }
25
+ let(:example) { instance_double('Example', metadata: example_metadata) }
26
+
27
+ it 'returns metadata of the example group to which the example belongs' do
28
+ expect(spec_selector.parent_data(example_metadata))
29
+ .to eq(example_group.metadata)
30
+ end
31
+ end
32
+
33
+ context 'when metadata hash is from an example group' do
34
+ context 'when the example group has a parent group' do
35
+ let(:example_group) do
36
+ instance_double('ExampleGroup', metadata: {
37
+ parent_example_group: { block: :parent_example_block }
38
+ })
39
+ end
40
+
41
+ it 'returns the parent group metadata' do
42
+ expect(spec_selector.parent_data(example_group.metadata))
43
+ .to eq(example_group.metadata[:parent_example_group])
44
+ end
45
+ end
46
+
47
+ context 'when the example group does not have a parent group' do
48
+ it 'returns nil' do
49
+ expect(spec_selector.parent_data(example_group.metadata)).to be_nil
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ describe '#map_group' do
56
+ let(:map) { spec_selector.ivar(:@map) }
57
+
58
+ before { spec_selector.map_group(example_group) }
59
+
60
+ context 'when example group has parent group' do
61
+ let(:example_group) do
62
+ instance_double('ExampleGroup', metadata: {
63
+ parent_example_group: { block: :parent_block }
64
+ })
65
+ end
66
+
67
+ it 'stores the parent block as a key in @map initialized to an array' do
68
+ expect(map[:parent_block]).to be_an(Array)
69
+ end
70
+
71
+ it 'stores the example group in the parent block array' do
72
+ expect(map[:parent_block]).to include(example_group)
73
+ end
74
+ end
75
+
76
+ context 'when example group does not have parent group' do
77
+ it 'passes the example group to #top_level_push' do
78
+ expect(map[:top_level]).to include(example_group)
79
+ end
80
+ end
81
+ end
82
+
83
+ describe '#map_example' do
84
+ let(:example) do
85
+ build(:example, example_group: example_group)
86
+ end
87
+
88
+ before do
89
+ map[example_group.metadata[:block]] = []
90
+ example_group.examples << example
91
+ spec_selector.map_example(example)
92
+ end
93
+
94
+ it 'appends the example to its example group in @map' do
95
+ expect(map[example_group.metadata[:block]]).to include(example)
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,314 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe 'SpecSelectorUtil::DataPresentation' do
4
+ include_context 'shared'
5
+
6
+ let(:spec_selector) { SpecSelector.new(StringIO.new) }
7
+ let(:output) { spec_selector.ivar(:@output).string }
8
+ let(:notification) { build(:summary_notification) }
9
+
10
+ before do
11
+ allow(spec_selector).to receive(:exit_only)
12
+ end
13
+
14
+ describe '#test_data_summary' do
15
+ before do
16
+ allow_methods(:status_count, :print_summary)
17
+ spec_selector.test_data_summary
18
+ end
19
+
20
+ it 'calls #status_count' do
21
+ expect(spec_selector).to have_received(:status_count)
22
+ end
23
+
24
+ it 'calls #print_summary' do
25
+ expect(spec_selector).to have_received(:print_summary)
26
+ end
27
+ end
28
+
29
+ describe '#print_errors' do
30
+ let(:notification) do
31
+ build(:summary_notification, errors_outside_of_examples_count: 2)
32
+ end
33
+
34
+ before do
35
+ spec_selector.ivar(:@messages) << 'some message'
36
+ spec_selector.print_errors(notification)
37
+ end
38
+
39
+ it 'calls #print_messages' do
40
+ expect(output).to match(/[some message]/)
41
+ end
42
+
43
+ it 'passes notification to #errors_summary' do
44
+ expect(output).to match(/[2 errors occurred outside of examples]/)
45
+ end
46
+ end
47
+
48
+ describe '#print_messages' do
49
+ before do
50
+ spec_selector.ivar(:@messages) << 'example message one'
51
+ spec_selector.ivar(:@messages) << 'example message two'
52
+ end
53
+
54
+ it 'prints each message' do
55
+ spec_selector.print_messages
56
+ expect(output).to match(/example message one/)
57
+ expect(output).to match(/example message two/)
58
+ end
59
+ end
60
+
61
+ describe '#examples_summary' do
62
+ before do
63
+ allow(spec_selector).to receive(:status_summary).with(notification)
64
+ allow(spec_selector).to receive(:selector)
65
+ spec_selector.ivar(:@map)[:top_level] = :top_level
66
+ spec_selector.examples_summary(notification)
67
+ end
68
+
69
+ it 'assigns the summary notification to an instance variable' do
70
+ summary_notification = spec_selector.ivar(:@summary_notification)
71
+ expect(summary_notification).to eq(notification)
72
+ end
73
+
74
+ it 'passes the notification object to #status_summary' do
75
+ expect(spec_selector).to have_received(:status_summary).with(notification)
76
+ end
77
+
78
+ it 'sets the value of @list to @map[:top_level]' do
79
+ expect(spec_selector.ivar(:@list)).to eq(:top_level)
80
+ end
81
+
82
+ it 'calls #selector' do
83
+ expect(spec_selector).to have_received(:selector)
84
+ end
85
+ end
86
+
87
+ describe '#errors_summary' do
88
+ it 'prints text indicating number of errors outside examples' do
89
+ allow(notification).to receive(:errors_outside_of_examples_count).and_return(3)
90
+ allow(notification).to receive(:duration)
91
+ allow(notification).to receive(:load_time)
92
+ spec_selector.errors_summary(notification)
93
+ expect(output).to match(/3 errors occurred outside of examples/)
94
+ end
95
+ end
96
+
97
+ describe '#status_count' do
98
+ it 'calls #pass_count' do
99
+ spec_selector.ivar_set(:@pass_count, 5)
100
+ spec_selector.status_count
101
+ expect(output).to match(/PASS: 5/)
102
+ end
103
+
104
+ it 'calls #fail_count' do
105
+ spec_selector.ivar_set(:@fail_count, 3)
106
+ spec_selector.status_count
107
+ expect(output).to match(/FAIL: 3/)
108
+ end
109
+
110
+ context 'when there are pending examples' do
111
+ it 'calls #pending_count' do
112
+ spec_selector.ivar_set(:@pending_count, 2)
113
+ spec_selector.status_count
114
+ expect(output).to match(/PENDING: 2/)
115
+ end
116
+ end
117
+
118
+ context 'when there no pending examples' do
119
+ it 'does not call #pending_count' do
120
+ expect(output).not_to match(/PENDING:/)
121
+ end
122
+ end
123
+ end
124
+
125
+ describe '#print_summary' do
126
+ before do
127
+ spec_selector.ivar_set(:@example_count, 30)
128
+ allow(notification).to receive(:duration).and_return(1.5)
129
+ allow(notification).to receive(:load_time).and_return(2.3)
130
+ spec_selector.status_summary(notification)
131
+ spec_selector.print_summary
132
+ end
133
+
134
+ it 'prints total examples' do
135
+ expect(output).to match(/Total Examples: 30/)
136
+ end
137
+
138
+ it 'prints total time for examples to run' do
139
+ expect(output).to match(/Finished in 1.5 seconds/)
140
+ end
141
+
142
+ it 'prints total time for files to load' do
143
+ expect(output).to match(/Files loaded in 2.3 seconds/)
144
+ end
145
+ end
146
+
147
+ describe '#exclude_passing!' do
148
+ before do
149
+ spec_selector.ivar_set(:@map, mixed_map)
150
+ spec_selector.exclude_passing!
151
+ end
152
+
153
+ it 'sets @active_map to map that excludes all-passing example groups' do
154
+ expect(spec_selector.ivar(:@active_map)[:top_level]).not_to include(pass_group)
155
+ expect(spec_selector.ivar(:@active_map)[:top_level]).to include(fail_group)
156
+ end
157
+
158
+ it 'sets @exclude_passing to true' do
159
+ expect(spec_selector.ivar(:@exclude_passing)).to be true
160
+ end
161
+ end
162
+
163
+ describe '#include_passing!' do
164
+ before do
165
+ spec_selector.ivar_set(:@map, mixed_map)
166
+ spec_selector.exclude_passing!
167
+ end
168
+
169
+ it 'sets @active_map to @map' do
170
+ expect(spec_selector.ivar(:@active)).not_to eq(spec_selector.ivar(:@map))
171
+ spec_selector.include_passing!
172
+ expect(spec_selector.ivar(:@active_map)).to eq(spec_selector.ivar(:@map))
173
+ end
174
+
175
+ it 'sets @exclude_passing to false' do
176
+ expect(spec_selector.ivar(:@exclude_passing)).to be true
177
+ spec_selector.include_passing!
178
+ expect(spec_selector.ivar(:@exclude_passing)).to be false
179
+ end
180
+ end
181
+
182
+ describe '#toggle_passing' do
183
+ before do
184
+ allow_methods(:display_list, :navigate)
185
+ map = mixed_map
186
+ list = mixed_list
187
+ group = pass_group
188
+ ivars_set(:@map => map, :@list => list, :@selected => group)
189
+ end
190
+
191
+ context 'when displayed list includes all-passing example groups' do
192
+ it 'removes all-passing example groups from displayed list' do
193
+ spec_selector.toggle_passing
194
+ expect(spec_selector.ivar(:@list)).not_to include(pass_group)
195
+ end
196
+ end
197
+
198
+ context 'when all-passing example groups are already excluded' do
199
+ it 'reverses the exclusion of all-passing example groups' do
200
+ spec_selector.toggle_passing
201
+ expect(spec_selector.ivar(:@list)).not_to include(pass_group)
202
+ spec_selector.toggle_passing
203
+ expect(spec_selector.ivar(:@list)).to include(pass_group)
204
+ end
205
+ end
206
+ end
207
+
208
+ describe '#status_summary' do
209
+ let(:summary) { spec_selector.ivar(:@summary) }
210
+
211
+ before do
212
+ spec_selector.ivar_set(:@example_count, 25)
213
+ spec_selector.status_summary(notification)
214
+ end
215
+
216
+ it 'stores message indicating example total in @summary' do
217
+ expect(summary).to include(/Total Examples: 25/)
218
+ end
219
+
220
+ it 'stores message indicating total time to run examples in @summary' do
221
+ expect(summary).to include(/Finished in 1.5 seconds/)
222
+ end
223
+
224
+ it 'stores message indicating total time to load files in @summary' do
225
+ expect(summary).to include(/Files loaded in 0.5 seconds/)
226
+ end
227
+ end
228
+
229
+ describe '#display_list' do
230
+ before { allow(spec_selector).to receive(:test_data_summary) }
231
+
232
+ context 'when all examples have passed' do
233
+ it 'displays message indicating that all examples have passed' do
234
+ ivars_set(:@map => all_passing_map, :@list => [pass_group, pass_group])
235
+ allow(spec_selector).to receive(:all_passing?).and_return(true)
236
+ spec_selector.display_list
237
+ expect(output).to match(/ALL EXAMPLES PASSED/)
238
+ end
239
+ end
240
+
241
+ context 'when not all examples have passed' do
242
+ it 'does not display message indicating that all examples have passed' do
243
+ ivars_set(:@map => mixed_map, :@list => mixed_list)
244
+ allow(spec_selector).to receive(:all_passing?).and_return(false)
245
+ spec_selector.display_list
246
+ expect(output).not_to match(/ALL EXAMPLES PASSED/)
247
+ end
248
+ end
249
+
250
+ it 'displays list of example groups or examples in current level' do
251
+ ivars_set(:@map => mixed_map, :@list => mixed_list)
252
+ spec_selector.display_list
253
+ expect(output).to match(/[passing example group]/)
254
+ expect(output).to match(/[non-passing example group]/)
255
+ end
256
+ end
257
+
258
+ describe '#display_example' do
259
+ before { allow_methods(:test_data_summary, :navigate) }
260
+
261
+ context 'when example is pending' do
262
+ it 'displays example summary' do
263
+ summary_settings(pending_example)
264
+ spec_selector.display_example
265
+ expect(output).to match(/[pending example]/)
266
+ end
267
+ end
268
+
269
+ context 'when example is failed' do
270
+ it 'displays example summary' do
271
+ summary_settings(failed_example)
272
+ spec_selector.display_example
273
+ expect(output).to match(/[failed example]/)
274
+ end
275
+ end
276
+
277
+ context 'when example is passing' do
278
+ it 'displays text indicating the example passed' do
279
+ ivars_set(:@selected => passing_example, :@passed => [passing_example])
280
+ spec_selector.display_example
281
+ expect(output).to match(/[PASSED]/)
282
+ end
283
+ end
284
+ end
285
+
286
+ describe '#example_list' do
287
+ it 'returns example list corresponding to execution result status' do
288
+ spec_selector.ivar_set(:@selected, failed_example)
289
+ expect(spec_selector.example_list).to include(@failed)
290
+ end
291
+
292
+ context 'when example failed' do
293
+ let(:notification) do
294
+ build(:failed_example_notification, example: failed_example)
295
+ end
296
+
297
+ it 'returns failure summary of selected example' do
298
+ summary_settings(failed_example)
299
+ expect(spec_selector.example_list).to include(notification)
300
+ end
301
+ end
302
+
303
+ context 'when example is pending' do
304
+ let(:notification) do
305
+ build(:skipped_example_notification, example: pending_example)
306
+ end
307
+
308
+ it 'returns pending summary of selected example' do
309
+ summary_settings(pending_example)
310
+ expect(spec_selector.example_list).to include(notification)
311
+ end
312
+ end
313
+ end
314
+ end