unravel 0.1.1 → 0.2.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.
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