fail_fast 0.5.2 → 0.6.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 (48) hide show
  1. data/CHANGELOG.txt +6 -0
  2. data/README.markdown +16 -3
  3. data/fail_fast.gemspec +7 -3
  4. data/lib/fail_fast.rb +88 -1
  5. data/lib/fail_fast/base/base.rb +11 -7
  6. data/lib/fail_fast/base/console_utils.rb +20 -0
  7. data/lib/fail_fast/base/utils.rb +0 -13
  8. data/lib/fail_fast/error_reporter.rb +7 -0
  9. data/lib/fail_fast/error_reporter/base.rb +40 -0
  10. data/lib/fail_fast/error_reporter/hoptoad.rb +34 -0
  11. data/lib/fail_fast/error_reporter/hoptoad/post_error_request.xml.erb +23 -0
  12. data/lib/fail_fast/error_reporter/stdout.rb +9 -0
  13. data/lib/fail_fast/report.txt.erb +9 -4
  14. data/lib/fail_fast/support/error_details.rb +5 -1
  15. data/show_all_errors.rb +5 -5
  16. data/spec/{fixtures → _/fixtures}/empty.yml +0 -0
  17. data/spec/{fixtures → _/fixtures}/simple.yml +3 -3
  18. data/spec/_/support/errors.rb +13 -0
  19. data/spec/_/support/errors_reporters.rb +3 -0
  20. data/spec/_/support/hoptoad.rb +7 -0
  21. data/spec/_/support/it_extensions.rb +126 -0
  22. data/spec/_/support/stdout.rb +8 -0
  23. data/spec/_/support/vcr.rb +11 -0
  24. data/spec/_/support/webmock.rb +4 -0
  25. data/spec/_/support/xml.rb +4 -0
  26. data/spec/_vcr_cassette_library/FailFast_ErrorReporter_Hoptoad/when_1_errors_occurs_in_1_block.yml +35 -0
  27. data/spec/_vcr_cassette_library/FailFast_ErrorReporter_Hoptoad/when_2_errors_occur_in_1_block.yml +37 -0
  28. data/spec/_vcr_cassette_library/FailFast_ErrorReporter_Hoptoad/when_3_errors_occur_in_2_blocks.yml +71 -0
  29. data/spec/_vcr_cassette_library/FailFast_ErrorReporter_Hoptoad/when_the_API_token_is_invalid.yml +67 -0
  30. data/spec/_vcr_cassette_library/FailFast_ErrorReporter_Hoptoad/when_the_API_token_is_valid.yml +35 -0
  31. data/spec/base/error_details_spec.rb +8 -0
  32. data/spec/error_reporter/fail_fast_spec.rb +73 -0
  33. data/spec/error_reporter/global_reporters_spec.rb +22 -0
  34. data/spec/error_reporter/hoptoad/expected_error_1_request.xml.erb +21 -0
  35. data/spec/error_reporter/hoptoad/expected_error_2_request.xml.erb +22 -0
  36. data/spec/error_reporter/hoptoad/hoptoad_activation_spec.rb +14 -0
  37. data/spec/error_reporter/hoptoad/hoptoad_requests_spec.rb +63 -0
  38. data/spec/error_reporter/hoptoad/hoptoad_responses_spec.rb +35 -0
  39. data/spec/error_reporter/stdout/stdout_spec.rb +24 -0
  40. data/spec/extensions/file_or_directory_exists_spec.rb +2 -2
  41. data/spec/extensions/has_url_spec.rb +3 -6
  42. data/spec/extensions/key_prefix_spec.rb +1 -1
  43. data/spec/how_to_use_spec.rb +1 -3
  44. data/spec/spec_helper.rb +13 -134
  45. metadata +120 -18
  46. data/lib/fail_fast/base/messaging.rb +0 -40
  47. data/lib/fail_fast/main.rb +0 -34
  48. data/spec/base/report_printing_spec.rb +0 -29
@@ -1,3 +1,9 @@
1
+ 0.6.0
2
+ - report errors to Hoptoad (http://hoptoadapp.com)
3
+ ex:
4
+ FailFast.report_to :hoptoad => '<your-api-token>'
5
+ ... (rest of your FF script)
6
+
1
7
  0.5.2
2
8
  - changed: replace Jeweler & Rubygems by Bundler
3
9
  - add: all checkers methods return a success status (true/false)
@@ -109,7 +109,7 @@ If it fails, you'll get a report like this :
109
109
  +------------------------------------------------------------------------------------------
110
110
  | FAIL_FAST error : precondition(s) not met in
111
111
  | -----------------
112
- | file : "./spec/fixtures/simple.yml"
112
+ | file : "./spec/_/fixtures/simple.yml"
113
113
  | keys prefix : none
114
114
  +------------------------------------------------------------------------------------------
115
115
  | error key value
@@ -123,9 +123,9 @@ If it fails, you'll get a report like this :
123
123
  | * not_a_url test/host localhost
124
124
  | * url_not_reachable test/url_not_reachable http://xxx.zzz
125
125
  | * directory_not_found /foobarbaz
126
- | * directory_not_found test/a_file ./spec/fixtures/simple.yml
126
+ | * directory_not_found test/a_file ./spec/_/fixtures/simple.yml
127
127
  | * file_not_found /tmp/foo/bar/??nOTaFile
128
- | * file_not_found test/a_directory ./spec/fixtures
128
+ | * file_not_found test/a_directory ./spec/_/fixtures
129
129
  | * mongoDB_server_not_found 10.0.0.123
130
130
  | * mongoDB_server_not_found test/mongoDB localhost
131
131
  | * mongoDB_db_not_found not_a_known_db
@@ -160,6 +160,19 @@ If it fails, you'll get a report like this :
160
160
  end
161
161
 
162
162
 
163
+ ### Example 5 : also sending errors report to Hoptoad (http://hoptoadapp.com)
164
+
165
+ FailFast().check do
166
+ ... # errors only reported to stdout
167
+ end
168
+ ...
169
+ FailFast.report_to :hoptoad => '<your-api-token>' # <- add this in your FF script
170
+ ...
171
+ FailFast().check do
172
+ ... # errors also reported to your Hoptoad account
173
+ end
174
+
175
+
163
176
 
164
177
  ## Info :
165
178
 
@@ -4,7 +4,7 @@ $:.push File.expand_path("../lib", __FILE__)
4
4
 
5
5
  Gem::Specification.new do |s|
6
6
  s.name = 'fail_fast'
7
- s.version = '0.5.2'
7
+ s.version = '0.6.0'
8
8
 
9
9
  s.date = Time.now.utc.strftime("%Y-%m-%d")
10
10
  s.homepage = 'http://github.com/alainravet/fail_fast'
@@ -23,7 +23,11 @@ Gem::Specification.new do |s|
23
23
  s.require_paths = %w(lib)
24
24
 
25
25
  s.add_development_dependency 'rspec', '= 1.3.2'
26
- s.add_development_dependency 'activerecord', '>= 0'
26
+ s.add_development_dependency 'activerecord', '= 2.3.12'
27
27
  s.add_development_dependency 'mongo', '>= 0'
28
- s.add_development_dependency 'fakeweb'
28
+ s.add_development_dependency 'webmock'
29
+ s.add_development_dependency 'bson_ext'
30
+ s.add_development_dependency 'bson_ext'
31
+ s.add_development_dependency 'timecop'
32
+ s.add_development_dependency 'vcr'
29
33
  end
@@ -1,8 +1,95 @@
1
- require 'fail_fast/main'
1
+ require 'yaml'
2
+ require 'erb'
3
+
4
+ require File.expand_path(File.dirname(__FILE__) + '/fail_fast/support/error_db')
5
+ class FailFast
6
+
7
+ @@_errors_db = FailFast::ErrorDb.new
8
+ @@global_error_reporters = nil
9
+
10
+ def initialize(config_file_path=nil, keys_prefix=nil)
11
+ @config_file_path = config_file_path
12
+ @keys_prefix = keys_prefix
13
+ @errors_key = ErrorDb.key_for(config_file_path, keys_prefix)
14
+ FailFast.global_error_reporters.each do |r| register_errors_reporter(r) end
15
+ end
16
+
17
+
18
+ class << self
19
+ def fail_now
20
+ exit(1) unless errors_db.keys.empty?
21
+ end
22
+
23
+ def failed?
24
+ !global_errors.empty?
25
+ end
26
+
27
+ def errors_db #:nodoc:
28
+ @@_errors_db
29
+ end
30
+
31
+ # @param reporters [Object] 1 or many error reporters (must respond to :report)
32
+ def report_to(reporter)
33
+ reporter.is_a?(Hash) ?
34
+ init_global_error_reporter_from_hash(reporter) :
35
+ add_global_error_reporter(reporter)
36
+ end
37
+
38
+ def init_global_error_reporter_from_hash(params)
39
+ [].tap do |reporters|
40
+ if api_token = params.delete(:hoptoad)
41
+ r = FailFast::ErrorReporter::Hoptoad.new(api_token)
42
+ FailFast.report_to r
43
+ reporters.push(r)
44
+ end
45
+ end
46
+ end
47
+
48
+ def global_error_reporters
49
+ reset_global_error_reporters unless @@global_error_reporters
50
+ @@global_error_reporters
51
+ end
52
+ private
53
+ def reset_global_error_reporters
54
+ @@global_error_reporters = [ErrorReporter::Stdout.new]
55
+ end
56
+
57
+ def add_global_error_reporter(reporter)
58
+ global_error_reporters.push(reporter) unless global_error_reporters.include?(reporter)
59
+ reporter
60
+ end
61
+ end
62
+
63
+
64
+ def add_error(value)
65
+ @@_errors_db.append(@errors_key, value)
66
+ end
67
+
68
+ def errors
69
+ @@_errors_db.errors_for(@errors_key)
70
+ end
71
+
72
+
73
+
74
+ def error_reporters
75
+ @error_reporters ||= []
76
+ end
77
+
78
+ # @param reporters [Object] 1 or many error reporters (must respond to :report)
79
+ def register_errors_reporters(*reporters)
80
+ reporters.each do |r| register_errors_reporter(r) end
81
+ end
82
+
83
+ def register_errors_reporter(reporter)
84
+ error_reporters.push reporter unless error_reporters.include?(reporter)
85
+ end
86
+ end
87
+
2
88
  Dir.glob(File.dirname(__FILE__) + '/fail_fast/support/*.rb' ) {|file| require file }
3
89
  Dir.glob(File.dirname(__FILE__) + '/fail_fast/base/*.rb' ) {|file| require file }
4
90
  Dir.glob(File.dirname(__FILE__) + '/fail_fast/extensions/*.rb') {|file| require file }
5
91
 
92
+ require 'fail_fast/error_reporter'
6
93
 
7
94
  # alternative syntax
8
95
  def FailFast(config_file_path=nil, keys_prefix=nil)
@@ -1,7 +1,5 @@
1
1
  class FailFast
2
2
  module Base
3
- ERB_TEMPLATE = File.dirname(__FILE__) + '/../report.txt.erb'
4
-
5
3
  def check(&block)
6
4
  fail_now_mode = block_given? # false in the case of *.check_now.but_fail_now do .. end
7
5
 
@@ -12,7 +10,7 @@ class FailFast
12
10
  check_all_rules(&block) if block_given?
13
11
  end
14
12
  unless errors.empty?
15
- print_errors
13
+ report_errors
16
14
  exit(1) if fail_now_mode
17
15
  end
18
16
  self
@@ -24,7 +22,7 @@ class FailFast
24
22
  return if @config_file_not_found
25
23
  check_all_rules(&block) if block_given?
26
24
  unless errors.empty?
27
- print_errors
25
+ report_errors
28
26
  end
29
27
  end
30
28
  private
@@ -34,9 +32,15 @@ class FailFast
34
32
  self.instance_eval(&block)
35
33
  end
36
34
 
37
- def print_errors #:nodoc:
38
- @errors = errors
39
- puts "\n\n\n" + ERB.new(File.read(ERB_TEMPLATE)).result(binding) + "\n\n"
35
+ def report_errors #:nodoc:
36
+ context = {
37
+ :errors_to_report => errors,
38
+ :config_file_path => @config_file_path,
39
+ :keys_prefix => @keys_prefix,
40
+ }
41
+ error_reporters.each do |reporter|
42
+ reporter.report(errors, context)
43
+ end
40
44
  end
41
45
  end
42
46
  end
@@ -0,0 +1,20 @@
1
+ class FailFast
2
+
3
+ module ConsoleUtils #:nodoc:
4
+
5
+ NO_COLOUR="\033[0m"
6
+ RED ="\033[31m"
7
+ LRED ="\033[1;31m"
8
+ BLUE ="\033[34m"
9
+ GREEN ="\033[32m"
10
+ YELLOW ="\033[1;33m"
11
+
12
+ def red( str) [RED, str, NO_COLOUR].join end
13
+ def lred( str) [LRED, str, NO_COLOUR].join end
14
+ def blue( str) [BLUE, str, NO_COLOUR].join end
15
+ def green( str) [GREEN, str, NO_COLOUR].join end
16
+ def yellow(str) [YELLOW, str, NO_COLOUR].join end
17
+ end
18
+ end
19
+
20
+ FailFast.send :include, FailFast::ConsoleUtils
@@ -40,19 +40,6 @@ class FailFast
40
40
 
41
41
  Params.new(key, value, regexp, options)
42
42
  end
43
-
44
- NO_COLOUR="\033[0m"
45
- RED ="\033[31m"
46
- LRED ="\033[1;31m"
47
- BLUE ="\033[34m"
48
- GREEN ="\033[32m"
49
- YELLOW ="\033[1;33m"
50
-
51
- def red( str) [RED, str, NO_COLOUR].join end
52
- def lred( str) [LRED, str, NO_COLOUR].join end
53
- def blue( str) [BLUE, str, NO_COLOUR].join end
54
- def green( str) [GREEN, str, NO_COLOUR].join end
55
- def yellow(str) [YELLOW, str, NO_COLOUR].join end
56
43
  end
57
44
  end
58
45
 
@@ -0,0 +1,7 @@
1
+ class FailFast
2
+ module ErrorReporter
3
+ autoload :Base, 'fail_fast/error_reporter/base'
4
+ autoload :Stdout, 'fail_fast/error_reporter/stdout'
5
+ autoload :Hoptoad, 'fail_fast/error_reporter/hoptoad'
6
+ end
7
+ end
@@ -0,0 +1,40 @@
1
+ module FailFast::ErrorReporter
2
+ class Base
3
+ include FailFast::ConsoleUtils
4
+
5
+ protected
6
+
7
+ def default_message_for(e, use_color=true)
8
+ @use_color = use_color
9
+ qc_value = "'#{vcol(e.value)}'"
10
+ qc_key = "'#{kcol(e.key)}'"
11
+ details = if e.value.nil? then " for the key #{qc_key}"
12
+ elsif e.key.nil? then " #{qc_value}"
13
+ else " #{qc_value} for the key #{qc_key}"
14
+ end
15
+
16
+ s = case e.kind
17
+ when :config_file_not_found then mcol("The config file could not be found") + " : #{yellow(e.value)}."
18
+ when :missing_value then mcol("Missing value") +" #{details}."
19
+ when :value_does_not_match then mcol("Invalid value") +" #{details}."
20
+ when :not_an_email then mcol("Invalid email address") + " #{details}."
21
+ when :not_a_url then mcol("Invalid url") + " #{details}."
22
+ when :url_not_reachable then mcol("Could not reach the url") + " #{details}."
23
+ when :directory_not_found then mcol("Missing directory") + " #{details}."
24
+ when :file_not_found then mcol("Missing file") + " #{details}."
25
+ when :not_on_path then mcol("App not on path : ") + " #{details}."
26
+ when :mongoDB_server_not_found then mcol("Could not connect to the mongoDb server") + " #{details}."
27
+ when :mongoDB_db_not_found then mcol("Could not open the mongoDb db") + " #{details}."
28
+ when :active_record_db_connection_error then mcol("Could not connect to the DB server") + " #{details}."
29
+ when :fail then mcol(e.value)
30
+ else
31
+ "%-38s %-35s %-30s \n" % [ e.kind, e.key, qc_value]
32
+ end
33
+ e.message ? "#{e.message}\n| #{s}": s
34
+ end
35
+
36
+ def mcol(msg) @use_color ? lred(msg) : msg end
37
+ def kcol(key) @use_color ? yellow(key) : key end
38
+ def vcol(val) @use_color ? yellow(val) : val end
39
+ end
40
+ end
@@ -0,0 +1,34 @@
1
+ module FailFast::ErrorReporter
2
+ class Hoptoad < Base
3
+ REQUEST_ERB_TEMPLATE = File.join(File.dirname(__FILE__), 'hoptoad', 'post_error_request.xml.erb')
4
+
5
+ attr_reader :response
6
+
7
+ def initialize(api_key)
8
+ @api_key = api_key
9
+ end
10
+
11
+ def report(errors, context)
12
+ require 'net/http'
13
+ uri = URI.parse('http://hoptoadapp.com/notifier_api/v2/notices')
14
+ @response = Net::HTTP.start(uri.host, uri.port) {|http|
15
+ req = Net::HTTP::Post.new(uri.path)
16
+ req['Content-Type'] = 'text/xml'
17
+ req['Accept' ] = 'text/xml'
18
+ http.request(req, xml_req(errors, context))
19
+ }
20
+ end
21
+
22
+ private
23
+ def xml_req(errors, context)
24
+ config_path = context[:config_file_path]
25
+ project_root = '/testapp'
26
+ environment_name = 'test'
27
+ app_version = '1.0.0'
28
+ error_msg = default_message_for(errors.first, false)
29
+
30
+ ERB.new(File.read(REQUEST_ERB_TEMPLATE)).result(binding).gsub(/[\t\r\n]/,'')
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,23 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <notice version="2.0">
3
+ <api-key><%=@api_key%></api-key>
4
+ <notifier>
5
+ <name>FailFast Hoptoad Notifier</name>
6
+ <version>0.1</version>
7
+ <url>https://github.com/alainravet/fail_fast</url>
8
+ </notifier>
9
+ <error>
10
+ <class>FailFastError</class>
11
+ <message>FailFastError at <%= Time.now %></message>
12
+ <backtrace>
13
+ <% errors.each_with_index do |error, i| %>
14
+ <line method="<%= config_path %>" file="<%= default_message_for(error, false) %>" number='<%= 1+i %>'/>
15
+ <% end %>
16
+ </backtrace>
17
+ </error>
18
+ <server-environment>
19
+ <project-root><%= project_root %></project-root>
20
+ <environment-name><%= environment_name %></environment-name>
21
+ <app-version><%= app_version %></app-version>
22
+ </server-environment>
23
+ </notice>
@@ -0,0 +1,9 @@
1
+ module FailFast::ErrorReporter
2
+ class Stdout < Base
3
+ ERB_TEMPLATE = File.dirname(__FILE__) + '/../report.txt.erb'
4
+
5
+ def report(errors, context)
6
+ puts "\n\n\n" + ERB.new(File.read(FailFast::ErrorReporter::Stdout::ERB_TEMPLATE)).result(binding) + "\n\n"
7
+ end
8
+ end
9
+ end
@@ -1,13 +1,18 @@
1
- <% if @config_file_path %>
1
+ <%
2
+ config_file_path = context[:config_file_path]
3
+ keys_prefix = context[:keys_prefix]
4
+ errors_to_report = context[:errors_to_report]
5
+ %>
6
+ <% if config_file_path %>
2
7
  +------------------------------------------------------------------------------------------
3
8
  | <%= lred 'FAIL_FAST error' %> : precondition(s) not met in
4
9
  | -----------------
5
- | file : "<%= @config_file_path %>"
6
- <% if @keys_prefix%>| keys prefix : <%= @keys_prefix.inspect %>
10
+ | file : "<%= config_file_path %>"
11
+ <% if keys_prefix %>| keys prefix : <%= keys_prefix.inspect %>
7
12
  <% end %>+------------------------------------------------------------------------------------------
8
13
  <% else %>
9
14
  +------------------------------------------------------------------------------------------
10
15
  | <%= lred 'FAIL_FAST error' %> : precondition(s) not met.
11
16
  +------------------------------------------------------------------------------------------
12
- <% end %>| <%= @errors.collect{|e| default_message_for(e)}.join("\n| ") %>
17
+ <% end %>| <%= errors_to_report.collect{|e| default_message_for(e)}.join("\n| ") %>
13
18
  +------------------------------------------------------------------------------------------
@@ -1,4 +1,4 @@
1
- class FailFast::ErrorDetails < Struct.new(:key, :kind, :value, :message)
1
+ class FailFast::ErrorDetails
2
2
 
3
3
  attr_reader :key, :kind, :value, :message
4
4
 
@@ -6,6 +6,10 @@ class FailFast::ErrorDetails < Struct.new(:key, :kind, :value, :message)
6
6
  @key, @kind, @value, @message = key, kind, value, message
7
7
  end
8
8
 
9
+ def ==(other)
10
+ self.key == other.key && self.kind == other.kind && self.value == other.value && self.message == other.message
11
+ end
12
+
9
13
  def has_key_and_kind?(akey, akind) #:nodoc:
10
14
  (key.to_s == akey.to_s) && kind.to_sym == akind.to_sym
11
15
  end
@@ -3,10 +3,10 @@ gem 'mongo', '1.0'
3
3
  require 'mongo'
4
4
 
5
5
  def fake_http_server
6
- require 'fakeweb'
7
- FakeWeb.register_uri(:get, "http://example.com/index.html", :body => "I'm reachable!")
8
- FakeWeb.register_uri(:get, "http://localhost/index.html", :body => "I'm reachable!")
9
- FakeWeb.register_uri(:get, "http://example.com", :body => "I'm reachable!")
6
+ require 'webmock'
7
+ stub_request(:get, "http://example.com/index.html").to_return(:body => "I'm reachable!")
8
+ stub_request(:get, "http://localhost/index.html" ).to_return(:body => "I'm reachable!")
9
+ stub_request(:get, "http://example.com" ).to_return(:body => "I'm reachable!")
10
10
  end
11
11
  def fake_mongo_server_absent
12
12
  require 'mocha'
@@ -34,7 +34,7 @@ FailFast().check_now.but_fail_later do
34
34
  fail '_why could not be found on the path' unless `which _why` =~ /_why$/
35
35
  end
36
36
 
37
- FailFast(SPEC_DIR + '/fixtures/simple.yml').check_now.but_fail_later do
37
+ FailFast(SPEC_DIR + '/_/fixtures/simple.yml').check_now.but_fail_later do
38
38
 
39
39
  #test values :
40
40
  has_value_for :first_keyNOT , :message => 'this is a custom message' # single absent key
File without changes
@@ -26,9 +26,9 @@ test:
26
26
  host: localhost
27
27
  database: unknown_mongoDB_db
28
28
 
29
- a_directory: <%= SPEC_DIR + '/fixtures' %>
29
+ a_directory: <%= SPEC_DIR + '/_/fixtures' %>
30
30
 
31
- a_file: <%= SPEC_DIR + '/fixtures/simple.yml' %>
31
+ a_file: <%= SPEC_DIR + '/_/fixtures/simple.yml' %>
32
32
 
33
33
  db_connection:
34
34
  adapter: mysql
@@ -36,7 +36,7 @@ db_connection:
36
36
 
37
37
  number_six: 6
38
38
  letter_x: x
39
- nda_file: <%= SPEC_DIR + '/fixtures/simple.yml' %>
39
+ nda_file: <%= SPEC_DIR + '/_/fixtures/simple.yml' %>
40
40
  development:
41
41
  t_url: 'http://localhost:3200'
42
42
  t_email: t_email@test.com