copycopter_client 1.0.2 → 1.0.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.
@@ -194,15 +194,19 @@ end
194
194
 
195
195
  share_examples_for "applied configuration" do
196
196
  let(:backend) { stub('i18n-backend') }
197
- let(:sync) { stub('sync', :start => nil) }
197
+ let(:cache) { stub('cache') }
198
198
  let(:client) { stub('client') }
199
+ let(:poller) { stub('poller') }
200
+ let(:process_guard) { stub('process_guard', :start => nil) }
199
201
  let(:logger) { FakeLogger.new }
200
202
  subject { CopycopterClient::Configuration.new }
201
203
 
202
204
  before do
203
205
  CopycopterClient::I18nBackend.stubs(:new => backend)
204
206
  CopycopterClient::Client.stubs(:new => client)
205
- CopycopterClient::Sync.stubs(:new => sync)
207
+ CopycopterClient::Cache.stubs(:new => cache)
208
+ CopycopterClient::Poller.stubs(:new => poller)
209
+ CopycopterClient::ProcessGuard.stubs(:new => process_guard)
206
210
  subject.logger = logger
207
211
  apply
208
212
  end
@@ -211,11 +215,20 @@ share_examples_for "applied configuration" do
211
215
 
212
216
  it "builds and assigns an I18n backend" do
213
217
  CopycopterClient::Client.should have_received(:new).with(subject.to_hash)
214
- CopycopterClient::Sync.should have_received(:new).with(client, subject.to_hash)
215
- CopycopterClient::I18nBackend.should have_received(:new).with(sync)
218
+ CopycopterClient::Cache.should have_received(:new).with(client, subject.to_hash)
219
+ CopycopterClient::I18nBackend.should have_received(:new).with(cache)
216
220
  I18n.backend.should == backend
217
221
  end
218
222
 
223
+ it "builds and assigns a poller" do
224
+ CopycopterClient::Poller.should have_received(:new).with(cache, subject.to_hash)
225
+ end
226
+
227
+ it "builds a process guard" do
228
+ CopycopterClient::ProcessGuard.should have_received(:new).
229
+ with(cache, poller, subject.to_hash)
230
+ end
231
+
219
232
  it "logs that it's ready" do
220
233
  logger.should have_entry(:info, "Client #{CopycopterClient::VERSION} ready")
221
234
  end
@@ -228,15 +241,15 @@ share_examples_for "applied configuration" do
228
241
  CopycopterClient.client.should == client
229
242
  end
230
243
 
231
- it "stores the sync" do
232
- CopycopterClient.sync.should == sync
244
+ it "stores the cache" do
245
+ CopycopterClient.cache.should == cache
233
246
  end
234
247
  end
235
248
 
236
249
  describe CopycopterClient::Configuration, "applied when testing" do
237
250
  it_should_behave_like "applied configuration" do
238
- it "doesn't start sync" do
239
- sync.should have_received(:start).never
251
+ it "doesn't start the process guard" do
252
+ process_guard.should have_received(:start).never
240
253
  end
241
254
  end
242
255
 
@@ -248,8 +261,8 @@ end
248
261
 
249
262
  describe CopycopterClient::Configuration, "applied when not testing" do
250
263
  it_should_behave_like "applied configuration" do
251
- it "starts sync" do
252
- sync.should have_received(:start)
264
+ it "starts the process guard" do
265
+ process_guard.should have_received(:start)
253
266
  end
254
267
  end
255
268
 
@@ -1,29 +1,29 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe CopycopterClient::I18nBackend do
4
- let(:sync) { {} }
4
+ let(:cache) { {} }
5
5
 
6
6
  def build_backend
7
- backend = CopycopterClient::I18nBackend.new(sync)
7
+ backend = CopycopterClient::I18nBackend.new(cache)
8
8
  I18n.backend = backend
9
9
  backend
10
10
  end
11
11
 
12
12
  before do
13
13
  @default_backend = I18n.backend
14
- sync.stubs(:wait_for_download)
14
+ cache.stubs(:wait_for_download)
15
15
  end
16
16
 
17
17
  after { I18n.backend = @default_backend }
18
18
 
19
19
  subject { build_backend }
20
20
 
21
- it "reloads locale files and waits for the sync to complete" do
21
+ it "reloads locale files and waits for the download to complete" do
22
22
  I18n.stubs(:load_path => [])
23
23
  subject.reload!
24
24
  subject.translate('en', 'test.key', :default => 'something')
25
25
 
26
- sync.should have_received(:wait_for_download)
26
+ cache.should have_received(:wait_for_download)
27
27
  I18n.should have_received(:load_path)
28
28
  end
29
29
 
@@ -31,21 +31,21 @@ describe CopycopterClient::I18nBackend do
31
31
  should be_kind_of(I18n::Backend::Base)
32
32
  end
33
33
 
34
- it "looks up a key in sync" do
34
+ it "looks up a key in cache" do
35
35
  value = 'hello'
36
- sync['en.prefix.test.key'] = value
36
+ cache['en.prefix.test.key'] = value
37
37
 
38
38
  backend = build_backend
39
39
 
40
40
  backend.translate('en', 'test.key', :scope => 'prefix').should == value
41
41
  end
42
42
 
43
- it "finds available locales from locale files and sync" do
43
+ it "finds available locales from locale files and cache" do
44
44
  YAML.stubs(:load_file => { 'es' => { 'key' => 'value' } })
45
45
  I18n.stubs(:load_path => ["test.yml"])
46
46
 
47
- sync['en.key'] = ''
48
- sync['fr.key'] = ''
47
+ cache['en.key'] = ''
48
+ cache['fr.key'] = ''
49
49
 
50
50
  subject.available_locales.should =~ [:en, :es, :fr]
51
51
  end
@@ -55,14 +55,14 @@ describe CopycopterClient::I18nBackend do
55
55
 
56
56
  subject.translate('en', 'test.key', :default => default).should == default
57
57
 
58
- sync['en.test.key'].should == default
58
+ cache['en.test.key'].should == default
59
59
  end
60
60
 
61
61
  it "queues missing keys without default" do
62
62
  expect { subject.translate('en', 'test.key') }.
63
63
  to raise_error(I18n::MissingTranslationData)
64
64
 
65
- sync['en.test.key'].should == ""
65
+ cache['en.test.key'].should == ""
66
66
  end
67
67
 
68
68
  it "queues missing keys with scope" do
@@ -71,17 +71,17 @@ describe CopycopterClient::I18nBackend do
71
71
  subject.translate('en', 'key', :default => default, :scope => ['test']).
72
72
  should == default
73
73
 
74
- sync['en.test.key'].should == default
74
+ cache['en.test.key'].should == default
75
75
  end
76
76
 
77
77
  it "marks strings as html safe" do
78
- sync['en.test.key'] = FakeHtmlSafeString.new("Hello")
78
+ cache['en.test.key'] = FakeHtmlSafeString.new("Hello")
79
79
  backend = build_backend
80
80
  backend.translate('en', 'test.key').should be_html_safe
81
81
  end
82
82
 
83
83
  it "looks up an array of defaults" do
84
- sync['en.key.one'] = "Expected"
84
+ cache['en.key.one'] = "Expected"
85
85
  backend = build_backend
86
86
  backend.translate('en', 'key.three', :default => [:"key.two", :"key.one"]).
87
87
  should == 'Expected'
@@ -94,14 +94,14 @@ describe CopycopterClient::I18nBackend do
94
94
  subject.store_translations('en', 'test' => { 'key' => 'Expected' })
95
95
  subject.translate('en', 'test.key', :default => 'Unexpected').
96
96
  should include('Expected')
97
- sync['en.test.key'].should == 'Expected'
97
+ cache['en.test.key'].should == 'Expected'
98
98
  end
99
99
 
100
100
  it "preserves interpolation markers in the stored translation" do
101
101
  subject.store_translations('en', 'test' => { 'key' => '%{interpolate}' })
102
102
  subject.translate('en', 'test.key', :interpolate => 'interpolated').
103
103
  should include('interpolated')
104
- sync['en.test.key'].should == '%{interpolate}'
104
+ cache['en.test.key'].should == '%{interpolate}'
105
105
  end
106
106
 
107
107
  it "uses the default if the stored translations don't have the key" do
@@ -109,9 +109,9 @@ describe CopycopterClient::I18nBackend do
109
109
  should include('Expected')
110
110
  end
111
111
 
112
- it "uses the syncd key when present" do
112
+ it "uses the cached key when present" do
113
113
  subject.store_translations('en', 'test' => { 'key' => 'Unexpected' })
114
- sync['en.test.key'] = 'Expected'
114
+ cache['en.test.key'] = 'Expected'
115
115
  subject.translate('en', 'test.key', :default => 'default').
116
116
  should include('Expected')
117
117
  end
@@ -120,14 +120,14 @@ describe CopycopterClient::I18nBackend do
120
120
  nested = { :nested => 'value' }
121
121
  subject.store_translations('en', 'key' => nested)
122
122
  subject.translate('en', 'key', :default => 'Unexpected').should == nested
123
- sync['en.key.nested'].should == 'value'
123
+ cache['en.key.nested'].should == 'value'
124
124
  end
125
125
 
126
126
  it "returns an array directly without storing" do
127
127
  array = ['value']
128
128
  subject.store_translations('en', 'key' => array)
129
129
  subject.translate('en', 'key', :default => 'Unexpected').should == array
130
- sync['en.key'].should be_nil
130
+ cache['en.key'].should be_nil
131
131
  end
132
132
 
133
133
  it "looks up an array of defaults" do
@@ -151,7 +151,7 @@ describe CopycopterClient::I18nBackend do
151
151
 
152
152
  subject.translate('en', 'test.key', :default => default).should == default
153
153
 
154
- sync['en.test.key'].should == default
154
+ cache['en.test.key'].should == default
155
155
  end
156
156
  end
157
157
  end
@@ -0,0 +1,110 @@
1
+ require 'spec_helper'
2
+
3
+ describe CopycopterClient::Poller do
4
+ POLLING_DELAY = 0.5
5
+
6
+ let(:client) { FakeClient.new }
7
+ let(:cache) { CopycopterClient::Cache.new(client, :logger => FakeLogger.new) }
8
+
9
+ def build_poller(config = {})
10
+ config[:logger] ||= FakeLogger.new
11
+ config[:polling_delay] = POLLING_DELAY
12
+ default_config = CopycopterClient::Configuration.new.to_hash
13
+ poller = CopycopterClient::Poller.new(cache, default_config.update(config))
14
+ @pollers << poller
15
+ poller
16
+ end
17
+
18
+ def wait_for_next_sync
19
+ sleep(POLLING_DELAY * 3)
20
+ end
21
+
22
+ before do
23
+ @pollers = []
24
+ end
25
+
26
+ after do
27
+ @pollers.each { |poller| poller.stop }
28
+ end
29
+
30
+ it "it polls after being started" do
31
+ poller = build_poller
32
+ poller.start
33
+
34
+ client['test.key'] = 'value'
35
+ wait_for_next_sync
36
+
37
+ cache['test.key'].should == 'value'
38
+ end
39
+
40
+ it "it doesn't poll before being started" do
41
+ poller = build_poller
42
+ client['test.key'] = 'value'
43
+
44
+ wait_for_next_sync
45
+
46
+ cache['test.key'].should be_nil
47
+ end
48
+
49
+ it "stops polling when stopped" do
50
+ poller = build_poller
51
+
52
+ poller.start
53
+ poller.stop
54
+
55
+ client['test.key'] = 'value'
56
+ wait_for_next_sync
57
+
58
+ cache['test.key'].should be_nil
59
+ end
60
+
61
+ it "stops polling with an invalid api key" do
62
+ failure = "server is napping"
63
+ logger = FakeLogger.new
64
+ cache.stubs(:download).raises(CopycopterClient::InvalidApiKey.new(failure))
65
+ poller = build_poller(:logger => logger)
66
+
67
+ cache['upload.key'] = 'upload'
68
+ poller.start
69
+ wait_for_next_sync
70
+
71
+ logger.should have_entry(:error, failure)
72
+
73
+ client['test.key'] = 'test value'
74
+ wait_for_next_sync
75
+
76
+ cache['test.key'].should be_nil
77
+ end
78
+
79
+ it "logs an error if the background thread can't start" do
80
+ Thread.stubs(:new => nil)
81
+ logger = FakeLogger.new
82
+
83
+ build_poller(:logger => logger).start
84
+
85
+ logger.should have_entry(:error, "Couldn't start poller thread")
86
+ end
87
+
88
+ it "flushes the log when polling" do
89
+ logger = FakeLogger.new
90
+ logger.stubs(:flush)
91
+
92
+ build_poller(:logger => logger).start
93
+
94
+ wait_for_next_sync
95
+
96
+ logger.should have_received(:flush).at_least_once
97
+ end
98
+
99
+ it "starts from the top-level constant" do
100
+ poller = build_poller
101
+ CopycopterClient.poller = poller
102
+ poller.stubs(:start)
103
+
104
+ CopycopterClient.start_poller
105
+
106
+ poller.should have_received(:start)
107
+ end
108
+ end
109
+
110
+
@@ -0,0 +1,118 @@
1
+ require 'spec_helper'
2
+
3
+ describe CopycopterClient::ProcessGuard do
4
+ include DefinesConstants
5
+
6
+ before do
7
+ @original_process_name = $0
8
+ end
9
+
10
+ after do
11
+ $0 = @original_process_name
12
+ end
13
+
14
+ let(:cache) { stub('cache', :flush => nil) }
15
+ let(:poller) { stub('poller', :start => nil) }
16
+
17
+ def build_process_guard(options = {})
18
+ options[:logger] ||= FakeLogger.new
19
+ options[:cache] ||= cache
20
+ CopycopterClient::ProcessGuard.new(options[:cache], poller, options)
21
+ end
22
+
23
+ it "starts polling from a worker process" do
24
+ process_guard = build_process_guard
25
+
26
+ process_guard.start
27
+
28
+ poller.should have_received(:start)
29
+ end
30
+
31
+ it "registers passenger hooks from the passenger master" do
32
+ logger = FakeLogger.new
33
+ passenger = define_constant('PhusionPassenger', FakePassenger.new)
34
+ passenger.become_master
35
+
36
+ process_guard = build_process_guard(:logger => logger)
37
+ process_guard.start
38
+
39
+ logger.should have_entry(:info, "Registered Phusion Passenger fork hook")
40
+ poller.should have_received(:start).never
41
+ end
42
+
43
+ it "starts polling from a passenger worker" do
44
+ logger = FakeLogger.new
45
+ passenger = define_constant('PhusionPassenger', FakePassenger.new)
46
+ passenger.become_master
47
+ process_guard = build_process_guard(:logger => logger)
48
+
49
+ process_guard.start
50
+ passenger.spawn
51
+
52
+ poller.should have_received(:start)
53
+ end
54
+
55
+ it "registers unicorn hooks from the unicorn master" do
56
+ logger = FakeLogger.new
57
+ define_constant('Unicorn', Module.new)
58
+ http_server = Class.new(FakeUnicornServer)
59
+ unicorn = define_constant('Unicorn::HttpServer', http_server).new
60
+ unicorn.become_master
61
+
62
+ process_guard = build_process_guard(:logger => logger)
63
+ process_guard.start
64
+
65
+ logger.should have_entry(:info, "Registered Unicorn fork hook")
66
+ poller.should have_received(:start).never
67
+ end
68
+
69
+ it "starts polling from a unicorn worker" do
70
+ logger = FakeLogger.new
71
+ define_constant('Unicorn', Module.new)
72
+ http_server = Class.new(FakeUnicornServer)
73
+ unicorn = define_constant('Unicorn::HttpServer', http_server).new
74
+ unicorn.become_master
75
+ process_guard = build_process_guard(:logger => logger)
76
+
77
+ process_guard.start
78
+ unicorn.spawn
79
+
80
+ poller.should have_received(:start)
81
+ end
82
+
83
+ it "flushes when the process terminates" do
84
+ cache = WritingCache.new
85
+ pid = fork do
86
+ process_guard = build_process_guard(:cache => cache)
87
+ process_guard.start
88
+ exit
89
+ end
90
+ Process.wait
91
+
92
+ cache.should be_written
93
+ end
94
+
95
+ it "flushes after running a resque job" do
96
+ logger = FakeLogger.new
97
+ cache = WritingCache.new
98
+ define_constant('Resque', Module.new)
99
+ job_class = define_constant('Resque::Job', FakeResqueJob)
100
+ job = job_class.new
101
+ process_guard = build_process_guard(:cache => cache, :logger => logger)
102
+
103
+ process_guard.start
104
+ job.fork_and_perform
105
+
106
+ cache.should be_written
107
+ logger.should have_entry(:info, "Registered Resque after_perform hook")
108
+ end
109
+
110
+ it "doesn't fail if only Resque is defined and not Resque::Job" do
111
+ logger = FakeLogger.new
112
+ cache = WritingCache.new
113
+ define_constant('Resque', Module.new)
114
+ process_guard = build_process_guard(:cache => cache, :logger => logger)
115
+
116
+ process_guard.start
117
+ end
118
+ end