ruby-watchman 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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