cucover 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
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