fail_fast 0.5.2 → 0.6.0

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