tconsole-rails4 2.2.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 +7 -0
- data/.gitignore +6 -0
- data/.gitmodules +3 -0
- data/Gemfile +8 -0
- data/README.md +293 -0
- data/Rakefile +1 -0
- data/bin/rconsole +5 -0
- data/bin/tconsole +5 -0
- data/cibuild +4 -0
- data/lib/tconsole.rb +18 -0
- data/lib/tconsole/config.rb +251 -0
- data/lib/tconsole/console.rb +150 -0
- data/lib/tconsole/minitest_handler.rb +93 -0
- data/lib/tconsole/minitest_reporter.rb +207 -0
- data/lib/tconsole/minitest_server.rb +123 -0
- data/lib/tconsole/reporter.rb +113 -0
- data/lib/tconsole/rspec_server.rb +108 -0
- data/lib/tconsole/runner.rb +139 -0
- data/lib/tconsole/server.rb +174 -0
- data/lib/tconsole/test_result.rb +65 -0
- data/lib/tconsole/util.rb +33 -0
- data/lib/tconsole/version.rb +3 -0
- data/spec/config_spec.rb +94 -0
- data/spec/runner_spec.rb +38 -0
- data/spec/sample_config +3 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/util_spec.rb +26 -0
- data/tconsole.gemspec +27 -0
- data/test/lib/tconsole/config_test.rb +15 -0
- data/test/lib/tconsole_test.rb +6 -0
- data/test/test_helper.rb +8 -0
- metadata +121 -0
@@ -0,0 +1,150 @@
|
|
1
|
+
module TConsole
|
2
|
+
class Console
|
3
|
+
KNOWN_COMMANDS = ["exit", "reload", "help", "info", "!failed", "!timings", "set"]
|
4
|
+
|
5
|
+
attr_accessor :config, :reporter
|
6
|
+
|
7
|
+
def initialize(config, reporter)
|
8
|
+
self.config = config
|
9
|
+
self.reporter = reporter
|
10
|
+
read_history
|
11
|
+
end
|
12
|
+
|
13
|
+
def define_autocomplete(pipe_server)
|
14
|
+
Readline.completion_append_character = ""
|
15
|
+
|
16
|
+
# Proc for helping us figure out autocompletes
|
17
|
+
Readline.completion_proc = Proc.new do |str|
|
18
|
+
known_commands = KNOWN_COMMANDS.grep(/^#{Regexp.escape(str)}/)
|
19
|
+
known_commands.concat(@config.file_sets.keys.grep(/^#{Regexp.escape(str)}/))
|
20
|
+
|
21
|
+
known_elements = []
|
22
|
+
unless pipe_server.nil?
|
23
|
+
known_elements = send_message(pipe_server, :autocomplete, str)
|
24
|
+
end
|
25
|
+
|
26
|
+
known_commands.concat(known_elements)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns true if the app should keep running, false otherwise
|
31
|
+
def read_and_execute(pipe_server)
|
32
|
+
prompt = "#{config.app}> "
|
33
|
+
|
34
|
+
trap("SIGTSTP", "SYSTEM_DEFAULT")
|
35
|
+
trap("SIGCONT") do
|
36
|
+
print prompt
|
37
|
+
end
|
38
|
+
|
39
|
+
# Run any commands that have been passed
|
40
|
+
result = process_command(pipe_server, @config.run_command)
|
41
|
+
@config.run_command = ""
|
42
|
+
if result == :exit || @config.once
|
43
|
+
return false
|
44
|
+
elsif result == :reload
|
45
|
+
return true
|
46
|
+
end
|
47
|
+
|
48
|
+
define_autocomplete(pipe_server)
|
49
|
+
|
50
|
+
# The command entry loop
|
51
|
+
while command = Readline.readline(prompt, false)
|
52
|
+
command.strip!
|
53
|
+
result = process_command(pipe_server, command)
|
54
|
+
|
55
|
+
if result == :exit
|
56
|
+
return false
|
57
|
+
elsif result == :reload
|
58
|
+
return true
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
send_message(:stop)
|
63
|
+
false
|
64
|
+
end
|
65
|
+
|
66
|
+
# Public: Process a command however it needs to be handled.
|
67
|
+
#
|
68
|
+
# pipe_server - The pipe server we're working with
|
69
|
+
# command - The command we need to parse and handle
|
70
|
+
def process_command(pipe_server, command)
|
71
|
+
args = Shellwords.shellwords(command)
|
72
|
+
|
73
|
+
# save the command unless we're exiting or repeating the last command
|
74
|
+
unless args[0] == "exit" || (Readline::HISTORY.length > 0 && Readline::HISTORY[Readline::HISTORY.length - 1] == command)
|
75
|
+
Readline::HISTORY << command
|
76
|
+
end
|
77
|
+
|
78
|
+
if command == ""
|
79
|
+
# do nothing
|
80
|
+
elsif args[0] == "exit"
|
81
|
+
send_message(pipe_server, :stop)
|
82
|
+
return :exit
|
83
|
+
elsif args[0] == "reload"
|
84
|
+
send_message(pipe_server, :stop)
|
85
|
+
return :reload
|
86
|
+
elsif args[0] == "help"
|
87
|
+
reporter.help_message
|
88
|
+
elsif args[0] == "!failed"
|
89
|
+
send_message(pipe_server, :run_failed)
|
90
|
+
elsif args[0] == "!timings"
|
91
|
+
send_message(pipe_server, :show_performance, args[1])
|
92
|
+
elsif args[0] == "info"
|
93
|
+
send_message(pipe_server, :run_info)
|
94
|
+
elsif args[0] == "set"
|
95
|
+
send_message(pipe_server, :set, args[1], args[2])
|
96
|
+
elsif args[0].start_with?(".")
|
97
|
+
shell_command(command[1, command.length - 1])
|
98
|
+
elsif @config.file_sets.has_key?(args[0])
|
99
|
+
send_message(pipe_server, :run_file_set, args[0])
|
100
|
+
else
|
101
|
+
send_message(pipe_server, :run_all_tests, args)
|
102
|
+
end
|
103
|
+
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
|
107
|
+
def send_message(pipe_server, message, *args)
|
108
|
+
pipe_server.write({:action => message.to_sym, :args => args})
|
109
|
+
pipe_server.read
|
110
|
+
end
|
111
|
+
|
112
|
+
# Internal: Runs a shell command on the console and outputs the results.
|
113
|
+
def shell_command(command)
|
114
|
+
system(command)
|
115
|
+
|
116
|
+
result = $?
|
117
|
+
|
118
|
+
reporter.info
|
119
|
+
if result.exitstatus == 0
|
120
|
+
reporter.exclaim("Command exited with status code: 0")
|
121
|
+
else
|
122
|
+
reporter.error("Command exited with status code: #{result.exitstatus}")
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def history_file
|
127
|
+
File.join(ENV['HOME'], ".tconsole_history")
|
128
|
+
end
|
129
|
+
|
130
|
+
# Stores last 50 items in history to $HOME/.tconsole_history
|
131
|
+
def store_history
|
132
|
+
if ENV['HOME']
|
133
|
+
File.open(history_file, "w") do |f|
|
134
|
+
Readline::HISTORY.to_a.reverse[0..49].each do |item|
|
135
|
+
f.puts(item)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Loads history from past sessions
|
142
|
+
def read_history
|
143
|
+
if ENV['HOME'] && File.exist?(history_file)
|
144
|
+
File.readlines(history_file).reverse.each do |line|
|
145
|
+
Readline::HISTORY << line
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'minitest'
|
3
|
+
module TConsole
|
4
|
+
class MiniTestHandler
|
5
|
+
attr_accessor :interrupted
|
6
|
+
|
7
|
+
def initialize(config)
|
8
|
+
@reporter = ::TConsole::MinitestReporter.new
|
9
|
+
@reporter.tc_results.suite_counts = config.cached_suite_counts
|
10
|
+
@reporter.tc_results.elements = config.cached_elements
|
11
|
+
@interrupted = false
|
12
|
+
end
|
13
|
+
|
14
|
+
def match_and_run(match_patterns, config)
|
15
|
+
|
16
|
+
suites = Minitest::Runnable.runnables
|
17
|
+
|
18
|
+
@reporter.ready
|
19
|
+
suites.each do |suite|
|
20
|
+
suite_id = @reporter.tc_results.elements[suite.to_s]
|
21
|
+
|
22
|
+
suite_printed = false
|
23
|
+
suite.methods_matching(/^test/).map do |method|
|
24
|
+
if @interrupted
|
25
|
+
return @reporter.tc_results
|
26
|
+
end
|
27
|
+
|
28
|
+
id = @reporter.tc_results.add_element(suite, method)
|
29
|
+
|
30
|
+
unless match_patterns.nil?
|
31
|
+
match = match_patterns.find do |pattern|
|
32
|
+
pattern == suite.to_s ||
|
33
|
+
pattern == "#{suite.to_s}##{method.to_s}" ||
|
34
|
+
pattern == suite_id.to_s ||
|
35
|
+
pattern == id
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
if !suite_printed && (match_patterns.nil? || match_patterns.empty? || !match.nil?)
|
40
|
+
print(::Term::ANSIColor.cyan, suite, ::Term::ANSIColor.reset,
|
41
|
+
::Term::ANSIColor.magenta, " #{suite_id} \n")
|
42
|
+
suite_printed = true
|
43
|
+
end
|
44
|
+
|
45
|
+
if match_patterns.nil? || match_patterns.empty? || !match.nil?
|
46
|
+
@reporter.current_element_id = id
|
47
|
+
# TODO мб понтово свой метод run в минитест захуярить,
|
48
|
+
# который список suites принимать будет
|
49
|
+
Minitest::Runnable.run_one_method(suite, method, @reporter)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
if suite_printed
|
54
|
+
puts
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
[@reporter.tc_results, @reporter]
|
59
|
+
end
|
60
|
+
|
61
|
+
# Preloads our element cache for autocompletion. Assumes tests are already loaded
|
62
|
+
def self.preload_elements
|
63
|
+
patch_minitest
|
64
|
+
results = TConsole::TestResult.new
|
65
|
+
suites = Minitest::Runnable.runnables
|
66
|
+
suites.each do |suite|
|
67
|
+
suite.methods_matching(/^test/).map do |method|
|
68
|
+
id = results.add_element(suite, method)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
results
|
73
|
+
end
|
74
|
+
|
75
|
+
# We're basically breaking Minitest autorun here, since we want to manually run our
|
76
|
+
# tests and Rails relies on autorun
|
77
|
+
# A big reason for the need for this is that we're trying to work in the Rake environment
|
78
|
+
# rather than rebuilding all of the code in Rake just to get test prep happening
|
79
|
+
# correctly.
|
80
|
+
def self.patch_minitest
|
81
|
+
Minitest.class_eval do
|
82
|
+
class << self
|
83
|
+
alias_method :old_run, :run
|
84
|
+
def run(args = [])
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Make sure that output is only colored when it should be
|
93
|
+
Term::ANSIColor::coloring = STDOUT.isatty
|
@@ -0,0 +1,207 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'minitest'
|
3
|
+
require 'ansi/code'
|
4
|
+
|
5
|
+
module TConsole
|
6
|
+
# Turn-like reporter that reads like a spec.
|
7
|
+
#
|
8
|
+
# Based upon
|
9
|
+
# https://github.com/kern/minitest-reporters/blob/master/lib%2Fminitest%2Freporters%2Fbase_reporter.rb and spec_reporter
|
10
|
+
class BaseReporter < Minitest::StatisticsReporter
|
11
|
+
attr_accessor :tests
|
12
|
+
|
13
|
+
def initialize(options={})
|
14
|
+
super($stdout, options)
|
15
|
+
self.tests = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_defaults(defaults)
|
19
|
+
self.options = defaults.merge(options)
|
20
|
+
end
|
21
|
+
|
22
|
+
# called by our own before hooks
|
23
|
+
def before_test(test)
|
24
|
+
last_test = tests.last
|
25
|
+
if last_test.class != test.class
|
26
|
+
after_suite(last_test.class) if last_test
|
27
|
+
before_suite(test.class)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def record(test)
|
32
|
+
super
|
33
|
+
tests << test
|
34
|
+
end
|
35
|
+
|
36
|
+
# called by our own after hooks
|
37
|
+
def after_test(test)
|
38
|
+
end
|
39
|
+
|
40
|
+
def report
|
41
|
+
super
|
42
|
+
after_suite(tests.last.class)
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
|
47
|
+
def after_suite(test)
|
48
|
+
end
|
49
|
+
|
50
|
+
def before_suite(test)
|
51
|
+
end
|
52
|
+
|
53
|
+
def result(test)
|
54
|
+
if test.error?
|
55
|
+
:error
|
56
|
+
elsif test.skipped?
|
57
|
+
:skip
|
58
|
+
elsif test.failure
|
59
|
+
:fail
|
60
|
+
else
|
61
|
+
:pass
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def print_colored_status(test)
|
66
|
+
if test.passed?
|
67
|
+
print(green { pad_mark( result(test).to_s.upcase ) })
|
68
|
+
elsif test.skipped?
|
69
|
+
print(yellow { pad_mark( result(test).to_s.upcase ) })
|
70
|
+
else
|
71
|
+
print(red { pad_mark( result(test).to_s.upcase ) })
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def total_time
|
76
|
+
super || Time.now - start_time
|
77
|
+
end
|
78
|
+
|
79
|
+
def total_count
|
80
|
+
options[:total_count]
|
81
|
+
end
|
82
|
+
|
83
|
+
def filter_backtrace(backtrace)
|
84
|
+
Minitest.filter_backtrace(backtrace)
|
85
|
+
end
|
86
|
+
|
87
|
+
def puts(*args)
|
88
|
+
io.puts(*args)
|
89
|
+
end
|
90
|
+
|
91
|
+
def print(*args)
|
92
|
+
io.print(*args)
|
93
|
+
end
|
94
|
+
|
95
|
+
def print_info(e, name=true)
|
96
|
+
e.message.each_line { |line| print_with_info_padding(line) }
|
97
|
+
|
98
|
+
trace = filter_backtrace(e.backtrace)
|
99
|
+
trace.each { |line| print_with_info_padding(line) }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
module RelativePosition
|
104
|
+
TEST_PADDING = 2
|
105
|
+
TEST_SIZE = 63
|
106
|
+
MARK_SIZE = 5
|
107
|
+
INFO_PADDING = 8
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def print_with_info_padding(line)
|
112
|
+
puts pad(line, INFO_PADDING)
|
113
|
+
end
|
114
|
+
|
115
|
+
def pad(str, size = INFO_PADDING)
|
116
|
+
' ' * size + str
|
117
|
+
end
|
118
|
+
|
119
|
+
def pad_mark(str)
|
120
|
+
"%#{MARK_SIZE}s" % str
|
121
|
+
end
|
122
|
+
|
123
|
+
def pad_test(str)
|
124
|
+
pad("%-#{TEST_SIZE}s" % str, TEST_PADDING)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
class MinitestReporter < BaseReporter
|
129
|
+
include ANSI::Code
|
130
|
+
include RelativePosition
|
131
|
+
attr_accessor :tc_results
|
132
|
+
attr_accessor :current_element_id
|
133
|
+
|
134
|
+
def initialize(*args)
|
135
|
+
@current_element_id = 0
|
136
|
+
@tc_results = TConsole::TestResult.new
|
137
|
+
super
|
138
|
+
end
|
139
|
+
|
140
|
+
def start # not used
|
141
|
+
#super
|
142
|
+
end
|
143
|
+
|
144
|
+
def ready
|
145
|
+
#super
|
146
|
+
if defined? Minitest.clock_time
|
147
|
+
self.start_time = Minitest.clock_time
|
148
|
+
else
|
149
|
+
self.start_time = Time.now
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def report
|
154
|
+
super
|
155
|
+
puts('Finished in %.5fs' % total_time)
|
156
|
+
res_str = '%d tests, ' % [count]
|
157
|
+
res_str += '%d assertions, ' % assertions
|
158
|
+
passed = count - (failures + errors + skips)
|
159
|
+
res_str += green { '%d passed, ' } % passed
|
160
|
+
color = failures.zero? && errors.zero? ? :green : :red
|
161
|
+
res_str += send(color) { '%d failures, %d errors, ' } % [failures, errors]
|
162
|
+
res_str += yellow { '%d skips' } % skips
|
163
|
+
puts(res_str)
|
164
|
+
if failures == 0 && errors == 0
|
165
|
+
puts
|
166
|
+
puts(green { "All tests passed! You are good!" })
|
167
|
+
end
|
168
|
+
puts
|
169
|
+
end
|
170
|
+
|
171
|
+
def record(test)
|
172
|
+
super
|
173
|
+
# TODO хорошо бы избавиться от @current_element_id
|
174
|
+
str = "#{::Term::ANSIColor.magenta(@current_element_id)} #{colored_string(test.name, test)}"
|
175
|
+
print pad_test(str)
|
176
|
+
print_colored_status(test)
|
177
|
+
print(" (%.2fs)" % test.time)
|
178
|
+
print()
|
179
|
+
puts
|
180
|
+
if !test.skipped? && test.failure
|
181
|
+
@tc_results.failures << @current_element_id
|
182
|
+
print_info(test.failure)
|
183
|
+
puts
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
protected
|
188
|
+
|
189
|
+
def colored_string(str, test)
|
190
|
+
if test.passed?
|
191
|
+
::Term::ANSIColor.green(str)
|
192
|
+
elsif test.skipped?
|
193
|
+
::Term::ANSIColor.yellow(str)
|
194
|
+
else
|
195
|
+
::Term::ANSIColor.red(str)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def before_suite(suite)
|
200
|
+
puts suite
|
201
|
+
end
|
202
|
+
|
203
|
+
def after_suite(suite)
|
204
|
+
puts
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|