vagrant-mirror 0.1.0.alpha

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.
@@ -0,0 +1,161 @@
1
+ describe Vagrant::Mirror::Config do
2
+
3
+ context "with no configuration" do
4
+
5
+ it "returns an empty array of mirrored folders" do
6
+ subject.folders.should eq([])
7
+ end
8
+
9
+ end
10
+
11
+ context "when a mirrored folder is added with no options" do
12
+
13
+ before(:each) do
14
+ subject.folder "foo", "/var/path"
15
+ end
16
+
17
+ it "adds it to the list of folders" do
18
+ subject.folders.count.should eq 1
19
+ subject.folders[0][:name].should eq 'foo'
20
+ subject.folders[0][:guest_path].should eq "/var/path"
21
+ end
22
+
23
+ it "does not beep by default" do
24
+ subject.folders[0][:beep].should be_false
25
+ end
26
+
27
+ it "does not delete by default" do
28
+ subject.folders[0][:delete].should be_false
29
+ end
30
+
31
+ it "does not exclude anything by default" do
32
+ subject.folders[0][:exclude].should eq([])
33
+ end
34
+
35
+ it "does not make any symlinks by default" do
36
+ subject.folders[0][:symlinks].should eq([])
37
+ end
38
+
39
+ end
40
+
41
+ it "provides a shortcut for mirroring the vagrant root to the guest" do
42
+ subject.vagrant_root "/var/vagrant"
43
+
44
+ subject.folders.count.should eq 1
45
+ subject.folders[0][:name].should eq 'v-root'
46
+ subject.folders[0][:guest_path].should eq "/var/vagrant"
47
+ end
48
+
49
+ it "can accept a folder with the delete option set" do
50
+ subject.vagrant_root "/var/vagrant", { :delete => true }
51
+
52
+ subject.folders[0][:delete].should be_true
53
+ end
54
+
55
+ it "can accept a folder with the beep option set" do
56
+ subject.vagrant_root "/var/vagrant", { :beep => true }
57
+
58
+ subject.folders[0][:beep].should be_true
59
+ end
60
+
61
+ it "can accept a folder with exclude patterns set" do
62
+ subject.vagrant_root "/var/vagrant", { :exclude => ['/docs'] }
63
+
64
+ subject.folders[0][:exclude].should eq(['/docs'])
65
+ end
66
+
67
+ context "when adding a folder with excludes and symlinks" do
68
+ before (:each) do
69
+ subject.vagrant_root "/var/vagrant", { :exclude => ['/docs'], :symlinks => ['/log'] }
70
+ end
71
+
72
+ it "stores the list of symlinks" do
73
+ subject.folders[0][:symlinks].should eq(['/log'])
74
+ end
75
+
76
+ it "adds the symlink to the rsync exclude list" do
77
+ subject.folders[0][:exclude].should eq(['/docs', '/log'])
78
+ end
79
+
80
+ end
81
+
82
+ context "when adding multiple folders" do
83
+ before (:each) do
84
+ subject.vagrant_root "/var/vagrant", { :exclude => ['/docs'], :symlinks => ['/log'] }
85
+ subject.folder "foo", "/var/foo", { :exclude => ['/docs2'], :symlinks => ['/log2'] }
86
+ end
87
+
88
+ it "builds a list of folders" do
89
+ subject.folders.count.should eq(2)
90
+ subject.folders[0][:name].should eq('v-root')
91
+ subject.folders[1][:name].should eq('foo')
92
+ end
93
+ end
94
+
95
+ context "when validating configuration" do
96
+
97
+ before :each do
98
+ @env = Vagrant::Environment.new
99
+ @errors = Vagrant::Config::ErrorRecorder.new
100
+ end
101
+
102
+ it "rejects folders with an empty shared folder name" do
103
+ subject.folder "", "/var/path"
104
+ subject.validate(@env, @errors)
105
+
106
+ @errors.errors.empty?.should be_false
107
+ end
108
+
109
+ it "rejects folders with an empty guest path" do
110
+ subject.folder "foo", ""
111
+ subject.validate(@env, @errors)
112
+
113
+ @errors.errors.empty?.should be_false
114
+ end
115
+
116
+ it "rejects folders which are not already a vagrant shared folder" do
117
+ pending
118
+ end
119
+
120
+ it "rejects folders with nil paths" do
121
+ subject.folder nil, nil
122
+ subject.validate(@env, @errors)
123
+ @errors.errors.count.should eq 2
124
+ end
125
+
126
+ it "rejects unknown options" do
127
+ subject.vagrant_root "/guest/path", { :foo => :bar }
128
+ subject.validate(@env, @errors)
129
+
130
+ @errors.errors.empty?.should be_false
131
+ end
132
+
133
+ it "validates with valid options" do
134
+ subject.vagrant_root "/var/guest", { :symlinks => [ 'logs'], :exclude => ['exclude'], :delete => true, :beep => true }
135
+ subject.validate(@env, @errors)
136
+
137
+ @errors.errors.empty?.should be_true
138
+ end
139
+
140
+ end
141
+
142
+ context "when merging configuration" do
143
+
144
+ it "combines the folders from each configuration source" do
145
+ subject.folder "low", "/low-level"
146
+ next_config = Vagrant::Mirror::Config.new
147
+ next_config.folder "top", "/top-level"
148
+ merged = subject.merge(next_config)
149
+
150
+ merged.should be_a Vagrant::Mirror::Config
151
+ merged.folders.should have(2).items
152
+
153
+ expected_result = [
154
+ subject.folders[0],
155
+ next_config.folders[0]
156
+ ]
157
+
158
+ merged.folders.should eq expected_result
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,96 @@
1
+ describe Vagrant::Mirror::Listener::Host do
2
+
3
+ let (:queue) { double("Queue").as_null_object }
4
+
5
+ subject { Vagrant::Mirror::Listener::Host.new('c:/host', queue) }
6
+
7
+ before (:each) do
8
+ Listen.stub(:to).as_null_object
9
+ end
10
+
11
+ describe "#listen!" do
12
+ it "listens to the host directory" do
13
+ Listen.should_receive(:to).with('c:/host', anything())
14
+
15
+ subject.listen!
16
+ end
17
+
18
+ it "requests relative paths" do
19
+ Listen.should_receive(:to)
20
+ .with(anything(), hash_including( :relative_paths => true ))
21
+
22
+ subject.listen!
23
+ end
24
+
25
+ context "when changes received" do
26
+ before(:each) do
27
+ Listen.stub(:to)
28
+ .and_yield(['modified'],[],[])
29
+ .and_yield([],['added'],[])
30
+ .and_yield([],[],['removed'])
31
+ .and_yield(['modified1'],['added1'],['removed1'])
32
+ end
33
+
34
+ it "pushes added files onto the queue" do
35
+ queue.should_receive(:"<<")
36
+ .with(hash_including(:added => ['added']))
37
+
38
+ subject.listen!
39
+ end
40
+
41
+ it "pushes modified files onto the queue" do
42
+ queue.should_receive(:"<<")
43
+ .with(hash_including(:modified => ['modified']))
44
+
45
+ subject.listen!
46
+ end
47
+
48
+ it "pushes removed files onto the queue" do
49
+ queue.should_receive(:"<<")
50
+ .with(hash_including(:removed => ['removed']))
51
+
52
+ subject.listen!
53
+ end
54
+
55
+ it "handles simultaneous changes" do
56
+ queue.should_receive(:"<<")
57
+ .with(hash_including({
58
+ :added => ['added1'],
59
+ :modified => ['modified1'],
60
+ :removed => ['removed1']
61
+ }))
62
+
63
+ subject.listen!
64
+ end
65
+
66
+ end
67
+
68
+ context "with a close signal" do
69
+ it "stops listening"
70
+ end
71
+ end
72
+
73
+ describe "#listen" do
74
+ let (:thread) { double("Thread") }
75
+
76
+ before (:each) do
77
+ Thread.stub(:new).and_yield.and_return(thread)
78
+ end
79
+
80
+ it "starts a new thread" do
81
+ Thread.should_receive(:new)
82
+
83
+ subject.listen
84
+ end
85
+
86
+ it "runs listen! in the new thread" do
87
+ Listen.should_receive(:to)
88
+
89
+ subject.listen
90
+ end
91
+
92
+ it "returns the thread" do
93
+ subject.listen.should eq thread
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,188 @@
1
+ describe Vagrant::Mirror::Middleware::Mirror do
2
+ # Shared mocks
3
+ let (:env) { Vagrant::Action::Environment.new }
4
+ let (:vm) { double("Vagrant::VM").as_null_object }
5
+ let (:ui) { double("Vagrant::UI::Interface").as_null_object }
6
+ let (:app) { double("Object").as_null_object }
7
+ let (:channel) { double("Vagrant::Communication::SSH").as_null_object }
8
+ let (:config) { double("Object").as_null_object }
9
+ let (:configmirror) { Vagrant::Mirror::Config.new }
10
+ let (:configvm) { Vagrant::Config::VMConfig.new }
11
+ let (:queue) { double("Queue").as_null_object }
12
+ let (:guard) { double("Vagrant::Mirror::Listener::Host").as_null_object }
13
+ let (:rsync) { double("Vagrant::Mirror::Rsync").as_null_object }
14
+
15
+ # Set basic stubs for shared mocks
16
+ before (:each) do
17
+ env[:vm] = vm
18
+ env[:ui] = ui
19
+ env[:root_path] = Dir.pwd
20
+
21
+ vm.stub(:config).and_return config
22
+ config.stub(:mirror).and_return configmirror
23
+ config.stub(:vm).and_return configvm
24
+
25
+ vm.stub(:channel).and_return channel
26
+
27
+ Vagrant::Mirror::Rsync.stub(:new).and_return rsync
28
+
29
+ app.stub(:call)
30
+
31
+ Vagrant::Mirror::Listener::Host.stub(:new).and_return(guard)
32
+ Queue.stub(:new).and_return(queue)
33
+ queue.stub(:pop).and_return({ :quit => true})
34
+ end
35
+
36
+ subject { Vagrant::Mirror::Middleware::Mirror.new(app, env) }
37
+
38
+ describe "#call" do
39
+ shared_examples "chained mirror middleware" do
40
+ it "calls the next middleware" do
41
+ app.should_receive(:call).with(env)
42
+
43
+ subject.call(env)
44
+ end
45
+ end
46
+
47
+ context "with no mirrored folders" do
48
+ before (:each) do
49
+ configmirror.stub(:folders).and_return([])
50
+ end
51
+
52
+ it_behaves_like "chained mirror middleware"
53
+ end
54
+
55
+ context "with a mirrored folder" do
56
+
57
+ before (:each) do
58
+ config.mirror.vagrant_root '/var/guest'
59
+ config.vm.share_folder("v-root", "/vagrant", ".")
60
+ end
61
+
62
+ it "logs the start of mirroring" do
63
+ ui.should_receive(:info).with("Beginning directory mirroring")
64
+
65
+ subject.call(env)
66
+ end
67
+
68
+ it "creates a Guard listener on the host path" do
69
+ Vagrant::Mirror::Listener::Host.should_receive(:new)
70
+ .with(env[:root_path], queue)
71
+
72
+ subject.call(env)
73
+ end
74
+
75
+ context "creates an rsync class for the folder pair" do
76
+ it "creates an rsync class for the folder pair" do
77
+ Vagrant::Mirror::Rsync.should_receive(:new)
78
+ .and_return(rsync)
79
+
80
+ subject.call(env)
81
+ end
82
+
83
+ it "passes the vm to rsync" do
84
+ Vagrant::Mirror::Rsync.should_receive(:new)
85
+ .with(vm, anything, anything, anything)
86
+ .and_return(rsync)
87
+
88
+ subject.call(env)
89
+ end
90
+
91
+ it "passes the guest shared folder path for the folder pair to rsync" do
92
+ Vagrant::Mirror::Rsync.should_receive(:new)
93
+ .with(anything, "/vagrant", anything, anything)
94
+ .and_return(rsync)
95
+
96
+ subject.call(env)
97
+ end
98
+
99
+ it "passes the host shared folder path for the folder pair to rsync" do
100
+ Vagrant::Mirror::Rsync.should_receive(:new)
101
+ .with(anything, anything, env[:root_path], anything)
102
+ .and_return(rsync)
103
+
104
+ subject.call(env)
105
+ end
106
+
107
+ it "passes the folder configuration to rsync" do
108
+ Vagrant::Mirror::Rsync.should_receive(:new)
109
+ .with(anything, anything, anything, config.mirror.folders[0])
110
+ .and_return(rsync)
111
+
112
+ subject.call(env)
113
+ end
114
+ end
115
+
116
+ it "listens for changes in the host path" do
117
+ guard.should_receive(:listen)
118
+
119
+ subject.call(env)
120
+ end
121
+
122
+ context "with notifications in the queue" do
123
+
124
+ let (:added) { ['added'] }
125
+ let (:modified) { ['modified','modified2'] }
126
+ let (:removed) { ['removed'] }
127
+ let (:rm_exists) { true }
128
+
129
+ before (:each) do
130
+ queue.stub(:pop).and_return(
131
+ { :added => added, :modified => modified, :removed => removed },
132
+ { :added => added, :modified => modified, :removed => removed },
133
+ { :quit => true })
134
+ File.stub(:exists?).with("#{env[:root_path]}/removed").and_return(rm_exists)
135
+ end
136
+
137
+ it "runs rsync one by one for each added and modified file" do
138
+ rsync.should_receive(:run).with("added")
139
+ rsync.should_receive(:run).with("modified")
140
+ rsync.should_receive(:run).with("modified2")
141
+
142
+ subject.call(env)
143
+ sleep 0.2
144
+ end
145
+
146
+ # Sometimes Guard flags a file as deleted that still exists - during certain types of
147
+ # atomic file writes, we think. So we should delete the file remotely if so, or sync if not.
148
+ context "if deleted files have been deleted" do
149
+ let (:rm_exists) { false }
150
+
151
+ it "deletes them by SSH" do
152
+ channel.should_receive(:sudo).with('rm /var/guest/removed')
153
+
154
+ subject.call(env)
155
+ sleep 0.2
156
+ end
157
+ end
158
+
159
+ context "if deleted files have not been deleted" do
160
+ let (:rm_exists) { true }
161
+
162
+ it "runs rsync on the file" do
163
+ rsync.should_receive(:run).with('removed')
164
+
165
+ subject.call(env)
166
+ sleep 0.2
167
+ end
168
+ end
169
+
170
+ end
171
+
172
+ shared_examples "finishing mirroring" do
173
+ it "signals the Guard listener to quit"
174
+ it "waits for the Guard listener to quit"
175
+ it "processes remaining jobs in the queue"
176
+ it "calls the next middleware"
177
+ end
178
+
179
+ context "when signaled to quit" do
180
+ it_behaves_like "finishing mirroring"
181
+ end
182
+
183
+ context "when user presses q on the console" do
184
+ it_behaves_like "finishing mirroring"
185
+ end
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,152 @@
1
+ describe Vagrant::Mirror::Middleware::Sync do
2
+ # Shared mocks
3
+ let (:env) { Vagrant::Action::Environment.new }
4
+ let (:vm) { double("Vagrant::VM").as_null_object }
5
+ let (:channel) { double("Vagrant::Communication::SSH").as_null_object }
6
+ let (:ui) { double("Vagrant::UI::Interface").as_null_object }
7
+ let (:app) { double("Object").as_null_object }
8
+ let (:config) { double("Object").as_null_object }
9
+ let (:configmirror) { Vagrant::Mirror::Config.new }
10
+ let (:configvm) { Vagrant::Config::VMConfig.new }
11
+ let (:rsync) { double("Vagrant::Mirror::Rsync").as_null_object }
12
+
13
+ # Set basic stubs for shared mocks
14
+ before (:each) do
15
+ env[:vm] = vm
16
+ env[:ui] = ui
17
+ env[:root_path] = Dir.pwd
18
+
19
+ vm.stub(:config).and_return config
20
+ config.stub(:mirror).and_return configmirror
21
+ config.stub(:vm).and_return configvm
22
+
23
+ vm.stub(:channel).and_return channel
24
+
25
+ Vagrant::Mirror::Rsync.stub(:new).and_return rsync
26
+
27
+ app.stub(:call)
28
+
29
+ end
30
+
31
+ subject { Vagrant::Mirror::Middleware::Sync.new(app, env) }
32
+
33
+ describe "#call" do
34
+
35
+ shared_examples "chained middleware" do
36
+ it "calls the next middleware" do
37
+ app.should_receive(:call).with(env)
38
+
39
+ subject.call(env)
40
+ end
41
+ end
42
+
43
+ context "with no mirrored folders" do
44
+ it_behaves_like "chained middleware"
45
+ end
46
+
47
+ context "with a mirrored folder" do
48
+ before (:each) do
49
+ config.mirror.vagrant_root '/var/guest'
50
+ config.vm.share_folder("v-root", "/vagrant", ".")
51
+ end
52
+
53
+ it_behaves_like "chained middleware"
54
+
55
+ it "creates an rsync class for the folder pair" do
56
+ Vagrant::Mirror::Rsync.should_receive(:new)
57
+ .and_return(rsync)
58
+
59
+ subject.call(env)
60
+ end
61
+
62
+ it "passes the vm to rsync" do
63
+ Vagrant::Mirror::Rsync.should_receive(:new)
64
+ .with(vm, anything, anything, anything)
65
+ .and_return(rsync)
66
+
67
+ subject.call(env)
68
+ end
69
+
70
+ it "passes the guest shared folder path for the folder pair to rsync" do
71
+ Vagrant::Mirror::Rsync.should_receive(:new)
72
+ .with(anything, "/vagrant", anything, anything)
73
+ .and_return(rsync)
74
+
75
+ subject.call(env)
76
+ end
77
+
78
+ it "passes the host shared folder path for the folder pair to rsync" do
79
+ Vagrant::Mirror::Rsync.should_receive(:new)
80
+ .with(anything, anything, env[:root_path], anything)
81
+ .and_return(rsync)
82
+
83
+ subject.call(env)
84
+ end
85
+
86
+ it "passes the folder configuration to rsync" do
87
+ Vagrant::Mirror::Rsync.should_receive(:new)
88
+ .with(anything, anything, anything, config.mirror.folders[0])
89
+ .and_return(rsync)
90
+
91
+ subject.call(env)
92
+ end
93
+
94
+ it "runs rsync for the root of the mirrored folder" do
95
+ rsync.should_receive(:run)
96
+ .with("/")
97
+
98
+ subject.call(env)
99
+ end
100
+ end
101
+
102
+ context "with two mirrored folders" do
103
+ before (:each) do
104
+ config.mirror.folder "foo", "/var/foo"
105
+ config.mirror.folder "bar", "/var/bar"
106
+ end
107
+
108
+ it "throws an exception as this is not yet supported" do
109
+ expect { subject.call(env) }.to raise_error(Vagrant::Mirror::Errors::MultipleFoldersNotSupported)
110
+ end
111
+ end
112
+
113
+ context "when the mirror config includes symlinks" do
114
+ let (:host_path) { File.join(Dir.pwd, 'logs') }
115
+
116
+ before (:each) do
117
+ config.mirror.vagrant_root "/var/vagrant", { :symlinks => ['logs'] }
118
+ config.vm.share_folder("v-root", "/vagrant", ".")
119
+
120
+ File.stub(:exists?).with(host_path).and_return true
121
+ end
122
+
123
+ context "if the host folder does not exist" do
124
+ let (:host_path) { File.join(Dir.pwd, 'logs') }
125
+
126
+ before (:each) do
127
+ File.stub(:exists?).with(host_path).and_return false
128
+ end
129
+
130
+ it "creates the folder on the host" do
131
+ FileUtils.stub(:mkdir_p).with(host_path)
132
+ end
133
+ end
134
+
135
+ it "creates a symlink on the guest" do
136
+ channel.should_receive(:sudo).with('rm -f /var/vagrant/logs && mkdir -p /var/vagrant && ln -s /vagrant/logs /var/vagrant/logs')
137
+
138
+ subject.call(env)
139
+ end
140
+ end
141
+
142
+ context "when the mirrored config includes invalid shared folders" do
143
+ before (:each) do
144
+ config.mirror.folder "unknown", "/var/whoops"
145
+ end
146
+
147
+ it "throws an exception" do
148
+ expect { subject.call(env) }.to raise_error(Vagrant::Mirror::Errors::SharedFolderNotMapped)
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,75 @@
1
+ describe Vagrant::Mirror::Rsync do
2
+
3
+ # Shared mocks
4
+ let (:vm) { double("Vagrant::VM").as_null_object }
5
+ let (:channel) { double("Vagrant::Communication::SSH").as_null_object }
6
+ let (:guest_sf_path) { '/vagrant' }
7
+ let (:host_path) { 'c:/vagrant' }
8
+ let (:cfg_delete) { false }
9
+ let (:cfg_exclude) { [] }
10
+ let (:cfg_guest) { '/var/vagrant' }
11
+ let (:mirror_config) { { :name => 'v-root', :guest_path => '/var/vagrant', :delete => false,
12
+ :exclude => [], :beep => false, :symlinks => [] } }
13
+
14
+ # Set basic stubs for shared mocks
15
+ before (:each) do
16
+ vm.stub(:channel).and_return channel
17
+
18
+ File.stub(:directory?).and_return(false)
19
+ File.stub(:directory?).with('c:/vagrant/dir').and_return(true)
20
+ end
21
+
22
+ subject { Vagrant::Mirror::Rsync.new(vm, guest_sf_path, host_path, mirror_config) }
23
+
24
+ describe "#run" do
25
+
26
+ shared_examples "running rsync to update the path" do | path, expect_source, expect_dest |
27
+ context "with no exclude paths" do
28
+ it "runs the expected rsync command" do
29
+ channel.should_receive(:sudo).with("rsync -av #{expect_source} #{expect_dest}")
30
+
31
+ subject.run(path)
32
+ end
33
+ end
34
+
35
+ context "with exclude paths" do
36
+ let (:mirror_config) { { :name => 'v-root', :guest_path => '/var/vagrant', :delete => false,
37
+ :exclude => ['.git', 'cache'], :beep => false, :symlinks => [] } }
38
+
39
+ it "runs the expected rsync command" do
40
+ channel.should_receive(:sudo).with("rsync -av --exclude '.git' --exclude 'cache' #{expect_source} #{expect_dest}")
41
+
42
+ subject.run(path)
43
+ end
44
+ end
45
+
46
+ context "with delete enabled" do
47
+ let (:mirror_config) { { :name => 'v-root', :guest_path => '/var/vagrant', :delete => true,
48
+ :exclude => [], :beep => false, :symlinks => [] } }
49
+
50
+ it "runs the expected rsync command" do
51
+ channel.should_receive(:sudo).with("rsync -av --del #{expect_source} #{expect_dest}")
52
+
53
+ subject.run(path)
54
+ end
55
+ end
56
+ end
57
+
58
+ context "when passed an empty path" do
59
+ it_behaves_like "running rsync to update the path", '', '/vagrant/', '/var/vagrant/'
60
+ end
61
+
62
+ context "when passed the root path" do
63
+ it_behaves_like "running rsync to update the path", '/', '/vagrant/', '/var/vagrant/'
64
+ end
65
+
66
+ context "when passed a file path" do
67
+ it_behaves_like "running rsync to update the path", 'file', '/vagrant/file', '/var/vagrant/file'
68
+ end
69
+
70
+ context "when passed a directory path" do
71
+ it_behaves_like "running rsync to update the path", 'dir', '/vagrant/dir/', '/var/vagrant/dir/'
72
+ end
73
+
74
+ end
75
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe Vagrant::Mirror do
4
+ it { should be_a(Module) }
5
+ end