mobiusloop 0.1.3 → 0.1.5

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -2
  3. data/README.md +179 -117
  4. data/bin/a2h +17 -0
  5. data/bin/bcat +17 -0
  6. data/bin/btee +17 -0
  7. data/bin/coderay +17 -0
  8. data/bin/coveralls +17 -0
  9. data/bin/htmldiff +17 -0
  10. data/bin/kramdown +17 -0
  11. data/bin/ldiff +17 -0
  12. data/bin/{mobiusloop → mobius} +0 -0
  13. data/bin/nokogiri +17 -0
  14. data/bin/pry +17 -0
  15. data/bin/rackup +17 -0
  16. data/bin/rake +17 -0
  17. data/bin/rspec +17 -0
  18. data/bin/term_cdiff +17 -0
  19. data/bin/term_colortab +17 -0
  20. data/bin/term_decolor +17 -0
  21. data/bin/term_display +17 -0
  22. data/bin/term_mandel +17 -0
  23. data/bin/thor +17 -0
  24. data/bin/tilt +17 -0
  25. data/bin/yard +17 -0
  26. data/bin/yardoc +17 -0
  27. data/bin/yri +17 -0
  28. data/build +7 -0
  29. data/docs/Outcome_Options.docx +0 -0
  30. data/examples/mobiusloop/increase_readers.goal +6 -8
  31. data/gherkin-languages.json +6 -3
  32. data/lib/cucumber/cli/main.rb +1 -1
  33. data/lib/cucumber/project_initializer.rb +11 -6
  34. data/lib/mobiusloop/config/config.yml +9 -0
  35. data/lib/mobiusloop/config/config_file.rb +20 -0
  36. data/lib/mobiusloop/hooks.rb +22 -0
  37. data/lib/mobiusloop/mobius_steps.rb +7 -5
  38. data/lib/mobiusloop/objective.rb +67 -1
  39. data/lib/mobiusloop/outcome.rb +68 -12
  40. data/lib/mobiusloop/scale.rb +3 -16
  41. data/lib/mobiusloop/version +1 -0
  42. data/mobiusloop.gemspec +17 -15
  43. data/mobiusloop.gemspec.lock +10 -0
  44. data/spec/mobiusloop/measure_spec.rb +17 -0
  45. data/spec/mobiusloop/objective_spec.rb +52 -0
  46. data/spec/mobiusloop/outcome_spec.rb +59 -0
  47. data/spec/mobiusloop/page_response_scale_spec.rb +16 -0
  48. data/spec/mobiusloop/scale_error.rb +16 -0
  49. data/spec/mobiusloop/scale_sample.rb +18 -0
  50. data/spec/mobiusloop/scale_spec.rb +23 -0
  51. metadata +183 -25
  52. data/CONTRIBUTING.md +0 -68
@@ -562,7 +562,8 @@
562
562
  ],
563
563
  "given": [
564
564
  "* ",
565
- "Given "
565
+ "Given ",
566
+ "Baseline: "
566
567
  ],
567
568
  "name": "English",
568
569
  "native": "English",
@@ -577,11 +578,13 @@
577
578
  ],
578
579
  "then": [
579
580
  "* ",
580
- "Then "
581
+ "Then ",
582
+ "Scale: "
581
583
  ],
582
584
  "when": [
583
585
  "* ",
584
- "When "
586
+ "When ",
587
+ "Target: "
585
588
  ]
586
589
  },
587
590
  "en-Scouse": {
@@ -46,7 +46,7 @@ module Cucumber
46
46
  @err.puts("Couldn't open #{e.path}")
47
47
  exit_unable_to_finish
48
48
  rescue FeatureFolderNotFoundException => e
49
- @err.puts(e.message + ". You can use `mobiusloop --init` to get started.")
49
+ @err.puts("Could not find goals directory. Try this: mobiusloop --init")
50
50
  exit_unable_to_finish
51
51
  rescue ProfilesNotDefinedError, YmlLoadError, ProfileNotFound => e
52
52
  @err.puts(e.message)
@@ -3,6 +3,8 @@ module Cucumber
3
3
  # Generates generic file structure for a cucumber project
4
4
  class ProjectInitializer
5
5
  def run
6
+ puts "file expand path = #{File.expand_path(__FILE__)}"
7
+ @version = File.read(File.expand_path("../../../lib/mobiusloop/version", __FILE__))
6
8
  # normal cucumber init, replacing /features for /goals
7
9
  create_directory('goals')
8
10
  create_directory('goals/step_definitions')
@@ -10,7 +12,9 @@ module Cucumber
10
12
  create_file('goals/support/env.rb')
11
13
 
12
14
  # extra mobiusloop initiialization
13
- copy_step_defs('mobius_steps.rb', 'goals/step_definitions') # install mobius_steps.rb step definition
15
+ copy_step_defs('mobius_steps.rb', 'goals/step_definitions')
16
+ copy_step_defs('hooks.rb', 'goals/support')
17
+ copy_step_defs('config/config.yml', 'goals/support')
14
18
  copy_step_defs('scales/page_response_scale.rb', 'goals/step_definitions') # example scale to measure response time
15
19
  copy_gherkin_languages('gherkin-languages.json') # copy modified gherkin-languages.json file to gherkin gem(s)
16
20
  copy_example_file('total_articles_scale.rb', 'goals/step_definitions')
@@ -22,22 +26,24 @@ module Cucumber
22
26
 
23
27
  def copy_step_defs(spec_file, target)
24
28
  gem_dir = `gem environment gemdir`
25
- steps_file = gem_dir.gsub("\n","") + "/gems/mobiusloop-?.?.?/lib/mobiusloop/" + spec_file
29
+ steps_file = gem_dir.gsub("\n","") + "/gems/mobiusloop-#{@version}/lib/mobiusloop/" + spec_file
26
30
  report_copying(spec_file, target)
27
31
  copy_file(steps_file, target)
28
32
  end
29
33
 
34
+ # note: this is penned to v3.2.0 of gherkin because 4.0.0 was causing issues
35
+ # this is not very elegant, but works for now
30
36
  def copy_gherkin_languages(gherkin_file)
31
37
  gem_dir = `gem environment gemdir`
32
- source_gherkin = gem_dir.gsub("\n","") + "/gems/mobiusloop-?.?.?/" + gherkin_file
33
- target_gherkin = gem_dir.gsub("\n","") + "/gems/gherkin-?.?.?/lib/gherkin/"
38
+ source_gherkin = gem_dir.gsub("\n","") + "/gems/mobiusloop-#{@version}/" + gherkin_file
39
+ target_gherkin = gem_dir.gsub("\n","") + "/gems/gherkin-3.2.0/lib/gherkin/"
34
40
  report_copying(gherkin_file, target_gherkin)
35
41
  copy_file(source_gherkin, target_gherkin)
36
42
  end
37
43
 
38
44
  def copy_example_file(file, target)
39
45
  gem_dir = `gem environment gemdir`
40
- steps_file = gem_dir.gsub("\n","") + "/gems/mobiusloop-?.?.?/examples/mobiusloop/" + file
46
+ steps_file = gem_dir.gsub("\n","") + "/gems/mobiusloop-#{@version}/examples/mobiusloop/" + file
41
47
  report_copying(file, target)
42
48
  copy_file(steps_file, target)
43
49
  end
@@ -63,7 +69,6 @@ module Cucumber
63
69
  end
64
70
 
65
71
  report_exists(file_name) || return if File.exists?(file_name)
66
-
67
72
  report_creating(file_name)
68
73
  FileUtils.send file_type, file_name
69
74
  end
@@ -0,0 +1,9 @@
1
+ # This contains configuration options
2
+
3
+ measures:
4
+ save: false # whether to save each run as .json file under goals/measures/
5
+ # set to true to enable, false to disable
6
+ progress:
7
+ good_percent: 10 # this percent or less deviation from target is "green"
8
+ bad_percent: 30 # this percent or greater deviation from target is "red"
9
+ # good must be less than bad and between these two numbers is "yellow"
@@ -0,0 +1,20 @@
1
+ require 'yaml'
2
+
3
+ class ConfigFile
4
+
5
+ # Loads YAML file at config_path and returns Hash of key/value pairs
6
+ def self.load_template(config_file)
7
+ if config_file == nil || File.exists?(config_file) == false
8
+ raise SyntaxError, "config file: #{File.expand_path(config_file)} not found"
9
+ end
10
+
11
+ #puts " config: #{File.expand_path(config_file)}".colorize(:blue)
12
+ config_yaml = YAML.load_file(config_file)
13
+ config = Hash.new
14
+ for item in config_yaml
15
+ config.store(item[0], item[1])
16
+ end
17
+ return config
18
+ end
19
+
20
+ end
@@ -0,0 +1,22 @@
1
+ require 'mobiusloop/objective'
2
+ require 'mobiusloop/config/config_file'
3
+
4
+ Before do |scenario|
5
+ Objective.add_current(scenario.feature.name)
6
+ #puts "scenario name #{scenario.name}"
7
+ end
8
+
9
+ After do |scenario|
10
+ config_dir = File.dirname(File.absolute_path(__FILE__))
11
+ config_filepath = "#{config_dir}/config.yml"
12
+ config = ConfigFile.load_template(config_filepath)
13
+
14
+ if config.has_key?('measures') && config['measures'].has_key?('save')
15
+ persist = config['measures']['save']
16
+ if persist
17
+ objective = Objective.get_current
18
+ objective.save
19
+ end
20
+ end
21
+
22
+ end
@@ -1,5 +1,6 @@
1
1
  require 'mobiusloop/outcome'
2
2
  require 'mobiusloop/scale'
3
+ require 'mobiusloop/objective'
3
4
 
4
5
  @name = nil
5
6
  @outcome = nil
@@ -21,16 +22,17 @@ Given(/^a target of (\d+) "([^"]*)" by "([^"]*)"$/) do |arg1, arg2, arg3|
21
22
  @target_date = arg3
22
23
  end
23
24
 
24
- When(/^we measure progress with "([^"]*)"$/) do |arg1|
25
+ # Replace outcome name with scenario name (in hooks.rb)
26
+ Then(/^measure progress with "([^"]*)"$/) do |arg1|
27
+ objective = Objective.get_current
25
28
  scale_class = arg1.gsub(/\s+/, "") # turns 'My Custom Scale' into MyCustomScale
26
29
  @outcome = Outcome.new(@name, Object::const_get(scale_class).new())
30
+ objective.add_outcome(@outcome)
31
+
27
32
  @outcome.baseline = @baseline.to_i
28
33
  @outcome.baseline_date = @baseline_date
29
34
  @outcome.target = @target.to_i
30
35
  @outcome.target_date = @target_date
31
36
  puts @outcome.measure
32
- end
33
-
34
- Then(/^report progress towards targets$/) do
35
37
  puts @outcome.report
36
- end
38
+ end
@@ -1,10 +1,76 @@
1
+ require 'json'
2
+ require 'fileutils'
1
3
 
2
4
  class Objective
3
5
 
4
- attr_accessor :name
6
+ @@objectives = Array.new # class array holds instances of objectives one per .goal file
7
+
8
+ # needs to just be adding 1
9
+ def self.add_current(name)
10
+ objective = Objective.new(name)
11
+ if @@objectives.include?(objective) == false
12
+ @@objectives.push(objective)
13
+ end
14
+ self.get_current
15
+ end
16
+
17
+ def self.get_current
18
+ @@objectives.last
19
+ end
20
+
21
+ def self.get_all
22
+ @@objectives
23
+ end
24
+
25
+
26
+ attr_reader :name
27
+ attr_reader :outcomes
5
28
 
6
29
  def initialize(name)
7
30
  @name = name
31
+ @outcomes = []
32
+ end
33
+
34
+
35
+ def add_outcome(outcome)
36
+ @outcomes.push(outcome)
37
+ end
38
+
39
+
40
+ # persist the results. May be overridden by subclasses
41
+ def save
42
+ filename = Time.now.strftime("measures-%Y-%m-%d-%H%M%S.json")
43
+ dirname = "goals/measures"
44
+ hash = { "objective" => @name}
45
+ @outcomes.each {|o|
46
+ hash.store("outcome", o.name)
47
+ hash.store("scale", "#{o.scale.class.name}.rb")
48
+ hash.store("baseline", o.baseline)
49
+ hash.store("baseline date", o.baseline_date)
50
+ hash.store("target", o.target)
51
+ hash.store("target date", o.target_date)
52
+ hash.store("measure", o.last_measure.value)
53
+ hash.store("measure date", Time.now.strftime("%b %-d, %Y"))
54
+ }
55
+
56
+ if !File.exists? dirname
57
+ FileUtils.mkdir_p dirname
58
+ end
59
+
60
+ File.open("#{dirname}/#{filename}","a") do |f|
61
+ f.write(JSON.pretty_generate(hash))
62
+ f.write("\n")
63
+ end
64
+ end
65
+
66
+
67
+ def ==(other)
68
+ other.class == self.class && other.name == self.name
69
+ end
70
+
71
+
72
+ def state
73
+ self.instance_variables.map { |variable| self.instance_variable_get variable }
8
74
  end
9
75
 
10
76
  end
@@ -1,30 +1,39 @@
1
1
  # Represents a specific outcome or key result to be achieved
2
2
 
3
+ require 'json'
4
+ require 'colorize'
5
+
3
6
  class Outcome
4
7
 
8
+ FILENAME = 'measures'
9
+
5
10
  attr_reader :name
6
11
  attr_accessor :baseline
7
12
  attr_accessor :baseline_date
8
13
  attr_accessor :target
9
14
  attr_accessor :target_date
10
15
  attr_reader :scale
16
+ attr_accessor :persist
17
+ attr_reader :last_measure
11
18
 
12
19
  # register a name (String) and scale (Scale) of measurement at creation time
13
- def initialize(name, scale)
20
+ def initialize(name, scale, persist = false)
14
21
  raise "name must not be nil" if name == nil
15
22
  raise "scale must be of type Scale" unless scale.is_a? Scale
16
23
  @name = name
17
24
  @scale = scale
25
+ @persist = persist
18
26
  end
19
27
 
20
28
  # perform the measurement using the provide scale
21
29
  # delegate the details to any class extending Scale
30
+ # TODO: Change measurement to benchmark
22
31
  def measure
23
32
  begin
24
33
  start_time = Time.now
25
- @scale.record_measurement
34
+ @last_measure = @scale.measure
26
35
  elapsed_time = (Time.now - start_time)
27
- return "Success! Measure is " + format_number(@scale.measurements.last) + " " + @name + " in #{elapsed_time.round(1)} seconds!"
36
+ return "Success! Measured " + format_number(@last_measure) + " " + @name + " in #{elapsed_time.round(1)} seconds!"
28
37
  rescue Exception => e
29
38
  return "Error! Measurement failed with message: " + e.message
30
39
  end
@@ -33,7 +42,6 @@ class Outcome
33
42
  # used to report progress towards goals
34
43
  def report
35
44
  report = ""
36
- # report << status
37
45
  report << report_progress
38
46
  report << report_remaining
39
47
  report
@@ -49,13 +57,13 @@ class Outcome
49
57
  end
50
58
  end
51
59
 
60
+
52
61
  private
53
62
 
54
63
  # report the status. Very simplistic now, need to validate if there is need
55
64
  # for this or we should just drop and let others determine status
56
65
  def status
57
66
  status = percent_progress - percent_schedule
58
-
59
67
  # this value of 10 is temporary. TODO: need to make this more flexible and explainable
60
68
  if status.abs < 10
61
69
  "Hooray! You are on track!\n"
@@ -66,21 +74,69 @@ class Outcome
66
74
 
67
75
  # report percentage progress towards the target
68
76
  def report_progress
69
- "#{percent_progress.round(0)}% progress to target using #{percent_schedule.round(0)}% of the time (" + progress_days.to_s + " days)\n"
77
+ config = ConfigFile.load_template("#{Dir.pwd}/goals/support/config.yml")
78
+
79
+ progress = percent_progress.round(0)
80
+ schedule = percent_schedule.round(0)
81
+ delta = (progress - schedule).abs
82
+ days = progress_days
83
+ result = ""
84
+
85
+ # TODO: FIX LOGIC, ADD MORE TEST CASES
86
+
87
+ if config.has_key?('progress') && config['progress'].has_key?('green_threshold') &&
88
+ config['progress'].has_key?('red_threshold')
89
+ good = config['progress']['green_threshold'].to_i
90
+ bad = config['progress']['red_threshold'].to_i
91
+
92
+ if (delta <= good)
93
+ result = "#{progress}% progress to target using #{schedule}% of the time (#{days} days)\n".colorize(:green)
94
+ elsif (delta >= bad)
95
+ result = "#{progress}% progress to target using #{schedule}% of the time (#{days} days)\n".colorize(:red)
96
+ else
97
+ result = "#{progress}% progress to target using #{schedule}% of the time (#{days} days)\n".colorize(:yellow)
98
+ end
99
+ end
100
+ result
70
101
  end
71
102
 
72
- # report remaining progress towards the target
73
103
  def report_remaining
74
- if percent_remaining.round(0) <= 0
75
- "Target achieved!"
76
- else
77
- "#{percent_remaining.round(0)}% remaining to target in #{remaining_days} days\n"
104
+ config = ConfigFile.load_template("#{Dir.pwd}/goals/support/config.yml")
105
+ days_remaining = remaining_days
106
+ remaining = percent_remaining.round(0)
107
+ progress = percent_progress.round(0)
108
+ schedule = percent_schedule.round(0)
109
+ delta = (progress - schedule).abs
110
+ result = ""
111
+
112
+ if config.has_key?('progress') &&
113
+ config['progress'].has_key?('good_percent') &&
114
+ config['progress'].has_key?('bad_percent')
115
+
116
+ good = config['progress']['good_percent'].to_i
117
+ bad = config['progress']['bad_percent'].to_i
118
+
119
+ # if delta is positive, green
120
+ # if delta is negative, but still within green threshold, green
121
+ # if delta is negative, and greater than red threshold, red
122
+ # else yellow
123
+
124
+ if (delta >= 0)
125
+ result = "#{remaining}% remaining to target in #{days_remaining} days\n".colorize(:green)
126
+ elsif (delta < 0 && delta <= good)
127
+ result = "#{remaining}% remaining to target in #{days_remaining} days\n".colorize(:green)
128
+ elsif (delta < 0 && delta >= bad)
129
+ result = "#{remaining}% remaining to target in #{days_remaining} days\n".colorize(:red)
130
+ else
131
+ result = "#{remaining}% remaining to target in #{days_remaining} days\n".colorize(:yellow)
132
+ end
78
133
  end
134
+ result
79
135
  end
80
136
 
81
137
  # percentage progress towards target as of now
82
138
  def percent_progress
83
- ((@scale.measurements.last.value.to_i - @baseline.to_i).to_f /
139
+ ((@last_measure.value.to_i - @baseline.to_i).to_f /
84
140
  (@target.to_i - @baseline.to_i).to_f * 100)
85
141
  end
86
142
 
@@ -2,21 +2,13 @@
2
2
  # Designed to be extended with the measure method implemented
3
3
  # by the specific
4
4
 
5
- class Scale
6
5
 
7
- attr_reader :measurements
6
+ class Scale
8
7
 
9
8
  # creates internal array to store measurements
10
- # if sub-classes need their own initialize logic
11
- # call super first
9
+ # by default, persisting measures is turned off
10
+ # if sub-classes need their own initialize logic call super first
12
11
  def initialize
13
- @measurements = Array.new
14
- end
15
-
16
- # performs measurement and adds to internal array
17
- def record_measurement()
18
- new_measure = measure
19
- add(new_measure)
20
12
  end
21
13
 
22
14
  # method to perform a measurement, should return an instance of Measure
@@ -24,9 +16,4 @@ class Scale
24
16
  raise "Someone forgot to override Scale.measure with their unique logic"
25
17
  end
26
18
 
27
- # adds a new measure to end of array
28
- def add(measure)
29
- @measurements.push(measure)
30
- end
31
-
32
19
  end
@@ -0,0 +1 @@
1
+ 0.1.5
@@ -10,23 +10,22 @@ Gem::Specification.new do |s|
10
10
  s.homepage = "http://mobiusloop.com"
11
11
  s.platform = Gem::Platform::RUBY
12
12
  s.required_ruby_version = ">= 1.9.3"
13
- s.add_dependency 'cucumber-core', '~> 1.4.0'
14
- s.add_dependency 'builder', '>= 2.1.2'
15
- s.add_dependency 'diff-lcs', '>= 1.1.3'
16
- s.add_dependency 'gherkin', '~> 3.2'
13
+ s.add_dependency 'cucumber-core', '~> 1.4', '= 1.4.0'
14
+ s.add_dependency 'builder', '~> 3.2', '>= 3.2.0'
15
+ s.add_dependency 'diff-lcs', '~> 1.1', '>= 1.1.3'
16
+ s.add_dependency 'gherkin', '~> 3.2', '= 3.2.0'
17
17
  s.add_dependency 'multi_json', '>= 1.7.5', '< 2.0'
18
- s.add_dependency 'multi_test', '>= 0.1.2'
19
- s.add_dependency 'cucumber-wire', '~> 0.0.1'
20
-
18
+ s.add_dependency 'multi_test', '~> 0.1', '>= 0.1.2'
19
+ s.add_dependency 'cucumber-wire', '~> 0.0.1', '>= 0.0.1'
21
20
  s.add_development_dependency 'aruba', '~> 0.6.1'
22
21
  s.add_development_dependency 'json', '~> 1.7'
23
22
  s.add_development_dependency 'nokogiri', '~> 1.5'
24
- s.add_development_dependency 'rake', '>= 0.9.2'
25
- s.add_development_dependency 'rspec', '>= 3.0'
26
- s.add_development_dependency 'simplecov', '>= 0.6.2'
23
+ s.add_development_dependency 'rake', '~> 11.1', '>= 11.1'
24
+ s.add_development_dependency 'simplecov', '~> 0.6', '>= 0.6.2'
27
25
  s.add_development_dependency 'coveralls', '~> 0.7'
28
- s.add_development_dependency 'syntax', '>= 1.0.0'
29
- s.add_development_dependency 'pry'
26
+ s.add_development_dependency 'syntax', '~> 1.0', '>= 1.0.0'
27
+ s.add_development_dependency 'pry', '~> 0'
28
+ s.add_development_dependency 'rspec', '~> 3.0', '>= 3.0'
30
29
 
31
30
  # For Documentation:
32
31
  s.add_development_dependency 'bcat', '~> 0.6.2'
@@ -34,9 +33,12 @@ Gem::Specification.new do |s|
34
33
  s.add_development_dependency 'yard', '~> 0.8.0'
35
34
 
36
35
  # Needed for examples (rake examples)
37
- s.add_development_dependency 'capybara', '>= 2.1'
38
- s.add_development_dependency 'rack-test', '>= 0.6.1'
39
- s.add_development_dependency 'sinatra', '>= 1.3.2'
36
+ s.add_development_dependency 'capybara', '~> 2.1', '>= 2.1'
37
+ s.add_development_dependency 'rack-test', '~> 0.6', '>= 0.6.1'
38
+ s.add_development_dependency 'sinatra', '~> 1.3', '>= 1.3.2'
39
+
40
+ # Added by Mobius
41
+ s.add_dependency 'colorize', '~> 0.8.1'
40
42
 
41
43
  s.rubygems_version = ">= 1.6.1"
42
44
  s.files = `git ls-files`.split("\n").reject {|path| path =~ /\.gitignore$/ }