listen 2.7.5 → 2.7.6

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -0
  3. data/.rspec +0 -0
  4. data/.rubocop.yml +0 -0
  5. data/.travis.yml +0 -1
  6. data/.yardopts +0 -0
  7. data/CHANGELOG.md +0 -0
  8. data/CONTRIBUTING.md +0 -0
  9. data/Gemfile +25 -4
  10. data/Guardfile +0 -0
  11. data/LICENSE.txt +0 -0
  12. data/README.md +18 -10
  13. data/Rakefile +0 -0
  14. data/lib/listen.rb +2 -4
  15. data/lib/listen/adapter.rb +13 -4
  16. data/lib/listen/adapter/base.rb +33 -16
  17. data/lib/listen/adapter/bsd.rb +21 -38
  18. data/lib/listen/adapter/darwin.rb +17 -25
  19. data/lib/listen/adapter/linux.rb +34 -52
  20. data/lib/listen/adapter/polling.rb +9 -25
  21. data/lib/listen/adapter/tcp.rb +27 -14
  22. data/lib/listen/adapter/windows.rb +67 -23
  23. data/lib/listen/change.rb +26 -23
  24. data/lib/listen/cli.rb +0 -0
  25. data/lib/listen/directory.rb +47 -58
  26. data/lib/listen/file.rb +66 -101
  27. data/lib/listen/listener.rb +214 -155
  28. data/lib/listen/queue_optimizer.rb +104 -0
  29. data/lib/listen/record.rb +15 -5
  30. data/lib/listen/silencer.rb +14 -10
  31. data/lib/listen/tcp.rb +0 -1
  32. data/lib/listen/tcp/broadcaster.rb +31 -26
  33. data/lib/listen/tcp/message.rb +2 -2
  34. data/lib/listen/version.rb +1 -1
  35. data/listen.gemspec +1 -1
  36. data/spec/acceptance/listen_spec.rb +151 -239
  37. data/spec/acceptance/tcp_spec.rb +125 -134
  38. data/spec/lib/listen/adapter/base_spec.rb +13 -30
  39. data/spec/lib/listen/adapter/bsd_spec.rb +7 -35
  40. data/spec/lib/listen/adapter/darwin_spec.rb +18 -30
  41. data/spec/lib/listen/adapter/linux_spec.rb +49 -55
  42. data/spec/lib/listen/adapter/polling_spec.rb +20 -35
  43. data/spec/lib/listen/adapter/tcp_spec.rb +25 -27
  44. data/spec/lib/listen/adapter/windows_spec.rb +7 -33
  45. data/spec/lib/listen/adapter_spec.rb +10 -10
  46. data/spec/lib/listen/change_spec.rb +55 -57
  47. data/spec/lib/listen/directory_spec.rb +105 -155
  48. data/spec/lib/listen/file_spec.rb +186 -73
  49. data/spec/lib/listen/listener_spec.rb +233 -216
  50. data/spec/lib/listen/record_spec.rb +60 -22
  51. data/spec/lib/listen/silencer_spec.rb +48 -75
  52. data/spec/lib/listen/tcp/broadcaster_spec.rb +78 -69
  53. data/spec/lib/listen/tcp/listener_spec.rb +28 -71
  54. data/spec/lib/listen/tcp/message_spec.rb +48 -14
  55. data/spec/lib/listen_spec.rb +3 -3
  56. data/spec/spec_helper.rb +6 -3
  57. data/spec/support/acceptance_helper.rb +250 -31
  58. data/spec/support/fixtures_helper.rb +6 -4
  59. data/spec/support/platform_helper.rb +2 -2
  60. metadata +5 -5
  61. data/lib/listen/tcp/listener.rb +0 -108
@@ -1,29 +1,32 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Listen::Record do
4
- let(:registry) { double(Celluloid::Registry) }
5
- let(:listener) { double(Listen::Listener, registry: registry, options: {}) }
4
+ let(:registry) { instance_double(Celluloid::Registry) }
5
+
6
+ let(:listener) do
7
+ instance_double(Listen::Listener, registry: registry, options: {})
8
+ end
9
+
6
10
  let(:record) { Listen::Record.new(listener) }
7
11
  let(:path) { '/dir/path/file.rb' }
8
- let(:data) { { type: 'File' } }
9
12
 
10
13
  describe '#set_path' do
11
- it 'sets path by spliting direname and basename' do
12
- record.set_path(path, data)
13
- expect(record.paths).to eq('/dir/path' => { 'file.rb' => data })
14
+ it 'sets path by spliting dirname and basename' do
15
+ record.set_path(:file, path)
16
+ expect(record.paths['/dir/path']).to eq('file.rb' => { type: :file })
14
17
  end
15
18
 
16
19
  it 'sets path and keeps old data not overwritten' do
17
- record.set_path(path, data.merge(foo: 1, bar: 2))
18
- record.set_path(path, data.merge(foo: 3))
19
- expected = { '/dir/path' => { 'file.rb' => data.merge(foo: 3, bar: 2) } }
20
- expect(record.paths).to eq(expected)
20
+ record.set_path(:file, path, foo: 1, bar: 2)
21
+ record.set_path(:file, path, foo: 3)
22
+ file_data = record.paths['/dir/path']['file.rb']
23
+ expect(file_data).to eq(foo: 3, bar: 2, type: :file)
21
24
  end
22
25
  end
23
26
 
24
27
  describe '#unset_path' do
25
28
  context 'path is present' do
26
- before { record.set_path(path, data) }
29
+ before { record.set_path(:file, path) }
27
30
 
28
31
  it 'unsets path' do
29
32
  record.unset_path(path)
@@ -41,10 +44,10 @@ describe Listen::Record do
41
44
 
42
45
  describe '#file_data' do
43
46
  context 'path is present' do
44
- before { record.set_path(path, data) }
47
+ before { record.set_path(:file, path) }
45
48
 
46
49
  it 'returns file data' do
47
- expect(record.file_data(path)).to eq data
50
+ expect(record.file_data(path)).to eq(type: :file)
48
51
  end
49
52
  end
50
53
 
@@ -57,10 +60,11 @@ describe Listen::Record do
57
60
 
58
61
  describe '#dir_entries' do
59
62
  context 'path is present' do
60
- before { record.set_path(path, data) }
63
+ before { record.set_path(:file, path) }
61
64
 
62
65
  it 'returns file path' do
63
- expect(record.dir_entries('/dir/path')).to eq('file.rb' => data)
66
+ entries = record.dir_entries('/dir/path')
67
+ expect(entries).to eq('file.rb' => { type: :file })
64
68
  end
65
69
  end
66
70
 
@@ -73,24 +77,58 @@ describe Listen::Record do
73
77
 
74
78
  describe '#build' do
75
79
  let(:directories) { ['dir_path'] }
76
- let(:change_pool) { double(Listen::Change, terminate: true) }
80
+
81
+ let(:actor) do
82
+ instance_double(Listen::Change, change: nil, terminate: true)
83
+ end
84
+
77
85
  before do
78
- change_pool.stub(:change)
79
- registry.stub(:[]).with(:change_pool) { change_pool }
80
- listener.stub(:directories) { directories }
86
+ allow(listener).to receive(:directories) { directories }
87
+ allow(listener).to receive(:sync).with(:change_pool) { actor }
81
88
  end
82
89
 
83
90
  it 're-inits paths' do
84
- record.set_path(path, data)
91
+ record.set_path(:file, path)
85
92
  record.build
86
93
  expect(record.file_data(path)).to be_empty
87
94
  end
88
95
 
89
96
  it 'calls change asynchronously on all directories to build record' do
90
- expect(change_pool).to receive(:change).
91
- with('dir_path', type: 'Dir', recursive: true, silence: true)
97
+ expect(actor).to receive(:change).
98
+ with(:dir, 'dir_path', recursive: true, silence: true, build: true)
99
+ record.build
100
+ end
101
+ end
102
+
103
+ describe '#still_building!' do
104
+ let(:directories) { ['dir_path'] }
92
105
 
106
+ let(:actor) do
107
+ instance_double(Listen::Change, change: nil, terminate: true)
108
+ end
109
+
110
+ before do
111
+ allow(listener).to receive(:directories) { directories }
112
+ allow(listener).to receive(:sync).with(:change_pool) { actor }
113
+ end
114
+
115
+ it 'keeps the build blocking longer' do
116
+ record # To avoid initializing record in thread
117
+
118
+ th = Thread.new do
119
+ 10.times do
120
+ sleep 0.05
121
+ record.still_building!
122
+ end
123
+ end
124
+
125
+ started = Time.now
93
126
  record.build
127
+ ended = Time.now
128
+
129
+ th.join
130
+
131
+ expect(ended - started).to be > 0.5
94
132
  end
95
133
  end
96
134
  end
@@ -4,10 +4,11 @@ describe Listen::Silencer do
4
4
  let(:options) { {} }
5
5
 
6
6
  let(:listener) do
7
- double(Listen::Listener,
8
- directories: [Pathname.pwd, Pathname.new('/Users/Shared/')],
9
- options: options
10
- )
7
+ instance_double(
8
+ Listen::Listener,
9
+ directories: [Pathname.pwd, Pathname.new('/Users/Shared/')],
10
+ options: options
11
+ )
11
12
  end
12
13
 
13
14
  let(:silencer) { Listen::Silencer.new(listener) }
@@ -19,46 +20,42 @@ describe Listen::Silencer do
19
20
  hidden_dirs = %w(.git .svn .hg .rbx .bundle)
20
21
  other_dirs = %w(bundle vendor/bundle log tmp vendor/ruby)
21
22
  (hidden_dirs + other_dirs).each do |dir|
22
- describe do
23
- let(:path) { pwd.join(dir) }
23
+ it "silences #{dir}" do
24
+ expect(silencer.silenced?(pwd + dir, :dir)).to be_truthy
25
+ end
24
26
 
25
- it "silences default ignored directory: #{dir}" do
26
- expect(silencer.silenced?(path)).to be_true
27
- end
27
+ it "doesn't silence #{dir}foo" do
28
+ expect(silencer.silenced?(pwd + "#{dir}foo", :dir)).to be_falsey
29
+ end
28
30
 
29
- context 'with a directory beginning with the same name' do
30
- let(:path) { pwd.join("#{dir}foo") }
31
+ it "doesn't silence foo#{dir}" do
32
+ expect(silencer.silenced?(pwd + "foo#{dir}", :dir)).to be_falsey
33
+ end
34
+ end
31
35
 
32
- it "doesn't silences default ignored directory: #{dir}foo" do
33
- expect(silencer.silenced?(path)).to be_false
34
- end
35
- end
36
+ all_files = %w(.DS_Store foo.tmp foo~)
36
37
 
37
- context 'with a directory ending with the same name' do
38
- let(:path) { pwd.join("foo#{dir}") }
38
+ # Gedit swap files
39
+ all_files += %w(.goutputstream-S3FBGX)
39
40
 
40
- it "doesn't silences default ignored directory: foo#{dir}" do
41
- expect(silencer.silenced?(path)).to be_false
42
- end
43
- end
44
- end
45
- end
41
+ # Kate editor swap files
42
+ all_files += %w(foo.rbo54321.new foo.rbB22583.new foo.rb.kate-swp)
46
43
 
47
- gedit_files = %w(.goutputstream-S3FBGX)
48
- kate_files = %w(foo.rbo54321.new foo.rbB22583.new foo.rb.kate-swp)
49
- (%w(.DS_Store foo.tmp foo~) + kate_files + gedit_files).each do |path|
50
- describe do
51
- it "by default silences files like: #{path}" do
52
- expect(silencer.silenced?(pwd + path)).to be_true
53
- end
44
+ # Intellij swap files
45
+ all_files += %w(foo.rb___jb_bak___ foo.rb___jb_old___)
46
+
47
+ # Vim swap files
48
+ all_files += %w(foo.swp foo.swx foo.swpx 4913)
49
+
50
+ all_files.each do |path|
51
+ it "silences #{path}" do
52
+ expect(silencer.silenced?(pwd + path, :file)).to be_truthy
54
53
  end
55
54
  end
56
55
 
57
- %w(foo.tmpl file.new file54321.new).each do |path|
58
- describe do
59
- it "by default does not silence files like: #{path}" do
60
- expect(silencer.silenced?(pwd + path)).to be_false
61
- end
56
+ %w(foo.tmpl file.new file54321.new a.swf 14913 49131).each do |path|
57
+ it "does not silence #{path}" do
58
+ expect(silencer.silenced?(pwd + path, :file)).to be_falsey
62
59
  end
63
60
  end
64
61
  end
@@ -67,7 +64,7 @@ describe Listen::Silencer do
67
64
  let(:options) { { ignore: /\.pid$/ } }
68
65
 
69
66
  it 'silences path matching custom ignore regex' do
70
- expect(silencer.silenced?(pwd + 'foo.pid')).to be_true
67
+ expect(silencer.silenced?(pwd + 'foo.pid', :file)).to be_truthy
71
68
  end
72
69
  end
73
70
 
@@ -75,8 +72,8 @@ describe Listen::Silencer do
75
72
  let(:options) { { ignore: [%r{^foo/bar}, /\.pid$/] } }
76
73
 
77
74
  it 'silences paths matching custom ignore regexes' do
78
- expect(silencer.silenced?(pwd + 'foo/bar/baz')).to be_true
79
- expect(silencer.silenced?(pwd + 'foo.pid')).to be_true
75
+ expect(silencer.silenced?(pwd + 'foo/bar/baz', :file)).to be_truthy
76
+ expect(silencer.silenced?(pwd + 'foo.pid', :file)).to be_truthy
80
77
  end
81
78
  end
82
79
 
@@ -84,90 +81,66 @@ describe Listen::Silencer do
84
81
  let(:options) { { ignore!: /\.pid$/ } }
85
82
 
86
83
  it 'silences custom ignored directory' do
87
- expect(silencer.silenced?(pwd + 'foo.pid')).to be_true
84
+ expect(silencer.silenced?(pwd + 'foo.pid', :file)).to be_truthy
88
85
  end
89
86
 
90
87
  it "doesn't silence default ignored directory" do
91
- expect(silencer.silenced?(pwd + '.git')).to be_false
88
+ expect(silencer.silenced?(pwd + '.git', :file)).to be_falsey
92
89
  end
93
90
  end
94
91
 
95
92
  context 'with only options (regexp)' do
96
93
  let(:options) { { only: %r{foo} } }
97
94
 
98
- it 'do not take only regex in account if type is Unknown' do
99
- expect(silencer.silenced?(pwd + 'baz')).to be_false
100
- end
101
-
102
95
  it 'do not silence path matches only regex if type is File' do
103
- expect(silencer.silenced?(pwd + 'foo', 'File')).to be_false
96
+ expect(silencer.silenced?(pwd + 'foo', :file)).to be_falsey
104
97
  end
105
98
 
106
99
  it 'silences other directory' do
107
- expect(silencer.silenced?(pwd + 'bar', 'File')).to be_true
100
+ expect(silencer.silenced?(pwd + 'bar', :file)).to be_truthy
108
101
  end
109
102
  end
110
103
 
111
104
  context 'with only options (array)' do
112
105
  let(:options) { { only: [%r{^foo/}, %r{\.txt$}] } }
113
106
 
114
- it 'do not take only regex in account if type is Unknown' do
115
- expect(silencer.silenced?(pwd + 'baz')).to be_false
116
- end
117
-
118
107
  it "doesn't silence good directory" do
119
- expect(silencer.silenced?(pwd + 'foo/bar.rb', 'File')).to be_false
108
+ expect(silencer.silenced?(pwd + 'foo/bar.rb', :file)).to be_falsey
120
109
  end
121
110
 
122
111
  it "doesn't silence good file" do
123
- expect(silencer.silenced?(pwd + 'bar.txt', 'File')).to be_false
112
+ expect(silencer.silenced?(pwd + 'bar.txt', :file)).to be_falsey
124
113
  end
125
114
 
126
115
  it 'silences other directory' do
127
- expect(silencer.silenced?(pwd + 'bar/baz.rb', 'File')).to be_true
116
+ expect(silencer.silenced?(pwd + 'bar/baz.rb', :file)).to be_truthy
128
117
  end
129
118
 
130
119
  it 'silences other file' do
131
- expect(silencer.silenced?(pwd + 'bar.rb', 'File')).to be_true
120
+ expect(silencer.silenced?(pwd + 'bar.rb', :file)).to be_truthy
132
121
  end
133
122
  end
134
123
 
135
124
  context 'with ignore and only options' do
136
125
  let(:options) { { only: /\.pid$/, ignore: %r{^bar} } }
137
126
 
138
- context 'with Unknown type' do
139
- context 'when not matching :only' do
140
- context 'when not matching :ignore' do
141
- it 'does not silence' do
142
- expect(silencer.silenced?(pwd + 'baz')).to be_false
143
- end
144
- end
145
-
146
- context 'when matching :ignore' do
147
- it 'silences' do
148
- expect(silencer.silenced?(pwd + 'bar')).to be_true
149
- end
150
- end
151
- end
152
- end
153
-
154
127
  context 'with File type' do
155
128
  context 'when not matching :only' do
156
129
  it 'silences' do
157
- expect(silencer.silenced?(pwd + 'foo.rb', 'File')).to be_true
130
+ expect(silencer.silenced?(pwd + 'foo.rb', :file)).to be_truthy
158
131
  end
159
132
  end
160
133
 
161
134
  context 'when matching :only' do
162
135
  context 'when matching :ignore' do
163
136
  it 'silences' do
164
- expect(silencer.silenced?(pwd + 'bar.pid', 'File')).to be_true
137
+ expect(silencer.silenced?(pwd + 'bar.pid', :file)).to be_truthy
165
138
  end
166
139
  end
167
140
 
168
141
  context 'when not matching :ignore' do
169
142
  it 'does not silence' do
170
- expect(silencer.silenced?(pwd + 'foo.pid', 'File')).to be_false
143
+ expect(silencer.silenced?(pwd + 'foo.pid', :file)).to be_falsey
171
144
  end
172
145
  end
173
146
  end
@@ -175,8 +148,8 @@ describe Listen::Silencer do
175
148
  end
176
149
 
177
150
  it "doesn't silence normal path" do
178
- path = pwd + 'some_dir' + 'some_file.rb'
179
- expect(silencer.silenced?(path)).to be_false
151
+ path = (pwd + 'some_dir') + 'some_file.rb'
152
+ expect(silencer.silenced?(path, :file)).to be_falsey
180
153
  end
181
154
  end
182
155
 
@@ -1,115 +1,124 @@
1
1
  require 'spec_helper'
2
2
 
3
+ require 'listen/tcp/broadcaster'
4
+
3
5
  describe Listen::TCP::Broadcaster do
4
6
 
5
7
  let(:host) { '10.0.0.2' }
6
8
  let(:port) { 4000 }
7
9
 
8
10
  subject { described_class.new(host, port) }
9
- let(:server) { double(described_class::TCPServer, close: true, accept: nil) }
10
- let(:socket) { double(described_class::TCPSocket, write: true) }
11
+
12
+ let(:server) do
13
+ instance_double(described_class::TCPServer, close: true, accept: nil)
14
+ end
15
+
16
+ let(:socket) do
17
+ instance_double(described_class::TCPSocket, close: true, write: true)
18
+ end
19
+
20
+ let(:socket2) do
21
+ instance_double(described_class::TCPSocket, close: true, write: true)
22
+ end
23
+
11
24
  let(:payload) { Listen::TCP::Message.new.payload }
12
25
 
13
26
  before do
14
27
  expect(described_class::TCPServer).to receive(:new).
15
28
  with(host, port).and_return server
29
+ allow(server).to receive(:accept).and_raise('stub called')
16
30
  end
17
31
 
18
32
  after do
19
33
  subject.terminate
20
34
  end
21
35
 
22
- describe '#initialize' do
23
- it 'initializes and exposes a server' do
24
- expect(subject.server).to be server
25
- end
26
-
27
- it 'initializes and exposes a list of sockets' do
28
- expect(subject.sockets).to eq []
29
- end
30
- end
31
-
32
36
  describe '#start' do
33
- let(:async) { double('TCP-listener async') }
34
-
35
37
  it 'invokes run loop asynchronously' do
36
- subject.stub(:async).and_return async
37
- expect(async).to receive(:run)
38
+ expect_any_instance_of(described_class).to receive(:run)
38
39
  subject.start
39
40
  end
40
41
  end
41
42
 
42
43
  describe '#finalize' do
43
- it 'clears sockets' do
44
- expect(subject.sockets).to receive(:clear)
45
- subject.finalize
46
- end
44
+ before { allow(server).to receive(:accept).and_return nil }
47
45
 
48
46
  it 'closes server' do
49
- expect(subject.server).to receive(:close)
47
+ expect(server).to receive(:close)
50
48
  subject.finalize
51
- expect(subject.server).to be_nil
52
49
  end
53
50
  end
54
51
 
55
52
  describe '#broadcast' do
56
- it 'unicasts to connected sockets' do
57
- subject.handle_connection socket
58
- expect(subject.wrapped_object).to receive(:unicast).with socket, payload
59
- subject.broadcast payload
60
- end
61
- end
62
-
63
- describe '#unicast' do
64
- before do
65
- subject.handle_connection socket
66
- end
53
+ context 'with active socket' do
54
+ before { allow(server).to receive(:accept).and_return socket, nil }
67
55
 
68
- context 'when succesful' do
69
- it 'returns true and leaves socket untouched' do
70
- expect(subject.unicast(socket, payload)).to be_true
71
- expect(subject.sockets).to include socket
56
+ it 'should broadcast payload' do
57
+ expect(socket).to receive(:write).with(payload)
58
+ subject.run
59
+ subject.broadcast payload
72
60
  end
73
- end
74
61
 
75
- context 'on IO errors' do
76
- it 'returns false and removes socket from list' do
77
- socket.stub(:write).and_raise IOError
78
- expect(subject.unicast(socket, payload)).to be_false
79
- expect(subject.sockets).not_to include socket
62
+ it 'should keep socket' do
63
+ expect(socket).to receive(:write).twice.with(payload)
64
+ subject.run
65
+ 2.times { subject.broadcast payload }
80
66
  end
81
- end
82
67
 
83
- context 'on connection reset by peer' do
84
- it 'returns false and removes socket from list' do
85
- socket.stub(:write).and_raise Errno::ECONNRESET
86
- expect(subject.unicast(socket, payload)).to be_false
87
- expect(subject.sockets).not_to include socket
68
+ context 'with IOError' do
69
+ it 'should remove socket from list' do
70
+ allow(socket).to receive(:write).once.and_raise IOError
71
+ subject.run
72
+ 2.times { subject.broadcast payload }
73
+ end
88
74
  end
89
- end
90
75
 
91
- context 'on broken pipe' do
92
- it 'returns false and removes socket from list' do
93
- socket.stub(:write).and_raise Errno::EPIPE
94
- expect(subject.unicast(socket, payload)).to be_false
95
- expect(subject.sockets).not_to include socket
76
+ context 'when reset by peer' do
77
+ it 'should remove socket from list' do
78
+ allow(socket).to receive(:write).once.and_raise Errno::ECONNRESET
79
+ subject.run
80
+ 2.times { subject.broadcast payload }
81
+ end
96
82
  end
97
- end
98
- end
99
83
 
100
- describe '#run' do
101
- it 'handles incoming connections' do
102
- server.stub(:accept).and_return socket, nil
103
- expect(subject.wrapped_object).to receive(:handle_connection).with socket
104
- subject.run
105
- end
106
- end
84
+ context 'when broken pipe' do
85
+ it 'should remove socket from list' do
86
+ allow(socket).to receive(:write).once.and_raise Errno::EPIPE
87
+ subject.run
88
+ 2.times { subject.broadcast payload }
89
+ end
90
+ end
107
91
 
108
- describe '#handle_connection' do
109
- it 'adds socket to list' do
110
- subject.handle_connection socket
111
- expect(subject.sockets).to include socket
92
+ context 'with another active socket' do
93
+ before do
94
+ allow(server).to receive(:accept).and_return socket, socket2, nil
95
+ end
96
+
97
+ it 'should broadcast payload to both' do
98
+ expect(socket).to receive(:write).with(payload)
99
+ expect(socket2).to receive(:write).with(payload)
100
+ subject.run
101
+ subject.broadcast payload
102
+ end
103
+
104
+ context 'with a failure in first socket' do
105
+ before do
106
+ allow(socket).to receive(:write).once.and_raise Errno::EPIPE
107
+ end
108
+
109
+ it 'should still broadcast to remaining socket' do
110
+ expect(socket2).to receive(:write).with(payload)
111
+ subject.run
112
+ subject.broadcast payload
113
+ end
114
+
115
+ it 'should broadcast to only remaining socket' do
116
+ expect(socket2).to receive(:write).twice.with(payload)
117
+ subject.run
118
+ 2.times { subject.broadcast payload }
119
+ end
120
+ end
121
+ end
112
122
  end
113
123
  end
114
-
115
124
  end