spec_selector 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/README.md +48 -0
- data/lib/spec_selector.rb +92 -0
- data/lib/spec_selector/data_map.rb +38 -0
- data/lib/spec_selector/data_presentation.rb +179 -0
- data/lib/spec_selector/format.rb +99 -0
- data/lib/spec_selector/helpers.rb +63 -0
- data/lib/spec_selector/initialize.rb +83 -0
- data/lib/spec_selector/instructions.rb +139 -0
- data/lib/spec_selector/scripts/rerun.sh +40 -0
- data/lib/spec_selector/state.rb +142 -0
- data/lib/spec_selector/terminal.rb +45 -0
- data/lib/spec_selector/ui.rb +169 -0
- data/license.md +8 -0
- data/spec/factories.rb +89 -0
- data/spec/shared.rb +145 -0
- data/spec/spec_helper.rb +47 -0
- data/spec/spec_selector_spec.rb +165 -0
- data/spec/spec_selector_util/data_map_spec.rb +98 -0
- data/spec/spec_selector_util/data_presentation_spec.rb +314 -0
- data/spec/spec_selector_util/format_spec.rb +213 -0
- data/spec/spec_selector_util/helpers_spec.rb +222 -0
- data/spec/spec_selector_util/initialize_spec.rb +93 -0
- data/spec/spec_selector_util/ui_spec.rb +459 -0
- metadata +96 -0
- metadata.gz.sig +1 -0
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SpecSelectorUtil
|
4
|
+
# The Helpers module contains helper methods shared across multiple
|
5
|
+
# concerns.
|
6
|
+
module Helpers
|
7
|
+
def all_passing?
|
8
|
+
(@pending_count + @fail_count).zero? && @pass_count.positive?
|
9
|
+
end
|
10
|
+
|
11
|
+
def none_passing?
|
12
|
+
(@pending_count + @fail_count).positive? && @pass_count.zero?
|
13
|
+
end
|
14
|
+
|
15
|
+
def all_passed?(examples)
|
16
|
+
examples.all? { |example| example.execution_result.status == :passed }
|
17
|
+
end
|
18
|
+
|
19
|
+
def any_failed?(examples)
|
20
|
+
examples.any? { |example| example.execution_result.status == :failed }
|
21
|
+
end
|
22
|
+
|
23
|
+
def any_pending?(examples)
|
24
|
+
examples.any? { |example| example.execution_result.status == :pending }
|
25
|
+
end
|
26
|
+
|
27
|
+
def example?(item)
|
28
|
+
item.is_a?(RSpec::Core::Example)
|
29
|
+
end
|
30
|
+
|
31
|
+
def status(example)
|
32
|
+
example.execution_result.status
|
33
|
+
end
|
34
|
+
|
35
|
+
def description_mode?
|
36
|
+
@filter_mode == :description
|
37
|
+
end
|
38
|
+
|
39
|
+
def location_mode?
|
40
|
+
@filter_mode == :location
|
41
|
+
end
|
42
|
+
|
43
|
+
def empty_line
|
44
|
+
@output.puts "\n"
|
45
|
+
end
|
46
|
+
|
47
|
+
def top_level?
|
48
|
+
@list == @active_map[:top_level]
|
49
|
+
end
|
50
|
+
|
51
|
+
def filter_view?
|
52
|
+
@list == @inclusion_filter
|
53
|
+
end
|
54
|
+
|
55
|
+
def current_path
|
56
|
+
File.dirname(__FILE__)
|
57
|
+
end
|
58
|
+
|
59
|
+
def one_liner?(example)
|
60
|
+
example.metadata[:description_args].empty?
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SpecSelectorUtil
|
4
|
+
# The Initialize module contains methods that initialize specific sets of
|
5
|
+
# instance variables for the SpecSelector instance.
|
6
|
+
module Initialize
|
7
|
+
def init_example_store
|
8
|
+
@failed = []
|
9
|
+
@passed = []
|
10
|
+
@pending = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def init_summaries
|
14
|
+
@failure_summaries = {}
|
15
|
+
@pending_summaries = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def init_counters
|
19
|
+
@pass_count = 0
|
20
|
+
@fail_count = 0
|
21
|
+
@pending_count = 0
|
22
|
+
end
|
23
|
+
|
24
|
+
def init_pass_inclusion
|
25
|
+
@exclude_passing = false
|
26
|
+
end
|
27
|
+
|
28
|
+
def init_map
|
29
|
+
@groups = {}
|
30
|
+
@map = {}
|
31
|
+
@active_map = @map
|
32
|
+
@list = nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def init_selector
|
36
|
+
@selected = nil
|
37
|
+
@selector_index = 0
|
38
|
+
end
|
39
|
+
|
40
|
+
def get_locations
|
41
|
+
if File.exist?(@locations_file)
|
42
|
+
locations = File.open(@locations_file)
|
43
|
+
@last_run_locations = JSON.load(locations)
|
44
|
+
@filter_mode = :location
|
45
|
+
else
|
46
|
+
@last_run_locations = []
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def get_descriptions
|
51
|
+
if File.exist?(@descriptions_file)
|
52
|
+
included = File.open(@descriptions_file)
|
53
|
+
@last_run_descriptions = JSON.load(included)
|
54
|
+
else
|
55
|
+
@last_run_descriptions = []
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def init_filter
|
60
|
+
inclusion_filter_path = "#{current_path}/inclusion_filter"
|
61
|
+
Dir.mkdir(inclusion_filter_path) unless Dir.exist?(inclusion_filter_path)
|
62
|
+
@descriptions_file = "#{current_path}/inclusion_filter/descriptions.json"
|
63
|
+
@locations_file = "#{current_path}/inclusion_filter/locations.json"
|
64
|
+
@inclusion_filter = []
|
65
|
+
@filter_mode = :description
|
66
|
+
get_descriptions
|
67
|
+
get_locations
|
68
|
+
end
|
69
|
+
|
70
|
+
def initialize_all
|
71
|
+
@messages = []
|
72
|
+
@notices = []
|
73
|
+
init_example_store
|
74
|
+
init_summaries
|
75
|
+
init_counters
|
76
|
+
init_pass_inclusion
|
77
|
+
init_map
|
78
|
+
init_selector
|
79
|
+
init_filter
|
80
|
+
@instructions = false
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SpecSelectorUtil
|
4
|
+
# The Instructions module contains methods used to render the
|
5
|
+
# appropriate user instructions.
|
6
|
+
module Instructions
|
7
|
+
def basic_instructions
|
8
|
+
i_for_instructions
|
9
|
+
up_down_select_instructions
|
10
|
+
q_to_exit
|
11
|
+
end
|
12
|
+
|
13
|
+
def all_passed_message
|
14
|
+
bold("ALL EXAMPLES PASSED\n")
|
15
|
+
end
|
16
|
+
|
17
|
+
def empty_filter_notice
|
18
|
+
notice = '**********FILTER EMPTY**********'
|
19
|
+
row = term_width / 2 - notice.length / 2
|
20
|
+
position_cursor(1, row)
|
21
|
+
@output.puts notice
|
22
|
+
reset_cursor
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def display_filter_mode
|
27
|
+
unless @inclusion_filter.empty?
|
28
|
+
notice = "FILTER MODE: #{@filter_mode.to_s.upcase}"
|
29
|
+
col = term_width / 2 - notice.length / 2
|
30
|
+
position_cursor(1, col)
|
31
|
+
italicize notice
|
32
|
+
reset_cursor
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def back_instructions
|
37
|
+
back_inst = 'Press [back] to view to parent group list'
|
38
|
+
escape_inst = 'Press [escape] to view to top-level group list'
|
39
|
+
|
40
|
+
[back_inst, escape_inst].each do |inst|
|
41
|
+
if @instructions
|
42
|
+
bold(inst)
|
43
|
+
empty_line
|
44
|
+
else
|
45
|
+
@output.puts inst
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def q_to_exit
|
51
|
+
@output.puts 'Press Q to exit'
|
52
|
+
end
|
53
|
+
|
54
|
+
def view_other_examples(status)
|
55
|
+
verb = (status == :passed ? 'passing' : status.to_s)
|
56
|
+
@output.puts "Press ↑ or ↓ to view other #{verb} examples"
|
57
|
+
end
|
58
|
+
|
59
|
+
def filter_pass_instructions
|
60
|
+
verb = @exclude_passing ? 'show' : 'hide'
|
61
|
+
bold "Press P to #{verb} passing examples in current set"
|
62
|
+
end
|
63
|
+
|
64
|
+
def i_for_instructions
|
65
|
+
@output.puts 'Press I to view full instructions'
|
66
|
+
end
|
67
|
+
|
68
|
+
def up_down_select_instructions
|
69
|
+
up_down_inst = 'Press ↑ or ↓ to navigate list' if @list.count > 1
|
70
|
+
select_inst = 'press [enter] to select'
|
71
|
+
|
72
|
+
[up_down_inst, select_inst].each do |inst|
|
73
|
+
if @instructions
|
74
|
+
bold(inst)
|
75
|
+
empty_line
|
76
|
+
else
|
77
|
+
@output.puts inst
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def view_instructions_page
|
83
|
+
@instructions = true
|
84
|
+
open_alt_buffer
|
85
|
+
|
86
|
+
unless @failed.empty? || @selected == @failed.first
|
87
|
+
top_fail_text
|
88
|
+
empty_line
|
89
|
+
end
|
90
|
+
|
91
|
+
unless all_passing? || none_passing?
|
92
|
+
filter_pass_instructions
|
93
|
+
empty_line
|
94
|
+
end
|
95
|
+
|
96
|
+
up_down_select_instructions
|
97
|
+
back_instructions unless top_level?
|
98
|
+
bold('Press R to rerun examples with filter selection')
|
99
|
+
empty_line
|
100
|
+
bold('Press F to rerun only failed examples')
|
101
|
+
empty_line
|
102
|
+
bold('Press SHIFT + T to rerun only the top failed example')
|
103
|
+
empty_line
|
104
|
+
bold('Press M to include or remove selected item from run filter')
|
105
|
+
empty_line
|
106
|
+
|
107
|
+
if @inclusion_filter.size.positive?
|
108
|
+
bold('Press C to clear filter')
|
109
|
+
empty_line
|
110
|
+
bold('Press A to clear filter and rerun all examples')
|
111
|
+
empty_line
|
112
|
+
end
|
113
|
+
|
114
|
+
bold('Press I to exit instructions')
|
115
|
+
empty_line
|
116
|
+
bold('Press Q to quit')
|
117
|
+
bind_input
|
118
|
+
end
|
119
|
+
|
120
|
+
def top_fail_text
|
121
|
+
bold 'Press T to view top failed example'
|
122
|
+
end
|
123
|
+
|
124
|
+
def exit_instruction_page
|
125
|
+
@instructions = false
|
126
|
+
close_alt_buffer
|
127
|
+
end
|
128
|
+
|
129
|
+
def example_summary_instructions
|
130
|
+
i_for_instructions
|
131
|
+
@output.puts 'Press M to remove from filter' if @selected.metadata[:include]
|
132
|
+
@output.puts 'Press C to clear filter' unless @inclusion_filter.empty?
|
133
|
+
top_fail_text unless @failed.empty? || @selected == @failed.first
|
134
|
+
back_instructions
|
135
|
+
q_to_exit
|
136
|
+
empty_line
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
cd $2
|
4
|
+
args=("$@")
|
5
|
+
len=${args[@]: -1}
|
6
|
+
let start=-$len-1
|
7
|
+
|
8
|
+
filter() {
|
9
|
+
descriptions=()
|
10
|
+
for arg in "${args[@]: $start: $len}"
|
11
|
+
do description=$(echo $arg | sed 's/^\[//;s/\]$//;s/,$//')
|
12
|
+
for (( i=0; i<${#description}; i++ )); do
|
13
|
+
if [ "${description:$i:1}" = "'" ]; then
|
14
|
+
description="\"$description\""
|
15
|
+
escaped=true
|
16
|
+
break
|
17
|
+
fi
|
18
|
+
done
|
19
|
+
|
20
|
+
if ! [ $escaped ]; then
|
21
|
+
description="'$description"
|
22
|
+
description="$description'"
|
23
|
+
fi
|
24
|
+
|
25
|
+
descriptions+=" -e "
|
26
|
+
descriptions+=$description
|
27
|
+
done
|
28
|
+
}
|
29
|
+
|
30
|
+
let length=$#+$start-2
|
31
|
+
|
32
|
+
if [ $len -eq 0 ]
|
33
|
+
then
|
34
|
+
eval "${args[@]:2:$length}"
|
35
|
+
else
|
36
|
+
filter
|
37
|
+
eval "${args[@]:2:$length} ${descriptions[@]}"
|
38
|
+
fi
|
39
|
+
|
40
|
+
kill $1
|
@@ -0,0 +1,142 @@
|
|
1
|
+
module SpecSelectorUtil
|
2
|
+
module State
|
3
|
+
def rerun
|
4
|
+
prepare_rerun
|
5
|
+
descriptions, marker = appended_arguments
|
6
|
+
rerun_script = current_path + '/scripts/rerun.sh'
|
7
|
+
prepend = [rerun_script, Process.pid, Dir.pwd].join(' ')
|
8
|
+
Signal.trap('TERM') { clear_frame; exit }
|
9
|
+
system("#{prepend} #{$0} #{@rerun_arguments} #{descriptions} #{marker}")
|
10
|
+
end
|
11
|
+
|
12
|
+
def filter_include(item = @selected)
|
13
|
+
@filter_mode = :location if one_liner?(item)
|
14
|
+
item.metadata[:include] = true
|
15
|
+
@inclusion_filter << item
|
16
|
+
end
|
17
|
+
|
18
|
+
def prepare_rerun
|
19
|
+
display_rerun
|
20
|
+
persist_filter
|
21
|
+
reset_arguments
|
22
|
+
prepare_location_arguments if location_mode?
|
23
|
+
delete_filter_data if @inclusion_filter.empty?
|
24
|
+
end
|
25
|
+
|
26
|
+
def display_rerun
|
27
|
+
close_alt_buffer if @instructions
|
28
|
+
clear_frame
|
29
|
+
italicize('running examples...')
|
30
|
+
end
|
31
|
+
|
32
|
+
def appended_arguments
|
33
|
+
return [nil, 0] if location_mode?
|
34
|
+
|
35
|
+
[prepare_description_arguments, @filtered_descriptions.count]
|
36
|
+
end
|
37
|
+
|
38
|
+
def persist_filter
|
39
|
+
persist_descriptions
|
40
|
+
persist_locations if location_mode?
|
41
|
+
end
|
42
|
+
|
43
|
+
def run_only_fails
|
44
|
+
return if @failed.empty?
|
45
|
+
|
46
|
+
@inclusion_filter = []
|
47
|
+
@failed.each { |example| filter_include(example) }
|
48
|
+
rerun
|
49
|
+
end
|
50
|
+
|
51
|
+
def rerun_all
|
52
|
+
@inclusion_filter = []
|
53
|
+
rerun
|
54
|
+
end
|
55
|
+
|
56
|
+
def filter_remove
|
57
|
+
@inclusion_filter -= [@selected]
|
58
|
+
@selected.metadata[:include] = nil
|
59
|
+
@filter_mode = :descripton unless @inclusion_filter.any? { |item| one_liner?(item) }
|
60
|
+
end
|
61
|
+
|
62
|
+
def persist_descriptions
|
63
|
+
@filtered_descriptions = @inclusion_filter.map do |item|
|
64
|
+
item.metadata[:full_description]
|
65
|
+
end
|
66
|
+
|
67
|
+
filter = @filtered_descriptions.to_json
|
68
|
+
File.write(@descriptions_file, filter)
|
69
|
+
end
|
70
|
+
|
71
|
+
def delete_filter_data
|
72
|
+
[@descriptions_file, @locations_file].each do |file|
|
73
|
+
File.delete(file) if File.exist?(file)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def reset_arguments
|
78
|
+
remove_old_descriptions
|
79
|
+
remove_old_locations
|
80
|
+
end
|
81
|
+
|
82
|
+
def remove_old_locations
|
83
|
+
return if @last_run_locations.empty?
|
84
|
+
|
85
|
+
@last_run_locations.each { |loc| @rerun_arguments.slice!(loc) }
|
86
|
+
end
|
87
|
+
|
88
|
+
def remove_old_descriptions
|
89
|
+
old_descriptions = @last_run_descriptions.map { |desc| "-e #{desc}" }
|
90
|
+
@rerun_arguments = ARGV.join(' ')
|
91
|
+
old_descriptions.each { |desc| @rerun_arguments.slice!(desc) }
|
92
|
+
end
|
93
|
+
|
94
|
+
def persist_locations
|
95
|
+
@filtered_locations = @inclusion_filter.map { |item| item.location }
|
96
|
+
locations = @filtered_locations.to_json
|
97
|
+
File.write(@locations_file, locations)
|
98
|
+
end
|
99
|
+
|
100
|
+
def prepare_location_arguments
|
101
|
+
@rerun_arguments += " #{@filtered_locations.join(' ')}"
|
102
|
+
end
|
103
|
+
|
104
|
+
def prepare_description_arguments
|
105
|
+
return if @inclusion_filter.empty?
|
106
|
+
|
107
|
+
contains_singles = @filtered_descriptions.select { |desc| desc.include?("'") }
|
108
|
+
included = @filtered_descriptions - contains_singles
|
109
|
+
return contains_singles.to_s if included.empty?
|
110
|
+
|
111
|
+
included = included.to_s.gsub('"', "'")
|
112
|
+
return included if contains_singles.empty?
|
113
|
+
|
114
|
+
contains_singles.map!(&:dump)
|
115
|
+
included[-1] = ", #{contains_singles.join(', ')}]"
|
116
|
+
included
|
117
|
+
end
|
118
|
+
|
119
|
+
def clear_filter
|
120
|
+
return if @inclusion_filter.empty?
|
121
|
+
|
122
|
+
@inclusion_filter.each { |item| item.metadata[:include] = nil }
|
123
|
+
@inclusion_filter = []
|
124
|
+
return if @instructions
|
125
|
+
|
126
|
+
@example_display ? display_example : top_level_list
|
127
|
+
end
|
128
|
+
|
129
|
+
def top_fail!
|
130
|
+
@inclusion_filter = []
|
131
|
+
filter_include(@failed.first)
|
132
|
+
rerun
|
133
|
+
end
|
134
|
+
|
135
|
+
def check_inclusion_status(item)
|
136
|
+
if @last_run_descriptions.include?(item.metadata[:full_description])
|
137
|
+
@inclusion_filter << item
|
138
|
+
item.metadata[:include] = true
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|