freud 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/Gemfile +3 -0
- data/Guardfile +7 -0
- data/LICENSE.txt +202 -0
- data/README.md +33 -0
- data/Rakefile +13 -0
- data/bin/freud +5 -0
- data/freud.gemspec +27 -0
- data/lib/freud.rb +5 -0
- data/lib/freud/config.rb +227 -0
- data/lib/freud/launcher.rb +121 -0
- data/lib/freud/logging.rb +44 -0
- data/lib/freud/pidfile.rb +49 -0
- data/lib/freud/runner.rb +197 -0
- data/lib/freud/variables.rb +64 -0
- data/lib/freud/version.rb +3 -0
- data/spec/config_spec.rb +141 -0
- data/spec/fixtures/true.json +6 -0
- data/spec/launcher_spec.rb +114 -0
- data/spec/pidfile_spec.rb +41 -0
- data/spec/runner_spec.rb +133 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/variables_spec.rb +74 -0
- metadata +194 -0
data/spec/config_spec.rb
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "freud/config"
|
3
|
+
|
4
|
+
RSpec.describe Freud::Config do
|
5
|
+
let(:config) { Freud::Config.new }
|
6
|
+
|
7
|
+
describe "#snakify_string" do
|
8
|
+
def call(input)
|
9
|
+
config.snakify_string(input)
|
10
|
+
end
|
11
|
+
|
12
|
+
it { expect(call("")).to eq("") }
|
13
|
+
|
14
|
+
it { expect(call("hello")).to eq("hello") }
|
15
|
+
|
16
|
+
it { expect(call("helloWorld")).to eq("hello_world") }
|
17
|
+
|
18
|
+
it { expect(call("HelloWorld")).to eq("hello_world") }
|
19
|
+
|
20
|
+
it { expect(call("Helloworld")).to eq("helloworld") }
|
21
|
+
|
22
|
+
it { expect(call("ThinkABC")).to eq("think_abc") }
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "#expand_path" do
|
26
|
+
def call(path, relative_to)
|
27
|
+
config.expand_path(path, relative_to)
|
28
|
+
end
|
29
|
+
|
30
|
+
it { expect(call(".", "/tmp")).to eq("/tmp") }
|
31
|
+
|
32
|
+
it { expect(call("../root", "/tmp")).to eq("/root") }
|
33
|
+
|
34
|
+
it { expect(call("/root", "/tmp")).to eq("/root") }
|
35
|
+
|
36
|
+
it { expect(call("foo", "/tmp")).to eq("/tmp/foo") }
|
37
|
+
end
|
38
|
+
|
39
|
+
it "#deep_merge" do
|
40
|
+
left = { a: { b: { c: 42 } } }
|
41
|
+
right = { a: { b: { d: 72 } } }
|
42
|
+
output = { a: { b: { c: 42, d: 72 } } }
|
43
|
+
expect(config.deep_merge(left, right)).to eq(output)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "#snakify_keys" do
|
47
|
+
input = { "aKey" => "a" }
|
48
|
+
expect(config.snakify_keys(input)).to eq("a_key" => "a")
|
49
|
+
end
|
50
|
+
|
51
|
+
it "#snakify_keys not recursive" do
|
52
|
+
input = { "aKey" => { "bKey" => "b" } }
|
53
|
+
expect(config.snakify_keys(input)).to eq("a_key" => { "bKey" => "b" })
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "#load" do
|
57
|
+
def mock_file(name, content = nil)
|
58
|
+
default = { stages: { back: {} } }
|
59
|
+
content ||= block_given? ? yield : default
|
60
|
+
content = JSON.dump(content) if content.is_a?(Hash)
|
61
|
+
double(read: content, path: name, close: true)
|
62
|
+
end
|
63
|
+
|
64
|
+
def call(file, stage = "development")
|
65
|
+
Freud::Config.new.load(file, stage)
|
66
|
+
end
|
67
|
+
|
68
|
+
let(:root) { File.expand_path("#{File.dirname(__FILE__)}/..") }
|
69
|
+
|
70
|
+
it "name from file with extension" do
|
71
|
+
config = call(mock_file("monkey.json"))
|
72
|
+
expect(config.fetch("name")).to eq("monkey")
|
73
|
+
end
|
74
|
+
|
75
|
+
it "name from file with extension" do
|
76
|
+
config = call(mock_file("monkey"))
|
77
|
+
expect(config.fetch("name")).to eq("monkey")
|
78
|
+
end
|
79
|
+
|
80
|
+
it "pidfile tmp/%name" do
|
81
|
+
config = call(mock_file("monkey"))
|
82
|
+
expect(config.fetch("pidfile")).to eq("#{root}/tmp/monkey.pid")
|
83
|
+
end
|
84
|
+
|
85
|
+
it "pidfile %HOME/tmp/%name" do
|
86
|
+
home = ENV["HOME"]
|
87
|
+
content = { pidfile: "%HOME/tmp/%name.pid" }
|
88
|
+
config = call(mock_file("monkey", content))
|
89
|
+
expect(config.fetch("pidfile")).to eq("#{home}/tmp/monkey.pid")
|
90
|
+
end
|
91
|
+
|
92
|
+
it "logfile" do
|
93
|
+
content = { logfile: "log/%name.log" }
|
94
|
+
config = call(mock_file("monkey", content))
|
95
|
+
expect(config.fetch("logfile")).to eq("#{root}/log/monkey.log")
|
96
|
+
end
|
97
|
+
|
98
|
+
it "env FREUD_STAGE" do
|
99
|
+
config = call(mock_file("monkey"), "back")
|
100
|
+
env = config.to_hash.fetch("env")
|
101
|
+
expect(env["FREUD_STAGE"]).to eq("back")
|
102
|
+
end
|
103
|
+
|
104
|
+
it "background" do
|
105
|
+
config = call(mock_file("monkey", background: true))
|
106
|
+
expect(config.fetch("background")).to be(true)
|
107
|
+
end
|
108
|
+
|
109
|
+
it "reset_env" do
|
110
|
+
config = call(mock_file("monkey", reset_env: true))
|
111
|
+
expect(config.fetch("reset_env")).to be(true)
|
112
|
+
end
|
113
|
+
|
114
|
+
it "create_pidfile" do
|
115
|
+
config = call(mock_file("monkey", create_pidfile: true))
|
116
|
+
expect(config.fetch("create_pidfile")).to be(true)
|
117
|
+
end
|
118
|
+
|
119
|
+
it "javascript comments" do
|
120
|
+
content = mock_file("monkey", <<-END)
|
121
|
+
{
|
122
|
+
// Comment one.
|
123
|
+
"reset_env": true,
|
124
|
+
/* Comment two */
|
125
|
+
"name": "quux"
|
126
|
+
}
|
127
|
+
END
|
128
|
+
config = call(content)
|
129
|
+
expect(config.fetch("reset_env")).to be(true)
|
130
|
+
expect(config.fetch("name")).to eq("quux")
|
131
|
+
end
|
132
|
+
|
133
|
+
it "interpolates fully-merged" do
|
134
|
+
vars = { vars: { "RUNAS_USER" => "bob" } }
|
135
|
+
content = mock_file("monkey", sudo_user: "%RUNAS_USER",
|
136
|
+
stages: { development: vars })
|
137
|
+
config = call(content)
|
138
|
+
expect(config.fetch("sudo_user")).to eq("bob")
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "fileutils"
|
3
|
+
require "stringio"
|
4
|
+
require "freud/logging"
|
5
|
+
require "freud/launcher"
|
6
|
+
require "freud/pidfile"
|
7
|
+
|
8
|
+
RSpec.describe Freud::Launcher do
|
9
|
+
let(:log) { StringIO.new }
|
10
|
+
|
11
|
+
before(:each) { Freud::Logging.log_to(log) }
|
12
|
+
|
13
|
+
it "#new" do
|
14
|
+
fields = %w(name root pidfile logfile background create_pidfile
|
15
|
+
reset_env env commands)
|
16
|
+
command = Freud::Launcher.new(Hash[fields.map { |k| [ k, 42 ] }])
|
17
|
+
fields.each { |f| expect(command.send(f)).to eq(42) }
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#run" do
|
21
|
+
let(:process) { double }
|
22
|
+
|
23
|
+
let(:pidfile) { double(Freud::Pidfile) }
|
24
|
+
|
25
|
+
after(:each) { FileUtils.rm_f("tmp/foo.json") }
|
26
|
+
|
27
|
+
def try(config, runnable, *args, &block)
|
28
|
+
command = Freud::Launcher.new(config)
|
29
|
+
trial = lambda { command.run(runnable, args) }
|
30
|
+
return(trial.call) unless block_given?
|
31
|
+
expect(trial).to raise_error(Freud::RunnerExit, &block)
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "execute" do
|
35
|
+
it "bad external command" do
|
36
|
+
allow(pidfile).to receive(:read).and_return(35767)
|
37
|
+
config = { "process" => process, "pidfile" => pidfile,
|
38
|
+
"env" => {}, "commands" => {} }
|
39
|
+
try(config, "notacommand") { |e| expect(e.value).to eq(1) }
|
40
|
+
end
|
41
|
+
|
42
|
+
it "test: /bin/true" do
|
43
|
+
allow(pidfile).to receive(:read).and_return(35767)
|
44
|
+
config = { "process" => process, "pidfile" => pidfile,
|
45
|
+
"env" => {}, "commands" => { "test" => "/bin/true" } }
|
46
|
+
expect(process).to receive(:exec).with({}, "/bin/true",
|
47
|
+
{ unsetenv_others: false, chdir: nil, close_others: true,
|
48
|
+
in: "/dev/null", out: :err })
|
49
|
+
try(config, "test")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "daemonize" do
|
54
|
+
it "bad external command" do
|
55
|
+
allow(pidfile).to receive(:read).and_return(35767)
|
56
|
+
config = { "process" => process, "pidfile" => pidfile,
|
57
|
+
"env" => {}, "commands" => {} }
|
58
|
+
try(config, "start") { |e| expect(e.value).to eq(1) }
|
59
|
+
end
|
60
|
+
|
61
|
+
it "background: false" do
|
62
|
+
allow(pidfile).to receive(:read).and_return(nil)
|
63
|
+
allow(pidfile).to receive(:running?).and_return(false)
|
64
|
+
allow(process).to receive(:pid).and_return(35767)
|
65
|
+
config = { "process" => process, "pidfile" => pidfile,
|
66
|
+
"env" => {}, "background" => false,
|
67
|
+
"commands" => { "start" => "/bin/true" } }
|
68
|
+
expect(process).to receive(:exec).with({}, "/bin/true",
|
69
|
+
{ unsetenv_others: false, chdir: nil, close_others: true,
|
70
|
+
in: "/dev/null", out: :err })
|
71
|
+
try(config, "start")
|
72
|
+
end
|
73
|
+
|
74
|
+
it "background: true" do
|
75
|
+
allow(pidfile).to receive(:read).and_return(nil)
|
76
|
+
allow(pidfile).to receive(:running?).and_return(false)
|
77
|
+
config = { "process" => process, "pidfile" => pidfile,
|
78
|
+
"env" => {}, "background" => true,
|
79
|
+
"commands" => { "start" => "/bin/true" } }
|
80
|
+
expect(process).to receive(:spawn).with({}, "/bin/true",
|
81
|
+
{ unsetenv_others: false, chdir: nil, close_others: true,
|
82
|
+
in: "/dev/null", out: :err, pgroup: true })
|
83
|
+
try(config, "start")
|
84
|
+
end
|
85
|
+
|
86
|
+
it "reset_env" do
|
87
|
+
allow(pidfile).to receive(:read).and_return(nil)
|
88
|
+
allow(pidfile).to receive(:running?).and_return(false)
|
89
|
+
allow(process).to receive(:pid).and_return(35767)
|
90
|
+
config = { "process" => process, "pidfile" => pidfile,
|
91
|
+
"env" => {}, "reset_env" => true,
|
92
|
+
"commands" => { "start" => "/bin/true" } }
|
93
|
+
expect(process).to receive(:exec).with({}, "/bin/true",
|
94
|
+
{ unsetenv_others: true, chdir: nil, close_others: true,
|
95
|
+
in: "/dev/null", out: :err })
|
96
|
+
try(config, "start")
|
97
|
+
end
|
98
|
+
|
99
|
+
it "create_pidfile" do
|
100
|
+
allow(pidfile).to receive(:read).and_return(nil)
|
101
|
+
allow(pidfile).to receive(:running?).and_return(false)
|
102
|
+
allow(pidfile).to receive(:write).with(35767)
|
103
|
+
allow(process).to receive(:pid).and_return(35767)
|
104
|
+
config = { "process" => process, "pidfile" => pidfile,
|
105
|
+
"env" => {}, "create_pidfile" => true,
|
106
|
+
"commands" => { "start" => "/bin/true" } }
|
107
|
+
expect(process).to receive(:exec).with({}, "/bin/true",
|
108
|
+
{ unsetenv_others: false, chdir: nil, close_others: true,
|
109
|
+
in: "/dev/null", out: :err })
|
110
|
+
try(config, "start")
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "freud/pidfile"
|
3
|
+
|
4
|
+
RSpec.describe Freud::Pidfile do
|
5
|
+
PIDFILE = "tmp/freud.pid"
|
6
|
+
|
7
|
+
let(:pidfile) { Freud::Pidfile.new(PIDFILE) }
|
8
|
+
|
9
|
+
after(:each) { FileUtils.rm(PIDFILE) if File.exists?(PIDFILE) }
|
10
|
+
|
11
|
+
it "#write" do
|
12
|
+
pidfile.write(Process.pid)
|
13
|
+
expect(File.exists?(PIDFILE)).to be(true)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "#read" do
|
17
|
+
expect(pidfile.read).to be_nil
|
18
|
+
pidfile.write(Process.pid)
|
19
|
+
expect(pidfile.read).to eq(Process.pid)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "#running? true" do
|
23
|
+
pidfile.write(Process.pid)
|
24
|
+
expect(pidfile.running?).to be(true)
|
25
|
+
end
|
26
|
+
|
27
|
+
# NOTE: This will (obviously) fail if a process with that pid exists,
|
28
|
+
# but it's the maximum pid on a Linux system, and likely to be not used.
|
29
|
+
it "#running? false" do
|
30
|
+
pidfile.write(32768)
|
31
|
+
expect(pidfile.running?).to be(false)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "#to_s returns path" do
|
35
|
+
expect(pidfile.to_s).to eq(PIDFILE)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "== path" do
|
39
|
+
expect(pidfile).to eq(PIDFILE)
|
40
|
+
end
|
41
|
+
end
|
data/spec/runner_spec.rb
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "freud/runner"
|
3
|
+
require "set"
|
4
|
+
|
5
|
+
RSpec.describe Freud::Runner do
|
6
|
+
def unused_pid
|
7
|
+
used = Set.new(`ps -A | awk '{print $1}'`.split(/\n/))
|
8
|
+
output = 35767.step(1, -1).find { |pid| not used.include?(pid.to_s) }
|
9
|
+
raise("Can't find an unused pid.") unless output
|
10
|
+
output
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:root) { File.expand_path("#{File.dirname(__FILE__)}/..") }
|
14
|
+
|
15
|
+
let(:runner) { Freud::Runner.new }
|
16
|
+
|
17
|
+
let(:process) { double }
|
18
|
+
|
19
|
+
let(:pidfile) { double(Freud::Pidfile) }
|
20
|
+
|
21
|
+
let(:log) { StringIO.new }
|
22
|
+
|
23
|
+
before(:each) { Freud::Logging.log_to(log) }
|
24
|
+
|
25
|
+
after(:each) do
|
26
|
+
FileUtils.rm_f("tmp/generated.json")
|
27
|
+
FileUtils.rm_f("tmp/true.pid")
|
28
|
+
end
|
29
|
+
|
30
|
+
it ".run" do
|
31
|
+
expect { Freud::Runner.run([]) }.to raise_error(SystemExit)
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#run" do
|
35
|
+
def try(runnable, &block)
|
36
|
+
expect(runnable).to raise_error(Freud::RunnerExit, &block)
|
37
|
+
end
|
38
|
+
|
39
|
+
after(:each) do
|
40
|
+
ENV.delete("FREUD_CONFIG")
|
41
|
+
ENV.delete("FREUD_STAGE")
|
42
|
+
ENV.delete("FREUD_SERVICE_PATH")
|
43
|
+
end
|
44
|
+
|
45
|
+
it "@check (up)" do
|
46
|
+
Freud::Pidfile.new("#{root}/tmp/true.pid").write(Process.pid)
|
47
|
+
args = [ "@check", "spec/fixtures/true.json" ]
|
48
|
+
try(lambda { runner.run(args) }) { |r| expect(r.value).to eq(0) }
|
49
|
+
end
|
50
|
+
|
51
|
+
it "@check via FREUD_CONFIG" do
|
52
|
+
ENV["FREUD_CONFIG"] = "spec/fixtures/true.json"
|
53
|
+
Freud::Pidfile.new("#{root}/tmp/true.pid").write(Process.pid)
|
54
|
+
args = [ "@check" ]
|
55
|
+
try(lambda { runner.run(args) }) { |r| expect(r.value).to eq(0) }
|
56
|
+
log.rewind
|
57
|
+
expect(log.read).to match(/\bup\b/)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "@check (down)" do
|
61
|
+
ENV["FREUD_CONFIG"] = "spec/fixtures/true.json"
|
62
|
+
Freud::Pidfile.new("#{root}/tmp/true.pid").write(unused_pid)
|
63
|
+
args = [ "@check", "spec/fixtures/true.json" ]
|
64
|
+
try(lambda { runner.run(args) }) { |r| expect(r.value).to eq(0) }
|
65
|
+
log.rewind
|
66
|
+
expect(log.read).to match(/\bdown\b/)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "@wait-up (up)" do
|
70
|
+
ENV["FREUD_CONFIG"] = "spec/fixtures/true.json"
|
71
|
+
Freud::Pidfile.new("#{root}/tmp/true.pid").write(Process.pid)
|
72
|
+
args = [ "@wait-up", "-t", "1" ]
|
73
|
+
started_at = Time.now.to_i
|
74
|
+
try(lambda { runner.run(args) }) { |r| expect(r.value).to eq(0) }
|
75
|
+
expect(Time.now.to_i - started_at).to be < 2
|
76
|
+
end
|
77
|
+
|
78
|
+
it "@wait-up (down)" do
|
79
|
+
ENV["FREUD_CONFIG"] = "spec/fixtures/true.json"
|
80
|
+
Freud::Pidfile.new("#{root}/tmp/true.pid").write(unused_pid)
|
81
|
+
args = [ "@wait-up", "-t", "1" ]
|
82
|
+
started_at = Time.now.to_i
|
83
|
+
try(lambda { runner.run(args) }) { |r| expect(r.value).to eq(1) }
|
84
|
+
expect(Time.now.to_i - started_at).to be < 2
|
85
|
+
end
|
86
|
+
|
87
|
+
it "@wait-down (down)" do
|
88
|
+
ENV["FREUD_CONFIG"] = "spec/fixtures/true.json"
|
89
|
+
Freud::Pidfile.new("#{root}/tmp/true.pid").write(unused_pid)
|
90
|
+
args = [ "@wait-down", "-t", "1" ]
|
91
|
+
try(lambda { runner.run(args) }) { |r| expect(r.value).to eq(0) }
|
92
|
+
end
|
93
|
+
|
94
|
+
it "@wait-down (up)" do
|
95
|
+
ENV["FREUD_CONFIG"] = "spec/fixtures/true.json"
|
96
|
+
Freud::Pidfile.new("#{root}/tmp/true.pid").write(Process.pid)
|
97
|
+
args = [ "@wait-down", "-t", "1" ]
|
98
|
+
try(lambda { runner.run(args) }) { |r| expect(r.value).to eq(1) }
|
99
|
+
end
|
100
|
+
|
101
|
+
it "generate" do
|
102
|
+
Freud::Pidfile.new("#{root}/tmp/true.pid").write(unused_pid)
|
103
|
+
args = [ "g", "tmp/generated.json" ]
|
104
|
+
try(lambda { runner.run(args) }) { |r| expect(r.value).to eq(0) }
|
105
|
+
generated = "#{root}/tmp/generated.json"
|
106
|
+
expect(File.exists?(generated)).to be(true)
|
107
|
+
expect { JSON.parse(File.read(generated)) }.to_not raise_error
|
108
|
+
end
|
109
|
+
|
110
|
+
it "usage" do
|
111
|
+
Freud::Pidfile.new("#{root}/tmp/true.pid").write(unused_pid)
|
112
|
+
try(lambda { runner.run([]) }) do |result|
|
113
|
+
expect(result.value).to eq(1)
|
114
|
+
expect(result.message).to match(/usage/i)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
it "help" do
|
119
|
+
args = [ "help", "spec/fixtures/true.json" ]
|
120
|
+
try(lambda { runner.run(args) }) { |r| expect(r.value).to eq(0) }
|
121
|
+
log.rewind
|
122
|
+
expect(log.read).to match(/quux/)
|
123
|
+
end
|
124
|
+
|
125
|
+
it "FREUD_SERVICE_PATH" do
|
126
|
+
ENV["FREUD_SERVICE_PATH"] = "spec/fixtures"
|
127
|
+
args = [ "help", "true" ]
|
128
|
+
try(lambda { runner.run(args) }) { |r| expect(r.value).to eq(0) }
|
129
|
+
log.rewind
|
130
|
+
expect(log.read).to match(/quux/)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
root = File.join(File.dirname(File.expand_path(__FILE__)), "..")
|
2
|
+
$LOAD_PATH.unshift(root) unless $LOAD_PATH.include?(root)
|
3
|
+
|
4
|
+
require "simplecov"
|
5
|
+
|
6
|
+
SimpleCov.start do
|
7
|
+
add_filter "/spec/"
|
8
|
+
end
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
config.filter_run(:focus)
|
12
|
+
config.run_all_when_everything_filtered = true
|
13
|
+
config.order = :random
|
14
|
+
config.default_formatter = "doc" if config.files_to_run.one?
|
15
|
+
Kernel.srand(config.seed)
|
16
|
+
|
17
|
+
config.expect_with :rspec do |expectations|
|
18
|
+
expectations.syntax = :expect
|
19
|
+
end
|
20
|
+
|
21
|
+
config.mock_with :rspec do |mocks|
|
22
|
+
mocks.syntax = :expect
|
23
|
+
mocks.verify_partial_doubles = true
|
24
|
+
end
|
25
|
+
end
|