spec_selector 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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