brotli 0.5.0 → 0.7.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.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +1 -1
- data/Dockerfile +38 -0
- data/README.md +50 -1
- data/Rakefile +17 -0
- data/ext/brotli/brotli.c +123 -12
- data/ext/brotli/brotli.h +3 -0
- data/ext/brotli/buffer.c +6 -5
- data/ext/brotli/extconf.rb +9 -0
- data/lib/brotli/version.rb +1 -1
- data/test/brotli_test.rb +79 -3
- data/test/brotli_writer_test.rb +57 -0
- data/test/test_helper.rb +1 -0
- metadata +4 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c3722424560d3f3491399e46108213ae088a809ca502fe2c9f8a83d5c9383434
|
4
|
+
data.tar.gz: 4584239a0597b0c2e5cf47ba0d6c8a96e0198cc8af7c7b217f8bf1312bab56d8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ca8b9c2bfae436c8b19d657948e0a2f2a3137d05ef9af070f2de4625ffcbe9cb7dd2e6eb011c8e475ea16a74ac5825b9dc074ab95b809e68ce722a06ce72798d
|
7
|
+
data.tar.gz: b5864a5aeb5630e8ad9b491621d89aa4c12f65131a261e49b0499d35cd5b32d43288ddbccb10cfe2ab7201d140e1ae311a0cbb1328cc0a010fad4ad775db8f2d
|
data/.github/workflows/main.yml
CHANGED
data/Dockerfile
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
ARG GCC_VERSION=14
|
2
|
+
FROM gcc:${GCC_VERSION}
|
3
|
+
|
4
|
+
ARG USE_SYSTEM_BROTLI=true
|
5
|
+
|
6
|
+
# Install Ruby and development dependencies
|
7
|
+
RUN apt-get update && apt-get install -y \
|
8
|
+
git \
|
9
|
+
libbrotli-dev \
|
10
|
+
pkg-config \
|
11
|
+
ruby \
|
12
|
+
ruby-dev \
|
13
|
+
&& rm -rf /var/lib/apt/lists/*
|
14
|
+
|
15
|
+
# Verify GCC version
|
16
|
+
RUN gcc --version
|
17
|
+
|
18
|
+
# Set working directory
|
19
|
+
WORKDIR /app
|
20
|
+
|
21
|
+
# Copy source code
|
22
|
+
COPY . .
|
23
|
+
|
24
|
+
# Install bundler and dependencies
|
25
|
+
RUN gem install bundler
|
26
|
+
RUN bundle install
|
27
|
+
|
28
|
+
# Build and test
|
29
|
+
RUN if [ "${USE_SYSTEM_BROTLI}" = "true" ]; then \
|
30
|
+
echo "Building with system Brotli" && \
|
31
|
+
bundle exec rake compile; \
|
32
|
+
else \
|
33
|
+
echo "Building with vendor Brotli" && \
|
34
|
+
bundle exec rake compile -- --enable-vendor; \
|
35
|
+
fi
|
36
|
+
RUN bundle exec rake test
|
37
|
+
|
38
|
+
CMD ["bash"]
|
data/README.md
CHANGED
@@ -25,13 +25,62 @@ Or install it yourself as:
|
|
25
25
|
|
26
26
|
## Usage
|
27
27
|
|
28
|
+
### Basic Compression/Decompression
|
29
|
+
|
28
30
|
```ruby
|
29
31
|
require 'brotli'
|
30
32
|
compressed = Brotli.deflate(string)
|
31
33
|
decompressed = Brotli.inflate(compressed)
|
32
34
|
```
|
33
35
|
|
34
|
-
|
36
|
+
### Custom Dictionary Support
|
37
|
+
|
38
|
+
Brotli supports using custom dictionaries to improve compression ratio when you have repetitive data patterns:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
# Using a dictionary that contains common patterns in your data
|
42
|
+
dictionary = "common patterns in my data"
|
43
|
+
data = "This text contains common patterns in my data multiple times"
|
44
|
+
|
45
|
+
# Compress with dictionary
|
46
|
+
compressed = Brotli.deflate(data, dictionary: dictionary)
|
47
|
+
|
48
|
+
# Decompress with the same dictionary
|
49
|
+
decompressed = Brotli.inflate(compressed, dictionary: dictionary)
|
50
|
+
```
|
51
|
+
|
52
|
+
### Compression Options
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
# Combine dictionary with other compression options
|
56
|
+
compressed = Brotli.deflate(data,
|
57
|
+
dictionary: dictionary,
|
58
|
+
quality: 11, # 0-11, higher = better compression but slower
|
59
|
+
mode: :text, # :generic (default), :text, or :font
|
60
|
+
lgwin: 22, # window size (10-24)
|
61
|
+
lgblock: 0 # block size (0 or 16-24)
|
62
|
+
)
|
63
|
+
```
|
64
|
+
|
65
|
+
### Streaming Compression with Writer
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
# Basic usage
|
69
|
+
File.open('output.br', 'wb') do |file|
|
70
|
+
writer = Brotli::Writer.new(file)
|
71
|
+
writer.write(data)
|
72
|
+
writer.close
|
73
|
+
end
|
74
|
+
|
75
|
+
# With dictionary
|
76
|
+
File.open('output.br', 'wb') do |file|
|
77
|
+
writer = Brotli::Writer.new(file, dictionary: dictionary)
|
78
|
+
writer.write(data)
|
79
|
+
writer.close
|
80
|
+
end
|
81
|
+
```
|
82
|
+
|
83
|
+
See test/brotli_test.rb for more examples.
|
35
84
|
|
36
85
|
## Development
|
37
86
|
|
data/Rakefile
CHANGED
@@ -23,3 +23,20 @@ end
|
|
23
23
|
task :build => :compile
|
24
24
|
task :test => :compile
|
25
25
|
task :default => :test
|
26
|
+
|
27
|
+
task :docker do
|
28
|
+
gcc_versions = ["14", "15"]
|
29
|
+
brotli_configs = [true, false]
|
30
|
+
gcc_versions.product(brotli_configs).each do |gcc_version, use_system_brotli|
|
31
|
+
command = "docker build "\
|
32
|
+
"--progress=plain "\
|
33
|
+
"--build-arg GCC_VERSION=#{gcc_version} "\
|
34
|
+
"--build-arg USE_SYSTEM_BROTLI=#{use_system_brotli} "\
|
35
|
+
"-t brotli:#{gcc_version}#{use_system_brotli ? "-use_system_brotli" : ""} ."
|
36
|
+
puts "Running: #{command}"
|
37
|
+
system command
|
38
|
+
unless $?.exited?
|
39
|
+
raise "Docker build failed for GCC version #{gcc_version} with use_system_brotli=#{use_system_brotli}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/ext/brotli/brotli.c
CHANGED
@@ -28,6 +28,8 @@ typedef struct {
|
|
28
28
|
BrotliDecoderState* s;
|
29
29
|
buffer_t* buffer;
|
30
30
|
BrotliDecoderResult r;
|
31
|
+
uint8_t* dict;
|
32
|
+
size_t dict_len;
|
31
33
|
} brotli_inflate_args_t;
|
32
34
|
|
33
35
|
static void*
|
@@ -44,6 +46,14 @@ brotli_inflate_no_gvl(void *arg)
|
|
44
46
|
buffer_t* buffer = args->buffer;
|
45
47
|
BrotliDecoderState* s = args->s;
|
46
48
|
|
49
|
+
#ifdef HAVE_BROTLIDECODERATTACHDICTIONARY
|
50
|
+
/* Attach dictionary if provided */
|
51
|
+
if (args->dict && args->dict_len > 0) {
|
52
|
+
BrotliDecoderAttachDictionary(s, BROTLI_SHARED_DICTIONARY_RAW,
|
53
|
+
args->dict_len, args->dict);
|
54
|
+
}
|
55
|
+
#endif
|
56
|
+
|
47
57
|
for (;;) {
|
48
58
|
r = BrotliDecoderDecompressStream(s,
|
49
59
|
&available_in, &next_in,
|
@@ -68,14 +78,28 @@ brotli_inflate_no_gvl(void *arg)
|
|
68
78
|
return arg;
|
69
79
|
}
|
70
80
|
|
81
|
+
static ID id_read;
|
82
|
+
|
71
83
|
static VALUE
|
72
|
-
brotli_inflate(VALUE
|
84
|
+
brotli_inflate(int argc, VALUE *argv, VALUE self)
|
73
85
|
{
|
74
|
-
VALUE value = Qnil;
|
86
|
+
VALUE str = Qnil, opts = Qnil, value = Qnil, dict = Qnil;
|
75
87
|
brotli_inflate_args_t args;
|
76
88
|
|
89
|
+
rb_scan_args(argc, argv, "11", &str, &opts);
|
90
|
+
|
91
|
+
if (rb_respond_to(str, id_read)) {
|
92
|
+
str = rb_funcall(str, id_read, 0, 0);
|
93
|
+
}
|
94
|
+
|
77
95
|
StringValue(str);
|
78
96
|
|
97
|
+
/* Extract dictionary from options if provided */
|
98
|
+
if (!NIL_P(opts)) {
|
99
|
+
Check_Type(opts, T_HASH);
|
100
|
+
dict = rb_hash_aref(opts, CSTR2SYM("dictionary"));
|
101
|
+
}
|
102
|
+
|
79
103
|
args.str = (uint8_t*)RSTRING_PTR(str);
|
80
104
|
args.len = (size_t)RSTRING_LEN(str);
|
81
105
|
args.buffer = create_buffer(BUFSIZ);
|
@@ -84,6 +108,20 @@ brotli_inflate(VALUE self, VALUE str)
|
|
84
108
|
NULL);
|
85
109
|
args.r = BROTLI_DECODER_RESULT_ERROR;
|
86
110
|
|
111
|
+
/* Set dictionary parameters */
|
112
|
+
if (!NIL_P(dict)) {
|
113
|
+
#ifdef HAVE_BROTLIDECODERATTACHDICTIONARY
|
114
|
+
StringValue(dict);
|
115
|
+
args.dict = (uint8_t*)RSTRING_PTR(dict);
|
116
|
+
args.dict_len = (size_t)RSTRING_LEN(dict);
|
117
|
+
#else
|
118
|
+
rb_raise(rb_eBrotli, "Dictionary support not available in this build");
|
119
|
+
#endif
|
120
|
+
} else {
|
121
|
+
args.dict = NULL;
|
122
|
+
args.dict_len = 0;
|
123
|
+
}
|
124
|
+
|
87
125
|
#ifdef HAVE_RUBY_THREAD_H
|
88
126
|
rb_thread_call_without_gvl(brotli_inflate_no_gvl, (void *)&args, NULL, NULL);
|
89
127
|
#else
|
@@ -199,6 +237,8 @@ typedef struct {
|
|
199
237
|
BrotliEncoderState* s;
|
200
238
|
buffer_t* buffer;
|
201
239
|
BROTLI_BOOL finished;
|
240
|
+
uint8_t *dict;
|
241
|
+
size_t dict_len;
|
202
242
|
} brotli_deflate_args_t;
|
203
243
|
|
204
244
|
static void*
|
@@ -215,6 +255,19 @@ brotli_deflate_no_gvl(void *arg)
|
|
215
255
|
buffer_t* buffer = args->buffer;
|
216
256
|
BrotliEncoderState* s = args->s;
|
217
257
|
|
258
|
+
#if defined(HAVE_BROTLIENCODERPREPAREDICTIONARY) && defined(HAVE_BROTLIENCODERATTACHPREPAREDDICTIONARY)
|
259
|
+
/* Attach dictionary if provided */
|
260
|
+
if (args->dict && args->dict_len > 0) {
|
261
|
+
BrotliEncoderPreparedDictionary* dict = BrotliEncoderPrepareDictionary(
|
262
|
+
BROTLI_SHARED_DICTIONARY_RAW, args->dict_len, args->dict,
|
263
|
+
BROTLI_MAX_QUALITY, brotli_alloc, brotli_free, NULL);
|
264
|
+
if (dict) {
|
265
|
+
BrotliEncoderAttachPreparedDictionary(s, dict);
|
266
|
+
/* Note: dict is owned by encoder after attach, no need to free */
|
267
|
+
}
|
268
|
+
}
|
269
|
+
#endif
|
270
|
+
|
218
271
|
for (;;) {
|
219
272
|
r = BrotliEncoderCompressStream(s,
|
220
273
|
BROTLI_OPERATION_FINISH,
|
@@ -241,7 +294,7 @@ brotli_deflate_no_gvl(void *arg)
|
|
241
294
|
static VALUE
|
242
295
|
brotli_deflate(int argc, VALUE *argv, VALUE self)
|
243
296
|
{
|
244
|
-
VALUE str = Qnil, opts = Qnil, value = Qnil;
|
297
|
+
VALUE str = Qnil, opts = Qnil, value = Qnil, dict = Qnil;
|
245
298
|
brotli_deflate_args_t args;
|
246
299
|
|
247
300
|
rb_scan_args(argc, argv, "11", &str, &opts);
|
@@ -251,14 +304,35 @@ brotli_deflate(int argc, VALUE *argv, VALUE self)
|
|
251
304
|
}
|
252
305
|
StringValue(str);
|
253
306
|
|
307
|
+
/* Extract dictionary from options if provided */
|
308
|
+
if (!NIL_P(opts)) {
|
309
|
+
Check_Type(opts, T_HASH);
|
310
|
+
dict = rb_hash_aref(opts, CSTR2SYM("dictionary"));
|
311
|
+
}
|
312
|
+
|
254
313
|
args.str = (uint8_t*)RSTRING_PTR(str);
|
255
314
|
args.len = (size_t)RSTRING_LEN(str);
|
256
315
|
args.s = brotli_deflate_parse_options(
|
257
316
|
BrotliEncoderCreateInstance(brotli_alloc, brotli_free, NULL),
|
258
317
|
opts);
|
259
|
-
|
318
|
+
size_t max_compressed_size = BrotliEncoderMaxCompressedSize(args.len);
|
319
|
+
args.buffer = create_buffer(max_compressed_size);
|
260
320
|
args.finished = BROTLI_FALSE;
|
261
321
|
|
322
|
+
/* Set dictionary parameters */
|
323
|
+
if (!NIL_P(dict)) {
|
324
|
+
#if defined(HAVE_BROTLIENCODERPREPAREDICTIONARY) && defined(HAVE_BROTLIENCODERATTACHPREPAREDDICTIONARY)
|
325
|
+
StringValue(dict);
|
326
|
+
args.dict = (uint8_t*)RSTRING_PTR(dict);
|
327
|
+
args.dict_len = (size_t)RSTRING_LEN(dict);
|
328
|
+
#else
|
329
|
+
rb_raise(rb_eBrotli, "Dictionary support not available in this build");
|
330
|
+
#endif
|
331
|
+
} else {
|
332
|
+
args.dict = NULL;
|
333
|
+
args.dict_len = 0;
|
334
|
+
}
|
335
|
+
|
262
336
|
#ifdef HAVE_RUBY_THREAD_H
|
263
337
|
rb_thread_call_without_gvl(brotli_deflate_no_gvl, (void *)&args, NULL, NULL);
|
264
338
|
#else
|
@@ -366,6 +440,7 @@ static VALUE rb_writer_alloc(VALUE klass) {
|
|
366
440
|
static VALUE rb_writer_initialize(int argc, VALUE* argv, VALUE self) {
|
367
441
|
VALUE io = Qnil;
|
368
442
|
VALUE opts = Qnil;
|
443
|
+
VALUE dict = Qnil;
|
369
444
|
rb_scan_args(argc, argv, "11", &io, &opts);
|
370
445
|
if (NIL_P(io)) {
|
371
446
|
rb_raise(rb_eArgError, "io should not be nil");
|
@@ -376,6 +451,41 @@ static VALUE rb_writer_initialize(int argc, VALUE* argv, VALUE self) {
|
|
376
451
|
TypedData_Get_Struct(self, struct brotli, &brotli_data_type, br);
|
377
452
|
brotli_deflate_parse_options(br->state, opts);
|
378
453
|
br->io = io;
|
454
|
+
|
455
|
+
#if defined(HAVE_BROTLIENCODERPREPAREDICTIONARY) && defined(HAVE_BROTLIENCODERATTACHPREPAREDDICTIONARY)
|
456
|
+
/* Extract and attach dictionary if provided */
|
457
|
+
if (!NIL_P(opts)) {
|
458
|
+
Check_Type(opts, T_HASH);
|
459
|
+
dict = rb_hash_aref(opts, CSTR2SYM("dictionary"));
|
460
|
+
if (!NIL_P(dict)) {
|
461
|
+
StringValue(dict);
|
462
|
+
BrotliEncoderPreparedDictionary* prepared_dict = BrotliEncoderPrepareDictionary(
|
463
|
+
BROTLI_SHARED_DICTIONARY_RAW,
|
464
|
+
(size_t)RSTRING_LEN(dict),
|
465
|
+
(uint8_t*)RSTRING_PTR(dict),
|
466
|
+
BROTLI_MAX_QUALITY,
|
467
|
+
brotli_alloc,
|
468
|
+
brotli_free,
|
469
|
+
NULL);
|
470
|
+
if (prepared_dict) {
|
471
|
+
BrotliEncoderAttachPreparedDictionary(br->state, prepared_dict);
|
472
|
+
/* Note: dict is owned by encoder after attach, no need to free */
|
473
|
+
} else {
|
474
|
+
rb_raise(rb_eBrotli, "Failed to prepare dictionary for compression");
|
475
|
+
}
|
476
|
+
}
|
477
|
+
}
|
478
|
+
#else
|
479
|
+
/* Check if dictionary is requested but not supported */
|
480
|
+
if (!NIL_P(opts)) {
|
481
|
+
Check_Type(opts, T_HASH);
|
482
|
+
dict = rb_hash_aref(opts, CSTR2SYM("dictionary"));
|
483
|
+
if (!NIL_P(dict)) {
|
484
|
+
rb_raise(rb_eBrotli, "Dictionary support not available in this build");
|
485
|
+
}
|
486
|
+
}
|
487
|
+
#endif
|
488
|
+
|
379
489
|
return self;
|
380
490
|
}
|
381
491
|
|
@@ -469,18 +579,19 @@ Init_brotli(void)
|
|
469
579
|
rb_mBrotli = rb_define_module("Brotli");
|
470
580
|
rb_eBrotli = rb_define_class_under(rb_mBrotli, "Error", rb_eStandardError);
|
471
581
|
rb_global_variable(&rb_eBrotli);
|
472
|
-
rb_define_singleton_method(rb_mBrotli, "deflate",
|
473
|
-
rb_define_singleton_method(rb_mBrotli, "inflate",
|
474
|
-
rb_define_singleton_method(rb_mBrotli, "version",
|
582
|
+
rb_define_singleton_method(rb_mBrotli, "deflate", brotli_deflate, -1);
|
583
|
+
rb_define_singleton_method(rb_mBrotli, "inflate", brotli_inflate, -1);
|
584
|
+
rb_define_singleton_method(rb_mBrotli, "version", brotli_version, 0);
|
585
|
+
id_read = rb_intern("read");
|
475
586
|
// Brotli::Writer
|
476
587
|
id_write = rb_intern("write");
|
477
588
|
id_flush = rb_intern("flush");
|
478
589
|
id_close = rb_intern("close");
|
479
590
|
rb_Writer = rb_define_class_under(rb_mBrotli, "Writer", rb_cObject);
|
480
591
|
rb_define_alloc_func(rb_Writer, rb_writer_alloc);
|
481
|
-
rb_define_method(rb_Writer, "initialize",
|
482
|
-
rb_define_method(rb_Writer, "write",
|
483
|
-
rb_define_method(rb_Writer, "finish",
|
484
|
-
rb_define_method(rb_Writer, "flush",
|
485
|
-
rb_define_method(rb_Writer, "close",
|
592
|
+
rb_define_method(rb_Writer, "initialize", rb_writer_initialize, -1);
|
593
|
+
rb_define_method(rb_Writer, "write", rb_writer_write, 1);
|
594
|
+
rb_define_method(rb_Writer, "finish", rb_writer_finish, 0);
|
595
|
+
rb_define_method(rb_Writer, "flush", rb_writer_flush, 0);
|
596
|
+
rb_define_method(rb_Writer, "close", rb_writer_close, 0);
|
486
597
|
}
|
data/ext/brotli/brotli.h
CHANGED
data/ext/brotli/buffer.c
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
#include "buffer.h"
|
2
|
+
#include "ruby/ruby.h"
|
2
3
|
#define INITIAL (1024)
|
3
4
|
|
4
5
|
buffer_t*
|
5
6
|
create_buffer(const size_t initial) {
|
6
|
-
buffer_t *buffer =
|
7
|
+
buffer_t *buffer = ruby_xmalloc(sizeof(buffer_t));
|
7
8
|
if (buffer == NULL) {
|
8
9
|
return NULL;
|
9
10
|
}
|
@@ -12,7 +13,7 @@ create_buffer(const size_t initial) {
|
|
12
13
|
buffer->expand_count = 0;
|
13
14
|
buffer->expand_ratio = 130;
|
14
15
|
buffer->size = (size_t) (initial > 0 ? initial : INITIAL);
|
15
|
-
buffer->ptr =
|
16
|
+
buffer->ptr = ruby_xmalloc(buffer->size);
|
16
17
|
if (buffer->ptr == NULL) {
|
17
18
|
delete_buffer(buffer);
|
18
19
|
return NULL;
|
@@ -24,17 +25,17 @@ create_buffer(const size_t initial) {
|
|
24
25
|
void
|
25
26
|
delete_buffer(buffer_t* buffer) {
|
26
27
|
if (buffer->ptr != NULL) {
|
27
|
-
|
28
|
+
ruby_xfree(buffer->ptr);
|
28
29
|
buffer->ptr = NULL;
|
29
30
|
}
|
30
|
-
|
31
|
+
ruby_xfree(buffer);
|
31
32
|
}
|
32
33
|
|
33
34
|
static
|
34
35
|
buffer_t*
|
35
36
|
expand_buffer(buffer_t* const buffer, const size_t need) {
|
36
37
|
size_t size = need * buffer->expand_ratio / 100;
|
37
|
-
buffer->ptr =
|
38
|
+
buffer->ptr = ruby_xrealloc(buffer->ptr, size);
|
38
39
|
buffer->size = size;
|
39
40
|
buffer->expand_count += 1;
|
40
41
|
return buffer;
|
data/ext/brotli/extconf.rb
CHANGED
@@ -13,9 +13,18 @@ have_dev_pkg = [
|
|
13
13
|
pkg_config("libbrotlienc")
|
14
14
|
].all? { |e| e }
|
15
15
|
|
16
|
+
have_header("brotli/shared_dictionary.h")
|
17
|
+
have_func("BrotliEncoderPrepareDictionary", "brotli/encode.h")
|
18
|
+
have_func("BrotliEncoderAttachPreparedDictionary", "brotli/encode.h")
|
19
|
+
have_func("BrotliDecoderAttachDictionary", "brotli/decode.h")
|
20
|
+
|
16
21
|
if enable_config("vendor")
|
17
22
|
have_dev_pkg = false
|
18
23
|
Logging::message "Use vendor brotli\n"
|
24
|
+
$defs << "-DHAVE_BROTLI_SHARED_DICTIONARY_H"
|
25
|
+
$defs << "-DHAVE_BROTLIENCODERPREPAREDICTIONARY"
|
26
|
+
$defs << "-DHAVE_BROTLIENCODERATTACHPREPAREDDICTIONARY"
|
27
|
+
$defs << "-DHAVE_BROTLIDECODERATTACHDICTIONARY"
|
19
28
|
end
|
20
29
|
|
21
30
|
$CPPFLAGS << " -DOS_MACOSX" if RbConfig::CONFIG["host_os"] =~ /darwin|mac os/
|
data/lib/brotli/version.rb
CHANGED
data/test/brotli_test.rb
CHANGED
@@ -21,9 +21,10 @@ class BrotliTest < Test::Unit::TestCase
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
test ".version" do
|
25
|
+
assert do
|
26
|
+
puts "Brotli version: #{Brotli.version}"
|
27
|
+
["1.1.0", "1.0.9"].include? Brotli.version
|
27
28
|
end
|
28
29
|
end
|
29
30
|
|
@@ -77,6 +78,16 @@ class BrotliTest < Test::Unit::TestCase
|
|
77
78
|
assert_equal testdata, Brotli.inflate(testdata2)
|
78
79
|
end
|
79
80
|
|
81
|
+
test "works with StringIO" do
|
82
|
+
testdata3 = StringIO.new testdata2
|
83
|
+
assert_equal testdata, Brotli.inflate(testdata3)
|
84
|
+
end
|
85
|
+
|
86
|
+
test "works with File" do
|
87
|
+
f = File.open(File.expand_path(File.join("..", "vendor", "brotli", "tests", "testdata", "alice29.txt.compressed"), __dir__))
|
88
|
+
assert_equal testdata, Brotli.inflate(f)
|
89
|
+
end
|
90
|
+
|
80
91
|
test "raise error when pass insufficient data" do
|
81
92
|
assert_raise Brotli::Error do
|
82
93
|
Brotli.inflate(testdata2[0, 64])
|
@@ -90,6 +101,71 @@ class BrotliTest < Test::Unit::TestCase
|
|
90
101
|
end
|
91
102
|
end
|
92
103
|
|
104
|
+
sub_test_case "dictionary support" do
|
105
|
+
def setup
|
106
|
+
omit "Dictionary tests are skipped" if Brotli.version < "1.1.0"
|
107
|
+
end
|
108
|
+
|
109
|
+
def dictionary_data
|
110
|
+
"The quick brown fox jumps over the lazy dog"
|
111
|
+
end
|
112
|
+
|
113
|
+
def repetitive_data
|
114
|
+
dictionary_data * 10
|
115
|
+
end
|
116
|
+
|
117
|
+
test "deflate with dictionary produces smaller output" do
|
118
|
+
compressed_no_dict = Brotli.deflate(repetitive_data)
|
119
|
+
compressed_with_dict = Brotli.deflate(repetitive_data, dictionary: dictionary_data)
|
120
|
+
|
121
|
+
assert compressed_with_dict.bytesize < compressed_no_dict.bytesize
|
122
|
+
end
|
123
|
+
|
124
|
+
test "inflate with matching dictionary works" do
|
125
|
+
compressed = Brotli.deflate(repetitive_data, dictionary: dictionary_data)
|
126
|
+
decompressed = Brotli.inflate(compressed, dictionary: dictionary_data)
|
127
|
+
|
128
|
+
assert_equal repetitive_data, decompressed
|
129
|
+
end
|
130
|
+
|
131
|
+
test "inflate without dictionary fails on dictionary-compressed data" do
|
132
|
+
compressed = Brotli.deflate(repetitive_data, dictionary: dictionary_data)
|
133
|
+
|
134
|
+
assert_raise Brotli::Error do
|
135
|
+
Brotli.inflate(compressed)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
test "inflate with wrong dictionary fails" do
|
140
|
+
compressed = Brotli.deflate(repetitive_data, dictionary: dictionary_data)
|
141
|
+
wrong_dict = "A completely different dictionary"
|
142
|
+
|
143
|
+
assert_raise Brotli::Error do
|
144
|
+
Brotli.inflate(compressed, dictionary: wrong_dict)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
test "deflate and inflate work with binary dictionary" do
|
149
|
+
binary_dict = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09" * 10
|
150
|
+
data = "Test data " * 100 + binary_dict
|
151
|
+
|
152
|
+
compressed = Brotli.deflate(data, dictionary: binary_dict)
|
153
|
+
decompressed = Brotli.inflate(compressed, dictionary: binary_dict)
|
154
|
+
|
155
|
+
assert_equal data, decompressed
|
156
|
+
end
|
157
|
+
|
158
|
+
test "deflate with dictionary and other options" do
|
159
|
+
compressed = Brotli.deflate(repetitive_data,
|
160
|
+
dictionary: dictionary_data,
|
161
|
+
quality: 11,
|
162
|
+
mode: :text)
|
163
|
+
decompressed = Brotli.inflate(compressed, dictionary: dictionary_data)
|
164
|
+
|
165
|
+
assert_equal repetitive_data, decompressed
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
93
169
|
sub_test_case "Ractor safe" do
|
94
170
|
test "able to invoke non-main ractor" do
|
95
171
|
unless defined? ::Ractor
|
data/test/brotli_writer_test.rb
CHANGED
@@ -33,4 +33,61 @@ class BrotliWriterTest < Test::Unit::TestCase
|
|
33
33
|
Brotli::Writer.new nil
|
34
34
|
end
|
35
35
|
end
|
36
|
+
|
37
|
+
test "works with dictionary" do
|
38
|
+
omit_if(Brotli.version < "1.1.0", "Dictionary tests are skipped")
|
39
|
+
|
40
|
+
dictionary = "The quick brown fox jumps over the lazy dog"
|
41
|
+
data = dictionary * 10
|
42
|
+
|
43
|
+
writer = Brotli::Writer.new @tempfile, dictionary: dictionary
|
44
|
+
assert_equal data.bytesize, writer.write(data)
|
45
|
+
assert_equal @tempfile, writer.close
|
46
|
+
|
47
|
+
@tempfile.open
|
48
|
+
compressed = @tempfile.read
|
49
|
+
decompressed = Brotli.inflate(compressed, dictionary: dictionary)
|
50
|
+
assert_equal data, decompressed
|
51
|
+
end
|
52
|
+
|
53
|
+
test "writer with dictionary produces smaller output" do
|
54
|
+
omit_if(Brotli.version < "1.1.0", "Dictionary tests are skipped")
|
55
|
+
|
56
|
+
dictionary = "The quick brown fox jumps over the lazy dog"
|
57
|
+
data = dictionary * 10
|
58
|
+
|
59
|
+
# Without dictionary
|
60
|
+
@tempfile.rewind
|
61
|
+
writer_no_dict = Brotli::Writer.new @tempfile
|
62
|
+
writer_no_dict.write(data)
|
63
|
+
writer_no_dict.close
|
64
|
+
size_no_dict = @tempfile.size
|
65
|
+
|
66
|
+
# With dictionary
|
67
|
+
tempfile2 = Tempfile.new
|
68
|
+
writer_with_dict = Brotli::Writer.new tempfile2, dictionary: dictionary
|
69
|
+
writer_with_dict.write(data)
|
70
|
+
writer_with_dict.close
|
71
|
+
size_with_dict = tempfile2.size
|
72
|
+
|
73
|
+
assert size_with_dict < size_no_dict
|
74
|
+
|
75
|
+
tempfile2.close!
|
76
|
+
end
|
77
|
+
|
78
|
+
test "writer with dictionary and other options" do
|
79
|
+
omit_if(Brotli.version < "1.1.0", "Dictionary tests are skipped")
|
80
|
+
|
81
|
+
dictionary = "compression dictionary"
|
82
|
+
data = "test data " * 100 + dictionary * 5
|
83
|
+
|
84
|
+
writer = Brotli::Writer.new @tempfile, dictionary: dictionary, quality: 11, mode: :text
|
85
|
+
writer.write(data)
|
86
|
+
writer.finish
|
87
|
+
writer.close
|
88
|
+
|
89
|
+
@tempfile.open
|
90
|
+
decompressed = Brotli.inflate(@tempfile.read, dictionary: dictionary)
|
91
|
+
assert_equal data, decompressed
|
92
|
+
end
|
36
93
|
end
|
data/test/test_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: brotli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- miyucy
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies: []
|
13
12
|
description: Brotli compressor/decompressor
|
14
13
|
email:
|
@@ -23,6 +22,7 @@ files:
|
|
23
22
|
- ".gitignore"
|
24
23
|
- ".gitmodules"
|
25
24
|
- ".rspec"
|
25
|
+
- Dockerfile
|
26
26
|
- Gemfile
|
27
27
|
- LICENSE.txt
|
28
28
|
- README.md
|
@@ -140,7 +140,6 @@ homepage: https://github.com/miyucy/brotli
|
|
140
140
|
licenses:
|
141
141
|
- MIT
|
142
142
|
metadata: {}
|
143
|
-
post_install_message:
|
144
143
|
rdoc_options: []
|
145
144
|
require_paths:
|
146
145
|
- lib
|
@@ -155,8 +154,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
155
154
|
- !ruby/object:Gem::Version
|
156
155
|
version: '0'
|
157
156
|
requirements: []
|
158
|
-
rubygems_version: 3.
|
159
|
-
signing_key:
|
157
|
+
rubygems_version: 3.6.7
|
160
158
|
specification_version: 4
|
161
159
|
summary: Brotli compressor/decompressor
|
162
160
|
test_files:
|