meepo 1.5.2

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.
Files changed (108) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +16 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +29 -0
  6. data/.travis.yml +10 -0
  7. data/Dockerfile +7 -0
  8. data/Gemfile +13 -0
  9. data/MIT-LICENSE +20 -0
  10. data/Rakefile +15 -0
  11. data/TODO +5 -0
  12. data/bin/invoker +7 -0
  13. data/contrib/completion/invoker-completion.bash +70 -0
  14. data/contrib/completion/invoker-completion.zsh +62 -0
  15. data/examples/hello_sinatra.rb +26 -0
  16. data/examples/sample.ini +3 -0
  17. data/invoker.gemspec +43 -0
  18. data/lib/invoker.rb +152 -0
  19. data/lib/invoker/cli.rb +159 -0
  20. data/lib/invoker/cli/pinger.rb +23 -0
  21. data/lib/invoker/cli/question.rb +15 -0
  22. data/lib/invoker/cli/tail.rb +34 -0
  23. data/lib/invoker/cli/tail_watcher.rb +34 -0
  24. data/lib/invoker/command_worker.rb +60 -0
  25. data/lib/invoker/commander.rb +95 -0
  26. data/lib/invoker/daemon.rb +126 -0
  27. data/lib/invoker/dns_cache.rb +23 -0
  28. data/lib/invoker/errors.rb +17 -0
  29. data/lib/invoker/event/manager.rb +79 -0
  30. data/lib/invoker/ipc.rb +45 -0
  31. data/lib/invoker/ipc/add_command.rb +12 -0
  32. data/lib/invoker/ipc/add_http_command.rb +10 -0
  33. data/lib/invoker/ipc/base_command.rb +24 -0
  34. data/lib/invoker/ipc/client_handler.rb +26 -0
  35. data/lib/invoker/ipc/dns_check_command.rb +17 -0
  36. data/lib/invoker/ipc/list_command.rb +11 -0
  37. data/lib/invoker/ipc/message.rb +170 -0
  38. data/lib/invoker/ipc/message/list_response.rb +35 -0
  39. data/lib/invoker/ipc/message/tail_response.rb +10 -0
  40. data/lib/invoker/ipc/ping_command.rb +10 -0
  41. data/lib/invoker/ipc/reload_command.rb +12 -0
  42. data/lib/invoker/ipc/remove_command.rb +12 -0
  43. data/lib/invoker/ipc/server.rb +26 -0
  44. data/lib/invoker/ipc/tail_command.rb +11 -0
  45. data/lib/invoker/ipc/unix_client.rb +60 -0
  46. data/lib/invoker/logger.rb +13 -0
  47. data/lib/invoker/parsers/config.rb +184 -0
  48. data/lib/invoker/parsers/procfile.rb +86 -0
  49. data/lib/invoker/power/balancer.rb +131 -0
  50. data/lib/invoker/power/config.rb +77 -0
  51. data/lib/invoker/power/dns.rb +38 -0
  52. data/lib/invoker/power/http_parser.rb +68 -0
  53. data/lib/invoker/power/http_response.rb +81 -0
  54. data/lib/invoker/power/pf_migrate.rb +64 -0
  55. data/lib/invoker/power/port_finder.rb +49 -0
  56. data/lib/invoker/power/power.rb +3 -0
  57. data/lib/invoker/power/powerup.rb +29 -0
  58. data/lib/invoker/power/setup.rb +90 -0
  59. data/lib/invoker/power/setup/distro/arch.rb +15 -0
  60. data/lib/invoker/power/setup/distro/base.rb +57 -0
  61. data/lib/invoker/power/setup/distro/debian.rb +11 -0
  62. data/lib/invoker/power/setup/distro/mint.rb +10 -0
  63. data/lib/invoker/power/setup/distro/opensuse.rb +11 -0
  64. data/lib/invoker/power/setup/distro/redhat.rb +11 -0
  65. data/lib/invoker/power/setup/distro/ubuntu.rb +10 -0
  66. data/lib/invoker/power/setup/files/invoker_forwarder.sh.erb +17 -0
  67. data/lib/invoker/power/setup/files/socat_invoker.service +12 -0
  68. data/lib/invoker/power/setup/linux_setup.rb +105 -0
  69. data/lib/invoker/power/setup/osx_setup.rb +137 -0
  70. data/lib/invoker/power/templates/400.html +40 -0
  71. data/lib/invoker/power/templates/404.html +40 -0
  72. data/lib/invoker/power/templates/503.html +40 -0
  73. data/lib/invoker/power/url_rewriter.rb +40 -0
  74. data/lib/invoker/process_manager.rb +198 -0
  75. data/lib/invoker/process_printer.rb +43 -0
  76. data/lib/invoker/reactor.rb +37 -0
  77. data/lib/invoker/reactor/reader.rb +54 -0
  78. data/lib/invoker/version.rb +47 -0
  79. data/readme.md +25 -0
  80. data/spec/invoker/cli/pinger_spec.rb +22 -0
  81. data/spec/invoker/cli/tail_watcher_spec.rb +39 -0
  82. data/spec/invoker/cli_spec.rb +27 -0
  83. data/spec/invoker/command_worker_spec.rb +45 -0
  84. data/spec/invoker/commander_spec.rb +152 -0
  85. data/spec/invoker/config_spec.rb +361 -0
  86. data/spec/invoker/daemon_spec.rb +34 -0
  87. data/spec/invoker/event/manager_spec.rb +67 -0
  88. data/spec/invoker/invoker_spec.rb +71 -0
  89. data/spec/invoker/ipc/client_handler_spec.rb +54 -0
  90. data/spec/invoker/ipc/dns_check_command_spec.rb +32 -0
  91. data/spec/invoker/ipc/message/list_response_spec.rb +24 -0
  92. data/spec/invoker/ipc/message_spec.rb +49 -0
  93. data/spec/invoker/ipc/unix_client_spec.rb +29 -0
  94. data/spec/invoker/power/balancer_spec.rb +22 -0
  95. data/spec/invoker/power/config_spec.rb +18 -0
  96. data/spec/invoker/power/http_parser_spec.rb +32 -0
  97. data/spec/invoker/power/http_response_spec.rb +34 -0
  98. data/spec/invoker/power/pf_migrate_spec.rb +87 -0
  99. data/spec/invoker/power/port_finder_spec.rb +16 -0
  100. data/spec/invoker/power/setup/linux_setup_spec.rb +103 -0
  101. data/spec/invoker/power/setup/osx_setup_spec.rb +105 -0
  102. data/spec/invoker/power/setup_spec.rb +4 -0
  103. data/spec/invoker/power/url_rewriter_spec.rb +70 -0
  104. data/spec/invoker/power/web_sockets_spec.rb +61 -0
  105. data/spec/invoker/process_manager_spec.rb +130 -0
  106. data/spec/invoker/reactor_spec.rb +6 -0
  107. data/spec/spec_helper.rb +43 -0
  108. metadata +389 -0
@@ -0,0 +1,16 @@
1
+ require "spec_helper"
2
+
3
+ describe "PortFinder" do
4
+ before do
5
+ @port_finder = Invoker::Power::PortFinder.new()
6
+ @port_finder.find_ports
7
+ end
8
+
9
+ it "should find a http port" do
10
+ expect(@port_finder.http_port).not_to be_nil
11
+ end
12
+
13
+ it "should find a dns port" do
14
+ expect(@port_finder.dns_port).not_to be_nil
15
+ end
16
+ end
@@ -0,0 +1,103 @@
1
+ require "spec_helper"
2
+ require "invoker/power/setup/distro/ubuntu"
3
+ require "invoker/power/setup/distro/opensuse"
4
+
5
+ def mock_socat_scripts
6
+ FakeFS.deactivate!
7
+ socat_content = File.read(invoker_setup.forwarder_script)
8
+ socat_systemd = File.read(invoker_setup.socat_unit)
9
+ FakeFS.activate!
10
+ FileUtils.mkdir_p(File.dirname(invoker_setup.forwarder_script))
11
+ FileUtils.mkdir_p(File.dirname(invoker_setup.socat_unit))
12
+ File.open(invoker_setup.socat_unit, "w") do |fl|
13
+ fl.write(socat_systemd)
14
+ end
15
+ File.open(invoker_setup.forwarder_script, "w") do |fl|
16
+ fl.write(socat_content)
17
+ end
18
+ FileUtils.mkdir_p("/usr/bin")
19
+ end
20
+
21
+ describe Invoker::Power::LinuxSetup, fakefs: true do
22
+ before do
23
+ FileUtils.mkdir_p(inv_conf_dir)
24
+ FileUtils.mkdir_p(Invoker::Power::Distro::Base::RESOLVER_DIR)
25
+ end
26
+
27
+ let(:invoker_setup) { Invoker::Power::LinuxSetup.new('dev') }
28
+ let(:distro_installer) { Invoker::Power::Distro::Ubuntu.new('dev') }
29
+
30
+ describe "should only proceed after user confirmation" do
31
+ before { invoker_setup.distro_installer = distro_installer }
32
+
33
+ it "should create config file with port" do
34
+ invoker_setup.expects(:initialize_distro_installer).returns(true)
35
+ invoker_setup.expects(:get_user_confirmation?).returns(true)
36
+ invoker_setup.expects(:install_resolver).returns(true)
37
+ invoker_setup.expects(:install_port_forwarder).returns(true)
38
+ invoker_setup.expects(:drop_to_normal_user).returns(true)
39
+
40
+ distro_installer.expects(:install_required_software)
41
+ distro_installer.expects(:restart_services)
42
+
43
+ invoker_setup.setup_invoker
44
+
45
+ config = Invoker::Power::Config.load_config
46
+ expect(config.http_port).not_to be_nil
47
+ expect(config.dns_port).to be_nil
48
+ expect(config.https_port).not_to be_nil
49
+ end
50
+ end
51
+
52
+ describe "configuring dnsmasq and socat" do
53
+ before(:all) do
54
+ @original_invoker_config = Invoker.config
55
+ Invoker.config = mock
56
+ end
57
+
58
+ after(:all) do
59
+ Invoker.config = @original_invoker_config
60
+ end
61
+
62
+ before(:each) do
63
+ invoker_setup.distro_installer = distro_installer
64
+ mock_socat_scripts
65
+ end
66
+
67
+ it "should create proper config file" do
68
+ invoker_setup.expects(:initialize_distro_installer).returns(true)
69
+ invoker_setup.expects(:get_user_confirmation?).returns(true)
70
+ invoker_setup.expects(:drop_to_normal_user).returns(true)
71
+
72
+ distro_installer.expects(:install_required_software)
73
+ distro_installer.expects(:restart_services)
74
+
75
+ invoker_setup.setup_invoker
76
+
77
+ config = Invoker::Power::Config.load_config
78
+
79
+ dnsmasq_content = File.read(distro_installer.resolver_file)
80
+ expect(dnsmasq_content.strip).to_not be_empty
81
+ expect(dnsmasq_content).to match(/dev/)
82
+
83
+ socat_content = File.read(Invoker::Power::Distro::Base::SOCAT_SHELLSCRIPT)
84
+ expect(socat_content.strip).to_not be_empty
85
+ expect(socat_content.strip).to match(/#{config.https_port}/)
86
+ expect(socat_content.strip).to match(/#{config.http_port}/)
87
+
88
+ service_file = File.read(Invoker::Power::Distro::Base::SOCAT_SYSTEMD)
89
+ expect(service_file.strip).to_not be_empty
90
+ end
91
+ end
92
+
93
+ describe 'resolver file' do
94
+ context 'user sets up a custom top level domain' do
95
+ it 'should create the correct resolver file' do
96
+ linux_setup = Invoker::Power::LinuxSetup.new('local')
97
+ suse_installer = Invoker::Power::Distro::Opensuse.new('local')
98
+ linux_setup.distro_installer = suse_installer
99
+ expect(linux_setup.resolver_file).to eq('/etc/dnsmasq.d/local-tld')
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,105 @@
1
+ require "spec_helper"
2
+
3
+ describe Invoker::Power::OsxSetup, fakefs: true do
4
+ before do
5
+ FileUtils.mkdir_p(inv_conf_dir)
6
+ FileUtils.mkdir_p(Invoker::Power::OsxSetup::RESOLVER_DIR)
7
+ end
8
+
9
+ describe "when no setup exists" do
10
+ it "should create a config file with port etc" do
11
+ setup = Invoker::Power::OsxSetup.new('dev')
12
+ setup.expects(:install_resolver).returns(true)
13
+ setup.expects(:drop_to_normal_user).returns(true)
14
+ setup.expects(:install_firewall).once
15
+
16
+ setup.setup_invoker
17
+
18
+ config = Invoker::Power::Config.load_config
19
+ expect(config.http_port).not_to be_nil
20
+ expect(config.dns_port).not_to be_nil
21
+ expect(config.https_port).not_to be_nil
22
+ end
23
+ end
24
+
25
+ describe "when a setup file exists" do
26
+ it "should throw error about existing file" do
27
+ File.open(Invoker::Power::Config.config_file, "w") {|fl|
28
+ fl.write("foo test")
29
+ }
30
+ Invoker::Power::Setup.any_instance.expects(:setup_invoker).never
31
+ Invoker::Power::Setup.install('dev')
32
+ end
33
+ end
34
+
35
+ describe "when pow like setup exists" do
36
+ before {
37
+ File.open(File.join(Invoker::Power::OsxSetup::RESOLVER_DIR, "dev"), "w") { |fl|
38
+ fl.write("hello")
39
+ }
40
+ @setup = Invoker::Power::OsxSetup.new('dev')
41
+ }
42
+
43
+ describe "when user selects to overwrite it" do
44
+ it "should run setup normally" do
45
+ @setup.expects(:setup_resolver_file).returns(true)
46
+ @setup.expects(:drop_to_normal_user).returns(true)
47
+ @setup.expects(:install_resolver).returns(true)
48
+ @setup.expects(:install_firewall).once()
49
+
50
+ @setup.setup_invoker
51
+ end
52
+ end
53
+
54
+ describe "when user chose not to overwrite it" do
55
+ it "should abort the setup process" do
56
+ @setup.expects(:setup_resolver_file).returns(false)
57
+
58
+ @setup.expects(:install_resolver).never
59
+ @setup.expects(:install_firewall).never
60
+
61
+ @setup.setup_invoker
62
+ end
63
+ end
64
+ end
65
+
66
+ describe "uninstalling firewall rules" do
67
+ it "should uninstall firewall rules and remove all files created by setup" do
68
+ setup = Invoker::Power::OsxSetup.new('dev')
69
+
70
+ Invoker::CLI::Question.expects(:agree).returns(true)
71
+ setup.expects(:remove_resolver_file).once
72
+ setup.expects(:unload_firewall_rule).with(true).once
73
+ Invoker::Power::Config.expects(:delete).once
74
+
75
+ setup.uninstall_invoker
76
+ end
77
+ end
78
+
79
+ describe "setup on fresh osx install" do
80
+ context "when resolver directory does not exist" do
81
+ before do
82
+ @setup = Invoker::Power::OsxSetup.new('dev')
83
+ FileUtils.rm_rf(Invoker::Power::OsxSetup::RESOLVER_DIR)
84
+ end
85
+
86
+ it "should create the directory and install" do
87
+ @setup.expects(:setup_resolver_file).returns(true)
88
+ @setup.expects(:drop_to_normal_user).returns(true)
89
+ @setup.expects(:install_firewall).once()
90
+
91
+ @setup.setup_invoker
92
+ expect(Dir.exist?(Invoker::Power::OsxSetup::RESOLVER_DIR)).to be_truthy
93
+ end
94
+ end
95
+ end
96
+
97
+ describe '.resolver_file' do
98
+ context 'user sets up a custom top level domain' do
99
+ it 'should create the correct resolver file' do
100
+ setup = Invoker::Power::OsxSetup.new('local')
101
+ expect(setup.resolver_file).to eq('/etc/resolver/local')
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,4 @@
1
+ require "spec_helper"
2
+
3
+ describe "Setup" do
4
+ end
@@ -0,0 +1,70 @@
1
+ require 'spec_helper'
2
+
3
+ describe Invoker::Power::UrlRewriter do
4
+ let(:rewriter) { Invoker::Power::UrlRewriter.new }
5
+
6
+ context "matching domain part of incoming request" do
7
+ before(:all) do
8
+ @original_invoker_config = Invoker.config
9
+
10
+ Invoker.config = mock
11
+ Invoker.config.stubs(:tld).returns("dev")
12
+ end
13
+
14
+ after(:all) do
15
+ Invoker.config = @original_invoker_config
16
+ end
17
+
18
+ it "should match foo.dev" do
19
+ match = rewriter.extract_host_from_domain("foo.dev")
20
+ expect(match).to_not be_empty
21
+
22
+ matching_string = match[0]
23
+ expect(matching_string).to eq("foo")
24
+ end
25
+
26
+ it "should match foo.dev:1080" do
27
+ match = rewriter.extract_host_from_domain("foo.dev:1080")
28
+ expect(match).to_not be_empty
29
+
30
+ matching_string = match[0]
31
+ expect(matching_string).to eq("foo")
32
+ end
33
+
34
+ it "should match emacs.bar.dev" do
35
+ match = rewriter.extract_host_from_domain("emacs.bar.dev")
36
+ expect(match).to_not be_empty
37
+
38
+ expect(match[0]).to eq("emacs.bar")
39
+ expect(match[1]).to eq("bar")
40
+ end
41
+
42
+ it "should match hello-world.dev" do
43
+ match = rewriter.extract_host_from_domain("hello-world.dev")
44
+ expect(match).to_not be_nil
45
+
46
+ expect(match[0]).to eq("hello-world")
47
+ end
48
+
49
+ context 'user sets up a custom top level domain' do
50
+ before(:all) do
51
+ @original_invoker_config = Invoker.config
52
+
53
+ Invoker.config = mock
54
+ Invoker.config.stubs(:tld).returns("local")
55
+ end
56
+
57
+ it 'should match domain part of incoming request correctly' do
58
+ match = rewriter.extract_host_from_domain("foo.local")
59
+ expect(match).to_not be_empty
60
+
61
+ matching_string = match[0]
62
+ expect(matching_string).to eq("foo")
63
+ end
64
+
65
+ after(:all) do
66
+ Invoker.config = @original_invoker_config
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ # Full integration test. Start a server, and client. Let client interact with
4
+ # server do a ping-pong. Client checks whether ping pong is successful or not.
5
+ # Also, mock rewriter so that it returns valid port for request proxying.
6
+ # - Server will run on port 28080.
7
+ # - Balancer will run on port 28081 proxying to 28080
8
+ # - Client will connect to 28081 performing ping-pong
9
+
10
+ def websocket_server
11
+ require 'websocket-eventmachine-server'
12
+
13
+ EM.run do
14
+ WebSocket::EventMachine::Server.start(host: "0.0.0.0", port: 28080) do |ws|
15
+ ws.onerror { |e| p e }
16
+ ws.onmessage { ws.send "pong" }
17
+ end
18
+
19
+ EM.add_timer(2) { EM.stop }
20
+ end
21
+ end
22
+
23
+ def websocket_client
24
+ require 'websocket-eventmachine-client'
25
+
26
+ @message = ""
27
+
28
+ EM.run do
29
+ ws = WebSocket::EventMachine::Client.connect(uri: 'ws://0.0.0.0:28081')
30
+ ws.onerror { |e| p e }
31
+ ws.onopen { ws.send("ping") }
32
+ ws.onmessage { |m, _| @message = m }
33
+
34
+ EM.add_timer(2) do
35
+ expect(@message).to eq "pong"
36
+ EM.stop
37
+ end
38
+ end
39
+ end
40
+
41
+
42
+ describe 'Web sockets support' do
43
+ it 'can ping pong via balancer' do
44
+ dns_response = Struct.new(:port, :ip).new(28080, "0.0.0.0")
45
+ Invoker::Power::UrlRewriter.any_instance
46
+ .stubs(:select_backend_config)
47
+ .returns(dns_response)
48
+
49
+ EM.run do
50
+ EM.start_server("0.0.0.0", 28081, EM::ProxyServer::Connection, {}) do |conn|
51
+ Invoker::Power::Balancer.new(conn, "http").install_callbacks
52
+ end
53
+
54
+ fork { websocket_server }
55
+ fork { websocket_client }
56
+ EM.add_timer(3) { EM.stop }
57
+ end
58
+
59
+ Process.waitall
60
+ end
61
+ end
@@ -0,0 +1,130 @@
1
+ require "spec_helper"
2
+
3
+ describe Invoker::ProcessManager do
4
+ let(:process_manager) { Invoker::ProcessManager.new }
5
+
6
+ describe "#start_process_by_name" do
7
+ it "should find command by label and start it, if found" do
8
+ @original_invoker_config = Invoker.config
9
+ Invoker.config = mock
10
+
11
+ Invoker.config.stubs(:processes).returns([OpenStruct.new(:label => "resque", :cmd => "foo", :dir => "bar")])
12
+ Invoker.config.expects(:process).returns(OpenStruct.new(:label => "resque", :cmd => "foo", :dir => "bar"))
13
+ process_manager.expects(:start_process).returns(true)
14
+
15
+ process_manager.start_process_by_name("resque")
16
+
17
+ Invoker.config = @original_invoker_config
18
+ end
19
+
20
+ it "should not start already running process" do
21
+ process_manager.workers.expects(:[]).returns(OpenStruct.new(:pid => "bogus"))
22
+ expect(process_manager.start_process_by_name("resque")).to be_falsey
23
+ end
24
+ end
25
+
26
+ describe "#stop_process" do
27
+ let(:message) { MM::Remove.new(options) }
28
+ describe "when a worker is found" do
29
+ before do
30
+ process_manager.workers.expects(:[]).returns(OpenStruct.new(:pid => "bogus"))
31
+ end
32
+
33
+ describe "if a signal is specified" do
34
+ let(:options) { { process_name: 'bogus', signal: 'HUP' } }
35
+ it "should use that signal to kill the worker" do
36
+ process_manager.expects(:process_kill).with("bogus", "HUP").returns(true)
37
+ expect(process_manager.stop_process(message)).to be_truthy
38
+ end
39
+ end
40
+
41
+ describe "if no signal is specified" do
42
+ let(:options) { { process_name: 'bogus' } }
43
+ it "should use INT signal" do
44
+ process_manager.expects(:process_kill).with("bogus", "INT").returns(true)
45
+ expect(process_manager.stop_process(message)).to be_truthy
46
+ end
47
+ end
48
+ end
49
+
50
+ describe "when no worker is found" do
51
+ let(:options) { { process_name: 'bogus', signal: 'HUP' } }
52
+ before do
53
+ process_manager.workers.expects(:[]).returns(nil)
54
+ end
55
+
56
+ it "should not kill anything" do
57
+ process_manager.expects(:process_kill).never
58
+ process_manager.stop_process(message)
59
+ end
60
+ end
61
+ end
62
+
63
+ describe "#load_env" do
64
+ it "should load .env file from the specified directory" do
65
+ dir = "/tmp"
66
+ begin
67
+ env_file = File.new("#{dir}/.env", "w")
68
+ env_data =<<-EOD
69
+ FOO=foo
70
+ BAR=bar
71
+ EOD
72
+ env_file.write(env_data)
73
+ env_file.close
74
+ env_options = process_manager.load_env(dir)
75
+ expect(env_options).to include("FOO" => "foo", "BAR" => "bar")
76
+ ensure
77
+ File.delete(env_file.path)
78
+ end
79
+ end
80
+
81
+ it "should default to current directory if no directory is specified" do
82
+ dir = ENV["HOME"]
83
+ ENV.stubs(:[]).with("PWD").returns(dir)
84
+ begin
85
+ env_file = File.new("#{dir}/.env", "w")
86
+ env_data =<<-EOD
87
+ FOO=bar
88
+ BAR=foo
89
+ EOD
90
+ env_file.write(env_data)
91
+ env_file.close
92
+ env_options = process_manager.load_env
93
+ expect(env_options).to include("FOO" => "bar", "BAR" => "foo")
94
+ ensure
95
+ File.delete(env_file.path)
96
+ end
97
+ end
98
+
99
+ it "should return empty hash if there is no .env file" do
100
+ dir = "/tmp"
101
+ expect(process_manager.load_env(dir)).to eq({})
102
+ end
103
+
104
+ it "should load .local.env file if it exists" do
105
+ dir = "/tmp"
106
+ begin
107
+ env_file = File.new("#{dir}/.env", "w")
108
+ env_data =<<-EOD
109
+ FOO=foo
110
+ BAR=bar
111
+ EOD
112
+ env_file.write(env_data)
113
+ env_file.close
114
+
115
+ local_env_file = File.new("#{dir}/.env.local", "w")
116
+ local_env_data =<<-EOD
117
+ FOO=emacs
118
+ EOD
119
+ local_env_file.write(local_env_data)
120
+ local_env_file.close
121
+
122
+ env_options = process_manager.load_env(dir)
123
+ expect(env_options).to include("FOO" => "emacs", "BAR" => "bar")
124
+ ensure
125
+ File.delete(env_file.path)
126
+ File.delete(local_env_file.path)
127
+ end
128
+ end
129
+ end
130
+ end