stepdown 0.3.3 → 0.4.0

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 (45) hide show
  1. data/Gemfile +4 -2
  2. data/Gemfile.lock +14 -10
  3. data/History.txt +30 -0
  4. data/README.rdoc +2 -2
  5. data/Rakefile +13 -0
  6. data/bin/stepdown +4 -4
  7. data/lib/stepdown.rb +6 -0
  8. data/lib/stepdown/analyzer.rb +67 -0
  9. data/lib/stepdown/feature_parser.rb +30 -0
  10. data/lib/stepdown/html_reporter.rb +41 -0
  11. data/lib/stepdown/options.rb +59 -0
  12. data/lib/stepdown/reporter.rb +106 -0
  13. data/lib/stepdown/scenario.rb +25 -0
  14. data/lib/stepdown/step.rb +22 -0
  15. data/lib/stepdown/step_collection.rb +36 -0
  16. data/lib/stepdown/step_group.rb +45 -0
  17. data/lib/stepdown/step_instance.rb +63 -0
  18. data/lib/stepdown/step_usage.rb +15 -0
  19. data/lib/stepdown/text_reporter.rb +38 -0
  20. data/spec/lib/{feature_parser_spec.rb → stepdown/feature_parser_spec.rb} +13 -12
  21. data/spec/lib/{options_spec.rb → stepdown/options_spec.rb} +24 -6
  22. data/spec/lib/stepdown/reporter_spec.rb +184 -0
  23. data/spec/lib/stepdown/scenario_spec.rb +40 -0
  24. data/spec/lib/stepdown/step_collection_spec.rb +78 -0
  25. data/spec/lib/stepdown/step_group_spec.rb +43 -0
  26. data/spec/lib/{step_instance_spec.rb → stepdown/step_instance_spec.rb} +13 -12
  27. data/spec/spec_helper.rb +4 -0
  28. data/stepdown.gemspec +7 -4
  29. data/templates/main.html.haml +3 -3
  30. data/templates/style.sass +5 -4
  31. metadata +64 -34
  32. data/lib/counting_step.rb +0 -14
  33. data/lib/feature_parser.rb +0 -32
  34. data/lib/html_reporter.rb +0 -37
  35. data/lib/options.rb +0 -69
  36. data/lib/reporter.rb +0 -62
  37. data/lib/scenario.rb +0 -19
  38. data/lib/step.rb +0 -10
  39. data/lib/step_down.rb +0 -112
  40. data/lib/step_group.rb +0 -52
  41. data/lib/step_instance.rb +0 -60
  42. data/lib/step_usage.rb +0 -13
  43. data/lib/text_reporter.rb +0 -36
  44. data/spec/lib/scenario_spec.rb +0 -42
  45. data/spec/lib/step_group_spec.rb +0 -119
@@ -1,14 +0,0 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/step')
2
- class CountingStep < Step
3
- attr_accessor :count
4
-
5
- def initialize(id, regex)
6
- @count = 0
7
- super(id, regex)
8
- end
9
-
10
- def <=>(step)
11
- self.count <=> step.count
12
- end
13
-
14
- end
@@ -1,32 +0,0 @@
1
- #!/usr/bin/ruby
2
- require 'rubygems'
3
-
4
- require File.expand_path(File.dirname(__FILE__) + '/step')
5
- require File.expand_path(File.dirname(__FILE__) + '/scenario')
6
-
7
- class FeatureParser
8
-
9
- def process_feature(file, instance)
10
- @scenarios = []
11
- file_lines = read_feature_file(file)
12
-
13
- file_lines.each do |line|
14
-
15
- if line =~ /Scenario|Background/
16
- @scenario = Scenario.new
17
- @scenarios << @scenario
18
- else
19
- step = instance.line_matches(line)
20
- @scenario.add_step(step) if step
21
- end
22
- end
23
-
24
- return @scenarios
25
- end
26
-
27
- protected
28
- def read_feature_file(file_name)
29
- File.read(file_name).split("\n")
30
- end
31
- end
32
-
@@ -1,37 +0,0 @@
1
- require 'fileutils'
2
- require 'reporter'
3
- class HTMLReporter < Reporter
4
-
5
- def output_overview()
6
- puts "Generating report..."
7
- FileUtils.mkdir_p(Reporter::OUTPUT_DIR)
8
- copy_files
9
-
10
- template = File.open(File.expand_path(File.dirname(__FILE__)) + '/../templates/main.html.haml').read()
11
- engine = Haml::Engine.new(template)
12
-
13
- out = File.new(Reporter::OUTPUT_DIR + '/analysis.html','w+')
14
- out.puts engine.render(self)
15
- out.close
16
-
17
- template = File.open(File.expand_path(File.dirname(__FILE__)) + '/../templates/style.sass').read
18
- sass_engine = Sass::Engine.new(template)
19
-
20
- out = File.new(Reporter::OUTPUT_DIR + '/style.css', 'w+')
21
- out.puts sass_engine.render
22
-
23
- out.close
24
-
25
- $stdout.puts "\nReport output to #{Reporter::OUTPUT_DIR}/analysis.html"
26
- end
27
-
28
- protected
29
-
30
- def copy_files
31
- ['step_down.js', 'jquery-1.4.3.min.js'].each do |file|
32
- src = File.expand_path("#{File.dirname(__FILE__)}/../public/#{file}")
33
- FileUtils.cp(src, File.join(OUTPUT_DIR, "#{file}"))
34
- end
35
- end
36
-
37
- end
@@ -1,69 +0,0 @@
1
- require 'optparse'
2
-
3
- class Options
4
-
5
- OUTPUT_FORMATS = ["html", "text"]
6
-
7
- def parse(params)
8
- @@steps_dir = "features/step_definitions"
9
- @@features_dir = "features"
10
- @@reporter = "html"
11
- parser = OptionParser.new do |opts|
12
- opts.banner = "Usage: stepdown step_definition_dir feature_file_directory"
13
-
14
- opts.separator("")
15
-
16
- opts.on("--output=TYPE", OUTPUT_FORMATS, "Select ouput format (#{OUTPUT_FORMATS.join(',')}). Default: html") do |o|
17
- @@reporter = o
18
- end
19
-
20
- opts.on("--steps=STEPS_DIR", "Step definition directory. Default: ./features/step_definitions") do |o|
21
- @@steps_dir = o
22
- end
23
-
24
- opts.on("--features=FEATURE_DIR", "Feature file directory. Default: ./features") do |o|
25
- @@features_dir = o
26
- end
27
-
28
- opts.on_tail("-h", "--help", "You're looking at it") do |o|
29
- puts opts
30
- exit
31
- end
32
-
33
- end
34
-
35
- begin
36
- parser.parse(params)
37
-
38
- rescue OptionParser::ParseError => e
39
- puts e
40
- exit 1
41
- end
42
- end
43
-
44
- def validate
45
- @@steps_dir = File.join(Dir.pwd, @@steps_dir)
46
- @@features_dir = File.join(Dir.pwd, @@features_dir)
47
-
48
- [@@steps_dir, @@features_dir].each do |dir|
49
- unless File.exists?(dir)
50
- puts "Directory #{dir} does not exist"
51
- exit 1
52
- end
53
- end
54
-
55
- end
56
-
57
- def steps_dir
58
- @@steps_dir
59
- end
60
-
61
- def features_dir
62
- @@features_dir
63
- end
64
-
65
- def reporter
66
- @@reporter
67
- end
68
-
69
- end
@@ -1,62 +0,0 @@
1
-
2
- class Reporter
3
- OUTPUT_DIR = "./stepdown"
4
-
5
- attr_reader :scenarios, :usages, :steps, :grouping
6
-
7
- def initialize(scenarios, usages, grouping, steps)
8
- @scenarios = scenarios
9
- @usages = usages
10
- @steps = steps
11
- @grouping = grouping
12
- end
13
-
14
- def groupings
15
- @grouping
16
- end
17
-
18
- def total_scenarios
19
- @scenarios.length
20
- end
21
-
22
- def total_steps
23
- @steps.length
24
- end
25
-
26
- def steps_per_scenario
27
- steps_scenario(@scenarios)
28
- end
29
-
30
- def unique_steps
31
- uniq_steps_per_scenario(@scenarios)
32
- end
33
-
34
- def usages
35
- @usages.select{|use| use.total_usage > 0 }
36
- end
37
-
38
- def unused_steps
39
- @usages.select{|use| use.total_usage == 0}
40
- end
41
-
42
- def uniq_steps_per_scenario(scenarios)
43
- total_steps = 0.0
44
- uniq_steps = 0.0
45
- scenarios.each do |scen|
46
- total_steps += scen.steps.length
47
- uniq_steps += scen.uniq_steps.length
48
- end
49
- sprintf "%.2f", (total_steps / uniq_steps)
50
- end
51
-
52
- def steps_scenario(scenarios)
53
- scen_count = scenarios.length
54
- step_count = 0.0
55
- scenarios.each do |scenario|
56
- step_count += scenario.steps.length
57
- end
58
- sprintf "%.2f", (step_count / scen_count)
59
- end
60
-
61
- end
62
-
@@ -1,19 +0,0 @@
1
- class Scenario
2
-
3
- def add_step(step)
4
- steps << step
5
- end
6
-
7
- def steps
8
- @steps ||= []
9
- end
10
-
11
- def uniq_steps
12
- uniq_steps = []
13
- steps.each do |step|
14
- uniq_steps << step unless uniq_steps.any?{|uniq| uniq.id == step.id}
15
- end
16
- uniq_steps
17
- end
18
-
19
- end
@@ -1,10 +0,0 @@
1
- class Step
2
-
3
- attr_reader :id, :regex
4
-
5
- def initialize(id, regex)
6
- @id = id
7
- @regex = regex
8
- end
9
-
10
- end
@@ -1,112 +0,0 @@
1
- require 'feature_parser'
2
- require 'pp'
3
- require 'haml'
4
- require 'sass'
5
-
6
- require 'step_instance'
7
- require 'step_group'
8
- require 'step_usage'
9
- require 'html_reporter'
10
- require 'text_reporter'
11
- class StepDown
12
-
13
- def initialize(steps_dir, feature_dir, reporter)
14
- @feature_files = Dir.glob(feature_dir + '/**/*.feature')
15
- @step_files = Dir.glob(steps_dir + '/**/*.rb')
16
- @reporter = reporter
17
- end
18
-
19
- def analyse
20
- puts "Parsing feature files..."
21
- parser = FeatureParser.new
22
-
23
- scenarios = []
24
- @feature_files.each do |feature|
25
- scenarios << parser.process_feature(feature, instance)
26
- end
27
- scenarios.flatten!
28
-
29
- puts "Performing analysis..."
30
- usages = step_usage(scenarios)
31
- usages = usages.sort{|a,b| b.total_usage <=> a.total_usage }
32
- grouping = grouping(scenarios).sort{|a,b| b.use_count <=> a.use_count}
33
-
34
- reporter = reporter(@reporter, scenarios, usages, grouping, instance.steps)
35
- reporter.output_overview
36
-
37
- # #pp grouping(@scenarios)
38
- # puts YAML::dump(scenario) if scenario.use_count > 100 && scenario.use_count < 500
39
- # end
40
- end
41
-
42
- # don't want a factory just yet
43
- def reporter(type, scenarios, usages, grouping, steps)
44
- case type
45
- when "html"
46
- HTMLReporter.new(scenarios, usages, grouping, steps)
47
- when "text"
48
- TextReporter.new(scenarios, usages, grouping, steps)
49
- end
50
- end
51
-
52
- def step_usage(scenarios)
53
- usages = instance.steps.collect{|step| StepUsage.new(step) }
54
- scenarios.each do |scenario|
55
-
56
- scenario.steps.each do |step|
57
- usage = usages.detect{|use| use.step.id == step.id}
58
- usage.total_usage += 1 if usage
59
- end
60
-
61
- scenario.uniq_steps.each do |step|
62
- usage = usages.detect{|use| use.step.id == step.id}
63
- usage.number_scenarios += 1 if usage
64
- end
65
-
66
- end
67
-
68
- usages.each do |usage|
69
- if usage.number_scenarios > 0
70
- use = sprintf "%.2f", (usage.total_usage / Float(usage.number_scenarios))
71
- usage.use_scenario = use
72
- end
73
- end
74
-
75
- usages
76
- end
77
-
78
- def grouping(scenarios)
79
- step_groups = instance.steps.collect{|step| StepGroup.new(step) }
80
-
81
- step_groups.each do |step_group|
82
- scenarios.each do |scenario|
83
- used = scenario.steps.any?{|s| s.id == step_group.id}
84
-
85
- if used
86
- scenario.steps.each do |scen_step|
87
- step_group.add_step(scen_step)
88
- end
89
- step_group.update_use_count
90
- end
91
-
92
- end
93
-
94
- end
95
- step_groups
96
- end
97
-
98
- def step(id)
99
- instance.steps.detect{|step| step.id == id}
100
- end
101
-
102
- def instance
103
- @instance ||= begin
104
- new_inst = StepInstance.new
105
-
106
- Dir.glob(@step_files).each do |file_name|
107
- new_inst.instance_eval File.read(file_name)
108
- end
109
- new_inst
110
- end
111
- end
112
- end
@@ -1,52 +0,0 @@
1
- require 'cgi'
2
- require 'counting_step'
3
- class StepGroup
4
- attr_reader :id, :regex, :total_usage
5
-
6
- def initialize(step)
7
- @id = step.id
8
- @regex = step.regex
9
- @total_usage = 0
10
- @in_steps = {}
11
- end
12
-
13
- def in_steps
14
- @in_steps.sort{|a,b| b[1] <=> a[1]}
15
- end
16
-
17
- def add_step(step)
18
- if @in_steps[step.id]
19
- @in_steps[step.id].count += 1
20
- else
21
- @in_steps[step.id] = CountingStep.new(step.id, step.regex)
22
- @in_steps[step.id].count = 1
23
- end
24
- end
25
-
26
- def update_use_count
27
- @total_usage = 0
28
- @in_steps.each do |key,val|
29
- @total_usage += val.count
30
- end
31
- @total_usage
32
- end
33
-
34
- def use_count
35
- @total_usage || 0
36
- end
37
-
38
- def group_graph
39
- base = "https://chart.googleapis.com/chart?cht=gv:circo&chl=graph{"
40
- base += "a [label=\"#{CGI.escape(CGI.escapeHTML(@regex.inspect.to_s))}\"];"
41
-
42
- @in_steps.each do |id,in_step|
43
-
44
- next if in_step[:step].regex.nil?
45
- base += "a--\"#{CGI.escape(CGI.escapeHTML(in_step[:step].regex.inspect.to_s))}\" [weight=#{in_step[:count]}];"
46
- #a [label=\"#{grouping.in_steps[0][:step].regex.inspect}\"]; a--b [penwidth=3,weight=2];b--d}"
47
- end
48
- base += "}"
49
- base
50
- end
51
-
52
- end
@@ -1,60 +0,0 @@
1
- class StepInstance
2
- def initialize
3
- @steps = []
4
- end
5
-
6
- def Given(regex,&block)
7
- define_step(regex,&block)
8
- end
9
-
10
- def When(regex,&block)
11
- define_step(regex,&block)
12
- end
13
-
14
- def Then(regex,&block)
15
- define_step(regex,&block)
16
- end
17
-
18
- def define_step(regex,&block)
19
- @steps << regex
20
- end
21
-
22
- def self.method_missing(*args)
23
- #nothing
24
- end
25
-
26
- def method_missing(*args)
27
- #nothing
28
- end
29
-
30
- def self.const_missing(*args)
31
- self
32
- end
33
-
34
- def require(*args)
35
- # do nothing
36
- end
37
-
38
- def line_matches(line)
39
- stripped_line = line.strip.gsub(/^(And|Given|When|Then) (.*)$/,'\2')
40
-
41
- @steps.each_with_index do |regex,i|
42
- match = regex.match(stripped_line)
43
- if match
44
- return steps[i]
45
- end
46
- end
47
-
48
- return nil
49
- end
50
-
51
- def steps
52
- return @step_definitions if @step_definitions
53
- @step_definitions = []
54
- @steps.each_with_index do |regex, i|
55
- @step_definitions << Step.new(i, regex)
56
- end
57
- @step_definitions
58
- end
59
-
60
- end