rspec-live 0.0.1 → 0.0.2

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