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,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