rspec-live 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: dfd5934b1feefe47f06e7a318dbf945b4ffdce28
4
+ data.tar.gz: 53366efac24205bc76f277a2fd60229850a3b252
5
+ SHA512:
6
+ metadata.gz: 75a233e27e16b972e8e822e8322e3c7b87a5f0bfb42af40e24221e34457492b65910bcd52b95d0861878d1686ca04d76d78204015b311fe204aa5eecc2528deb
7
+ data.tar.gz: 7650642092fb1d3a46004bfbf7c8061ecb1b370569120ddfcc66f4a4e6bc7e9b72449546bfb15b43daea8246b45cb45ae475d3907e99b7f640fca8769474de07
data/History.md CHANGED
@@ -1,3 +1,10 @@
1
+ ### Version 0.0.2
2
+ 2014-9-19
3
+
4
+ * Use coverage analysis to choose which specs to rerun
5
+ * Don't block key commands while waiting for RSpec results
6
+ * Bugfixes
7
+
1
8
  ### Version 0.0.1
2
9
  2014-8-12
3
10
 
data/README.md CHANGED
@@ -2,9 +2,17 @@
2
2
 
3
3
  rspec-live is a test runner and formatter for RSpec 3+ that shows the state of your test suite
4
4
  clearly and concisely in a console window. As files in your project are updated, created, or
5
- removed, rspec-live reruns your tests in the background and continually updates the displayed
5
+ removed, rspec-live reruns your specs in the background and continually updates the displayed
6
6
  status.
7
7
 
8
+ Basic code coverage analysis is used to determine which specs to re-run based on which files
9
+ have changed. Currently, specs that run code in a separate process may not get re-run as often
10
+ as necessary (this includes acceptance tests that launch a JavaScript-capable browser). To see
11
+ updated results for these specs, you may need to re-run all specs by pressing "R".
12
+
13
+ RSpec background processes are managed intelligently, so that only one RSpec process is
14
+ running at a time, and future RSpec runs won't get queued up based on outdated file statuses.
15
+
8
16
  ### Requirements
9
17
 
10
18
  * Ruby 1.8.7 or newer
@@ -23,3 +31,17 @@ Update your bundle,
23
31
  And start it up.
24
32
 
25
33
  rspec-live
34
+
35
+ ### Key Commands
36
+
37
+ - **A** (all): Toggle the display of all specs (as opposed to just failing specs) in the
38
+ detailed list at the bottom of the screen.
39
+
40
+ - **R** (refresh): Re-run all specs.
41
+
42
+ - **N** (next): Reorder the detailed information about failing specs shown at the bottom of
43
+ the screen, by rotating the next spec into the top position.
44
+
45
+ - **V** (verbosity): Change the amount of backtrace information shown about failing specs.
46
+
47
+ - **Q** (quit)
data/bin/rspec-live CHANGED
@@ -1,9 +1,15 @@
1
1
  #!/usr/bin/env ruby
2
2
  require "rspec-live/suite"
3
3
  require "rspec-live/runner"
4
- require "rspec-live/watcher"
4
+ require "rspec-live/monitor"
5
5
  require "rspec-live/display"
6
+ require "rspec-live/result_detail"
7
+ require "rspec-live/file_watcher"
8
+ require "rspec-live/log"
6
9
 
7
- display = RSpecLive::Display.new
8
- suite = RSpecLive::Suite.new(RSpecLive::Runner.new, display.suite_display)
9
- RSpecLive::Watcher.new(suite, display.watcher_display).start
10
+ runner = RSpecLive::Runner.new
11
+ file_watcher = RSpecLive::FileWatcher.new(Dir.pwd)
12
+ suite = RSpecLive::Suite.new(runner, file_watcher)
13
+ detail = RSpecLive::ResultDetail.new(suite)
14
+ display = RSpecLive::Display.new(suite, detail)
15
+ RSpecLive::Monitor.new(suite, display, detail).start
@@ -0,0 +1,17 @@
1
+ class CoverageAnalyzer
2
+ def self.install
3
+ RSpec.configure do |c|
4
+ c.around(:example) do |example|
5
+ CoverageAnalyzer.run_example_with_coverage example
6
+ end
7
+ end
8
+ end
9
+
10
+ def self.run_example_with_coverage(example)
11
+ example.metadata[:files_touched] = {}
12
+ set_trace_func proc { |event, file| example.metadata[:files_touched][file] = true }
13
+ example.run
14
+ ensure
15
+ set_trace_func nil
16
+ end
17
+ end
@@ -8,6 +8,6 @@ class InventoryFormatter
8
8
  end
9
9
 
10
10
  def example_started(notification)
11
- @output << "#{JSON.unparse(name: notification.example.location)}\n"
11
+ @output << "\n#{JSON.unparse(name: notification.example.location)}\n"
12
12
  end
13
13
  end
@@ -1,30 +1,42 @@
1
+ require File.join(File.dirname(__FILE__), "coverage_analyzer")
1
2
  require "json"
2
3
 
3
4
  class UpdateFormatter
4
5
  RSpec::Core::Formatters.register self, :example_passed, :example_failed, :example_pending
6
+ CoverageAnalyzer.install
5
7
 
6
8
  def initialize(output)
7
9
  @output = output
8
10
  end
9
11
 
10
12
  def example_passed(notification)
11
- report :name => notification.example.location, :status => "passed"
13
+ report notification.example, "passed"
12
14
  end
13
15
 
14
16
  def example_failed(notification)
15
- report({
16
- :name => notification.example.location,
17
- :status => "failed",
17
+ report notification.example, "failed", {
18
18
  :backtrace => notification.exception.backtrace,
19
19
  :message => notification.exception.message,
20
- })
20
+ }
21
21
  end
22
22
 
23
23
  def example_pending(notification)
24
- report :name => notification.example.location, :status => "pending"
24
+ report notification.example, "pending"
25
25
  end
26
26
 
27
- def report(data)
28
- @output << "#{JSON.unparse data}\n"
27
+ private
28
+
29
+ def report(example, status, options = {})
30
+ data = {
31
+ :name => example.location,
32
+ :status => status,
33
+ :files_touched => filtered_files_touched(example),
34
+ }
35
+ @output << "\n#{JSON.unparse data.merge(options)}\n"
36
+ end
37
+
38
+ def filtered_files_touched(example)
39
+ raw_files = example.metadata[:files_touched].keys
40
+ raw_files.select { |file| file.start_with? Dir.pwd }
29
41
  end
30
42
  end
@@ -0,0 +1,48 @@
1
+ require "pty"
2
+
3
+ module RSpecLive
4
+ class ConcurrentProcess
5
+ def initialize(command)
6
+ @command = command
7
+ @output = ""
8
+ end
9
+
10
+ def running?
11
+ @running
12
+ end
13
+
14
+ def start
15
+ @started_at = Time.now
16
+ @stdout, @stdin, @pid = PTY.spawn @command
17
+ @running = true
18
+ end
19
+
20
+ def each_line
21
+ read_characters if running?
22
+ shift_completed_lines.each_line { |line| yield line }
23
+ end
24
+
25
+ private
26
+
27
+ def shift_completed_lines
28
+ lines = ""
29
+ while index = @output.index("\n")
30
+ lines << @output.slice(0, index + 1)
31
+ @output = @output.slice(index + 1, @output.length - index - 1)
32
+ end
33
+ lines
34
+ end
35
+
36
+ def read_characters
37
+ while char = @stdout.read_nonblock(100000)
38
+ @output << char
39
+ end
40
+ rescue IO::WaitReadable
41
+ rescue Errno::EIO
42
+ @stdout.close
43
+ @stdin.close
44
+ @running = false
45
+ @duration = Time.now - @started_at
46
+ end
47
+ end
48
+ end
@@ -1,64 +1,61 @@
1
- require "rspec-live/terminal"
1
+ require "rspec-live/screen"
2
2
 
3
3
  module RSpecLive
4
4
  class Display
5
- attr_reader :watcher_display, :suite_display
6
-
7
- def initialize
8
- @terminal = Terminal.new
9
- @watcher_display = WatcherDisplay.new(@terminal.add_section :xalign => :center)
10
- @terminal.add_section :display => :block, :content => key_command_info, :color => :blue
11
- @suite_display = SuiteDisplay.new(@terminal.add_section :display => :block)
5
+ def initialize(suite, detail)
6
+ @suite = suite
7
+ @detail = detail
8
+ @screen = Screen.new
12
9
  end
13
10
 
14
- private
15
-
16
- def key_command_info
17
- "Keys: A:show/hide-all N:next V:verbosity R:rerun Q:quit"
11
+ def update
12
+ @screen.start_frame
13
+ show_header
14
+ show_summary
15
+ show_details
16
+ @screen.end_frame
18
17
  end
19
- end
20
18
 
21
- class WatcherDisplay
22
- def initialize(section)
23
- @section = section
19
+ def update_required?
20
+ @screen.update_required?
24
21
  end
25
22
 
26
- def status=(status)
27
- @section.content = "RSpec summary for #{File.basename Dir.pwd} (#{status})"
28
- @section.refresh
29
- end
30
- end
23
+ private
31
24
 
32
- class SuiteDisplay
33
- def initialize(section)
34
- @section = section
25
+ def show_header
26
+ @screen.print "RSpec summary for #{File.basename Dir.pwd} (#{@suite.activity_status})\n"
27
+ @screen.print "Keys: A:show/hide-all N:next V:verbosity R:rerun Q:quit", :color => :blue
35
28
  end
36
29
 
37
- def show_examples(examples, suite_status, detailed_examples, verbosity)
38
- @section.clear
39
- @section.add_section :display => :block
40
- examples.map(&:status).each do |status|
41
- @section.add_section :content => character[status], :color => color[status]
30
+ def show_summary
31
+ @screen.print "\n\n"
32
+ @suite.ordered_examples.map(&:status).each do |status|
33
+ @screen.print character[status], :color => color[status]
42
34
  end
43
- @section.add_section :display => :block
44
- @section.add_section :content => "#{suite_status}", :display => :block
45
- @section.add_section :display => :block
35
+ @screen.print "\n\n"
36
+ @screen.print @suite.summary
37
+ end
38
+
39
+ def show_details
40
+ @screen.print "\n\n"
46
41
  last_failed = true
47
- bullet_width = (detailed_examples.length-1).to_s.length
48
- detailed_examples.each_with_index do |example, index|
42
+ bullet_width = (@detail.detailed_examples.length-1).to_s.length
43
+ @detail.detailed_examples.each_with_index do |example, index|
49
44
  bullet = "#{index+1}.".rjust(bullet_width+1, " ")
50
- @section.add_section :display => :block if (!last_failed && example.failed?)
51
- @section.add_section :content => example.details(verbosity), :display => :block, :color => color[example.status], :wrap => true, :bullet => bullet
52
- @section.add_section :display => :block if example.failed?
45
+ @screen.print "\n" if (!last_failed && example.failed?)
46
+ @screen.print "#{bullet} #{example.details @detail.verbosity}", {
47
+ :color => color[example.status],
48
+ :wrap => true,
49
+ :hanging_indent => (bullet_width + 1)
50
+ }
51
+ @screen.print "\n"
52
+ @screen.print "\n" if example.failed?
53
53
  last_failed = example.failed?
54
54
  end
55
- @section.refresh
56
55
  end
57
56
 
58
- private
59
-
60
57
  def character
61
- {:unknown => ".", :passed => ".", :failed => "F", :pending => "S"}
58
+ {:unknown => "?", :passed => ".", :failed => "F", :pending => "*"}
62
59
  end
63
60
 
64
61
  def color
@@ -15,6 +15,7 @@ module RSpecLive
15
15
  @status = data["status"].to_sym if data["status"]
16
16
  @backtrace = data["backtrace"] if data["backtrace"]
17
17
  @message = data["message"] if data["message"]
18
+ @files_touched = data["files_touched"] || []
18
19
  end
19
20
 
20
21
  def status=(value)
@@ -29,6 +30,10 @@ module RSpecLive
29
30
  @status == :failed
30
31
  end
31
32
 
33
+ def stale?
34
+ @status == :unknown
35
+ end
36
+
32
37
  def details(verbosity)
33
38
  failed? ? failure_message(verbosity) : name_component
34
39
  end
@@ -49,5 +54,19 @@ module RSpecLive
49
54
  def exception_components
50
55
  [@message.gsub("\n", " ").strip.inspect]
51
56
  end
57
+
58
+ def file_touched(name)
59
+ @status = :unknown if @files_touched.include? name
60
+ end
61
+
62
+ def in_file?(filename)
63
+ absolute_path == filename
64
+ end
65
+
66
+ private
67
+
68
+ def absolute_path
69
+ @name.split(":").first.gsub(/^\./, Dir.pwd)
70
+ end
52
71
  end
53
72
  end
@@ -0,0 +1,30 @@
1
+ module RSpecLive
2
+ class FileWatcher
3
+ def initialize(path)
4
+ @updated, @added, @removed = [], [], []
5
+ Listen.to(path) do |updated, added, removed|
6
+ @updated += updated
7
+ @added += added
8
+ @removed += removed
9
+ end.start
10
+ end
11
+
12
+ def updated
13
+ queued_results @updated
14
+ end
15
+
16
+ def added
17
+ queued_results @added
18
+ end
19
+
20
+ def removed
21
+ queued_results @removed
22
+ end
23
+
24
+ private
25
+
26
+ def queued_results(queue)
27
+ queue.pop queue.length
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,24 @@
1
+ module RSpecLive
2
+ class FormattedText
3
+ def initialize(text, options = {})
4
+ @text = text
5
+ @wrap = options[:wrap]
6
+ @width = options[:width]
7
+ @hanging_indent = options[:hanging_indent] || 0
8
+ end
9
+
10
+ def text
11
+ if @wrap
12
+ wrap(@text, @width - @hanging_indent).split("\n").join("\n" + (" " * @hanging_indent))
13
+ else
14
+ @text
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def wrap(text, width)
21
+ text.scan(/\S.{0,#{width-2}}\S(?=\s|$)|\S+/).join("\n")
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,36 @@
1
+ module RSpecLive
2
+ class KeyHandler
3
+ def initialize
4
+ @event = {}
5
+ end
6
+
7
+ def on(*keys, &block)
8
+ keys.each { |key| @event[key] = block }
9
+ end
10
+
11
+ def process_updates
12
+ any_processed = false
13
+ while key = get_character_if_available
14
+ handle key
15
+ any_processed = true
16
+ end
17
+ any_processed
18
+ end
19
+
20
+ private
21
+
22
+ def get_character_if_available
23
+ STDIN.read_nonblock 1
24
+ rescue Errno::EINTR
25
+ rescue Errno::EAGAIN
26
+ rescue EOFError
27
+ end
28
+
29
+ def handle(key)
30
+ if @event[key]
31
+ @event[key].call
32
+ @update_listener.call if @update_listener
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,7 @@
1
+ module RSpecLive
2
+ class Log
3
+ def self.do(text)
4
+ File.open(File.join(__dir__, "../../log.txt"), "a") { |f| f.puts text }
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,39 @@
1
+ require "listen"
2
+ require "rspec-live/key_handler"
3
+ require "rspec-live/result_detail"
4
+
5
+ module RSpecLive
6
+ class Monitor
7
+ def initialize(suite, display, detail)
8
+ @suite = suite
9
+ @display = display
10
+ @detail = detail
11
+ @quit = false
12
+ end
13
+
14
+ def start
15
+ @display.update
16
+ while !@quit do
17
+ @display.update if process_updates || @display.update_required?
18
+ sleep 0.05
19
+ end
20
+ rescue Interrupt
21
+ end
22
+
23
+ private
24
+
25
+ def process_updates
26
+ key_handler.process_updates || @suite.process_updates
27
+ end
28
+
29
+ def key_handler
30
+ @key_handler ||= KeyHandler.new.tap do |handler|
31
+ handler.on("a") { @detail.toggle_all }
32
+ handler.on("n") { @detail.focus_next }
33
+ handler.on("q") { @quit = true }
34
+ handler.on("r") { @suite.reset }
35
+ handler.on("v") { @detail.cycle_verbosity }
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,32 @@
1
+ module RSpecLive
2
+ class ResultDetail
3
+ attr_reader :verbosity
4
+
5
+ def initialize(suite)
6
+ @suite = suite
7
+ @show_all = false
8
+ @verbosity = 1
9
+ end
10
+
11
+ def toggle_all
12
+ @show_all = !@show_all
13
+ end
14
+
15
+ def focus_next
16
+ @focused = detailed_examples[1].name if detailed_examples[1]
17
+ end
18
+
19
+ def cycle_verbosity
20
+ @verbosity = (@verbosity + 1) % 4
21
+ end
22
+
23
+ def detailed_examples
24
+ all = @suite.ordered_examples
25
+ if @focused
26
+ index = @suite.ordered_example_names.index(@focused) || 0
27
+ all = all[index, all.length-index] + all[0, index]
28
+ end
29
+ @show_all ? all : all.select(&:failed?)
30
+ end
31
+ end
32
+ end
@@ -1,28 +1,48 @@
1
- require "pty"
1
+ require "rspec-live/concurrent_process"
2
2
  require "json"
3
3
 
4
4
  module RSpecLive
5
5
  class Runner
6
- def inventory(&block)
7
- run "inventory", "--dry-run", &block
6
+ def initialize
7
+ @queued_examples = []
8
+ @results = []
8
9
  end
9
10
 
10
- def update(&block)
11
- run "update", &block
11
+ def request_inventory(status)
12
+ @inventory_requested = status
13
+ end
14
+
15
+ def request_results(examples)
16
+ @queued_examples = examples
17
+ return if @queued_examples.empty?
18
+ end
19
+
20
+ def results
21
+ start_process unless @process && @process.running?
22
+ @process.each_line { |line| record_result line } if @process
23
+ @results.pop @results.length
12
24
  end
13
25
 
14
26
  private
15
27
 
16
- def run(formatter, options="", &block)
17
- PTY.spawn formatter_command(formatter, options) do |stdin, stdout, pid|
18
- begin
19
- stdin.each do |line|
20
- block.call JSON.parse line
21
- end
22
- rescue Errno::EIO
23
- end
28
+ def record_result(text)
29
+ @results << JSON.parse(text)
30
+ rescue JSON::ParserError
31
+ end
32
+
33
+ def start_process
34
+ if @inventory_requested
35
+ run "inventory", "--dry-run"
36
+ @inventory_requested = false
37
+ elsif @queued_examples.any?
38
+ run "update", @queued_examples.join(" ")
39
+ @queued_examples = []
24
40
  end
25
- rescue PTY::ChildExited
41
+ end
42
+
43
+ def run(formatter, options="", &block)
44
+ @process = ConcurrentProcess.new formatter_command(formatter, options)
45
+ @process.start
26
46
  end
27
47
 
28
48
  def formatter_command(formatter, options)
@@ -0,0 +1,64 @@
1
+ require "rspec-live/formatted_text"
2
+ require "curses"
3
+
4
+ module RSpecLive
5
+ class Screen
6
+ def initialize
7
+ reset_curses
8
+ Signal.trap("SIGWINCH", proc { reset_curses; @update_required = true })
9
+ end
10
+
11
+ def update_required?
12
+ @update_required
13
+ end
14
+
15
+ def start_frame
16
+ Curses.clear
17
+ Curses.setpos 0, 0
18
+ end
19
+
20
+ def end_frame
21
+ Curses.refresh
22
+ end
23
+
24
+ def print(text, options = {})
25
+ color = options[:color]
26
+ options[:width] = @width if options[:wrap]
27
+ text = FormattedText.new(text, options).text if options.any?
28
+ if color
29
+ Curses.attron(color_attribute color) { Curses.addstr text }
30
+ else
31
+ Curses.addstr text
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def reset_curses
38
+ Curses.init_screen
39
+ Curses.curs_set 0
40
+ Curses.clear
41
+ Curses.refresh
42
+ Curses.start_color
43
+ Curses.use_default_colors
44
+ available_colors.each do |name|
45
+ Curses.init_pair color_constant(name), color_constant(name), -1
46
+ end
47
+ @width = `tput cols`.to_i
48
+ @height = `tput lines`.to_i
49
+ Curses.resizeterm @height, @width
50
+ end
51
+
52
+ def available_colors
53
+ [:blue, :green, :red, :yellow, :white]
54
+ end
55
+
56
+ def color_constant(name)
57
+ Curses.const_get "COLOR_#{name.to_s.upcase}"
58
+ end
59
+
60
+ def color_attribute(name)
61
+ Curses.color_pair(color_constant name) | Curses::A_NORMAL
62
+ end
63
+ end
64
+ end
@@ -2,93 +2,82 @@ require "rspec-live/example"
2
2
 
3
3
  module RSpecLive
4
4
  class Suite
5
- def initialize(runner, display)
5
+ def initialize(runner, file_watcher)
6
6
  @runner = runner
7
- @display = display
8
- @example_names = []
7
+ @file_watcher = file_watcher
9
8
  @examples = {}
10
- @show_all = false
11
- @verbosity = 1
12
9
  end
13
10
 
14
- def toggle_all
15
- @show_all = !@show_all
16
- update_display
17
- end
18
-
19
- def inventory
20
- @example_names = []
21
- @runner.inventory do |example_data|
22
- update_or_create_example example_data
23
- update_display
11
+ def process_updates
12
+ any_processed = need_inventory = false
13
+ @file_watcher.updated.each do |path|
14
+ @examples.values.each { |example| example.file_touched path }
15
+ @examples.delete_if { |name, example| example.in_file? path }
16
+ any_processed = need_inventory = true
24
17
  end
25
- end
26
-
27
- def update
28
- @runner.update do |example_data|
29
- update_or_create_example example_data
30
- update_display
18
+ @file_watcher.removed.each do |path|
19
+ @examples.delete_if { |name, example| example.in_file? path }
20
+ any_processed = true
31
21
  end
22
+ if @examples.empty? || @file_watcher.added.any?
23
+ any_processed = need_inventory = true
24
+ end
25
+ @runner.request_inventory(need_inventory)
26
+ @runner.request_results stale_example_names
27
+ @runner.results.each do |result|
28
+ update_or_create_example result
29
+ any_processed = true
30
+ end
31
+ any_processed
32
32
  end
33
33
 
34
- def focus_next
35
- @focused = detailed_examples[1].name if detailed_examples[1]
36
- update_display
37
- end
38
-
39
- def clear_status
40
- @examples.each_value { |example| example.status = :unknown }
41
- end
42
-
43
- def cycle_verbosity
44
- @verbosity = (@verbosity + 1) % 4
45
- update_display
34
+ def stale_example_names
35
+ @examples.values.select(&:stale?).map(&:name)
46
36
  end
47
37
 
48
- private
49
-
50
- def next_visible(name)
38
+ def activity_status
39
+ run_count = stale_example_names.count
40
+ run_count.zero? ? "watching for updates..." : "running #{example_count run_count}"
51
41
  end
52
42
 
53
- def update_or_create_example(data)
54
- name = data["name"]
55
- @example_names << name unless @example_names.include? name
56
- sort_example_names
57
- @examples[name] ||= Example.new
58
- @examples[name].update data
59
- @examples[name]
43
+ def summary
44
+ passed = ordered_examples.select(&:passed?).length
45
+ total = ordered_examples.length
46
+ percent = total.zero? ? 0 : (100*passed/total.to_f).round
47
+ "#{passed} of #{example_count total} passed (#{percent}%)"
60
48
  end
61
49
 
62
- def update_display
63
- @display.show_examples ordered_examples, summary, detailed_examples, @verbosity
50
+ def example_count(number)
51
+ "#{number} example" + (number == 1 ? "" : "s")
64
52
  end
65
53
 
66
- def detailed_examples
67
- all = ordered_examples
68
- if @focused
69
- index = @example_names.index(@focused) || 0
70
- all = all[index, all.length-index] + all[0, index]
71
- end
72
- @show_all ? all : all.select(&:failed?)
54
+ def ordered_examples
55
+ ordered_example_names.map { |name| @examples[name] }
73
56
  end
74
57
 
75
- def ordered_examples
76
- @example_names.map { |name| @examples[name] }
58
+ def reset
59
+ @examples = {}
77
60
  end
78
61
 
79
- def sort_example_names
80
- @example_names.sort_by! do |name|
62
+ def ordered_example_names
63
+ example_names.sort_by do |name|
81
64
  file, line = name.split(":")
82
65
  line = line.rjust(8, "0")
83
66
  [file, line].join(":")
84
67
  end
85
68
  end
86
69
 
87
- def summary
88
- passed = ordered_examples.select(&:passed?).length
89
- total = ordered_examples.length
90
- percent = (100*passed/total.to_f).round
91
- "#{passed} of #{total} examples passed (#{percent}%)"
70
+ private
71
+
72
+ def update_or_create_example(data)
73
+ name = data["name"]
74
+ @examples[name] ||= Example.new
75
+ @examples[name].update data
76
+ @examples[name]
77
+ end
78
+
79
+ def example_names
80
+ @examples.keys
92
81
  end
93
82
  end
94
83
  end
@@ -1,3 +1,3 @@
1
1
  module RSpecLive
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
metadata CHANGED
@@ -1,36 +1,32 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec-live
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
5
- prerelease:
4
+ version: 0.0.2
6
5
  platform: ruby
7
6
  authors:
8
7
  - Brian Auton
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2014-08-12 00:00:00.000000000 Z
11
+ date: 2014-09-19 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: rspec
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
17
  - - "~>"
20
18
  - !ruby/object:Gem::Version
21
- version: 3.0.0
19
+ version: '3.0'
22
20
  type: :runtime
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
24
  - - "~>"
28
25
  - !ruby/object:Gem::Version
29
- version: 3.0.0
26
+ version: '3.0'
30
27
  - !ruby/object:Gem::Dependency
31
28
  name: listen
32
29
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
30
  requirements:
35
31
  - - "~>"
36
32
  - !ruby/object:Gem::Version
@@ -38,7 +34,6 @@ dependencies:
38
34
  type: :runtime
39
35
  prerelease: false
40
36
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
37
  requirements:
43
38
  - - "~>"
44
39
  - !ruby/object:Gem::Version
@@ -46,7 +41,6 @@ dependencies:
46
41
  - !ruby/object:Gem::Dependency
47
42
  name: curses
48
43
  requirement: !ruby/object:Gem::Requirement
49
- none: false
50
44
  requirements:
51
45
  - - "~>"
52
46
  - !ruby/object:Gem::Version
@@ -54,7 +48,6 @@ dependencies:
54
48
  type: :runtime
55
49
  prerelease: false
56
50
  version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
51
  requirements:
59
52
  - - "~>"
60
53
  - !ruby/object:Gem::Version
@@ -67,43 +60,49 @@ executables:
67
60
  extensions: []
68
61
  extra_rdoc_files: []
69
62
  files:
70
- - lib/rspec-live/terminal.rb
71
- - lib/rspec-live/example.rb
72
- - lib/rspec-live/runner.rb
73
- - lib/rspec-live/version.rb
74
- - lib/rspec-live/suite.rb
75
- - lib/rspec-live/watcher.rb
76
- - lib/rspec-live/backtrace.rb
77
- - lib/rspec-live/display.rb
78
- - lib/formatters/inventory_formatter.rb
79
- - lib/formatters/update_formatter.rb
80
- - README.md
81
63
  - History.md
82
64
  - License.txt
65
+ - README.md
83
66
  - bin/rspec-live
67
+ - lib/formatters/coverage_analyzer.rb
68
+ - lib/formatters/inventory_formatter.rb
69
+ - lib/formatters/update_formatter.rb
70
+ - lib/rspec-live/backtrace.rb
71
+ - lib/rspec-live/concurrent_process.rb
72
+ - lib/rspec-live/display.rb
73
+ - lib/rspec-live/example.rb
74
+ - lib/rspec-live/file_watcher.rb
75
+ - lib/rspec-live/formatted_text.rb
76
+ - lib/rspec-live/key_handler.rb
77
+ - lib/rspec-live/log.rb
78
+ - lib/rspec-live/monitor.rb
79
+ - lib/rspec-live/result_detail.rb
80
+ - lib/rspec-live/runner.rb
81
+ - lib/rspec-live/screen.rb
82
+ - lib/rspec-live/suite.rb
83
+ - lib/rspec-live/version.rb
84
84
  homepage: http://github.com/brianauton/rspec-live
85
85
  licenses:
86
86
  - MIT
87
+ metadata: {}
87
88
  post_install_message:
88
89
  rdoc_options: []
89
90
  require_paths:
90
91
  - lib
91
92
  required_ruby_version: !ruby/object:Gem::Requirement
92
- none: false
93
93
  requirements:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
97
  required_rubygems_version: !ruby/object:Gem::Requirement
98
- none: false
99
98
  requirements:
100
99
  - - ">="
101
100
  - !ruby/object:Gem::Version
102
101
  version: 1.3.6
103
102
  requirements: []
104
103
  rubyforge_project:
105
- rubygems_version: 1.8.29
104
+ rubygems_version: 2.2.2
106
105
  signing_key:
107
- specification_version: 3
106
+ specification_version: 4
108
107
  summary: Continually updating console output for RSpec 3+
109
108
  test_files: []
@@ -1,121 +0,0 @@
1
- require "curses"
2
-
3
- module RSpecLive
4
- class Terminal
5
- def initialize
6
- Terminal.reset_curses
7
- @root_section = TerminalSection.new
8
- Signal.trap("SIGWINCH", proc { Terminal.reset_curses; @root_section.refresh })
9
- end
10
-
11
- def self.reset_curses
12
- Curses.init_screen
13
- Curses.curs_set 0
14
- Curses.clear
15
- Curses.refresh
16
- Curses.start_color
17
- Curses.use_default_colors
18
- available_colors.each do |name|
19
- Curses.init_pair color_constant(name), color_constant(name), -1
20
- end
21
- @width = `tput cols`.to_i
22
- @height = `tput lines`.to_i
23
- Curses.resizeterm @height, @width
24
- end
25
-
26
- def self.width
27
- @width
28
- end
29
-
30
- def add_section(*args)
31
- @root_section.add_section(*args)
32
- end
33
-
34
- def self.color_constant(name)
35
- Curses.const_get "COLOR_#{name.to_s.upcase}"
36
- end
37
-
38
- def self.available_colors
39
- [:blue, :green, :red, :yellow, :white]
40
- end
41
- end
42
-
43
- class TerminalSection
44
- def initialize(parent = nil, options = {})
45
- @content = ""
46
- @parent = parent
47
- options.each_pair { |key, value| instance_variable_set "@#{key}", value }
48
- @children = []
49
- end
50
-
51
- def add_section(options = {})
52
- new_section = TerminalSection.new(self, options)
53
- @children << new_section
54
- new_section
55
- end
56
-
57
- def content=(value)
58
- @content = value.to_s
59
- end
60
-
61
- def clear
62
- @content = ""
63
- @children = []
64
- refresh
65
- end
66
-
67
- def refresh
68
- if @parent
69
- @parent.refresh
70
- else
71
- Curses.clear
72
- Curses.setpos 0, 0
73
- draw
74
- Curses.refresh
75
- end
76
- end
77
-
78
- def draw
79
- Curses.addstr "\n" if @display == :block
80
- draw_left_margin
81
- if @color
82
- Curses.attron(color_attribute @color) { draw_content }
83
- else
84
- draw_content
85
- end
86
- draw_right_margin
87
- @children.each(&:draw)
88
- end
89
-
90
- def draw_content
91
- text = @content
92
- bullet = @bullet ? "#{@bullet} " : ""
93
- if @display == :block && @wrap
94
- text = bullet + wrap_with_margin(text, Terminal.width-1, bullet.length)
95
- end
96
- Curses.addstr text
97
- end
98
-
99
- def wrap_with_margin(text, width, margin_width)
100
- wrap(text, width - margin_width).split("\n").join("\n" + (" " * margin_width))
101
- end
102
-
103
- def wrap(text, width)
104
- text.scan(/\S.{0,#{width-2}}\S(?=\s|$)|\S+/).join("\n")
105
- end
106
-
107
- def draw_left_margin
108
- Curses.addstr(("=" * [0, (((Terminal.width - @content.length) / 2) - 1)].max) + " ") if @align == :center
109
- end
110
-
111
- def draw_right_margin
112
- Curses.addstr(" " + ("=" * [0, (((Terminal.width - @content.length) / 2) - 2)].max)) if @align == :center
113
- end
114
-
115
- private
116
-
117
- def color_attribute(name)
118
- Curses.color_pair(Terminal.color_constant name) | Curses::A_NORMAL
119
- end
120
- end
121
- end
@@ -1,42 +0,0 @@
1
- require "listen"
2
-
3
- module RSpecLive
4
- class Watcher
5
- def initialize(suite, display)
6
- @suite = suite
7
- @display = display
8
- end
9
-
10
- def start
11
- process_tests
12
- Listen.to(Dir.pwd) { process_tests }.start
13
- while perform_key_command; end
14
- rescue Interrupt
15
- end
16
-
17
- private
18
-
19
- def perform_key_command
20
- key = STDIN.getc.chr.downcase
21
- @suite.toggle_all if key == "a"
22
- @suite.focus_next if key == "n"
23
- return false if key == "q"
24
- reset if key == "r"
25
- @suite.cycle_verbosity if key == "v"
26
- true
27
- end
28
-
29
- def reset
30
- @suite.clear_status
31
- process_tests
32
- end
33
-
34
- def process_tests
35
- @display.status = "analyzing specs"
36
- @suite.inventory
37
- @display.status = "running specs"
38
- @suite.update
39
- @display.status = "watching for updates..."
40
- end
41
- end
42
- end