cucover 0.1.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.
Files changed (106) hide show
  1. data/.gitignore +6 -0
  2. data/Licence.txt +22 -0
  3. data/README.markdown +69 -0
  4. data/Rakefile +86 -0
  5. data/VERSION +1 -0
  6. data/bin/cucover +3 -0
  7. data/cucover.gemspec +182 -0
  8. data/cucumber.yml +3 -0
  9. data/examples/self_test/rails/.gitignore +4 -0
  10. data/examples/self_test/rails/Rakefile +10 -0
  11. data/examples/self_test/rails/app/controllers/application_controller.rb +2 -0
  12. data/examples/self_test/rails/app/controllers/widgets_controller.rb +2 -0
  13. data/examples/self_test/rails/app/helpers/application_helper.rb +3 -0
  14. data/examples/self_test/rails/app/views/widgets/index.html.erb +1 -0
  15. data/examples/self_test/rails/config/boot.rb +110 -0
  16. data/examples/self_test/rails/config/cucumber.yml +7 -0
  17. data/examples/self_test/rails/config/database.yml +25 -0
  18. data/examples/self_test/rails/config/environment.rb +41 -0
  19. data/examples/self_test/rails/config/environments/cucumber.rb +27 -0
  20. data/examples/self_test/rails/config/environments/development.rb +17 -0
  21. data/examples/self_test/rails/config/environments/production.rb +28 -0
  22. data/examples/self_test/rails/config/environments/test.rb +28 -0
  23. data/examples/self_test/rails/config/initializers/backtrace_silencers.rb +7 -0
  24. data/examples/self_test/rails/config/initializers/inflections.rb +10 -0
  25. data/examples/self_test/rails/config/initializers/mime_types.rb +5 -0
  26. data/examples/self_test/rails/config/initializers/new_rails_defaults.rb +21 -0
  27. data/examples/self_test/rails/config/initializers/session_store.rb +15 -0
  28. data/examples/self_test/rails/config/locales/en.yml +5 -0
  29. data/examples/self_test/rails/config/routes.rb +43 -0
  30. data/examples/self_test/rails/db/seeds.rb +7 -0
  31. data/examples/self_test/rails/features/see_widgets.feature +7 -0
  32. data/examples/self_test/rails/features/step_definitions/web_steps.rb +273 -0
  33. data/examples/self_test/rails/features/support/env.rb +57 -0
  34. data/examples/self_test/rails/features/support/paths.rb +29 -0
  35. data/examples/self_test/rails/lib/tasks/cucumber.rake +47 -0
  36. data/examples/self_test/rails/public/404.html +30 -0
  37. data/examples/self_test/rails/public/422.html +30 -0
  38. data/examples/self_test/rails/public/500.html +30 -0
  39. data/examples/self_test/rails/public/favicon.ico +0 -0
  40. data/examples/self_test/rails/public/images/rails.png +0 -0
  41. data/examples/self_test/rails/public/index.html +275 -0
  42. data/examples/self_test/rails/public/javascripts/application.js +2 -0
  43. data/examples/self_test/rails/public/javascripts/controls.js +963 -0
  44. data/examples/self_test/rails/public/javascripts/dragdrop.js +973 -0
  45. data/examples/self_test/rails/public/javascripts/effects.js +1128 -0
  46. data/examples/self_test/rails/public/javascripts/prototype.js +4320 -0
  47. data/examples/self_test/rails/public/robots.txt +5 -0
  48. data/examples/self_test/rails/script/about +4 -0
  49. data/examples/self_test/rails/script/console +3 -0
  50. data/examples/self_test/rails/script/cucumber +10 -0
  51. data/examples/self_test/rails/script/dbconsole +3 -0
  52. data/examples/self_test/rails/script/destroy +3 -0
  53. data/examples/self_test/rails/script/generate +3 -0
  54. data/examples/self_test/rails/script/performance/benchmarker +3 -0
  55. data/examples/self_test/rails/script/performance/profiler +3 -0
  56. data/examples/self_test/rails/script/plugin +3 -0
  57. data/examples/self_test/rails/script/runner +3 -0
  58. data/examples/self_test/rails/script/server +3 -0
  59. data/examples/self_test/simple/features/call_foo.feature +4 -0
  60. data/examples/self_test/simple/features/call_foo_and_bar_together.feature +5 -0
  61. data/examples/self_test/simple/features/call_foo_from_background_then_bar.feature +7 -0
  62. data/examples/self_test/simple/features/call_foo_from_background_then_bar_then_baz.feature +10 -0
  63. data/examples/self_test/simple/features/call_foo_then_bar.feature +7 -0
  64. data/examples/self_test/simple/features/call_foo_then_bar_from_scenario_outline_examples.feature +9 -0
  65. data/examples/self_test/simple/features/fail.feature +10 -0
  66. data/examples/self_test/simple/features/step_definitions/main_steps.rb +15 -0
  67. data/examples/self_test/simple/lib/bar.rb +5 -0
  68. data/examples/self_test/simple/lib/baz.rb +5 -0
  69. data/examples/self_test/simple/lib/foo.rb +9 -0
  70. data/features/call_foo.feature +0 -0
  71. data/features/coverage_of.feature +81 -0
  72. data/features/fail.feature +60 -0
  73. data/features/help.feature +15 -0
  74. data/features/lazy_run.feature +76 -0
  75. data/features/lazy_run_per_scenario.feature +108 -0
  76. data/features/lazy_run_per_scenario_outline_example.feature +29 -0
  77. data/features/lazy_run_triggered_by_rails_view_change.feature +43 -0
  78. data/features/run.feature +25 -0
  79. data/features/show_recordings.feature +28 -0
  80. data/features/step_definitions/main_steps.rb +41 -0
  81. data/features/support/env.rb +52 -0
  82. data/lib/at_exit_hook.rb +3 -0
  83. data/lib/cucover.rb +65 -0
  84. data/lib/cucover/cli.rb +50 -0
  85. data/lib/cucover/cli_commands/coverage_of.rb +44 -0
  86. data/lib/cucover/cli_commands/cucumber.rb +46 -0
  87. data/lib/cucover/cli_commands/show_recordings.rb +29 -0
  88. data/lib/cucover/cli_commands/version.rb +13 -0
  89. data/lib/cucover/controller.rb +28 -0
  90. data/lib/cucover/cucumber_hooks.rb +45 -0
  91. data/lib/cucover/line_numbers.rb +10 -0
  92. data/lib/cucover/logging_config.rb +16 -0
  93. data/lib/cucover/monkey.rb +14 -0
  94. data/lib/cucover/rails.rb +23 -0
  95. data/lib/cucover/recorder.rb +37 -0
  96. data/lib/cucover/recording.rb +66 -0
  97. data/lib/cucover/recording/covered_file.rb +53 -0
  98. data/lib/cucover/store.rb +70 -0
  99. data/lib/dependencies.rb +10 -0
  100. data/spec/cucover/cli_spec.rb +31 -0
  101. data/spec/cucover/controller_spec.rb +16 -0
  102. data/spec/cucover/recording/covered_file_spec.rb +20 -0
  103. data/spec/cucover/store_spec.rb +28 -0
  104. data/spec/spec_helper.rb +7 -0
  105. data/tmp/.gitignore +0 -0
  106. metadata +236 -0
@@ -0,0 +1,29 @@
1
+ Feature: Lazy Run per Scenario Outline Example
2
+ In order save time
3
+ As a developer working on a feature with Scenario Outlines
4
+ I want to run only the examples that could fail
5
+
6
+ Background:
7
+ Given I am using the simple example app
8
+ And I have run cucover -- features/call_foo_then_bar_from_scenario_outline_examples.feature
9
+
10
+ Scenario: Edit a source file that should trigger just one of the examples to be run
11
+ When I edit the source file lib/bar.rb
12
+ And I run cucover -- -q features/call_foo_then_bar_from_scenario_outline_examples.feature
13
+ Then it should pass with:
14
+ """
15
+ Feature: Call Foo then Bar from Scenario Outline Examples
16
+
17
+ Scenario Outline: Call Something
18
+ When I call <Code>
19
+
20
+ Examples:
21
+ | Code |
22
+ | Foo | [ Cucover - Skipping clean scenario ]
23
+ | Bar |
24
+
25
+ 2 scenarios (1 skipped, 1 passed)
26
+ 2 steps (1 skipped, 1 passed)
27
+
28
+ """
29
+
@@ -0,0 +1,43 @@
1
+ @slow
2
+ Feature: Lazy Run Triggered By Rails View Change
3
+ In order to feel the cucover love
4
+ As a front-end developer
5
+ I want the changes I make to rails views to trigger cucover runs, the same as Ruby code does
6
+
7
+ Background:
8
+ And I am using the rails example app
9
+ And I have run cucover -- features/see_widgets.feature
10
+
11
+ Scenario: Change nothing and run cucover again
12
+ When I run cucover -- -q --format pretty features/see_widgets.feature
13
+ Then it should pass with:
14
+ """
15
+ Using the default profile...
16
+ Feature: See widgets
17
+
18
+ Scenario: See widgets
19
+ When I go to the widgets
20
+ [ Cucover - Skipping clean scenario ]
21
+ Then I should see "Look at all these lovely widgets"
22
+
23
+ 1 scenario (1 skipped)
24
+ 2 steps (2 skipped)
25
+
26
+ """
27
+
28
+ Scenario: Edit a view and run cucover again
29
+ When I edit the source file app/views/widgets/index.html.erb
30
+ When I run cucover -- -q --format pretty features/see_widgets.feature
31
+ Then it should pass with:
32
+ """
33
+ Using the default profile...
34
+ Feature: See widgets
35
+
36
+ Scenario: See widgets
37
+ When I go to the widgets
38
+ Then I should see "Look at all these lovely widgets"
39
+
40
+ 1 scenario (1 passed)
41
+ 2 steps (2 passed)
42
+
43
+ """
@@ -0,0 +1,25 @@
1
+ Feature: Run
2
+ In order to trust cucover and not have to switch between testing tools
3
+ As a developer
4
+ I want to be able to run features and get useful feedback through cucover
5
+
6
+ Scenario: Run features, minimal output
7
+ Given I am using the simple example app
8
+ When I run cucover -- features/call_foo.feature features/call_foo_and_bar_together.feature
9
+ Then it should pass with:
10
+ """
11
+ Feature: Call Foo
12
+
13
+ Scenario: Call Foo # features/call_foo.feature:3
14
+ When I call Foo # features/step_definitions/main_steps.rb:9
15
+
16
+ Feature: Call Foo and Bar Together
17
+
18
+ Scenario: Call Foo and Bar # features/call_foo_and_bar_together.feature:3
19
+ When I call Foo # features/step_definitions/main_steps.rb:9
20
+ And I call Bar # features/step_definitions/main_steps.rb:9
21
+
22
+ 2 scenarios (2 passed)
23
+ 3 steps (3 passed)
24
+
25
+ """
@@ -0,0 +1,28 @@
1
+ Feature: Show Recordings
2
+ In order to see what's going on inside Cucover
3
+ As a developer of Cucover
4
+ I want to be able to see a human-readable summary of the recordings
5
+
6
+ Background:
7
+ Given I am using the simple example app
8
+
9
+ Scenario: Run a couple of features, see the recordings
10
+ Given I have run cucover -- features/call_foo.feature
11
+ And I have run cucover -- features/call_foo_and_bar_together.feature
12
+ When I run cucover --show-recordings
13
+ Then it should pass with:
14
+ """
15
+
16
+ features/call_foo.feature:3
17
+ features/step_definitions/main_steps.rb:6:10
18
+ lib/foo.rb:2:3
19
+ features/call_foo.feature:<unknown lines>
20
+
21
+ features/call_foo_and_bar_together.feature:3
22
+ features/step_definitions/main_steps.rb:6:10
23
+ lib/bar.rb:2:3
24
+ lib/foo.rb:2:3
25
+ features/call_foo_and_bar_together.feature:<unknown lines>
26
+
27
+
28
+ """
@@ -0,0 +1,41 @@
1
+ Given /^I have (tried to |)run cucover (.*)$/ do |tried, args|
2
+ When %{I run cucover #{args}}
3
+ if tried.blank?
4
+ assert(@status == 0, @out)
5
+ end
6
+ end
7
+
8
+ Given /^I am using the (.*) example app$/ do |app_name|
9
+ @example_app = app_name
10
+ clear_cache!
11
+ end
12
+
13
+ When /^I run cucover (.*)$/ do |args|
14
+ cucover_binary = File.expand_path(File.dirname(__FILE__) + '../../../bin/cucover')
15
+ within_examples_dir do
16
+ full_cmd = "#{Cucumber::RUBY_BINARY} #{cucover_binary} #{args}"
17
+ @out = `#{full_cmd}`
18
+ @status = $?.exitstatus
19
+ end
20
+ end
21
+
22
+ When /^I edit the source file (.*)$/ do |source_file|
23
+ within_examples_dir do
24
+ edit source_file
25
+ end
26
+ end
27
+
28
+ Then /^it should (pass|fail) with:$/ do |expected_status, expected_text|
29
+ expected_status_code = expected_status == "pass" ? 0 : 1
30
+
31
+ unless @status == expected_status_code
32
+ raise "Expected #{expected_status} but return code was #{@status}: #{@out}"
33
+ end
34
+
35
+ strip_trailing_spaces(strip_duration(@out)).should == expected_text
36
+ end
37
+
38
+ Then /^I should see "([^\"]*)"$/ do |expected_text|
39
+ @out.should =~ /#{Regexp.escape(expected_text)}/
40
+ end
41
+
@@ -0,0 +1,52 @@
1
+ # require File.dirname(__FILE__) + '/../../lib/cucover'
2
+ require 'spec'
3
+ require 'test/unit/assertions'
4
+
5
+ module CucoverHelper
6
+ def edit(file)
7
+ original_mtime = File.mtime(file)
8
+ FileUtils.touch(file)
9
+ @edited_files ||= {}
10
+ @edited_files[file] = original_mtime
11
+ end
12
+
13
+ def restore_file_mtimes
14
+ return unless @edited_files
15
+ @edited_files.each do |file, original_mtime|
16
+ `touch -t #{original_mtime.strftime('%Y%m%d%H%M.%S')} #{examples_dir}/#{file}`
17
+ end
18
+ end
19
+
20
+ def strip_duration(s)
21
+ s.gsub(/^\d+m\d+\.\d+s\n/m, "")
22
+ end
23
+
24
+ def strip_trailing_spaces(s)
25
+ s.gsub(/ +$/, '')
26
+ end
27
+
28
+ def clear_cache!
29
+ `find examples -name cucover.data | xargs rm -rf`
30
+ end
31
+
32
+ def example_app
33
+ @example_app || raise("Please call the step 'Given I am using the .... example app' so I know which example app to run these features in.")
34
+ end
35
+
36
+ def within_examples_dir
37
+ Dir.chdir(examples_dir) do
38
+ yield
39
+ end
40
+ end
41
+
42
+ def examples_dir
43
+ File.expand_path(File.dirname(__FILE__) + "/../../examples/self_test/#{example_app}")
44
+ end
45
+ end
46
+
47
+ World CucoverHelper, Test::Unit::Assertions
48
+
49
+ After do
50
+ clear_cache!
51
+ restore_file_mtimes
52
+ end
@@ -0,0 +1,3 @@
1
+ at_exit do
2
+ exit(Cucover::CliCommands::Cucumber.exit_status || 0)
3
+ end
@@ -0,0 +1,65 @@
1
+ $:.unshift(File.dirname(__FILE__))
2
+ require 'dependencies'
3
+ require 'cucover/logging_config'
4
+ require 'cucover/cli_commands/coverage_of'
5
+ require 'cucover/cli_commands/cucumber'
6
+ require 'cucover/cli_commands/show_recordings'
7
+ require 'cucover/cli_commands/version'
8
+ require 'cucover/controller'
9
+ require 'cucover/cli'
10
+ require 'cucover/monkey'
11
+ require 'cucover/rails'
12
+ require 'cucover/recording'
13
+ require 'cucover/recorder'
14
+ require 'cucover/store'
15
+ require 'cucover/line_numbers'
16
+
17
+
18
+ require 'at_exit_hook'
19
+
20
+ module Cucover
21
+ class << self
22
+ def logger
23
+ Logging::Logger['Cucover']
24
+ end
25
+
26
+ def should_execute?(scenario_or_table_row)
27
+ controller(scenario_or_table_row).should_execute?
28
+ end
29
+
30
+ def start_recording!(scenario_or_table_row)
31
+ raise("Already recording. Please call stop first.") if recording?
32
+
33
+ @current_recorder = Recorder.new(scenario_or_table_row)
34
+ @current_recorder.start!
35
+ record_file(scenario_or_table_row.file_colon_line.split(':').first) # TODO: clean this by extending the feature element
36
+ end
37
+
38
+ def stop_recording!
39
+ return unless recording?
40
+
41
+ @current_recorder.stop!
42
+ store.keep!(@current_recorder.recording)
43
+ @current_recorder = nil
44
+ end
45
+
46
+ def record_file(source_file)
47
+ Cucover.logger.debug("Recording extra source file #{source_file}")
48
+ @current_recorder.record_file!(source_file)
49
+ end
50
+
51
+ private
52
+
53
+ def controller(scenario_or_table_row)
54
+ Controller.new(scenario_or_table_row.file_colon_line, store)
55
+ end
56
+
57
+ def recording?
58
+ !!@current_recorder
59
+ end
60
+
61
+ def store
62
+ @store ||= Store.new
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,50 @@
1
+ module Cucover
2
+ class Cli
3
+ def initialize(args)
4
+ @args = args
5
+ end
6
+
7
+ def start
8
+ command_type.new(@args).execute
9
+ end
10
+
11
+ private
12
+
13
+ def command_type
14
+ case @args.first
15
+ when '--'
16
+ CliCommands::Cucumber
17
+ when '--version', '-v'
18
+ CliCommands::Version
19
+ when '--coverage-of', '-c'
20
+ CliCommands::CoverageOf
21
+ when '--show-recordings', '-s'
22
+ CliCommands::ShowRecordings
23
+ else
24
+ puts help
25
+ Kernel.exit(0)
26
+ end
27
+ end
28
+
29
+ def help
30
+ <<-EOH
31
+ Usage: cucover -- [options] [ [FILE|DIR|URL][:LINE[:LINE]*] ]+
32
+
33
+ Examples:
34
+ cucover -- --format pretty features
35
+ cucover --coverage-of lib/monkeys.rb
36
+ cucover --show-recordings
37
+
38
+ -- [ARGS] Run cucumber while recording coverage. This will skip scenarios
39
+ if they have already been run and the covering code has not
40
+ been changed.
41
+ -c [FILE], --coverage-of [FILE] Show file with feature coverage information
42
+ -s, --show-recordings Show all coverage information currently recorded
43
+ -v, --version Show version
44
+ -h, --help You're looking at it.
45
+
46
+ EOH
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,44 @@
1
+ module Cucover
2
+ module CliCommands
3
+ class CoverageOf
4
+ CODE_COLUMN_WIDTH = 15
5
+
6
+ def initialize(cli_args)
7
+ @filespec = cli_args[1]
8
+ @store = Store.new
9
+ end
10
+
11
+ def execute
12
+ load_cucumber_enviroment
13
+ return unless recordings.any?
14
+
15
+ File.open(@filespec).each_with_index do |line_content, index|
16
+ line_number = index + 1
17
+ coverage_text = coverage(line_number).join(', ')
18
+ line_content.rstrip!
19
+ if line_content.length > CODE_COLUMN_WIDTH
20
+ truncated_line_content = "#{line_content[0..(CODE_COLUMN_WIDTH - 1)]}.."
21
+ else
22
+ truncated_line_content = "#{line_content} "
23
+ end
24
+ puts "#{line_number} #{truncated_line_content.ljust(CODE_COLUMN_WIDTH + 2)} #{coverage_text}"
25
+ end
26
+ end
27
+
28
+ def coverage(line_number)
29
+ recordings.select{ |r| r.covers_line?(@filespec, line_number) }.map{ |r| r.file_colon_line }
30
+ end
31
+
32
+ def recordings
33
+ @recordings ||= @store.recordings_covering(@filespec)
34
+ end
35
+
36
+ private
37
+ def load_cucumber_enviroment
38
+ support_files = Dir['features/**/*'].select {|file| file =~ /support\/env\..*/ }
39
+ support_files.each{ |env_file| puts env_file; require env_file }
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,46 @@
1
+ module Cucover
2
+ module CliCommands
3
+ class Cucumber
4
+
5
+ LANGUAGE = 'rb'
6
+
7
+ class << self
8
+ attr_accessor :exit_status
9
+ end
10
+
11
+ def initialize(cli_args)
12
+ Cucumber.exit_status = 0
13
+ @cli_args = cli_args
14
+ end
15
+
16
+ def execute
17
+ require 'rubygems'
18
+ require 'cucumber'
19
+
20
+ step_mother = ::Cucumber::StepMother.new
21
+ step_mother.load_programming_language(LANGUAGE)
22
+ require 'cucover/cucumber_hooks'
23
+
24
+ execute_cuke do
25
+ ::Cucumber::Cli::Main.new(ARGV).execute!(step_mother)
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def execute_cuke
32
+ ARGV.replace cucumber_args
33
+ Cucumber.exit_status = yield
34
+ Cucumber.exit_status = Cucumber.exit_status ? 1 : 0
35
+ ARGV.replace @cli_args
36
+ end
37
+
38
+ def cucumber_args
39
+ return nil unless @cli_args.index('--')
40
+ first = @cli_args.index('--') + 1
41
+ last = @cli_args.length - 1
42
+ @cli_args[first..last]
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,29 @@
1
+ module Cucover
2
+ module CliCommands
3
+ class ShowRecordings
4
+ def initialize(cli_args)
5
+ @store = Store.new
6
+ end
7
+
8
+ def execute
9
+ unless recordings.any?
10
+ puts "No recordings to show. Run some tests with cucover first."
11
+ return
12
+ end
13
+
14
+ recordings.each do |recording|
15
+ puts
16
+ puts "#{recording.file_colon_line}" # (#{recording.start_time.strftime('%Y-%m-%d %H:%M:%S')})
17
+ recording.covered_files.each do |covered_file|
18
+ puts " #{covered_file.to_s}"
19
+ end
20
+ end
21
+ puts
22
+ end
23
+
24
+ def recordings
25
+ @recordings ||= @store.latest_recordings.sort{ |x, y| x.file_colon_line <=> y.file_colon_line }
26
+ end
27
+ end
28
+ end
29
+ end