unravel 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: afd743ff5179fe977b6debe400556ab59ad99670
4
- data.tar.gz: 6695e4301a3613543d2c8dd8010575bb6cf11493
3
+ metadata.gz: 45a416271c45d03fd6342e3296481e60d966df23
4
+ data.tar.gz: 5268e00db454da494c02ca35dcadd1bccbd27371
5
5
  SHA512:
6
- metadata.gz: 6f36434d71cbd49e18c1fd821e57c924d6d84aa21bf5d26b811fe7912449b745ecaca0010a71116d498980e9c88535da35a2d56e596aa8530e986c41bb38e95e
7
- data.tar.gz: efe37ac90793b627fa77656f536cb216bb4cc29162be7ae06277a118a48ebbc54b888988d047e82187a8d5d17129a5df38c69ab5a313b0bc23faff168bf68ed6
6
+ metadata.gz: dca889ad5427a5173cbbe2545a2b997c89b25a63c2c2ef4eaee2c3da4a347e6a78fcfebc746a2c954094aa52dd12d4fcc10750839b52dc28e704e879a47a8731
7
+ data.tar.gz: 8b0b5d637a46caafa23e478218355460ab08db89c1c823b69c90718ee14b88a305512d8868d3857af29f69a912ef488168ce3e71ed35493f2222d24ef42b5211
data/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ Ruby lib for easily defining solutions for non-deterministic problems caused by interdependent "black-box" components
2
+
1
3
  Sorry ...
2
4
 
3
5
  - not ready for production yet
@@ -21,6 +21,19 @@ module Unravel
21
21
  class NoKnownRootCause < HumanInterventionNeeded ; end
22
22
  class SameCauseReoccurringCause < HumanInterventionNeeded; end
23
23
 
24
+ class NoErrorHandler
25
+ attr_reader :name
26
+ attr_reader :exception
27
+ def initialize(name, ex)
28
+ @name = name
29
+ @exception = ex
30
+ end
31
+
32
+ def message
33
+ "Achievement #{name.inspect} is not declared to handle exceptions of type: #{ex.class}"
34
+ end
35
+ end
36
+
24
37
  class Registry
25
38
  attr_reader :achievements, :symptoms, :fixes, :contexts, :errors
26
39
 
@@ -32,14 +45,18 @@ module Unravel
32
45
  @errors = {}
33
46
  end
34
47
 
35
- def get_fix(name)
36
- @fixes[name].tap do |cause|
37
- fail HumanInterventionNeeded, "No fix for: #{name}" unless cause
38
- end
48
+ def has_fix_for?(cause)
49
+ @fixes.key?(cause)
50
+ end
51
+
52
+ def get_fix_for(cause)
53
+ achievement_or_block = @fixes[cause]
54
+ fail HumanInterventionNeeded, "No fix for: #{cause}" unless achievement_or_block
55
+ achievement_or_block
39
56
  end
40
57
 
41
58
  def add_fix(name, block)
42
- fail HumanInterventionNeeded, "fix already exists: #{name}" if @fixes.key?(name)
59
+ fail HumanInterventionNeeded, "fix for root cause #{name} already exists" if @fixes.key?(name)
43
60
  @fixes[name] = block
44
61
  end
45
62
 
@@ -77,39 +94,62 @@ module Unravel
77
94
  end
78
95
 
79
96
  class Session
97
+ class DefaultConfig
98
+ def max_retries
99
+ 5
100
+ end
101
+
102
+ def after_achievement_success(name)
103
+ end
104
+ end
105
+
80
106
  attr_reader :registry
107
+ attr_reader :config
81
108
 
82
109
  class FixableError < RuntimeError
83
110
  attr_reader :symptom
84
- def initialize(symptom_name)
111
+ attr_reader :extracted_info
112
+ def initialize(symptom_name, extracted_info)
85
113
  @symptom = symptom_name
114
+
115
+ # TODO: clean this up
116
+ matchdata = extracted_info
117
+ @extracted_info = matchdata.captures.empty? ? [] : [matchdata]
86
118
  end
87
119
  end
88
120
 
89
- def initialize
121
+ def initialize(config = DefaultConfig.new)
122
+ @config = config
90
123
  @registry = Registry.new
91
124
  end
92
125
 
93
- def achieve(name, max_retries = 5)
126
+ def achieve(name, *args)
94
127
  check # TODO: overhead?
95
128
 
96
129
  prev_causes = Set.new
130
+ max_retries = config.max_retries
97
131
  retries_left = max_retries
98
132
 
99
133
  begin
100
134
  Unravel.logger.info("Achieving (attempt: #{max_retries - retries_left + 1}/#{max_retries}): #{name.inspect}")
101
135
 
102
136
  block = registry.get_achievement(name)
137
+ if block.arity >= 0
138
+ unless block.arity == args.size
139
+ fail ArgumentError, "expected #{block.arity} args for #{name.inspect}, got: #{args.inspect}"
140
+ end
141
+ end
142
+
103
143
  error_contexts = registry.error_contexts_for_achievement(name)
104
144
 
105
145
  begin
106
- res = return_wrap(&block)
146
+ res = return_wrap(*args, &block)
107
147
  rescue *error_contexts.keys
108
148
  ex = $!
109
149
  econtext = error_contexts[ex.class]
110
150
  unless econtext
111
151
  # TODO: not tested
112
- fail "No error context given for #{name} to handle exception: #{ex.message}"
152
+ fail NoErrorHandler.new(name, ex)
113
153
  end
114
154
 
115
155
  econtext.each do |fix_name|
@@ -119,7 +159,11 @@ module Unravel
119
159
  fail
120
160
  end
121
161
 
122
- return true if res == true
162
+ if res == true
163
+ config.after_achievement_success(name)
164
+ return true
165
+ end
166
+
123
167
  fail NotImplementedError, "#{name} unexpectedly returned #{res.inspect} (expected true or exception)"
124
168
 
125
169
  rescue FixableError => error
@@ -131,12 +175,18 @@ module Unravel
131
175
  fail NoKnownRootCause, "Can't find root cause for: #{error.symptom}, #{error.message}" unless cause
132
176
 
133
177
  Unravel.logger.info("#{name}: Cause: #{cause.inspect}")
134
- if prev_causes.include? cause
135
- fail SameCauseReoccurringCause, "#{cause.to_s} wasn't ultimately fixed (it occured again)"
178
+
179
+ # Since causes can be generic (parametized), they're unique based on error matchers
180
+ unique_context = error.extracted_info
181
+ unique_cause = [cause, unique_context]
182
+
183
+ if prev_causes.include? unique_cause
184
+ fail SameCauseReoccurringCause, "#{cause.to_s} (with #{unique_context.inspect}) wasn't ultimately fixed (it occured again)"
136
185
  end
137
186
 
138
- prev_causes << cause
139
- recipe_for(cause).call
187
+ prev_causes << unique_cause
188
+ fix = registry.get_fix_for(cause)
189
+ fix.call(error)
140
190
 
141
191
  retries_left -= 1
142
192
  retry if retries_left > 0
@@ -170,7 +220,13 @@ module Unravel
170
220
  if name.is_a?(Hash)
171
221
  name, achievement = *name.first
172
222
  end
173
- fix_for(name) { achieve achievement } unless block_given?
223
+ # TODO: this recursively calls self (just to provied block), though -
224
+ # is the block_given check needed?
225
+ unless block_given?
226
+ fix_for(name) do |error|
227
+ achieve(achievement, *error.extracted_info)
228
+ end
229
+ end
174
230
  end
175
231
  end
176
232
 
@@ -179,14 +235,18 @@ module Unravel
179
235
  root_cause_name = "no_#{fix_name}".to_sym
180
236
  error[error_name] = regexp
181
237
  root_cause_for error_name => root_cause_name
182
- fix_for root_cause_name => fix_name
238
+ unless registry.has_fix_for?(root_cause_name)
239
+ fix_for root_cause_name => fix_name
240
+ end
183
241
  achievement fix_name, handlers, &method(fix_name)
184
242
  end
185
243
 
186
244
  private
187
245
 
188
- def return_wrap(&block)
189
- Thread.new { return block.yield }.join
246
+ def return_wrap(*args, &block)
247
+ Thread.new do
248
+ return block.yield(*args)
249
+ end.join
190
250
  rescue LocalJumpError => ex
191
251
  ex.exit_value
192
252
  end
@@ -213,16 +273,14 @@ module Unravel
213
273
  unless regexp
214
274
  fail HumanInterventionNeeded, "Unregistered error: #{name} to match #{error.inspect}"
215
275
  end
276
+
216
277
  # TODO: encoding not tested
217
- fail FixableError.new(name) if regexp.match(error.force_encoding(Encoding::ASCII_8BIT))
278
+ match = regexp.match(error.force_encoding(Encoding::ASCII_8BIT))
279
+ fail FixableError.new(name, match) if match
218
280
  end
219
281
 
220
282
  def get_root_cause_for(symptom)
221
283
  registry.get_root_cause(symptom)
222
284
  end
223
-
224
- def recipe_for(name, &block)
225
- registry.get_fix(name)
226
- end
227
285
  end
228
286
  end
@@ -1,6 +1,35 @@
1
1
  module Unravel
2
2
  class Exec
3
- class Error < Unravel::HumanInterventionNeeded; end
3
+ class Error < Unravel::HumanInterventionNeeded
4
+ class Standard < Error
5
+ end
6
+
7
+ class Silent < Error
8
+ attr_reader :exitcode
9
+ attr_reader :exitstatus
10
+ def initialize(exitcode, stdout)
11
+ @exitcode = exitcode
12
+ @stdout = stdout
13
+ super()
14
+ end
15
+
16
+ def to_s
17
+ message
18
+ end
19
+
20
+ def message
21
+ lines = @stdout.lines.to_a
22
+ output =
23
+ if lines.size > 1
24
+ indent = "\n stdout"
25
+ "#{indent}: #{lines * indent}\n"
26
+ else
27
+ "stdout: #{@stdout.inspect}"
28
+ end
29
+ "No stderr available: #{output} (exited with #{@exitcode})"
30
+ end
31
+ end
32
+ end
4
33
  end
5
34
 
6
35
  class << self
@@ -16,10 +45,12 @@ module Unravel
16
45
  return true if status.success?
17
46
  Unravel.logger.debug "Errors from #{args.inspect}: -----"
18
47
  Unravel.logger.debug "#{error}"
19
- error = out if error.strip.empty?
20
- raise Exec::Error, error
48
+
49
+ # TODO: is strip a good idea?
50
+ raise Exec::Error::Silent.new(status.exitstatus, out) if error.strip.empty?
51
+ raise Exec::Error::Standard, error
21
52
  rescue Errno::ENOENT => e
22
- raise Exec::Error, e.message
53
+ raise Exec::Error::ENOENT, e.message
23
54
  end
24
55
 
25
56
  def Capture(args)
@@ -29,9 +60,9 @@ module Unravel
29
60
  Unravel.logger.debug "Errors from #{args.inspect}: -----"
30
61
  Unravel.logger.debug "#{error}"
31
62
  error = out if error.strip.empty?
32
- raise Exec::Error, error
63
+ raise Exec::Error::Standard, error
33
64
  rescue Errno::ENOENT => e
34
- raise Exec::Error, e.message
65
+ raise Exec::Error::ENOENT, e.message
35
66
  end
36
67
  end
37
68
  end
@@ -1,3 +1,3 @@
1
1
  module Unravel
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: unravel
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cezary Baginski
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-07-01 00:00:00.000000000 Z
11
+ date: 2016-03-26 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Tool for solving non-deterministic problems given goals, symptoms, fixes
14
14
  and root causes