cool.io 1.0.0 → 1.9.0

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 (83) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/test.yaml +54 -0
  3. data/.gitignore +30 -0
  4. data/.rspec +3 -0
  5. data/{CHANGES → CHANGES.md} +175 -79
  6. data/{README.markdown → README.md} +43 -49
  7. data/Rakefile +44 -69
  8. data/appveyor.yml +18 -0
  9. data/cool.io.gemspec +18 -131
  10. data/examples/callbacked_echo_server.rb +24 -0
  11. data/ext/cool.io/buffer.c +781 -0
  12. data/ext/cool.io/cool.io.h +13 -0
  13. data/ext/cool.io/cool.io_ext.c +1 -0
  14. data/ext/cool.io/ev_wrap.h +4 -2
  15. data/ext/cool.io/extconf.rb +27 -16
  16. data/ext/cool.io/iowatcher.c +17 -8
  17. data/ext/cool.io/loop.c +36 -84
  18. data/ext/cool.io/stat_watcher.c +103 -25
  19. data/ext/cool.io/timer_watcher.c +3 -3
  20. data/ext/cool.io/utils.c +5 -0
  21. data/ext/cool.io/watcher.h +1 -1
  22. data/ext/libev/Changes +152 -3
  23. data/ext/libev/LICENSE +2 -1
  24. data/ext/libev/README +8 -8
  25. data/ext/libev/ev.c +1719 -348
  26. data/ext/libev/ev.h +146 -118
  27. data/ext/libev/ev_epoll.c +71 -20
  28. data/ext/libev/ev_kqueue.c +36 -16
  29. data/ext/libev/ev_poll.c +14 -11
  30. data/ext/libev/ev_port.c +43 -18
  31. data/ext/libev/ev_select.c +82 -25
  32. data/ext/libev/ev_vars.h +28 -21
  33. data/ext/libev/ev_win32.c +19 -10
  34. data/ext/libev/ev_wrap.h +166 -152
  35. data/ext/libev/test_libev_win32.c +1 -1
  36. data/gems.rb +9 -0
  37. data/lib/cool.io/async_watcher.rb +1 -1
  38. data/lib/cool.io/custom_require.rb +9 -0
  39. data/lib/cool.io/dns_resolver.rb +30 -22
  40. data/lib/cool.io/dsl.rb +32 -28
  41. data/lib/cool.io/io.rb +55 -35
  42. data/lib/cool.io/iowatcher.rb +1 -1
  43. data/lib/cool.io/listener.rb +35 -17
  44. data/lib/cool.io/loop.rb +17 -25
  45. data/lib/cool.io/meta.rb +6 -5
  46. data/lib/cool.io/server.rb +5 -4
  47. data/lib/cool.io/socket.rb +41 -30
  48. data/lib/cool.io/timer_watcher.rb +1 -1
  49. data/lib/cool.io/version.rb +7 -0
  50. data/lib/cool.io.rb +11 -10
  51. data/lib/coolio.rb +1 -1
  52. data/libev_ruby_gil.diff +175 -0
  53. data/libev_win_select.diff +130 -0
  54. data/spec/async_watcher_spec.rb +5 -5
  55. data/spec/dns_spec.rb +27 -8
  56. data/spec/iobuffer_spec.rb +147 -0
  57. data/spec/spec_helper.rb +15 -1
  58. data/spec/stat_watcher_spec.rb +81 -0
  59. data/spec/tcp_server_spec.rb +225 -0
  60. data/spec/tcp_socket_spec.rb +185 -0
  61. data/spec/timer_watcher_spec.rb +23 -19
  62. data/spec/udp_socket_spec.rb +58 -0
  63. data/spec/unix_listener_spec.rb +8 -8
  64. data/spec/unix_server_spec.rb +10 -8
  65. metadata +109 -112
  66. data/VERSION +0 -1
  67. data/examples/httpclient.rb +0 -38
  68. data/ext/http11_client/.gitignore +0 -5
  69. data/ext/http11_client/LICENSE +0 -31
  70. data/ext/http11_client/ext_help.h +0 -14
  71. data/ext/http11_client/extconf.rb +0 -6
  72. data/ext/http11_client/http11_client.c +0 -300
  73. data/ext/http11_client/http11_parser.c +0 -403
  74. data/ext/http11_client/http11_parser.h +0 -48
  75. data/ext/http11_client/http11_parser.rl +0 -173
  76. data/lib/cool.io/eventmachine.rb +0 -234
  77. data/lib/cool.io/http_client.rb +0 -419
  78. data/lib/rev.rb +0 -4
  79. data/spec/possible_tests/schedules_other_threads.rb +0 -48
  80. data/spec/possible_tests/test_on_resolve_failed.rb +0 -9
  81. data/spec/possible_tests/test_resolves.rb +0 -27
  82. data/spec/possible_tests/test_write_during_resolve.rb +0 -27
  83. data/spec/possible_tests/works_straight.rb +0 -71
data/lib/coolio.rb CHANGED
@@ -1,2 +1,2 @@
1
1
  # For those people who don't like the cool.io styling...
2
- require 'cool.io'
2
+ require 'cool.io'
@@ -0,0 +1,175 @@
1
+ diff --git a/ext/libev/ev.c b/ext/libev/ev.c
2
+ index 39b9faf..dae87f1 100644
3
+ --- a/ext/libev/ev.c
4
+ +++ b/ext/libev/ev.c
5
+ @@ -37,6 +37,10 @@
6
+ * either the BSD or the GPL.
7
+ */
8
+
9
+ +/* ########## COOLIO PATCHERY HO! ########## */
10
+ +#include "ruby.h"
11
+ +/* ######################################## */
12
+ +
13
+ /* this big block deduces configuration from config.h */
14
+ #ifndef EV_STANDALONE
15
+ # ifdef EV_CONFIG_H
16
+ @@ -107,7 +111,7 @@
17
+ # undef EV_USE_POLL
18
+ # define EV_USE_POLL 0
19
+ # endif
20
+ -
21
+ +
22
+ # if HAVE_EPOLL_CTL && HAVE_SYS_EPOLL_H
23
+ # ifndef EV_USE_EPOLL
24
+ # define EV_USE_EPOLL EV_FEATURE_BACKENDS
25
+ @@ -116,7 +120,7 @@
26
+ # undef EV_USE_EPOLL
27
+ # define EV_USE_EPOLL 0
28
+ # endif
29
+ -
30
+ +
31
+ # if HAVE_KQUEUE && HAVE_SYS_EVENT_H
32
+ # ifndef EV_USE_KQUEUE
33
+ # define EV_USE_KQUEUE EV_FEATURE_BACKENDS
34
+ @@ -125,7 +129,7 @@
35
+ # undef EV_USE_KQUEUE
36
+ # define EV_USE_KQUEUE 0
37
+ # endif
38
+ -
39
+ +
40
+ # if HAVE_PORT_H && HAVE_PORT_CREATE
41
+ # ifndef EV_USE_PORT
42
+ # define EV_USE_PORT EV_FEATURE_BACKENDS
43
+ @@ -161,7 +165,7 @@
44
+ # undef EV_USE_EVENTFD
45
+ # define EV_USE_EVENTFD 0
46
+ # endif
47
+ -
48
+ +
49
+ #endif
50
+
51
+ #include <stdlib.h>
52
+ @@ -2174,7 +2178,7 @@ downheap (ANHE *heap, int N, int k)
53
+
54
+ heap [k] = heap [c];
55
+ ev_active (ANHE_w (heap [k])) = k;
56
+ -
57
+ +
58
+ k = c;
59
+ }
60
+
61
+ @@ -2594,7 +2598,7 @@ ev_supported_backends (void) EV_THROW
62
+ if (EV_USE_EPOLL ) flags |= EVBACKEND_EPOLL;
63
+ if (EV_USE_POLL ) flags |= EVBACKEND_POLL;
64
+ if (EV_USE_SELECT) flags |= EVBACKEND_SELECT;
65
+ -
66
+ +
67
+ return flags;
68
+ }
69
+
70
+ @@ -3398,9 +3402,33 @@ time_update (EV_P_ ev_tstamp max_block)
71
+ }
72
+ }
73
+
74
+ +/* ########## COOLIO PATCHERY HO! ########## */
75
+ +#if defined(HAVE_RB_THREAD_BLOCKING_REGION) || defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
76
+ +struct ev_poll_args {
77
+ + struct ev_loop *loop;
78
+ + ev_tstamp waittime;
79
+ +};
80
+ +
81
+ +static
82
+ +VALUE ev_backend_poll(void *ptr)
83
+ +{
84
+ + struct ev_poll_args *args = (struct ev_poll_args *)ptr;
85
+ + struct ev_loop *loop = args->loop;
86
+ + backend_poll (EV_A_ args->waittime);
87
+ + return Qnil;
88
+ +}
89
+ +#endif
90
+ +/* ######################################## */
91
+ +
92
+ int
93
+ ev_run (EV_P_ int flags)
94
+ {
95
+ +/* ########## COOLIO PATCHERY HO! ########## */
96
+ +#if defined(HAVE_RB_THREAD_BLOCKING_REGION) || defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
97
+ + struct ev_poll_args poll_args;
98
+ +#endif
99
+ +/* ######################################## */
100
+ +
101
+ #if EV_FEATURE_API
102
+ ++loop_depth;
103
+ #endif
104
+ @@ -3518,7 +3546,70 @@ ev_run (EV_P_ int flags)
105
+ ++loop_count;
106
+ #endif
107
+ assert ((loop_done = EVBREAK_RECURSE, 1)); /* assert for side effect */
108
+ - backend_poll (EV_A_ waittime);
109
+ +
110
+ +/*
111
+ +########################## COOLIO PATCHERY HO! ##########################
112
+ +
113
+ +According to the grandwizards of Ruby, locking and unlocking of the global
114
+ +interpreter lock are apparently too powerful a concept for a mere mortal to
115
+ +wield (although redefining what + and - do to numbers is totally cool).
116
+ +And so it came to pass that the only acceptable way to release the global
117
+ +interpreter lock is through a convoluted callback system that thakes a
118
+ +function pointer. While the grandwizard of libev foresaw this sort of scenario,
119
+ +he too attempted to place an API with callbacks on it, one that runs before
120
+ +the system call, and one that runs immediately after.
121
+ +
122
+ +And so it came to pass that trying to wrap everything up in callbacks created
123
+ +two incompatible APIs, Ruby's which releases the global interpreter lock and
124
+ +reacquires it when the callback returns, and libev's, which wants two
125
+ +callbacks, one which runs before the polling operation starts, and one which
126
+ +runs after it finishes.
127
+ +
128
+ +These two systems are incompatible as they both want to use callbacks to
129
+ +solve the same problem, however libev wants to use before/after callbacks,
130
+ +and Ruby wants to use an "around" callback. This presents a significant
131
+ +problem as these two patterns of callbacks are diametrical opposites of each
132
+ +other and thus cannot be composed.
133
+ +
134
+ +And thus we are left with no choice but to patch the internals of libev in
135
+ +order to release a mutex at just the precise moment.
136
+ +
137
+ +This is a great example of a situation where granular locking and unlocking
138
+ +of the GVL is practically required. The goal is to get as close to the
139
+ +system call as possible, and to keep the GVL unlocked for the shortest
140
+ +amount of time possible.
141
+ +
142
+ +Perhaps Ruby could benefit from such an API, e.g:
143
+ +
144
+ +rb_thread_unsafe_dangerous_crazy_blocking_region_begin(...);
145
+ +rb_thread_unsafe_dangerous_crazy_blocking_region_end(...);
146
+ +
147
+ +#######################################################################
148
+ +*/
149
+ +
150
+ +/*
151
+ + simulate to rb_thread_call_without_gvl using rb_theread_blocking_region.
152
+ + https://github.com/brianmario/mysql2/blob/master/ext/mysql2/client.h#L8
153
+ +*/
154
+ +
155
+ +#ifndef HAVE_RB_THREAD_CALL_WITHOUT_GVL
156
+ +#ifdef HAVE_RB_THREAD_BLOCKING_REGION
157
+ +#define rb_thread_call_without_gvl(func, data1, ubf, data2) \
158
+ + rb_thread_blocking_region((rb_blocking_function_t *)func, data1, ubf, data2)
159
+ +#endif
160
+ +#endif
161
+ +
162
+ +#if defined(HAVE_RB_THREAD_BLOCKING_REGION) || defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
163
+ + poll_args.loop = loop;
164
+ + poll_args.waittime = waittime;
165
+ + rb_thread_call_without_gvl(ev_backend_poll, (void *)&poll_args, RUBY_UBF_IO, 0);
166
+ +#else
167
+ + backend_poll (EV_A_ waittime);
168
+ +#endif
169
+ +/*
170
+ +############################# END PATCHERY ############################
171
+ +*/
172
+ +
173
+ assert ((loop_done = EVBREAK_CANCEL, 1)); /* assert for side effect */
174
+
175
+ pipe_write_wanted = 0; /* just an optimisation, no fence needed */
@@ -0,0 +1,130 @@
1
+ diff --git a/ext/libev/ev.c b/ext/libev/ev.c
2
+ index dae87f1..d15f6bd 100644
3
+ --- a/ext/libev/ev.c
4
+ +++ b/ext/libev/ev.c
5
+ @@ -207,6 +207,7 @@
6
+ #else
7
+ # include <io.h>
8
+ # define WIN32_LEAN_AND_MEAN
9
+ +# define FD_SETSIZE 1024
10
+ # include <winsock2.h>
11
+ # include <windows.h>
12
+ # ifndef EV_SELECT_IS_WINSOCKET
13
+ diff --git a/ext/libev/ev_select.c b/ext/libev/ev_select.c
14
+ index f38d6ca..7050778 100644
15
+ --- a/ext/libev/ev_select.c
16
+ +++ b/ext/libev/ev_select.c
17
+ @@ -67,6 +67,54 @@
18
+
19
+ #include <string.h>
20
+
21
+ +#ifdef _WIN32
22
+ +/*
23
+ +########## COOLIO PATCHERY HO! ##########
24
+ +
25
+ +Ruby undefs FD_* utilities for own implementation.
26
+ +It converts fd argument into socket handle internally on Windows,
27
+ +so libev should not use Ruby's FD_* utilities.
28
+ +
29
+ +Following FD_* utilities come from MinGW.
30
+ +RubyInstaller is built by MinGW so this should work.
31
+ +*/
32
+ +int PASCAL __WSAFDIsSet(SOCKET,fd_set*);
33
+ +#define EV_WIN_FD_CLR(fd,set) do { u_int __i;\
34
+ +for (__i = 0; __i < ((fd_set *)(set))->fd_count ; __i++) {\
35
+ + if (((fd_set *)(set))->fd_array[__i] == (fd)) {\
36
+ + while (__i < ((fd_set *)(set))->fd_count-1) {\
37
+ + ((fd_set*)(set))->fd_array[__i] = ((fd_set*)(set))->fd_array[__i+1];\
38
+ + __i++;\
39
+ + }\
40
+ + ((fd_set*)(set))->fd_count--;\
41
+ + break;\
42
+ + }\
43
+ +}\
44
+ +} while (0)
45
+ +#define EV_WIN_FD_SET(fd, set) do { u_int __i;\
46
+ +for (__i = 0; __i < ((fd_set *)(set))->fd_count ; __i++) {\
47
+ + if (((fd_set *)(set))->fd_array[__i] == (fd)) {\
48
+ + break;\
49
+ + }\
50
+ +}\
51
+ +if (__i == ((fd_set *)(set))->fd_count) {\
52
+ + if (((fd_set *)(set))->fd_count < FD_SETSIZE) {\
53
+ + ((fd_set *)(set))->fd_array[__i] = (fd);\
54
+ + ((fd_set *)(set))->fd_count++;\
55
+ + }\
56
+ +}\
57
+ +} while(0)
58
+ +#define EV_WIN_FD_ZERO(set) (((fd_set *)(set))->fd_count=0)
59
+ +#define EV_WIN_FD_ISSET(fd, set) __WSAFDIsSet((SOCKET)(fd), (fd_set *)(set))
60
+ +#define EV_WIN_FD_COUNT(set) (((fd_set *)(set))->fd_count)
61
+ +/* ######################################## */
62
+ +#else
63
+ +#define EV_WIN_FD_CLR FD_CLR
64
+ +#define EV_WIN_FD_SET FD_SET
65
+ +#define EV_WIN_FD_ZERO FD_ZERO
66
+ +#define EV_WIN_FD_ISSET FD_ISSET
67
+ +#endif
68
+ +
69
+ static void
70
+ select_modify (EV_P_ int fd, int oev, int nev)
71
+ {
72
+ @@ -91,17 +139,17 @@ select_modify (EV_P_ int fd, int oev, int nev)
73
+ if ((oev ^ nev) & EV_READ)
74
+ #endif
75
+ if (nev & EV_READ)
76
+ - FD_SET (handle, (fd_set *)vec_ri);
77
+ + EV_WIN_FD_SET (handle, (fd_set *)vec_ri);
78
+ else
79
+ - FD_CLR (handle, (fd_set *)vec_ri);
80
+ + EV_WIN_FD_CLR (handle, (fd_set *)vec_ri);
81
+
82
+ #if EV_SELECT_IS_WINSOCKET
83
+ if ((oev ^ nev) & EV_WRITE)
84
+ #endif
85
+ if (nev & EV_WRITE)
86
+ - FD_SET (handle, (fd_set *)vec_wi);
87
+ + EV_WIN_FD_SET (handle, (fd_set *)vec_wi);
88
+ else
89
+ - FD_CLR (handle, (fd_set *)vec_wi);
90
+ + EV_WIN_FD_CLR (handle, (fd_set *)vec_wi);
91
+
92
+ #else
93
+
94
+ @@ -197,8 +245,8 @@ select_poll (EV_P_ ev_tstamp timeout)
95
+ {
96
+ if (timeout)
97
+ {
98
+ - unsigned long ms = timeout * 1e3;
99
+ - Sleep (ms ? ms : 1);
100
+ + unsigned long ms = (unsigned long)(timeout * 1e3);
101
+ + SleepEx (ms ? ms : 1, TRUE);
102
+ }
103
+
104
+ return;
105
+ @@ -230,10 +278,10 @@ select_poll (EV_P_ ev_tstamp timeout)
106
+ int handle = fd;
107
+ #endif
108
+
109
+ - if (FD_ISSET (handle, (fd_set *)vec_ro)) events |= EV_READ;
110
+ - if (FD_ISSET (handle, (fd_set *)vec_wo)) events |= EV_WRITE;
111
+ + if (EV_WIN_FD_ISSET (handle, (fd_set *)vec_ro)) events |= EV_READ;
112
+ + if (EV_WIN_FD_ISSET (handle, (fd_set *)vec_wo)) events |= EV_WRITE;
113
+ #ifdef _WIN32
114
+ - if (FD_ISSET (handle, (fd_set *)vec_eo)) events |= EV_WRITE;
115
+ + if (EV_WIN_FD_ISSET (handle, (fd_set *)vec_eo)) events |= EV_WRITE;
116
+ #endif
117
+
118
+ if (expect_true (events))
119
+ @@ -279,9 +327,9 @@ select_init (EV_P_ int flags)
120
+ backend_poll = select_poll;
121
+
122
+ #if EV_SELECT_USE_FD_SET
123
+ - vec_ri = ev_malloc (sizeof (fd_set)); FD_ZERO ((fd_set *)vec_ri);
124
+ + vec_ri = ev_malloc (sizeof (fd_set)); EV_WIN_FD_ZERO ((fd_set *)vec_ri);
125
+ vec_ro = ev_malloc (sizeof (fd_set));
126
+ - vec_wi = ev_malloc (sizeof (fd_set)); FD_ZERO ((fd_set *)vec_wi);
127
+ + vec_wi = ev_malloc (sizeof (fd_set)); EV_WIN_FD_ZERO ((fd_set *)vec_wi);
128
+ vec_wo = ev_malloc (sizeof (fd_set));
129
+ #ifdef _WIN32
130
+ vec_eo = ev_malloc (sizeof (fd_set));
@@ -2,7 +2,7 @@ require File.expand_path('../spec_helper', __FILE__)
2
2
  require 'tempfile'
3
3
  require 'fcntl'
4
4
 
5
- describe Cool.io::AsyncWatcher do
5
+ describe Cool.io::AsyncWatcher, :env => :exclude_win do
6
6
 
7
7
  it "does not signal on spurious wakeups" do
8
8
  aw = Cool.io::AsyncWatcher.new
@@ -30,7 +30,7 @@ describe Cool.io::AsyncWatcher do
30
30
  end
31
31
 
32
32
  # ensure children are ready
33
- nr_fork.times { rd.sysread(1).should == '.' }
33
+ nr_fork.times { expect(rd.sysread(1)).to eq('.') }
34
34
 
35
35
  # send our signals
36
36
  nr_signal.times { aw.signal }
@@ -41,15 +41,15 @@ describe Cool.io::AsyncWatcher do
41
41
  children.each do |pid|
42
42
  Process.kill(:TERM, pid)
43
43
  _, status = Process.waitpid2(pid)
44
- status.exitstatus.should == 0
44
+ expect(status.exitstatus).to eq(0)
45
45
  end
46
46
 
47
47
  # we should've written a line for every signal we sent
48
48
  lines = tmp.readlines
49
- lines.size.should == nr_signal
49
+ expect(lines.size).to eq(nr_signal)
50
50
 
51
51
  # theoretically a bad kernel scheduler could give us fewer...
52
- lines.sort.uniq.size.should == nr_fork
52
+ expect(lines.sort.uniq.size).to eq(nr_fork)
53
53
 
54
54
  tmp.close!
55
55
  end
data/spec/dns_spec.rb CHANGED
@@ -19,21 +19,40 @@ end
19
19
  describe "DNS" do
20
20
  before :each do
21
21
  @loop = Cool.io::Loop.new
22
+ @preferred_localhost_address = ::Socket.getaddrinfo("localhost", nil).first[3]
22
23
  end
23
24
 
24
25
  it "connects to valid domains" do
25
- ConnectorThingy.connect(VALID_DOMAIN, 80).attach(@loop)
26
-
27
- proc do
28
- @loop.run
29
- end.should raise_error(ItWorked)
26
+ begin
27
+ c = ConnectorThingy.connect(VALID_DOMAIN, 80).attach(@loop)
28
+
29
+ expect do
30
+ @loop.run
31
+ end.to raise_error(ItWorked)
32
+ ensure
33
+ c.close
34
+ end
30
35
  end
31
36
 
32
37
  it "fires on_resolve_failed for invalid domains" do
33
38
  ConnectorThingy.connect(INVALID_DOMAIN, 80).attach(@loop)
34
39
 
35
- proc do
40
+ expect do
36
41
  @loop.run
37
- end.should raise_error(WontResolve)
42
+ end.to raise_error(WontResolve)
43
+ end
44
+
45
+ it "resolve localhost even though hosts is empty" do
46
+ Tempfile.open("empty") do |file|
47
+ expect( Coolio::DNSResolver.hosts("localhost", file.path)).to eq @preferred_localhost_address
48
+ end
38
49
  end
39
- end
50
+
51
+ it "resolve missing localhost even though hosts entries exist" do
52
+ Tempfile.open("empty") do |file|
53
+ file.puts("127.0.0.1 example.internal")
54
+ file.flush
55
+ expect( Coolio::DNSResolver.hosts("localhost", file.path)).to eq @preferred_localhost_address
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,147 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+
3
+
4
+ describe Cool.io::Buffer do
5
+
6
+ let :buffer do
7
+ Cool.io::Buffer.new
8
+ end
9
+
10
+ it "provides a subset of the methods available in Strings" do
11
+ expect(buffer << "foo").to eq "foo"
12
+ expect(buffer << "bar").to eq "bar"
13
+ expect(buffer.to_str).to eq "foobar"
14
+ expect(buffer.to_str).to eq "foobar"
15
+ expect(buffer.size).to eq 6
16
+ end
17
+
18
+ it "provides append and prepend" do
19
+ expect(buffer.append "bar").to eq "bar"
20
+ expect(buffer.prepend "foo").to eq "foo"
21
+ expect(buffer.append "baz").to eq "baz"
22
+ expect(buffer.to_str).to eq "foobarbaz"
23
+ end
24
+
25
+ context "#read" do
26
+ it "can be used to retrieve the contents of a buffer" do
27
+ expect(buffer << "foo").to eq "foo"
28
+ expect(buffer.read 2).to eq "fo"
29
+ expect(buffer << "bar").to eq "bar"
30
+ expect(buffer.read 2).to eq "ob"
31
+ expect(buffer << "baz").to eq "baz"
32
+ expect(buffer.read 3).to eq "arb"
33
+ end
34
+ end
35
+
36
+ describe "provides methods for performing non-blocking I/O" do
37
+ require "tempfile"
38
+
39
+ context "#read_from" do
40
+ context "using local file", :env => :exclude_win do
41
+ let :tmp do
42
+ t = Tempfile.open "read_from"
43
+ t << "foobar"
44
+ t.rewind
45
+ t
46
+ end
47
+
48
+ it "will read as much data as possible" do
49
+ expect(buffer.read_from tmp).to eq 6
50
+ expect(buffer.to_str).to eq "foobar"
51
+ end
52
+ end
53
+
54
+ context "using udp socket" do
55
+ before :each do
56
+ @receiver = UDPSocket.open
57
+ @receiver.bind nil, 0
58
+
59
+ @sender = UDPSocket.open
60
+ @sender.connect "localhost", @receiver.addr[1]
61
+ end
62
+ after :each do
63
+ @receiver.close
64
+ @sender.close
65
+ end
66
+
67
+ it "will read as much data as possible" do
68
+ select [], [@sender]
69
+ @sender.send "foo", 0
70
+ select [@receiver]
71
+ expect(buffer.read_from @receiver).to eq 3
72
+ expect(buffer.to_str).to eq "foo"
73
+
74
+ select [], [@sender]
75
+ @sender.send "barbaz", 0
76
+ select [@receiver]
77
+ expect(buffer.read_from @receiver).to eq 6
78
+ expect(buffer.to_str).to eq "foobarbaz"
79
+ end
80
+ end
81
+ end
82
+
83
+ context "#write_to" do
84
+ context "using local file", :env => :exclude_win do
85
+ let :tmp do
86
+ Tempfile.open "write_to"
87
+ end
88
+ it "writes the contents of the buffer" do
89
+ buffer << "foo"
90
+ expect(buffer.write_to tmp).to eq 3
91
+ tmp.rewind
92
+ expect(tmp.read 3).to eq "foo"
93
+ end
94
+ end
95
+
96
+ context "using udp socket" do
97
+ before :each do
98
+ @receiver = UDPSocket.open
99
+ @receiver.bind nil, 0
100
+
101
+ @sender = UDPSocket.open
102
+ @sender.connect "localhost", @receiver.addr[1]
103
+ end
104
+ after :each do
105
+ @receiver.close
106
+ @sender.close
107
+ end
108
+
109
+ it "will read as much data as possible" do
110
+ buffer << "foo"
111
+ select [], [@sender]
112
+ expect(buffer.write_to @sender).to eq 3
113
+ select [@receiver]
114
+ expect(@receiver.recvfrom_nonblock(3)[0]).to eq "foo"
115
+ end
116
+ end
117
+ end
118
+ end
119
+
120
+ context "#clear" do
121
+ it "clear all data" do
122
+ buffer << "foo"
123
+ expect(buffer.size).to eq 3
124
+ expect(buffer.empty?).to eq false
125
+ buffer.clear
126
+ expect(buffer.size).to eq 0
127
+ expect(buffer.empty?).to eq true
128
+ end
129
+ end
130
+
131
+ context "#read_frame" do
132
+ it "Read up to and including the given frame marker" do
133
+ buffer << "foo\nbarbaz"
134
+ data = ""
135
+ expect(buffer.read_frame data, "\n".ord).to eq true
136
+ expect(buffer.empty?).to eq false
137
+ expect(data).to eq "foo\n"
138
+ expect(buffer.to_str).to eq "barbaz"
139
+
140
+ expect(buffer.read_frame data, "\n".ord).to eq false
141
+ expect(buffer.empty?).to eq true
142
+ expect(data).to eq "foo\nbarbaz"
143
+ expect(buffer.to_str).to eq ""
144
+ end
145
+ end
146
+
147
+ end
data/spec/spec_helper.rb CHANGED
@@ -2,4 +2,18 @@ $LOAD_PATH.unshift File.dirname(__FILE__)
2
2
  $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
3
3
 
4
4
  require 'rspec'
5
- require 'cool.io'
5
+ require 'cool.io'
6
+
7
+ def unused_port
8
+ s = TCPServer.open(0)
9
+ port = s.addr[1]
10
+ s.close
11
+ port
12
+ end
13
+
14
+ RSpec.configure do |c|
15
+ if RUBY_PLATFORM =~ /mingw|mswin/
16
+ $stderr.puts "Skip some specs on Windows"
17
+ c.filter_run_excluding :env => :exclude_win
18
+ end
19
+ end
@@ -0,0 +1,81 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+
3
+ TEMP_FILE_PATH = "./test.txt"
4
+
5
+ INTERVAL = 0.010
6
+
7
+ class MyStatWatcher < Cool.io::StatWatcher
8
+ attr_accessor :accessed, :previous, :current
9
+
10
+ def initialize(path)
11
+ super path, INTERVAL
12
+ end
13
+
14
+ def on_change(previous, current)
15
+ self.accessed = true
16
+ self.previous = previous
17
+ self.current = current
18
+ end
19
+ end
20
+
21
+ def run_with_file_change(path)
22
+ reactor = Cool.io::Loop.new
23
+
24
+ sw = MyStatWatcher.new(path)
25
+ sw.attach(reactor)
26
+
27
+ tw = Cool.io::TimerWatcher.new(INTERVAL, true)
28
+ tw.on_timer do
29
+ reactor.stop if sw.accessed
30
+ write_file(path)
31
+ end
32
+ tw.attach(reactor)
33
+
34
+ reactor.run
35
+
36
+ tw.detach
37
+ sw.detach
38
+
39
+ sw
40
+ end
41
+
42
+ def write_file(path)
43
+ File.open(path, "w+") { |f| f.write(rand.to_s) }
44
+ end
45
+
46
+ def delete_file(path)
47
+ File.delete(TEMP_FILE_PATH)
48
+ end
49
+
50
+ describe Cool.io::StatWatcher do
51
+
52
+ let :watcher do
53
+ run_with_file_change(TEMP_FILE_PATH)
54
+ end
55
+
56
+ before :each do
57
+ write_file(TEMP_FILE_PATH)
58
+ end
59
+
60
+ after :each do
61
+ delete_file(TEMP_FILE_PATH)
62
+ end
63
+
64
+ it "fire on_change when the file it is watching is modified" do
65
+ expect(watcher.accessed).to eq(true)
66
+ end
67
+
68
+ it "should pass previous and current file stat info given a stat watcher" do
69
+ expect(watcher.previous.ino).to eq(watcher.current.ino)
70
+ end
71
+
72
+ it "should raise when the handler does not take 2 parameters" do
73
+ class MyStatWatcher < Cool.io::StatWatcher
74
+ remove_method :on_change
75
+ def on_change
76
+ end
77
+ end
78
+ expect { watcher.accessed }.to raise_error(ArgumentError)
79
+ end
80
+
81
+ end