ruby-watchman 0.0.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d97cec0f4ed9e90d780a524893af100bf3c0d05f
4
+ data.tar.gz: 7fff355ab24e5b9d8ef7a27ac365a71eb8f88027
5
+ SHA512:
6
+ metadata.gz: 3885a5743a0fcef6ac94f7ef08e9ff0fa4010671fb786e7e2e5b3b07f4fbd4024e04378da32ddebb3be140d0f58951c4b9a957ed8035cd8f69d0b3ea09e9a635
7
+ data.tar.gz: 714c4f1c4d3cab0c710adb6fec2655a57fdb9b811d5586b8076001a1e9e4a4f3804856bef27b2297d4f4b28d3e08026fbd4f7d2fc6b428257dd5fff0c840657f
@@ -0,0 +1,5 @@
1
+ # Copyright (c) 2014-present Facebook. All Rights Reserved.
2
+
3
+ CFLAGS += -Wall -Wextra -Wno-unused-parameter
4
+
5
+ watchman.o: watchman.c watchman.h
@@ -0,0 +1,24 @@
1
+ # Copyright (c) 2014-present Facebook. All Rights Reserved.
2
+
3
+ require 'mkmf'
4
+
5
+ def header(item)
6
+ unless find_header(item)
7
+ puts "couldn't find #{item} (required)"
8
+ exit 1
9
+ end
10
+ end
11
+
12
+ # mandatory headers
13
+ header('ruby.h')
14
+ header('fcntl.h')
15
+ header('sys/errno.h')
16
+ header('sys/socket.h')
17
+
18
+ # variable headers
19
+ have_header('ruby/st.h') # >= 1.9; sets HAVE_RUBY_ST_H
20
+ have_header('st.h') # 1.8; sets HAVE_ST_H
21
+
22
+ RbConfig::MAKEFILE_CONFIG['CC'] = ENV['CC'] if ENV['CC']
23
+
24
+ create_makefile('ruby-watchman/ext')
@@ -0,0 +1,698 @@
1
+ /*
2
+ Copyright (c) 2014, Facebook, Inc.
3
+ All rights reserved.
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ * Redistributions of source code must retain the above copyright notice,
9
+ this list of conditions and the following disclaimer.
10
+
11
+ * Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ * Neither the name Facebook nor the names of its contributors may be used to
16
+ endorse or promote products derived from this software without specific
17
+ prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
+ */
30
+
31
+ #include "watchman.h"
32
+
33
+ #if defined(HAVE_RUBY_ST_H)
34
+ #include <ruby/st.h>
35
+ #elif defined(HAVE_ST_H)
36
+ #include <st.h>
37
+ #else
38
+ #error no st.h header found
39
+ #endif
40
+
41
+ #include <fcntl.h> /* for fcntl() */
42
+ #include <sys/errno.h> /* for errno */
43
+ #include <sys/socket.h> /* for recv(), MSG_PEEK */
44
+
45
+ typedef struct {
46
+ uint8_t *data; // payload
47
+ size_t cap; // total capacity
48
+ size_t len; // current length
49
+ } watchman_t;
50
+
51
+ // Forward declarations:
52
+ VALUE watchman_load(char **ptr, char *end);
53
+ void watchman_dump(watchman_t *w, VALUE serializable);
54
+
55
+ #define WATCHMAN_DEFAULT_STORAGE 4096
56
+
57
+ #define WATCHMAN_BINARY_MARKER "\x00\x01"
58
+ #define WATCHMAN_ARRAY_MARKER 0x00
59
+ #define WATCHMAN_HASH_MARKER 0x01
60
+ #define WATCHMAN_STRING_MARKER 0x02
61
+ #define WATCHMAN_INT8_MARKER 0x03
62
+ #define WATCHMAN_INT16_MARKER 0x04
63
+ #define WATCHMAN_INT32_MARKER 0x05
64
+ #define WATCHMAN_INT64_MARKER 0x06
65
+ #define WATCHMAN_FLOAT_MARKER 0x07
66
+ #define WATCHMAN_TRUE 0x08
67
+ #define WATCHMAN_FALSE 0x09
68
+ #define WATCHMAN_NIL 0x0a
69
+ #define WATCHMAN_TEMPLATE_MARKER 0x0b
70
+ #define WATCHMAN_SKIP_MARKER 0x0c
71
+
72
+ #define WATCHMAN_HEADER \
73
+ WATCHMAN_BINARY_MARKER \
74
+ "\x06" \
75
+ "\x00\x00\x00\x00\x00\x00\x00\x00"
76
+
77
+ static const char watchman_array_marker = WATCHMAN_ARRAY_MARKER;
78
+ static const char watchman_hash_marker = WATCHMAN_HASH_MARKER;
79
+ static const char watchman_string_marker = WATCHMAN_STRING_MARKER;
80
+ static const char watchman_true = WATCHMAN_TRUE;
81
+ static const char watchman_false = WATCHMAN_FALSE;
82
+ static const char watchman_nil = WATCHMAN_NIL;
83
+
84
+ /**
85
+ * Appends `len` bytes, starting at `data`, to the watchman_t struct `w`
86
+ *
87
+ * Will attempt to reallocate the underlying storage if it is not sufficient.
88
+ */
89
+ void watchman_append(watchman_t *w, const char *data, size_t len) {
90
+ if (w->len + len > w->cap) {
91
+ w->cap += w->len + WATCHMAN_DEFAULT_STORAGE;
92
+ REALLOC_N(w->data, uint8_t, w->cap);
93
+ }
94
+ memcpy(w->data + w->len, data, len);
95
+ w->len += len;
96
+ }
97
+
98
+ /**
99
+ * Allocate a new watchman_t struct
100
+ *
101
+ * The struct has a small amount of extra capacity preallocated, and a blank
102
+ * header that can be filled in later to describe the PDU.
103
+ */
104
+ watchman_t *watchman_init() {
105
+ watchman_t *w = ALLOC(watchman_t);
106
+ w->cap = WATCHMAN_DEFAULT_STORAGE;
107
+ w->len = 0;
108
+ w->data = ALLOC_N(uint8_t, WATCHMAN_DEFAULT_STORAGE);
109
+
110
+ watchman_append(w, WATCHMAN_HEADER, sizeof(WATCHMAN_HEADER) - 1);
111
+ return w;
112
+ }
113
+
114
+ /**
115
+ * Free a watchman_t struct `w` that was previously allocated with
116
+ * `watchman_init`
117
+ */
118
+ void watchman_free(watchman_t *w) {
119
+ xfree(w->data);
120
+ xfree(w);
121
+ }
122
+
123
+ /**
124
+ * Encodes and appends the integer `num` to `w`
125
+ */
126
+ void watchman_dump_int(watchman_t *w, int64_t num) {
127
+ char encoded[1 + sizeof(int64_t)];
128
+
129
+ if (num == (int8_t)num) {
130
+ encoded[0] = WATCHMAN_INT8_MARKER;
131
+ encoded[1] = (int8_t)num;
132
+ watchman_append(w, encoded, 1 + sizeof(int8_t));
133
+ } else if (num == (int16_t)num) {
134
+ encoded[0] = WATCHMAN_INT16_MARKER;
135
+ *(int16_t *)(encoded + 1) = (int16_t)num;
136
+ watchman_append(w, encoded, 1 + sizeof(int16_t));
137
+ } else if (num == (int32_t)num) {
138
+ encoded[0] = WATCHMAN_INT32_MARKER;
139
+ *(int32_t *)(encoded + 1) = (int32_t)num;
140
+ watchman_append(w, encoded, 1 + sizeof(int32_t));
141
+ } else {
142
+ encoded[0] = WATCHMAN_INT64_MARKER;
143
+ *(int64_t *)(encoded + 1) = (int64_t)num;
144
+ watchman_append(w, encoded, 1 + sizeof(int64_t));
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Encodes and appends the string `string` to `w`
150
+ */
151
+ void watchman_dump_string(watchman_t *w, VALUE string) {
152
+ watchman_append(w, &watchman_string_marker, sizeof(watchman_string_marker));
153
+ watchman_dump_int(w, RSTRING_LEN(string));
154
+ watchman_append(w, RSTRING_PTR(string), RSTRING_LEN(string));
155
+ }
156
+
157
+ /**
158
+ * Encodes and appends the double `num` to `w`
159
+ */
160
+ void watchman_dump_double(watchman_t *w, double num) {
161
+ char encoded[1 + sizeof(double)];
162
+ encoded[0] = WATCHMAN_FLOAT_MARKER;
163
+ *(double *)(encoded + 1) = num;
164
+ watchman_append(w, encoded, sizeof(encoded));
165
+ }
166
+
167
+ /**
168
+ * Encodes and appends the array `array` to `w`
169
+ */
170
+ void watchman_dump_array(watchman_t *w, VALUE array) {
171
+ long i;
172
+ watchman_append(w, &watchman_array_marker, sizeof(watchman_array_marker));
173
+ watchman_dump_int(w, RARRAY_LEN(array));
174
+ for (i = 0; i < RARRAY_LEN(array); i++) {
175
+ watchman_dump(w, rb_ary_entry(array, i));
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Helper method that encodes and appends a key/value pair (`key`, `value`) from
181
+ * a hash to the watchman_t struct passed in via `data`
182
+ */
183
+ int watchman_dump_hash_iterator(VALUE key, VALUE value, VALUE data) {
184
+ watchman_t *w = (watchman_t *)data;
185
+ watchman_dump_string(w, StringValue(key));
186
+ watchman_dump(w, value);
187
+ return ST_CONTINUE;
188
+ }
189
+
190
+ /**
191
+ * Encodes and appends the hash `hash` to `w`
192
+ */
193
+ void watchman_dump_hash(watchman_t *w, VALUE hash) {
194
+ watchman_append(w, &watchman_hash_marker, sizeof(watchman_hash_marker));
195
+ watchman_dump_int(w, RHASH_SIZE(hash));
196
+ rb_hash_foreach(hash, watchman_dump_hash_iterator, (VALUE)w);
197
+ }
198
+
199
+ /**
200
+ * Encodes and appends the serialized Ruby object `serializable` to `w`
201
+ *
202
+ * Examples of serializable objects include arrays, hashes, strings, numbers
203
+ * (integers, floats), booleans, and nil.
204
+ */
205
+ void watchman_dump(watchman_t *w, VALUE serializable) {
206
+ switch (TYPE(serializable)) {
207
+ case T_ARRAY:
208
+ return watchman_dump_array(w, serializable);
209
+ case T_HASH:
210
+ return watchman_dump_hash(w, serializable);
211
+ case T_STRING:
212
+ return watchman_dump_string(w, serializable);
213
+ case T_FIXNUM: // up to 63 bits
214
+ return watchman_dump_int(w, FIX2LONG(serializable));
215
+ case T_BIGNUM:
216
+ return watchman_dump_int(w, NUM2LL(serializable));
217
+ case T_FLOAT:
218
+ return watchman_dump_double(w, NUM2DBL(serializable));
219
+ case T_TRUE:
220
+ return watchman_append(w, &watchman_true, sizeof(watchman_true));
221
+ case T_FALSE:
222
+ return watchman_append(w, &watchman_false, sizeof(watchman_false));
223
+ case T_NIL:
224
+ return watchman_append(w, &watchman_nil, sizeof(watchman_nil));
225
+ default:
226
+ rb_raise(rb_eTypeError, "unsupported type");
227
+ }
228
+ }
229
+
230
+ /**
231
+ * Extract and return the int encoded at `ptr`
232
+ *
233
+ * Moves `ptr` past the extracted int.
234
+ *
235
+ * Will raise an ArgumentError if extracting the int would take us beyond the
236
+ * end of the buffer indicated by `end`, or if there is no int encoded at `ptr`.
237
+ *
238
+ * @returns The extracted int
239
+ */
240
+ int64_t watchman_load_int(char **ptr, char *end) {
241
+ char *val_ptr = *ptr + sizeof(int8_t);
242
+ int64_t val = 0;
243
+
244
+ if (val_ptr >= end) {
245
+ rb_raise(rb_eArgError, "insufficient int storage");
246
+ }
247
+
248
+ switch (*ptr[0]) {
249
+ case WATCHMAN_INT8_MARKER:
250
+ if (val_ptr + sizeof(int8_t) > end) {
251
+ rb_raise(rb_eArgError, "overrun extracting int8_t");
252
+ }
253
+ val = *(int8_t *)val_ptr;
254
+ *ptr = val_ptr + sizeof(int8_t);
255
+ break;
256
+ case WATCHMAN_INT16_MARKER:
257
+ if (val_ptr + sizeof(int16_t) > end) {
258
+ rb_raise(rb_eArgError, "overrun extracting int16_t");
259
+ }
260
+ val = *(int16_t *)val_ptr;
261
+ *ptr = val_ptr + sizeof(int16_t);
262
+ break;
263
+ case WATCHMAN_INT32_MARKER:
264
+ if (val_ptr + sizeof(int32_t) > end) {
265
+ rb_raise(rb_eArgError, "overrun extracting int32_t");
266
+ }
267
+ val = *(int32_t *)val_ptr;
268
+ *ptr = val_ptr + sizeof(int32_t);
269
+ break;
270
+ case WATCHMAN_INT64_MARKER:
271
+ if (val_ptr + sizeof(int64_t) > end) {
272
+ rb_raise(rb_eArgError, "overrun extracting int64_t");
273
+ }
274
+ val = *(int64_t *)val_ptr;
275
+ *ptr = val_ptr + sizeof(int64_t);
276
+ break;
277
+ default:
278
+ rb_raise(
279
+ rb_eArgError,
280
+ "bad integer marker 0x%02x",
281
+ (unsigned int)*ptr[0]
282
+ );
283
+ break;
284
+ }
285
+
286
+ return val;
287
+ }
288
+
289
+ /**
290
+ * Reads and returns a string encoded in the Watchman binary protocol format,
291
+ * starting at `ptr` and finishing at or before `end`
292
+ */
293
+ VALUE watchman_load_string(char **ptr, char *end) {
294
+ if (*ptr >= end) {
295
+ rb_raise(rb_eArgError, "unexpected end of input");
296
+ }
297
+
298
+ if (*ptr[0] != WATCHMAN_STRING_MARKER) {
299
+ rb_raise(rb_eArgError, "not a number");
300
+ }
301
+
302
+ *ptr += sizeof(int8_t);
303
+ if (*ptr >= end) {
304
+ rb_raise(rb_eArgError, "invalid string header");
305
+ }
306
+
307
+ int64_t len = watchman_load_int(ptr, end);
308
+ if (len == 0) { // special case for zero-length strings
309
+ return rb_str_new2("");
310
+ } else if (*ptr + len > end) {
311
+ rb_raise(rb_eArgError, "insufficient string storage");
312
+ }
313
+
314
+ VALUE string = rb_str_new(*ptr, len);
315
+ *ptr += len;
316
+ return string;
317
+ }
318
+
319
+ /**
320
+ * Reads and returns a double encoded in the Watchman binary protocol format,
321
+ * starting at `ptr` and finishing at or before `end`
322
+ */
323
+ double watchman_load_double(char **ptr, char *end) {
324
+ *ptr += sizeof(int8_t); // caller has already verified the marker
325
+ if (*ptr + sizeof(double) > end) {
326
+ rb_raise(rb_eArgError, "insufficient double storage");
327
+ }
328
+ double val = *(double *)*ptr;
329
+ *ptr += sizeof(double);
330
+ return val;
331
+ }
332
+
333
+ /**
334
+ * Helper method which returns length of the array encoded in the Watchman
335
+ * binary protocol format, starting at `ptr` and finishing at or before `end`
336
+ */
337
+ int64_t watchman_load_array_header(char **ptr, char *end) {
338
+ if (*ptr >= end) {
339
+ rb_raise(rb_eArgError, "unexpected end of input");
340
+ }
341
+
342
+ // verify and consume marker
343
+ if (*ptr[0] != WATCHMAN_ARRAY_MARKER) {
344
+ rb_raise(rb_eArgError, "not an array");
345
+ }
346
+ *ptr += sizeof(int8_t);
347
+
348
+ // expect a count
349
+ if (*ptr + sizeof(int8_t) * 2 > end) {
350
+ rb_raise(rb_eArgError, "incomplete array header");
351
+ }
352
+ return watchman_load_int(ptr, end);
353
+ }
354
+
355
+ /**
356
+ * Reads and returns an array encoded in the Watchman binary protocol format,
357
+ * starting at `ptr` and finishing at or before `end`
358
+ */
359
+ VALUE watchman_load_array(char **ptr, char *end) {
360
+ int64_t count, i;
361
+ VALUE array;
362
+
363
+ count = watchman_load_array_header(ptr, end);
364
+ array = rb_ary_new2(count);
365
+
366
+ for (i = 0; i < count; i++) {
367
+ rb_ary_push(array, watchman_load(ptr, end));
368
+ }
369
+
370
+ return array;
371
+ }
372
+
373
+ /**
374
+ * Reads and returns a hash encoded in the Watchman binary protocol format,
375
+ * starting at `ptr` and finishing at or before `end`
376
+ */
377
+ VALUE watchman_load_hash(char **ptr, char *end) {
378
+ int64_t count, i;
379
+ VALUE hash, key, value;
380
+
381
+ *ptr += sizeof(int8_t); // caller has already verified the marker
382
+
383
+ // expect a count
384
+ if (*ptr + sizeof(int8_t) * 2 > end) {
385
+ rb_raise(rb_eArgError, "incomplete hash header");
386
+ }
387
+ count = watchman_load_int(ptr, end);
388
+
389
+ hash = rb_hash_new();
390
+
391
+ for (i = 0; i < count; i++) {
392
+ key = watchman_load_string(ptr, end);
393
+ value = watchman_load(ptr, end);
394
+ rb_hash_aset(hash, key, value);
395
+ }
396
+
397
+ return hash;
398
+ }
399
+
400
+ /**
401
+ * Reads and returns a templated array encoded in the Watchman binary protocol
402
+ * format, starting at `ptr` and finishing at or before `end`
403
+ *
404
+ * Templated arrays are arrays of hashes which have repetitive key information
405
+ * pulled out into a separate "headers" prefix.
406
+ *
407
+ * @see https://github.com/facebook/watchman/blob/master/BSER.markdown
408
+ */
409
+ VALUE watchman_load_template(char **ptr, char *end) {
410
+ int64_t header_items_count, i, row_count;
411
+ VALUE array, hash, header, key, value;
412
+
413
+ *ptr += sizeof(int8_t); // caller has already verified the marker
414
+
415
+ // process template header array
416
+ header_items_count = watchman_load_array_header(ptr, end);
417
+ header = rb_ary_new2(header_items_count);
418
+ for (i = 0; i < header_items_count; i++) {
419
+ rb_ary_push(header, watchman_load_string(ptr, end));
420
+ }
421
+
422
+ // process row items
423
+ row_count = watchman_load_int(ptr, end);
424
+ array = rb_ary_new2(header_items_count);
425
+ while (row_count--) {
426
+ hash = rb_hash_new();
427
+ for (i = 0; i < header_items_count; i++) {
428
+ if (*ptr >= end) {
429
+ rb_raise(rb_eArgError, "unexpected end of input");
430
+ }
431
+
432
+ if (*ptr[0] == WATCHMAN_SKIP_MARKER) {
433
+ *ptr += sizeof(uint8_t);
434
+ } else {
435
+ value = watchman_load(ptr, end);
436
+ key = rb_ary_entry(header, i);
437
+ rb_hash_aset(hash, key, value);
438
+ }
439
+ }
440
+ rb_ary_push(array, hash);
441
+ }
442
+ return array;
443
+ }
444
+
445
+ /**
446
+ * Reads and returns an object encoded in the Watchman binary protocol format,
447
+ * starting at `ptr` and finishing at or before `end`
448
+ */
449
+ VALUE watchman_load(char **ptr, char *end) {
450
+ if (*ptr >= end) {
451
+ rb_raise(rb_eArgError, "unexpected end of input");
452
+ }
453
+
454
+ switch (*ptr[0]) {
455
+ case WATCHMAN_ARRAY_MARKER:
456
+ return watchman_load_array(ptr, end);
457
+ case WATCHMAN_HASH_MARKER:
458
+ return watchman_load_hash(ptr, end);
459
+ case WATCHMAN_STRING_MARKER:
460
+ return watchman_load_string(ptr, end);
461
+ case WATCHMAN_INT8_MARKER:
462
+ case WATCHMAN_INT16_MARKER:
463
+ case WATCHMAN_INT32_MARKER:
464
+ case WATCHMAN_INT64_MARKER:
465
+ return LL2NUM(watchman_load_int(ptr, end));
466
+ case WATCHMAN_FLOAT_MARKER:
467
+ return rb_float_new(watchman_load_double(ptr, end));
468
+ case WATCHMAN_TRUE:
469
+ *ptr += 1;
470
+ return Qtrue;
471
+ case WATCHMAN_FALSE:
472
+ *ptr += 1;
473
+ return Qfalse;
474
+ case WATCHMAN_NIL:
475
+ *ptr += 1;
476
+ return Qnil;
477
+ case WATCHMAN_TEMPLATE_MARKER:
478
+ return watchman_load_template(ptr, end);
479
+ default:
480
+ rb_raise(rb_eTypeError, "unsupported type");
481
+ }
482
+
483
+ return Qnil; // keep the compiler happy
484
+ }
485
+
486
+ /**
487
+ * RubyWatchman.load(serialized)
488
+ *
489
+ * Converts the binary object, `serialized`, from the Watchman binary protocol
490
+ * format into a normal Ruby object.
491
+ */
492
+ VALUE RubyWatchman_load(VALUE self, VALUE serialized) {
493
+ serialized = StringValue(serialized);
494
+ long len = RSTRING_LEN(serialized);
495
+ char *ptr = RSTRING_PTR(serialized);
496
+ char *end = ptr + len;
497
+
498
+ // expect at least the binary marker and a int8_t length counter
499
+ if ((size_t)len < sizeof(WATCHMAN_BINARY_MARKER) - 1 + sizeof(int8_t) * 2) {
500
+ rb_raise(rb_eArgError, "undersized header");
501
+ }
502
+
503
+ int mismatched =
504
+ memcmp(ptr, WATCHMAN_BINARY_MARKER, sizeof(WATCHMAN_BINARY_MARKER) - 1);
505
+ if (mismatched) {
506
+ rb_raise(rb_eArgError, "missing binary marker");
507
+ }
508
+
509
+ // get size marker
510
+ ptr += sizeof(WATCHMAN_BINARY_MARKER) - 1;
511
+ uint64_t payload_size = watchman_load_int(&ptr, end);
512
+ if (!payload_size) {
513
+ rb_raise(rb_eArgError, "empty payload");
514
+ }
515
+
516
+ // sanity check length
517
+ if (ptr + payload_size != end) {
518
+ rb_raise(
519
+ rb_eArgError,
520
+ "payload size mismatch (%lu)",
521
+ (unsigned long)(end - (ptr + payload_size))
522
+ );
523
+ }
524
+
525
+ VALUE loaded = watchman_load(&ptr, end);
526
+
527
+ // one more sanity check
528
+ if (ptr != end) {
529
+ rb_raise(
530
+ rb_eArgError,
531
+ "payload termination mismatch (%lu)",
532
+ (unsigned long)(end - ptr)
533
+ );
534
+ }
535
+
536
+ return loaded;
537
+ }
538
+
539
+ /**
540
+ * RubyWatchman.dump(serializable)
541
+ *
542
+ * Converts the Ruby object, `serializable`, into a binary string in the
543
+ * Watchman binary protocol format.
544
+ *
545
+ * Examples of serializable objects include arrays, hashes, strings, numbers
546
+ * (integers, floats), booleans, and nil.
547
+ */
548
+ VALUE RubyWatchman_dump(VALUE self, VALUE serializable) {
549
+ watchman_t *w = watchman_init();
550
+ watchman_dump(w, serializable);
551
+
552
+ // update header with final length information
553
+ uint64_t *len =
554
+ (uint64_t *)(w->data + sizeof(WATCHMAN_HEADER) - sizeof(uint64_t) - 1);
555
+ *len = w->len - sizeof(WATCHMAN_HEADER) + 1;
556
+
557
+ // prepare final return value
558
+ VALUE serialized = rb_str_buf_new(w->len);
559
+ rb_str_buf_cat(serialized, (const char*)w->data, w->len);
560
+ watchman_free(w);
561
+ return serialized;
562
+ }
563
+
564
+ // How far we have to look to figure out the size of the PDU header
565
+ #define WATCHMAN_SNIFF_BUFFER_SIZE \
566
+ sizeof(WATCHMAN_BINARY_MARKER) - 1 + sizeof(int8_t)
567
+
568
+ // How far we have to peek, at most, to figure out the size of the PDU itself
569
+ #define WATCHMAN_PEEK_BUFFER_SIZE \
570
+ sizeof(WATCHMAN_BINARY_MARKER) - 1 + \
571
+ sizeof(WATCHMAN_INT64_MARKER) + \
572
+ sizeof(int64_t)
573
+
574
+ /**
575
+ * RubyWatchman.query(query, socket)
576
+ *
577
+ * Converts `query`, a Watchman query comprising Ruby objects, into the Watchman
578
+ * binary protocol format, transmits it over socket, and unserializes and
579
+ * returns the result.
580
+ */
581
+ VALUE RubyWatchman_query(VALUE self, VALUE query, VALUE socket) {
582
+ VALUE error = Qnil;
583
+ VALUE errorClass = Qnil;
584
+ VALUE loaded = Qnil;
585
+ void *buffer = NULL;
586
+ int fileno = NUM2INT(rb_funcall(socket, rb_intern("fileno"), 0));
587
+
588
+ // do blocking I/O to simplify the following logic
589
+ int flags = fcntl(fileno, F_GETFL);
590
+ if (
591
+ !(flags & O_NONBLOCK) &&
592
+ fcntl(fileno, F_SETFL, flags & ~O_NONBLOCK) == -1
593
+ ) {
594
+ error = rb_str_new2("unable to clear O_NONBLOCK flag");
595
+ goto cleanup;
596
+ }
597
+
598
+ // send the message
599
+ VALUE serialized = RubyWatchman_dump(self, query);
600
+ long query_len = RSTRING_LEN(serialized);
601
+ ssize_t sent = send(fileno, RSTRING_PTR(serialized), query_len, 0);
602
+ if (sent == -1) {
603
+ goto system_call_fail;
604
+ } else if (sent != query_len) {
605
+ error = rb_str_new2("sent byte count mismatch");
606
+ goto cleanup;
607
+ }
608
+
609
+ // sniff to see how large the header is
610
+ int8_t peek[WATCHMAN_PEEK_BUFFER_SIZE];
611
+ ssize_t received =
612
+ recv(fileno, peek, WATCHMAN_SNIFF_BUFFER_SIZE, MSG_PEEK | MSG_WAITALL);
613
+ if (received == -1) {
614
+ goto system_call_fail;
615
+ } else if (received != WATCHMAN_SNIFF_BUFFER_SIZE) {
616
+ error = rb_str_new2("failed to sniff PDU header");
617
+ goto cleanup;
618
+ }
619
+
620
+ // peek at size of PDU
621
+ int8_t sizes[] = { 0, 0, 0, 1, 2, 4, 8 };
622
+ int8_t sizes_idx = peek[sizeof(WATCHMAN_BINARY_MARKER) - 1];
623
+ if (sizes_idx < WATCHMAN_INT8_MARKER || sizes_idx > WATCHMAN_INT64_MARKER) {
624
+ error = rb_str_new2("bad PDU size marker");
625
+ goto cleanup;
626
+ }
627
+ ssize_t peek_size = sizeof(WATCHMAN_BINARY_MARKER) - 1 + sizeof(int8_t) +
628
+ sizes[sizes_idx];
629
+
630
+ received = recv(fileno, peek, peek_size, MSG_PEEK);
631
+ if (received == -1) {
632
+ goto system_call_fail;
633
+ } else if (received != peek_size) {
634
+ error = rb_str_new2("failed to peek at PDU header");
635
+ goto cleanup;
636
+ }
637
+ int8_t *pdu_size_ptr =
638
+ peek + sizeof(WATCHMAN_BINARY_MARKER) - sizeof(int8_t);
639
+ int64_t payload_size =
640
+ peek_size +
641
+ watchman_load_int((char **)&pdu_size_ptr, (char *)peek + peek_size);
642
+
643
+ // actually read the PDU
644
+ buffer = xmalloc(payload_size);
645
+ if (!buffer) {
646
+ errorClass = rb_eNoMemError;
647
+ error = rb_str_new2("failed to allocate");
648
+ goto cleanup;
649
+ }
650
+ received = recv(fileno, buffer, payload_size, MSG_WAITALL);
651
+ if (received == -1) {
652
+ goto system_call_fail;
653
+ } else if (received != payload_size) {
654
+ error = rb_str_new2("failed to load PDU");
655
+ goto cleanup;
656
+ }
657
+
658
+ if (!(flags & O_NONBLOCK) && fcntl(fileno, F_SETFL, flags) == -1) {
659
+ error = rb_str_new2("unable to restore fnctl flags");
660
+ goto cleanup;
661
+ }
662
+
663
+ char *payload = buffer + peek_size;
664
+ loaded = watchman_load(&payload, payload + payload_size);
665
+ goto cleanup;
666
+
667
+ system_call_fail:
668
+ errorClass = rb_eSystemCallError;
669
+ error = INT2FIX(errno);
670
+
671
+ cleanup:
672
+ if (buffer) {
673
+ xfree(buffer);
674
+ }
675
+
676
+ if (!(flags & O_NONBLOCK) && fcntl(fileno, F_SETFL, flags) == -1) {
677
+ rb_raise(rb_eRuntimeError, "unable to restore fnctl flags");
678
+ }
679
+
680
+ if (NIL_P(errorClass)) {
681
+ errorClass = rb_eRuntimeError;
682
+ }
683
+
684
+ if (!NIL_P(error)) {
685
+ rb_exc_raise(rb_class_new_instance(1, &error, errorClass));
686
+ }
687
+
688
+ return loaded;
689
+ }
690
+
691
+ VALUE mRubyWatchman = 0; // module RubyWatchman
692
+
693
+ void Init_ext() {
694
+ mRubyWatchman = rb_define_module("RubyWatchman");
695
+ rb_define_singleton_method(mRubyWatchman, "load", RubyWatchman_load, 1);
696
+ rb_define_singleton_method(mRubyWatchman, "dump", RubyWatchman_dump, 1);
697
+ rb_define_singleton_method(mRubyWatchman, "query", RubyWatchman_query, 2);
698
+ }
@@ -0,0 +1,60 @@
1
+ /*
2
+ Copyright (c) 2014, Facebook, Inc.
3
+ All rights reserved.
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ * Redistributions of source code must retain the above copyright notice,
9
+ this list of conditions and the following disclaimer.
10
+
11
+ * Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ * Neither the name Facebook nor the names of its contributors may be used to
16
+ endorse or promote products derived from this software without specific
17
+ prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
+ */
30
+
31
+ #include <ruby.h>
32
+
33
+ /**
34
+ * @module RubyWatchman
35
+ *
36
+ * Methods for working with the Watchman binary protocol
37
+ *
38
+ * @see https://github.com/facebook/watchman/blob/master/BSER.markdown
39
+ */
40
+ extern VALUE mRubyWatchman;
41
+
42
+ /**
43
+ * Convert an object serialized using the Watchman binary protocol[0] into an
44
+ * unpacked Ruby object
45
+ */
46
+ extern VALUE RubyWatchman_load(VALUE self, VALUE serialized);
47
+
48
+ /**
49
+ * Serialize a Ruby object into the Watchman binary protocol format
50
+ */
51
+ extern VALUE RubyWatchman_dump(VALUE self, VALUE serializable);
52
+
53
+ /**
54
+ * Issue `query` to the Watchman instance listening on `socket` (a `UNIXSocket`
55
+ * instance) and return the result
56
+ *
57
+ * The query is serialized following the Watchman binary protocol and the
58
+ * result is converted to native Ruby objects before returning to the caller.
59
+ */
60
+ extern VALUE RubyWatchman_query(VALUE self, VALUE query, VALUE socket);
@@ -0,0 +1,3 @@
1
+ # Copyright (c) 2014-present Facebook. All Rights Reserved.
2
+
3
+ require 'ruby-watchman/ext'
@@ -0,0 +1,5 @@
1
+ # Copyright (c) 2014-present Facebook. All Rights Reserved.
2
+
3
+ module RubyWatchman
4
+ VERSION = '0.0.2'
5
+ end
@@ -0,0 +1,397 @@
1
+ # Copyright (c) 2014-present Facebook. All Rights Reserved.
2
+
3
+ require 'spec_helper'
4
+
5
+ describe RubyWatchman do
6
+ def binary(str)
7
+ if str.respond_to?(:force_encoding) # Ruby >= 1.9
8
+ str.force_encoding('ASCII-8BIT')
9
+ else
10
+ str
11
+ end
12
+ end
13
+
14
+ def little_endian?
15
+ byte = [0xff00].pack('s')[0]
16
+ if byte.is_a?(Fixnum) # ie. Ruby 1.8
17
+ byte.zero?
18
+ elsif byte.is_a?(String) # ie. Ruby >= 1.9
19
+ byte == "\x00"
20
+ else
21
+ raise 'unable to determine endianness'
22
+ end
23
+ end
24
+
25
+ def roundtrip(value)
26
+ described_class.load(described_class.dump(value))
27
+ end
28
+
29
+ it 'roundtrips arrays' do
30
+ value = [1, 2, ['three', false]]
31
+ expect(roundtrip(value)).to eq(value)
32
+ end
33
+
34
+ it 'roundtrips hashes' do
35
+ value = {
36
+ 'foo' => 1,
37
+ 'bar' => {
38
+ 'baz' => 'bing',
39
+ }
40
+ }
41
+ expect(roundtrip(value)).to eq(value)
42
+ end
43
+
44
+ it 'roundtrips strings' do
45
+ expect(roundtrip('')).to eq('')
46
+ expect(roundtrip('/foo/bar/baz')).to eq('/foo/bar/baz')
47
+ end
48
+
49
+ it 'roundtrips uint8_t integers' do
50
+ expect(roundtrip(0)).to eq(0)
51
+ expect(roundtrip(1)).to eq(1)
52
+ expect(roundtrip(0xff)).to eq(0xff)
53
+ end
54
+
55
+ it 'roundtrips uint16_t integers' do
56
+ expect(roundtrip(0x1234)).to eq(0x1234)
57
+ end
58
+
59
+ it 'roundtrips uint32_t integers' do
60
+ expect(roundtrip(0x12345678)).to eq(0x12345678)
61
+ end
62
+
63
+ it 'roundtrips uint64_t integers' do
64
+ expect(roundtrip(0x12345678abcdef00)).to eq(0x12345678abcdef00)
65
+ end
66
+
67
+ it 'roundtrips floats' do
68
+ expect(roundtrip(1234.5678)).to eq(1234.5678)
69
+ end
70
+
71
+ it 'roundtrips `true` booleans' do
72
+ expect(roundtrip(true)).to be_true
73
+ end
74
+
75
+ it 'roundtrips `false` booleans' do
76
+ expect(roundtrip(false)).to be_false
77
+ end
78
+
79
+ it 'roundtrips nil' do
80
+ expect(roundtrip(nil)).to be_nil
81
+ end
82
+
83
+ describe '.load' do
84
+ it 'rejects undersized input' do
85
+ expect { described_class.load('') }.
86
+ to raise_error(ArgumentError, /undersized/i)
87
+ end
88
+
89
+ it 'rejects input without a binary marker' do
90
+ expect { described_class.load('gibberish') }.
91
+ to raise_error(ArgumentError, /missing/i)
92
+ end
93
+
94
+ it 'rejects a missing payload header' do
95
+ # binary protocol marker, but nothing else
96
+ input = binary("\x00\x01")
97
+ expect { described_class.load(input) }.
98
+ to raise_error(ArgumentError, /undersized/i)
99
+ end
100
+
101
+ it 'rejects empty payloads' do
102
+ # uint8_t size marker of zero
103
+ input = binary("\x00\x01\x03\x00")
104
+ expect { described_class.load(input) }.
105
+ to raise_error(ArgumentError, /empty/i)
106
+ end
107
+
108
+ it 'rejects unrecognized payload markers' do
109
+ # 0x10 is not a valid integer marker
110
+ input = binary("\x00\x01\x10\x00")
111
+ expect { described_class.load(input) }.
112
+ to raise_error(ArgumentError, /bad integer/i)
113
+ end
114
+
115
+ it 'rejects undersized payload markers' do
116
+ # int16_t marker, but only storage for int8_t
117
+ input = binary("\x00\x01\x04\x00")
118
+ expect { described_class.load(input) }.
119
+ to raise_error(ArgumentError, /overrun\b.+\bint16_t/i)
120
+ end
121
+
122
+ it 'loads array values' do
123
+ input = binary(
124
+ "\x00\x01\x03\x16\x00\x03\x05\x03\x01\x02\x03" \
125
+ "\x06foobar\x08\x09\x00\x03\x02\x03\x0a\x0a"
126
+ )
127
+ expect(described_class.load(input)).
128
+ to eq([1, 'foobar', true, false, [10, nil]])
129
+ end
130
+
131
+ it 'handles empty arrays' do
132
+ input = binary("\x00\x01\x03\x03\x00\x03\x00")
133
+ expect(described_class.load(input)).to eq([])
134
+ end
135
+
136
+ it 'rejects arrays with incomplete headers' do
137
+ input = binary("\x00\x01\x03\x02\x00\x03")
138
+ expect { described_class.load(input) }.
139
+ to raise_error(ArgumentError, /incomplete array header/i)
140
+ end
141
+
142
+ it 'rejects arrays with incomplete entries' do
143
+ input = binary("\x00\x01\x03\x05\x00\x03\x10\x0a\x0a")
144
+ expect { described_class.load(input) }.
145
+ to raise_error(ArgumentError, /unexpected end/i)
146
+ end
147
+
148
+ it 'loads hash values' do
149
+ input = binary(
150
+ "\x00\x01\x03\x1a\x01\x03\x02\x02\x03\x03foo\x0a" \
151
+ "\x02\x03\x03bar\x01\x03\x01\x02\x03\x03baz\x08"
152
+ )
153
+ expected = {
154
+ 'foo' => nil,
155
+ 'bar' => {
156
+ 'baz' => true,
157
+ }
158
+ }
159
+ expect(described_class.load(input)).to eq(expected)
160
+ end
161
+
162
+ it 'handles empty hashes' do
163
+ input = binary("\x00\x01\x03\x03\x01\x03\x00")
164
+ expect(described_class.load(input)).to eq({})
165
+ end
166
+
167
+ it 'rejects hashes with incomplete headers' do
168
+ input = binary("\x00\x01\x03\x02\x01\x03")
169
+ expect { described_class.load(input) }.
170
+ to raise_error(ArgumentError, /incomplete hash header/i)
171
+ end
172
+
173
+ it 'rejects hashes with invalid keys' do
174
+ # keys must be strings; this one uses uses a number instead
175
+ input = binary("\x00\x01\x03\x05\x01\x03\x01\x03\x00")
176
+ expect { described_class.load(input) }.
177
+ to raise_error(ArgumentError, /not a number/i)
178
+ end
179
+
180
+ it 'rejects hashes with missing keys' do
181
+ input = binary("\x00\x01\x03\x03\x01\x03\x01")
182
+ expect { described_class.load(input) }.
183
+ to raise_error(ArgumentError, /unexpected end/i)
184
+ end
185
+
186
+ it 'rejects hashes with missing values' do
187
+ input = binary("\x00\x01\x03\x09\x01\x03\x01\x02\x03\x03foo")
188
+ expect { described_class.load(input) }.
189
+ to raise_error(ArgumentError, /unexpected end/i)
190
+ end
191
+
192
+ it 'loads string values' do
193
+ input = binary("\x00\x01\x03\x06\x02\x03\x03foo")
194
+ expect(described_class.load(input)).to eq('foo')
195
+ end
196
+
197
+ it 'handles empty strings' do
198
+ input = binary("\x00\x01\x03\x03\x02\x03\x00")
199
+ expect(described_class.load(input)).to eq('')
200
+ end
201
+
202
+ if String.new.respond_to?(:encoding) # ie. Ruby >= 1.9
203
+ it 'loads string values as ASCII-8BIT encoded strings' do
204
+ input = binary("\x00\x01\x03\x06\x02\x03\x03foo")
205
+ expect(described_class.load(input).encoding.to_s).to eq('ASCII-8BIT')
206
+ end
207
+ end
208
+
209
+ it 'rejects string values with incomplete headers' do
210
+ input = binary("\x00\x01\x03\x01\x02")
211
+ expect { described_class.load(input) }.
212
+ to raise_error(ArgumentError, /invalid string header/i)
213
+ end
214
+
215
+ it 'rejects string values with invalid headers' do
216
+ # expect a number indicating the string length, get a boolean instead
217
+ input = binary("\x00\x01\x03\x05\x02\x08foo")
218
+ expect { described_class.load(input) }.
219
+ to raise_error(ArgumentError, /bad integer/i)
220
+ end
221
+
222
+ it 'rejects string values with insufficient storage' do
223
+ # expect 3 bytes, get 2 instead
224
+ input = binary("\x00\x01\x03\x05\x02\x03\x03fo")
225
+ expect { described_class.load(input) }.
226
+ to raise_error(ArgumentError, /insufficient string storage/i)
227
+ end
228
+
229
+ it 'loads uint8_t values' do
230
+ input = binary("\x00\x01\x03\x02\x03\x12")
231
+ expect(described_class.load(input)).to eq(0x12)
232
+ end
233
+
234
+ it 'loads uint16_t values' do
235
+ if little_endian?
236
+ input = binary("\x00\x01\x03\x03\x04\x34\x12")
237
+ else
238
+ input = binary("\x00\x01\x03\x03\x04\x12\x34")
239
+ end
240
+
241
+ expect(described_class.load(input)).to eq(0x1234)
242
+ end
243
+
244
+ it 'loads uint32_t values' do
245
+ if little_endian?
246
+ input = binary("\x00\x01\x03\x05\x05\x78\x56\x34\x12")
247
+ else
248
+ input = binary("\x00\x01\x03\x05\x05\x12\x34\x56\x78")
249
+ end
250
+
251
+ expect(described_class.load(input)).to eq(0x12345678)
252
+ end
253
+
254
+ it 'loads int uint64_t values' do
255
+ if little_endian?
256
+ input = binary("\x00\x01\x03\x09\x06\xef\xcd\xab\x90\x78\x56\x34\x12")
257
+ else
258
+ input = binary("\x00\x01\x03\x09\x06\x12\x34\x56\x78\x90\xab\xcd\xef")
259
+ end
260
+ expect(described_class.load(input)).to eq(0x1234567890abcdef)
261
+ end
262
+
263
+ it 'rejects int markers with missing values' do
264
+ # expect an integer, but hit the end of the buffer
265
+ input = binary("\x00\x01\x03\x01\x05")
266
+ expect { described_class.load(input) }.
267
+ to raise_error(ArgumentError, /insufficient int storage/i)
268
+ end
269
+
270
+ it 'rejects double markers with insufficient storage' do
271
+ # double with 7 bytes of storage instead of the required 8 bytes
272
+ input = binary("\x00\x01\x03\x08\x07\x00\x00\x00\x00\x00\x00\x00")
273
+ expect { described_class.load(input) }.
274
+ to raise_error(ArgumentError, /insufficient double storage/i)
275
+ end
276
+
277
+ it 'loads boolean `true` values' do
278
+ input = binary("\x00\x01\x03\x01\x08")
279
+ expect(described_class.load(input)).to be_true
280
+ end
281
+
282
+ it 'loads boolean `false` values' do
283
+ input = binary("\x00\x01\x03\x01\x09")
284
+ expect(described_class.load(input)).to be_false
285
+ end
286
+
287
+ it 'loads nil' do
288
+ input = binary("\x00\x01\x03\x01\x0a")
289
+ expect(described_class.load(input)).to be_nil
290
+ end
291
+
292
+ it 'loads templates' do
293
+ # this example includes a "skip" marker
294
+ input = binary(
295
+ "\x00\x01\x03\x28\x0b\x00\x03\x02\x02\x03\x04name" \
296
+ "\x02\x03\x03age\x03\x03\x02\x03\x04fred\x03" \
297
+ "\x14\x02\x03\x04pete\x03\x1e\x0c\x03\x19"
298
+ )
299
+ expected = [
300
+ { 'name' => 'fred', 'age' => 20 },
301
+ { 'name' => 'pete', 'age' => 30 },
302
+ { 'age' => 25 },
303
+ ]
304
+ expect(described_class.load(input)).to eq(expected)
305
+ end
306
+
307
+ it 'handles empty templates' do
308
+ input = binary(
309
+ "\x00\x01\x03\x12\x0b\x00\x03\x02\x02" \
310
+ "\x03\x03foo\x02\x03\x03bar\x03\x00"
311
+ )
312
+ expect(described_class.load(input)).to eq([])
313
+ end
314
+
315
+ it 'rejects templates without a header array' do
316
+ input = binary("\x00\x01\x03\x01\x0b")
317
+ expect { described_class.load(input) }.
318
+ to raise_error(ArgumentError, /unexpected end/i)
319
+ end
320
+
321
+ it 'rejects templates without a row items array' do
322
+ input = binary(
323
+ "\x00\x01\x03\x10\x0b\x00\x03\x02\x02" \
324
+ "\x03\x03foo\x02\x03\x03bar"
325
+ )
326
+ expect { described_class.load(input) }.
327
+ to raise_error(ArgumentError, /insufficient/i)
328
+ end
329
+
330
+ it 'rejects templates with non-string header items' do
331
+ input = binary(
332
+ "\x00\x01\x03\x0e\x0b\x00\x03\x02\x02" \
333
+ "\x03\x03foo\x03\x03\x03\x00"
334
+ )
335
+ expect { described_class.load(input) }.
336
+ to raise_error(ArgumentError, /not a number/)
337
+ end
338
+
339
+ it 'rejects templates with a header item array count mismatch' do
340
+ input = binary(
341
+ "\x00\x01\x03\x0a\x0b\x00\x03\x02\x02" \
342
+ "\x03\x03foo"
343
+ )
344
+ expect { described_class.load(input) }.
345
+ to raise_error(ArgumentError, /unexpected end/)
346
+ end
347
+
348
+ it 'rejects templates with a row item count mismatch' do
349
+ input = binary(
350
+ "\x00\x01\x03\x25\x0b\x00\x03\x02\x02\x03\x04name" \
351
+ "\x02\x03\x03age\x03\x03\x02\x03\x04fred\x03" \
352
+ "\x14\x02\x03\x04pete\x03\x1e"
353
+ )
354
+ expect { described_class.load(input) }.
355
+ to raise_error(ArgumentError, /unexpected end/)
356
+ end
357
+ end
358
+
359
+ describe '.dump' do
360
+ let(:query) do
361
+ # typical kind of query that Command-T issues
362
+ ['query', '/some/path', {
363
+ 'expression' => ['type', 'f'],
364
+ 'fields' => ['name'],
365
+ }]
366
+ end
367
+
368
+ it 'serializes' do
369
+ expect { described_class.dump(query) }.to_not raise_error
370
+ end
371
+
372
+ if String.new.respond_to?(:encoding) # ie. Ruby >= 1.9
373
+ it 'serializes to an ASCII-8BIT string' do
374
+ expect(described_class.dump(query).encoding.to_s).to eq('ASCII-8BIT')
375
+ end
376
+ end
377
+
378
+ it 'generates a correct serialization' do
379
+ # in Ruby 1.8, hashes aren't ordered, so two serializations are possible
380
+ expected = [
381
+ binary(
382
+ "\x00\x01\x06\x49\x00\x00\x00\x00\x00\x00\x00\x00\x03\x03\x02\x03" \
383
+ "\x05query\x02\x03\x0a/some/path\x01\x03\x02\x02\x03\x0aexpression" \
384
+ "\x00\x03\x02\x02\x03\x04type\x02\x03\x01f\x02\x03\x06fields\x00" \
385
+ "\x03\x01\x02\x03\x04name"
386
+ ),
387
+ binary(
388
+ "\x00\x01\x06\x49\x00\x00\x00\x00\x00\x00\x00\x00\x03\x03\x02\x03" \
389
+ "\x05query\x02\x03\x0a/some/path\x01\x03\x02\x02\x03\x06fields\x00" \
390
+ "\x03\x01\x02\x03\x04name\x02\x03\x0aexpression\x00\x03\x02\x02" \
391
+ "\x03\x04type\x02\x03\x01f"
392
+ )
393
+ ]
394
+ expect(expected).to include(described_class.dump(query))
395
+ end
396
+ end
397
+ end
@@ -0,0 +1,5 @@
1
+ # Copyright (c) 2014-present Facebook. All Rights Reserved.
2
+
3
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
4
+ $LOAD_PATH.unshift File.expand_path('../../ext', __FILE__)
5
+ require 'ruby-watchman'
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby-watchman
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Greg Hurrell
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-05-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.5'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: |
56
+ A fast implementation of the Watchman binary protocol written in C.
57
+ email:
58
+ - glh@fb.com
59
+ executables: []
60
+ extensions:
61
+ - ext/ruby-watchman/extconf.rb
62
+ extra_rdoc_files: []
63
+ files:
64
+ - ext/ruby-watchman/watchman.c
65
+ - ext/ruby-watchman/watchman.h
66
+ - ext/ruby-watchman/extconf.rb
67
+ - ext/ruby-watchman/depend
68
+ - lib/ruby-watchman/version.rb
69
+ - lib/ruby-watchman.rb
70
+ - spec/ruby_watchman_spec.rb
71
+ - spec/spec_helper.rb
72
+ homepage: https://github.com/facebook/watchman/tree/master/ruby/ruby-watchman
73
+ licenses:
74
+ - BSD
75
+ metadata: {}
76
+ post_install_message:
77
+ rdoc_options: []
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - '>='
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubyforge_project:
92
+ rubygems_version: 2.0.14
93
+ signing_key:
94
+ specification_version: 4
95
+ summary: Ruby implementation of Watchman binary protocol
96
+ test_files:
97
+ - spec/ruby_watchman_spec.rb
98
+ - spec/spec_helper.rb