tconsole-rails4 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,123 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
module TConsole
|
3
|
+
class MinitestServer < Server
|
4
|
+
|
5
|
+
# Loads the files that match globs and then executes tests against them. Limit tests
|
6
|
+
# with class names, method names, and test ids using match_patterns.
|
7
|
+
def run_tests(globs, match_patterns, message = "Running tests...")
|
8
|
+
time = Benchmark.realtime do
|
9
|
+
reporter.info(message)
|
10
|
+
reporter.info
|
11
|
+
|
12
|
+
paths = []
|
13
|
+
globs.each do |glob|
|
14
|
+
paths.concat(Dir.glob(glob))
|
15
|
+
end
|
16
|
+
|
17
|
+
if paths.length == 0
|
18
|
+
reporter.warn("No test files match your requested test set: #{globs.join(",")}.")
|
19
|
+
reporter.warn("Skipping execution.")
|
20
|
+
return nil
|
21
|
+
end
|
22
|
+
|
23
|
+
self.last_result = run_in_fork do
|
24
|
+
|
25
|
+
paths.each do |path|
|
26
|
+
reporter.trace("Requested path `#{path}` doesn't exist.") unless File.exist?(path)
|
27
|
+
require File.expand_path(path)
|
28
|
+
end
|
29
|
+
|
30
|
+
reporter.trace("Running before_test_run callback")
|
31
|
+
config.before_test_run!
|
32
|
+
reporter.trace("Completed before_test_run callback")
|
33
|
+
|
34
|
+
result = nil
|
35
|
+
if defined?(::Minitest)
|
36
|
+
reporter.trace("Detected minitest.")
|
37
|
+
require File.join(File.dirname(__FILE__), "minitest_handler")
|
38
|
+
require File.join(File.dirname(__FILE__), "minitest_reporter")
|
39
|
+
|
40
|
+
reporter.trace("Running tests.")
|
41
|
+
runner = MiniTestHandler.new(config)
|
42
|
+
|
43
|
+
# TODO надо починить Ctrl+C
|
44
|
+
# Handle trapping interrupts
|
45
|
+
trap("SIGINT") do
|
46
|
+
reporter.warn
|
47
|
+
reporter.warn("Trapped interrupt. Halting tests.")
|
48
|
+
|
49
|
+
runner.interrupted = true
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
result, res_reporter = runner.match_and_run(match_patterns, config)
|
54
|
+
|
55
|
+
res_reporter.report
|
56
|
+
# Make sure minitest doesn't run automatically
|
57
|
+
MiniTestHandler.patch_minitest
|
58
|
+
|
59
|
+
reporter.trace("Finished running tests.")
|
60
|
+
|
61
|
+
if runner.interrupted
|
62
|
+
reporter.error("Test run was interrupted.")
|
63
|
+
end
|
64
|
+
|
65
|
+
elsif defined?(::Test::Unit)
|
66
|
+
reporter.error("Sorry, but #{config.app} doesn't support Test::Unit")
|
67
|
+
end
|
68
|
+
|
69
|
+
result
|
70
|
+
end
|
71
|
+
|
72
|
+
if self.last_result == nil
|
73
|
+
# Just in case anything crazy goes down with marshalling
|
74
|
+
self.last_result = TConsole::TestResult.new
|
75
|
+
end
|
76
|
+
|
77
|
+
config.cache_test_ids(self.last_result)
|
78
|
+
|
79
|
+
true
|
80
|
+
end
|
81
|
+
|
82
|
+
reporter.info
|
83
|
+
reporter.info("Tests ran in #{"%0.6f" % time}s. Finished at #{Time.now.strftime('%Y-%m-%d %l:%M:%S %p')}.")
|
84
|
+
reporter.info
|
85
|
+
end
|
86
|
+
|
87
|
+
# Preloads our autocomplete cache
|
88
|
+
def preload_test_ids
|
89
|
+
result = run_in_fork do
|
90
|
+
paths = []
|
91
|
+
config.file_sets["all"].each do |glob|
|
92
|
+
paths.concat(Dir.glob(glob))
|
93
|
+
end
|
94
|
+
|
95
|
+
paths.each { |path| require File.expand_path(path) }
|
96
|
+
|
97
|
+
require File.join(File.dirname(__FILE__), "minitest_handler")
|
98
|
+
MiniTestHandler.preload_elements
|
99
|
+
end
|
100
|
+
|
101
|
+
config.cache_test_ids(result) unless result.nil?
|
102
|
+
end
|
103
|
+
|
104
|
+
# Runs all tests against the match patterns given
|
105
|
+
def run_all_tests(match_patterns = nil)
|
106
|
+
run_tests(config.file_sets["all"], match_patterns)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Runs a file set out of the config
|
110
|
+
def run_file_set(set)
|
111
|
+
run_tests(config.file_sets[set], nil)
|
112
|
+
end
|
113
|
+
|
114
|
+
def run_failed
|
115
|
+
if last_result.failures.empty?
|
116
|
+
reporter.info("No tests failed in your last run, or you haven't run any tests in this session yet.")
|
117
|
+
reporter.info
|
118
|
+
else
|
119
|
+
run_tests(config.file_sets["all"], last_result.failures)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# Manages all output for TConsole
|
2
|
+
module TConsole
|
3
|
+
class Reporter
|
4
|
+
attr_accessor :config
|
5
|
+
|
6
|
+
def initialize(config)
|
7
|
+
self.config = config
|
8
|
+
end
|
9
|
+
|
10
|
+
# Public: Ouptuts an informative message.
|
11
|
+
def info(message = "")
|
12
|
+
puts message
|
13
|
+
end
|
14
|
+
|
15
|
+
# Public: Outputs a positive informative message.
|
16
|
+
# Colors it green if the console supports it.
|
17
|
+
def exclaim(message = "")
|
18
|
+
puts ::Term::ANSIColor.green(message)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Public: Outputs a warning message.
|
22
|
+
def warn(message = "")
|
23
|
+
puts ::Term::ANSIColor.yellow(message)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Public: Outputs an error message.
|
27
|
+
def error(message = "")
|
28
|
+
puts ::Term::ANSIColor.red(message)
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
# Public: Outputs a trace message, when needed.
|
33
|
+
def trace(message = "")
|
34
|
+
puts "[tconsole trace] #{message}" if config.trace?
|
35
|
+
end
|
36
|
+
|
37
|
+
# Public: Prints a backtrace out.
|
38
|
+
def trace_backtrace(exception)
|
39
|
+
trace("===========")
|
40
|
+
trace(exception.backtrace.join("\n"))
|
41
|
+
trace("===========")
|
42
|
+
end
|
43
|
+
|
44
|
+
# Public: Outputs a timing for the timings command, using the proper
|
45
|
+
# color logic
|
46
|
+
def timing(timing, test_id)
|
47
|
+
output = "#{"%0.6f" % timing[:time]}s #{timing[:name]}"
|
48
|
+
if timing[:time] > 1
|
49
|
+
print ::Term::ANSIColor.red, output, ::Term::ANSIColor.reset
|
50
|
+
else
|
51
|
+
print ::Term::ANSIColor.green, output, ::Term::ANSIColor.reset
|
52
|
+
end
|
53
|
+
|
54
|
+
print ::Term::ANSIColor.magenta, " #{last_result}", ::Term::ANSIColor.reset, "\n"
|
55
|
+
end
|
56
|
+
|
57
|
+
# Public: Prints a list of available commands
|
58
|
+
def help_message
|
59
|
+
puts
|
60
|
+
puts "Available commands:"
|
61
|
+
puts
|
62
|
+
puts "reload # Reload your Rails environment"
|
63
|
+
puts "set [variable] [value] # Sets a runtime variable (see below for details)"
|
64
|
+
puts "exit # Exit the console"
|
65
|
+
puts "!failed # Runs the last set of failing tests"
|
66
|
+
puts "!timings [limit] # Lists the timings for the last test run, sorted."
|
67
|
+
puts "[filename] [test_pattern] # Run the tests contained in the given file"
|
68
|
+
puts ".[command] # Executes the given command in a subshell"
|
69
|
+
puts
|
70
|
+
puts "Running file sets"
|
71
|
+
puts
|
72
|
+
puts "File sets are sets of files that are typically run together. For example,"
|
73
|
+
puts "in Rails projects it's common to run `rake test:units` to run all of the"
|
74
|
+
puts "tests under the units directory."
|
75
|
+
puts
|
76
|
+
puts "Available file sets:"
|
77
|
+
|
78
|
+
config.file_sets.each do |set, paths|
|
79
|
+
puts set
|
80
|
+
end
|
81
|
+
|
82
|
+
puts
|
83
|
+
puts "Working with test patterns:"
|
84
|
+
puts
|
85
|
+
puts "All of the test execution commands include an optional test_pattern argument. A"
|
86
|
+
puts "test pattern can be given to filter the executed tests to only those tests whose"
|
87
|
+
puts "name matches the pattern given. This is especially useful when rerunning a failing"
|
88
|
+
puts "test."
|
89
|
+
puts
|
90
|
+
puts "Runtime Variables"
|
91
|
+
puts
|
92
|
+
puts "You can set runtime variables with the set command. This helps out with changing"
|
93
|
+
puts "features that you may want to change at runtime. At present, the following"
|
94
|
+
puts "runtime variables are available:"
|
95
|
+
puts
|
96
|
+
puts "fast # Turns on fail fast mode. Values: on, off"
|
97
|
+
puts
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
# Public: Outputs the tconsole welcome message
|
102
|
+
def welcome_message
|
103
|
+
info
|
104
|
+
info("Welcome to #{config.app} (v#{TConsole::VERSION}). Type 'help' for help or 'exit' to quit.")
|
105
|
+
end
|
106
|
+
|
107
|
+
# Public: Outputs the tconsole exit message
|
108
|
+
def exit_message
|
109
|
+
info
|
110
|
+
info("Exiting. Bye!")
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module TConsole
|
2
|
+
class RSpecServer < Server
|
3
|
+
|
4
|
+
# Loads the files that match globs and then executes tests against them. Limit tests
|
5
|
+
# with class names, method names, and test ids using match_patterns.
|
6
|
+
def run_tests(globs, match_patterns, message = "Running tests...")
|
7
|
+
time = Benchmark.realtime do
|
8
|
+
reporter.info(message)
|
9
|
+
reporter.info
|
10
|
+
|
11
|
+
paths = []
|
12
|
+
globs.each do |glob|
|
13
|
+
paths.concat(Dir.glob(glob))
|
14
|
+
end
|
15
|
+
|
16
|
+
if paths.length == 0
|
17
|
+
reporter.warn("No test files match your requested test set: #{globs.join(",")}.")
|
18
|
+
reporter.warn("Skipping execution.")
|
19
|
+
return nil
|
20
|
+
end
|
21
|
+
|
22
|
+
self.last_result = run_in_fork do
|
23
|
+
|
24
|
+
# Make sure rspec is loaded up
|
25
|
+
require 'rspec'
|
26
|
+
|
27
|
+
paths.each do |path|
|
28
|
+
reporter.trace("Requested path `#{path}` doesn't exist.") unless File.exist?(path)
|
29
|
+
require File.expand_path(path)
|
30
|
+
end
|
31
|
+
|
32
|
+
reporter.trace("Running before_test_run callback")
|
33
|
+
config.before_test_run!
|
34
|
+
reporter.trace("Completed before_test_run callback")
|
35
|
+
|
36
|
+
result = nil
|
37
|
+
if defined?(::RSpec)
|
38
|
+
reporter.trace("Detected rspec.")
|
39
|
+
|
40
|
+
reporter.trace("Running tests.")
|
41
|
+
|
42
|
+
# Handle trapping interrupts
|
43
|
+
trap("SIGINT") do
|
44
|
+
reporter.warn
|
45
|
+
reporter.warn("Trapped interrupt. Halting tests.")
|
46
|
+
end
|
47
|
+
|
48
|
+
# Actually run the tests!
|
49
|
+
configuration = RSpec::configuration
|
50
|
+
world = RSpec::world
|
51
|
+
options = RSpec::Core::ConfigurationOptions.new([])
|
52
|
+
options.parse_options
|
53
|
+
|
54
|
+
configuration.error_stream = STDERR
|
55
|
+
configuration.output_stream = STDOUT
|
56
|
+
|
57
|
+
options.configure(configuration)
|
58
|
+
|
59
|
+
configuration.files_to_run = paths
|
60
|
+
|
61
|
+
configuration.reporter.report(world.example_count, configuration.randomize? ? configuration.seed : nil) do |reporter|
|
62
|
+
begin
|
63
|
+
configuration.run_hook(:before, :suite)
|
64
|
+
world.example_groups.ordered.map {|g| g.run(reporter)}.all? ? 0 : configuration.failure_exit_code
|
65
|
+
ensure
|
66
|
+
configuration.run_hook(:after, :suite)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Patch RSpec to disable autorun
|
71
|
+
::RSpec::Core::Runner.class_eval do
|
72
|
+
def self.run(args = [], err=$stderr, out=$stdout)
|
73
|
+
# do nothing
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
reporter.trace("Finished running tests.")
|
78
|
+
end
|
79
|
+
|
80
|
+
nil
|
81
|
+
end
|
82
|
+
|
83
|
+
if self.last_result == nil
|
84
|
+
# Just in case anything crazy goes down with marshalling
|
85
|
+
self.last_result = TConsole::TestResult.new
|
86
|
+
end
|
87
|
+
|
88
|
+
config.cache_test_ids(self.last_result)
|
89
|
+
|
90
|
+
true
|
91
|
+
end
|
92
|
+
|
93
|
+
reporter.info
|
94
|
+
reporter.info("Tests ran in #{"%0.6f" % time}s. Finished at #{Time.now.strftime('%Y-%m-%d %l:%M:%S %p')}.")
|
95
|
+
reporter.info
|
96
|
+
end
|
97
|
+
|
98
|
+
# Runs all tests against the match patterns given
|
99
|
+
def run_all_tests(match_patterns = nil)
|
100
|
+
run_tests(config.file_sets["all"], match_patterns)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Runs a file set out of the config
|
104
|
+
def run_file_set(set)
|
105
|
+
run_tests(config.file_sets[set], nil)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
module TConsole
|
2
|
+
class Runner
|
3
|
+
|
4
|
+
attr_accessor :mode, :config, :reporter, :console, :stty_save
|
5
|
+
|
6
|
+
# Public: Sets up the new runner's config.
|
7
|
+
def initialize(mode, argv = [])
|
8
|
+
self.mode = mode
|
9
|
+
|
10
|
+
# try to load the default configs
|
11
|
+
Config.load_config(File.join(Dir.home, ".#{Config.app(mode)}"))
|
12
|
+
Config.load_config(File.join(Dir.pwd, ".#{Config.app(mode)}"))
|
13
|
+
self.config = Config.configure(mode, argv)
|
14
|
+
self.reporter = Reporter.new(config)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Spawns a new environment. Looks at the results of the environment to determine
|
18
|
+
# whether to stop or keep running
|
19
|
+
def run
|
20
|
+
prepare_process
|
21
|
+
reporter.welcome_message
|
22
|
+
exit(1) if print_config_errors
|
23
|
+
|
24
|
+
# Set up our console input handling and history
|
25
|
+
console = Console.new(config, reporter)
|
26
|
+
|
27
|
+
# Start the server
|
28
|
+
while console_run_loop(console)
|
29
|
+
# just need to run the loop
|
30
|
+
end
|
31
|
+
|
32
|
+
console.store_history
|
33
|
+
|
34
|
+
cleanup_process
|
35
|
+
end
|
36
|
+
|
37
|
+
# Internal: Set up the process and console.
|
38
|
+
def prepare_process
|
39
|
+
self.stty_save = `stty -g`.chomp
|
40
|
+
|
41
|
+
trap("SIGINT", "IGNORE")
|
42
|
+
trap("SIGTSTP", "IGNORE")
|
43
|
+
end
|
44
|
+
|
45
|
+
# Internal: Cleans up the process at the end of execution.
|
46
|
+
def cleanup_process
|
47
|
+
reporter.exit_message
|
48
|
+
system("stty", self.stty_save);
|
49
|
+
end
|
50
|
+
|
51
|
+
# Internal: Prints config errors, if there are any.
|
52
|
+
#
|
53
|
+
# Returns true if there were errors, false otherwise.
|
54
|
+
def print_config_errors
|
55
|
+
config_errors = @config.validation_errors
|
56
|
+
if config_errors.length > 0
|
57
|
+
reporter.error
|
58
|
+
reporter.error(config_errors.first)
|
59
|
+
true
|
60
|
+
else
|
61
|
+
false
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Internal: Environment reload run loop.
|
66
|
+
#
|
67
|
+
# This run loop handles spawning a new tconsole environment - it's basically
|
68
|
+
# just there to handle reloads. Also calls out to the input loop for the
|
69
|
+
# console.
|
70
|
+
#
|
71
|
+
# Returns false if tconsole needs to stop, true otherwise.
|
72
|
+
def console_run_loop(console)
|
73
|
+
pipe_server = ChattyProc::PipeServer.new
|
74
|
+
|
75
|
+
reporter.trace("Forking test server.")
|
76
|
+
server_pid = fork do
|
77
|
+
server_run_loop(pipe_server)
|
78
|
+
end
|
79
|
+
|
80
|
+
pipe_server.caller!
|
81
|
+
unless load_environment(pipe_server)
|
82
|
+
pipe_server.write({:action => "exit"})
|
83
|
+
return false
|
84
|
+
end
|
85
|
+
|
86
|
+
continue = console.read_and_execute(pipe_server)
|
87
|
+
reporter.trace("Console read loop returned - continue: #{continue}")
|
88
|
+
|
89
|
+
Process.waitall
|
90
|
+
|
91
|
+
continue
|
92
|
+
end
|
93
|
+
|
94
|
+
# Internal: Asks the server to load the environment.
|
95
|
+
#
|
96
|
+
# Returns true if the environment was loaded, or false otherwise.
|
97
|
+
def load_environment(pipe_server)
|
98
|
+
reporter.trace("Attempting to load environment.")
|
99
|
+
pipe_server.write({:action => "load_environment"})
|
100
|
+
|
101
|
+
if pipe_server.read
|
102
|
+
reporter.trace("Environment loaded successfully.")
|
103
|
+
true
|
104
|
+
else
|
105
|
+
reporter.error("Couldn't load the test environment. Exiting.")
|
106
|
+
false
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Internal: Run loop for the server.
|
111
|
+
def server_run_loop(pipe_server)
|
112
|
+
pipe_server.callee!
|
113
|
+
|
114
|
+
if mode == :minitest
|
115
|
+
server = MinitestServer.new(config, reporter)
|
116
|
+
elsif mode == :rspec
|
117
|
+
server = RSpecServer.new(config, reporter)
|
118
|
+
else
|
119
|
+
reporter.error
|
120
|
+
reporter.error("The given test mode isn't supported.")
|
121
|
+
reporter.error
|
122
|
+
exit
|
123
|
+
end
|
124
|
+
|
125
|
+
while message = pipe_server.read
|
126
|
+
reporter.trace("Server Received Message: #{message[:action]}")
|
127
|
+
begin
|
128
|
+
result = server.handle(message)
|
129
|
+
pipe_server.write(result)
|
130
|
+
rescue => e
|
131
|
+
reporter.error
|
132
|
+
reporter.error("An error occured: #{e.message}")
|
133
|
+
reporter.trace_backtrace(e)
|
134
|
+
pipe_server.write(nil)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|