fate 0.2.2 → 0.2.3
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.
- data/README.md +34 -0
- data/lib/fate.rb +4 -2
- metadata +72 -76
- data/lib/hash_tree.rb +0 -313
data/README.md
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
|
2
|
+
Example of a service spec:
|
3
|
+
|
4
|
+
```json
|
5
|
+
{
|
6
|
+
"commands": {
|
7
|
+
"redis": "redis-server ./redis.conf",
|
8
|
+
"mongod": "mongod run --quiet",
|
9
|
+
"http_server": "./bin/server -h 127.0.0.1 -p 8080",
|
10
|
+
}
|
11
|
+
}
|
12
|
+
|
13
|
+
```
|
14
|
+
|
15
|
+
Command line usage:
|
16
|
+
|
17
|
+
fate -c service.json
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
Usage within Ruby:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
require "fate"
|
25
|
+
require "json"
|
26
|
+
string = File.read("service.json")
|
27
|
+
configuration = JSON.parse(string, :symbolize_names => true)
|
28
|
+
spawner = Fate.new(configuration, :service_log => "logs/service.log")
|
29
|
+
|
30
|
+
spawner.start do
|
31
|
+
# run your tests
|
32
|
+
# when this block finishes evaluation, Fate shuts down the service
|
33
|
+
end
|
34
|
+
```
|
data/lib/fate.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
require "set"
|
2
2
|
|
3
|
+
gem "term-ansicolor"
|
4
|
+
gem "squeeze"
|
3
5
|
require "term/ansicolor"
|
6
|
+
require "squeeze/hash_tree"
|
4
7
|
|
5
|
-
require "hash_tree"
|
6
8
|
# Cross-VM compatibility
|
7
9
|
# thanks to http://ku1ik.com/2010/09/18/open3-and-the-pid-of-the-spawn.html
|
8
10
|
# TODO: consider using systemu: https://github.com/ahoward/systemu/
|
@@ -30,7 +32,7 @@ class Fate
|
|
30
32
|
else
|
31
33
|
@log = STDOUT
|
32
34
|
end
|
33
|
-
commands = HashTree[@configuration[:commands]]
|
35
|
+
commands = Squeeze::HashTree[@configuration[:commands]]
|
34
36
|
|
35
37
|
@completions = Set.new
|
36
38
|
|
metadata
CHANGED
@@ -1,118 +1,114 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: fate
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.3
|
5
5
|
prerelease:
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 2
|
9
|
-
- 2
|
10
|
-
version: 0.2.2
|
11
6
|
platform: ruby
|
12
|
-
authors:
|
7
|
+
authors:
|
13
8
|
- Matthew King
|
14
9
|
autorequire:
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
dependencies:
|
21
|
-
- !ruby/object:Gem::Dependency
|
12
|
+
date: 2012-08-30 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
22
15
|
name: consolize
|
23
|
-
|
24
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
25
17
|
none: false
|
26
|
-
requirements:
|
27
|
-
- -
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
hash: 23
|
30
|
-
segments:
|
31
|
-
- 0
|
32
|
-
- 2
|
33
|
-
- 0
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
34
21
|
version: 0.2.0
|
35
22
|
type: :runtime
|
36
|
-
|
37
|
-
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.2.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
38
31
|
name: open4
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 1.3.0
|
38
|
+
type: :runtime
|
39
39
|
prerelease: false
|
40
|
-
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
41
|
none: false
|
42
|
-
requirements:
|
43
|
-
- -
|
44
|
-
- !ruby/object:Gem::Version
|
45
|
-
hash: 27
|
46
|
-
segments:
|
47
|
-
- 1
|
48
|
-
- 3
|
49
|
-
- 0
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
50
45
|
version: 1.3.0
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: squeeze
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 0.2.0
|
51
54
|
type: :runtime
|
52
|
-
version_requirements: *id002
|
53
|
-
- !ruby/object:Gem::Dependency
|
54
|
-
name: term-ansicolor
|
55
55
|
prerelease: false
|
56
|
-
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.2.0
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: term-ansicolor
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
57
65
|
none: false
|
58
|
-
requirements:
|
59
|
-
- -
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
hash: 23
|
62
|
-
segments:
|
63
|
-
- 1
|
64
|
-
- 0
|
65
|
-
- 0
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
66
69
|
version: 1.0.0
|
67
70
|
type: :runtime
|
68
|
-
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 1.0.0
|
69
78
|
description:
|
70
79
|
email:
|
71
|
-
executables:
|
80
|
+
executables:
|
72
81
|
- fate
|
73
82
|
extensions: []
|
74
|
-
|
75
83
|
extra_rdoc_files: []
|
76
|
-
|
77
|
-
files:
|
84
|
+
files:
|
78
85
|
- bin/fate
|
79
86
|
- LICENSE
|
87
|
+
- README.md
|
80
88
|
- lib/fate.rb
|
81
|
-
- lib/hash_tree.rb
|
82
89
|
- lib/fate/console.rb
|
83
|
-
has_rdoc: true
|
84
90
|
homepage: https://github.com/automatthew/fate
|
85
91
|
licenses: []
|
86
|
-
|
87
92
|
post_install_message:
|
88
93
|
rdoc_options: []
|
89
|
-
|
90
|
-
require_paths:
|
94
|
+
require_paths:
|
91
95
|
- lib
|
92
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
97
|
none: false
|
94
|
-
requirements:
|
95
|
-
- -
|
96
|
-
- !ruby/object:Gem::Version
|
97
|
-
|
98
|
-
|
99
|
-
- 0
|
100
|
-
version: "0"
|
101
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
103
|
none: false
|
103
|
-
requirements:
|
104
|
-
- -
|
105
|
-
- !ruby/object:Gem::Version
|
106
|
-
|
107
|
-
segments:
|
108
|
-
- 0
|
109
|
-
version: "0"
|
104
|
+
requirements:
|
105
|
+
- - ! '>='
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0'
|
110
108
|
requirements: []
|
111
|
-
|
112
109
|
rubyforge_project:
|
113
|
-
rubygems_version: 1.
|
110
|
+
rubygems_version: 1.8.23
|
114
111
|
signing_key:
|
115
112
|
specification_version: 3
|
116
113
|
summary: Tool for running and interacting with a multi-process service
|
117
114
|
test_files: []
|
118
|
-
|
data/lib/hash_tree.rb
DELETED
@@ -1,313 +0,0 @@
|
|
1
|
-
module Traversable
|
2
|
-
|
3
|
-
# Follow or create the path specified by the signature and assign
|
4
|
-
# the value as a terminating leaf node.
|
5
|
-
#
|
6
|
-
# h.set([:a, :b, :c], "This is a retrievable value")
|
7
|
-
#
|
8
|
-
def set(sig, val)
|
9
|
-
raise ArgumentError if sig.empty?
|
10
|
-
create_path(sig) do |node, key|
|
11
|
-
node[key] = val
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
def reduce(sig, base=0)
|
16
|
-
create_path(sig) do |node, key|
|
17
|
-
node[key] = base unless node.has_key?(key)
|
18
|
-
node[key] = yield node[key]
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def increment(sig, val=1)
|
23
|
-
val = yield if block_given?
|
24
|
-
create_path(sig) do |node, key|
|
25
|
-
if node.has_key?(key)
|
26
|
-
node[key] = node[key] + val
|
27
|
-
else
|
28
|
-
node[key] = val
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
# Usage:
|
34
|
-
# a = ht.reducer([:a, :b, :c], 0) {|acc, v| acc + v }
|
35
|
-
# a[1]
|
36
|
-
def reducer(sig, base, &block)
|
37
|
-
p = nil
|
38
|
-
create_path(sig) do |node, key|
|
39
|
-
unless node.has_key?(key)
|
40
|
-
node[key] = base
|
41
|
-
end
|
42
|
-
p = lambda do |newval|
|
43
|
-
node[key] = block.call(node[key], newval)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
p
|
47
|
-
end
|
48
|
-
|
49
|
-
def sum(*args)
|
50
|
-
out = 0
|
51
|
-
retrieve(*args) { |v| out += v }
|
52
|
-
out
|
53
|
-
end
|
54
|
-
|
55
|
-
def count(*args)
|
56
|
-
args = args + [:_count]
|
57
|
-
sum(*args)
|
58
|
-
end
|
59
|
-
|
60
|
-
def unique(*args)
|
61
|
-
out = 0
|
62
|
-
filter(*args) { |v| out += v.size }
|
63
|
-
out
|
64
|
-
end
|
65
|
-
|
66
|
-
# like retrieve, but will return any kind of node
|
67
|
-
def filter(*sig)
|
68
|
-
results = []
|
69
|
-
search(sig) do |node|
|
70
|
-
results << node
|
71
|
-
yield(node) if block_given?
|
72
|
-
end
|
73
|
-
results
|
74
|
-
end
|
75
|
-
|
76
|
-
# Given a signature array, attempt to retrieve matching leaf values.
|
77
|
-
def retrieve(*sig)
|
78
|
-
results = []
|
79
|
-
search(sig) do |node|
|
80
|
-
results << node unless node.respond_to?(:children)
|
81
|
-
yield(node) if block_given?
|
82
|
-
end
|
83
|
-
results
|
84
|
-
end
|
85
|
-
|
86
|
-
# Generic tree search method
|
87
|
-
def search(sig)
|
88
|
-
current_nodes = [self]
|
89
|
-
|
90
|
-
while !current_nodes.empty?
|
91
|
-
next_nodes = []
|
92
|
-
matcher = sig.shift
|
93
|
-
if matcher
|
94
|
-
current_nodes.each do |node|
|
95
|
-
if node.respond_to?(:children)
|
96
|
-
next_nodes += node.children(matcher)
|
97
|
-
end
|
98
|
-
end
|
99
|
-
else
|
100
|
-
current_nodes.each {|n| yield(n) }
|
101
|
-
end
|
102
|
-
current_nodes = next_nodes
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
def traverse
|
107
|
-
current_nodes = [self]
|
108
|
-
while !current_nodes.empty?
|
109
|
-
next_nodes = []
|
110
|
-
current_nodes.each do |node|
|
111
|
-
if node.respond_to?(:children)
|
112
|
-
next_nodes += node.children(true)
|
113
|
-
yield(node)
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
current_nodes = next_nodes
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
end
|
122
|
-
|
123
|
-
class HashTree < Hash
|
124
|
-
include Traversable
|
125
|
-
|
126
|
-
# Override the constructor to provide a default_proc
|
127
|
-
# NOTE: there's a better way to do this in 1.9.2, it seems.
|
128
|
-
# See Hash#default_proc=
|
129
|
-
def self.new()
|
130
|
-
hash = Hash.new { |h,k| h[k] = HashTree.new }
|
131
|
-
super.replace(hash)
|
132
|
-
end
|
133
|
-
|
134
|
-
def self.[](hash)
|
135
|
-
ht = self.new
|
136
|
-
ht << hash
|
137
|
-
ht
|
138
|
-
end
|
139
|
-
|
140
|
-
def _dump(depth)
|
141
|
-
h = Hash[self]
|
142
|
-
h.delete_if {|k,v| v.is_a? Proc }
|
143
|
-
Marshal.dump(h)
|
144
|
-
end
|
145
|
-
|
146
|
-
def self._load(*args)
|
147
|
-
h = Marshal.load(*args)
|
148
|
-
ht = self.new
|
149
|
-
ht.replace(h)
|
150
|
-
ht
|
151
|
-
end
|
152
|
-
|
153
|
-
# Follow the path specified, creating new nodes where necessary.
|
154
|
-
# Returns the value at the end of the path. If a block is supplied,
|
155
|
-
# it will be called with the last node and the last key as parameters,
|
156
|
-
# analogous to Hash.new's default proc. This is necessary to allow
|
157
|
-
# setting a value at the end of the path. See the implementation of #insert.
|
158
|
-
def create_path(sig)
|
159
|
-
final_key = sig.pop
|
160
|
-
hash = self
|
161
|
-
sig.each do |a|
|
162
|
-
hash = hash[a]
|
163
|
-
end
|
164
|
-
yield(hash, final_key) if block_given?
|
165
|
-
hash[final_key]
|
166
|
-
end
|
167
|
-
|
168
|
-
# Attempt to retrieve the value at the end of the path specified,
|
169
|
-
# without creating new nodes. Returns nil on failure.
|
170
|
-
# TODO: consider whether splatting the signature is wise.
|
171
|
-
def find(sig)
|
172
|
-
stage = self
|
173
|
-
sig.each do |a|
|
174
|
-
if stage.has_key?(a)
|
175
|
-
stage = stage[a]
|
176
|
-
else
|
177
|
-
return nil
|
178
|
-
end
|
179
|
-
end
|
180
|
-
stage
|
181
|
-
end
|
182
|
-
|
183
|
-
def remove(*sig)
|
184
|
-
stage = self
|
185
|
-
s2 = sig.slice(0..-2)
|
186
|
-
s2.each do |a|
|
187
|
-
if stage.has_key?(a)
|
188
|
-
stage = stage[a]
|
189
|
-
else
|
190
|
-
return nil
|
191
|
-
end
|
192
|
-
end
|
193
|
-
stage.delete(sig.last)
|
194
|
-
end
|
195
|
-
|
196
|
-
def children(matcher=true)
|
197
|
-
next_keys = self.keys.select do |key|
|
198
|
-
match?(matcher, key)
|
199
|
-
end
|
200
|
-
self.values_at(*next_keys)
|
201
|
-
end
|
202
|
-
|
203
|
-
def +(other)
|
204
|
-
out = HashTree.new
|
205
|
-
_plus(other, out)
|
206
|
-
out
|
207
|
-
end
|
208
|
-
|
209
|
-
def _plus(ht2, out)
|
210
|
-
self.each do |k1,v1|
|
211
|
-
v1 = v1.respond_to?(:dup) ? v1 : v1.dup
|
212
|
-
if ht2.has_key?(k1)
|
213
|
-
v2 = ht2[k1]
|
214
|
-
if v1.respond_to?(:_plus)
|
215
|
-
out[k1] = v1
|
216
|
-
v1._plus(v2, out[k1])
|
217
|
-
elsif v2.respond_to?(:_plus)
|
218
|
-
raise ArgumentError,
|
219
|
-
"Can't merge leaf with non-leaf:\n#{v1.inspect}\n#{v2.inspect}"
|
220
|
-
else
|
221
|
-
if v2.is_a?(Numeric) && v1.is_a?(Numeric)
|
222
|
-
out[k1] = v1 + v2
|
223
|
-
else
|
224
|
-
out[k1] = [v1, ht2[k1]]
|
225
|
-
end
|
226
|
-
end
|
227
|
-
else
|
228
|
-
# should anything happen here?
|
229
|
-
end
|
230
|
-
end
|
231
|
-
ht2.each do |k,v|
|
232
|
-
if self.has_key?(k)
|
233
|
-
# should anything happen here?
|
234
|
-
else
|
235
|
-
v = v.respond_to?(:dup) ? v : v.dup
|
236
|
-
out[k] = v
|
237
|
-
end
|
238
|
-
end
|
239
|
-
end
|
240
|
-
|
241
|
-
def <<(other)
|
242
|
-
other.each do |k,v1|
|
243
|
-
if self.has_key?(k)
|
244
|
-
v2 = self[k]
|
245
|
-
if v1.respond_to?(:has_key?) && v2.respond_to?(:has_key?)
|
246
|
-
v2 << v1
|
247
|
-
elsif v1.is_a?(Numeric) && v2.is_a?(Numeric)
|
248
|
-
self[k] = v1 + v2
|
249
|
-
else
|
250
|
-
raise ArgumentError,
|
251
|
-
"Can't merge leaf with non-leaf:\n#{v1.inspect}\n#{v2.inspect}"
|
252
|
-
end
|
253
|
-
else
|
254
|
-
if v1.respond_to?(:has_key?)
|
255
|
-
self[k] << v1
|
256
|
-
else
|
257
|
-
self[k] = v1
|
258
|
-
end
|
259
|
-
end
|
260
|
-
end
|
261
|
-
end
|
262
|
-
|
263
|
-
def match?(val, key)
|
264
|
-
case val
|
265
|
-
when true
|
266
|
-
true
|
267
|
-
when String, Symbol
|
268
|
-
key == val
|
269
|
-
when Regexp
|
270
|
-
key =~ val
|
271
|
-
when Proc
|
272
|
-
val.call(key)
|
273
|
-
when nil
|
274
|
-
false
|
275
|
-
else
|
276
|
-
raise ArgumentError, "Unexpected matcher type: #{val.inspect}"
|
277
|
-
end
|
278
|
-
end
|
279
|
-
|
280
|
-
def each_path(stack=[], &block)
|
281
|
-
self.each do |k, v|
|
282
|
-
stack.push(k)
|
283
|
-
if v.respond_to?(:each_path)
|
284
|
-
v.each_path(stack, &block)
|
285
|
-
else
|
286
|
-
block.call(stack, v)
|
287
|
-
end
|
288
|
-
stack.pop
|
289
|
-
end
|
290
|
-
end
|
291
|
-
|
292
|
-
def paths
|
293
|
-
out = []
|
294
|
-
end
|
295
|
-
|
296
|
-
def each_leaf(stack=[], &block)
|
297
|
-
self.each do |k,v|
|
298
|
-
stack.push(k)
|
299
|
-
if v.respond_to?(:each_leaf)
|
300
|
-
v.each_leaf(stack, &block)
|
301
|
-
else
|
302
|
-
block.call(v)
|
303
|
-
end
|
304
|
-
stack.pop
|
305
|
-
end
|
306
|
-
end
|
307
|
-
|
308
|
-
end
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|