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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 97953895465e26d5937e5959f86954aa58de2d2221301bc64ff639e682565605
4
- data.tar.gz: 4f26bccb546a19c743eea5b0fd9207f8e623bfd73f05e08f3dae275b76c51482
3
+ metadata.gz: c3722424560d3f3491399e46108213ae088a809ca502fe2c9f8a83d5c9383434
4
+ data.tar.gz: 4584239a0597b0c2e5cf47ba0d6c8a96e0198cc8af7c7b217f8bf1312bab56d8
5
5
  SHA512:
6
- metadata.gz: b5e79aee2804a948da509247c56b1f79e9248927ec6430319b1d2b6202973ed41bca1ceba215f8d4954ad98a52637a91e49bfa316af4ab3d35fcb779f0e94aa3
7
- data.tar.gz: 46d42e9297f79aef7481b068e43da47c60d45b0f781a0ceb1fc7626cdb9c59bc306feaab0b05321cba17e23256e492814f340725db8fb21dc9e4c84198507bf5
6
+ metadata.gz: ca8b9c2bfae436c8b19d657948e0a2f2a3137d05ef9af070f2de4625ffcbe9cb7dd2e6eb011c8e475ea16a74ac5825b9dc074ab95b809e68ce722a06ce72798d
7
+ data.tar.gz: b5864a5aeb5630e8ad9b491621d89aa4c12f65131a261e49b0499d35cd5b32d43288ddbccb10cfe2ab7201d140e1ae311a0cbb1328cc0a010fad4ad775db8f2d
@@ -7,7 +7,7 @@ jobs:
7
7
  strategy:
8
8
  fail-fast: false
9
9
  matrix:
10
- ruby: [2.7, 3.1, 3.2, 3.3]
10
+ ruby: [3.1, 3.2, 3.3, 3.4]
11
11
  runs-on: ubuntu-latest
12
12
  steps:
13
13
  - uses: actions/checkout@v4
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
- See test/brotli_test.rb
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 self, VALUE str)
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
- args.buffer = create_buffer(BUFSIZ);
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", RUBY_METHOD_FUNC(brotli_deflate), -1);
473
- rb_define_singleton_method(rb_mBrotli, "inflate", RUBY_METHOD_FUNC(brotli_inflate), 1);
474
- rb_define_singleton_method(rb_mBrotli, "version", RUBY_METHOD_FUNC(brotli_version), 0);
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", RUBY_METHOD_FUNC(rb_writer_initialize), -1);
482
- rb_define_method(rb_Writer, "write", RUBY_METHOD_FUNC(rb_writer_write), 1);
483
- rb_define_method(rb_Writer, "finish", RUBY_METHOD_FUNC(rb_writer_finish), 0);
484
- rb_define_method(rb_Writer, "flush", RUBY_METHOD_FUNC(rb_writer_flush), 0);
485
- rb_define_method(rb_Writer, "close", RUBY_METHOD_FUNC(rb_writer_close), 0);
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
@@ -10,6 +10,9 @@
10
10
 
11
11
  #include "brotli/encode.h"
12
12
  #include "brotli/decode.h"
13
+ #ifdef HAVE_BROTLI_SHARED_DICTIONARY_H
14
+ #include "brotli/shared_dictionary.h"
15
+ #endif
13
16
  #include "buffer.h"
14
17
 
15
18
  #endif /* BROTLI_H */
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 = malloc(sizeof(buffer_t));
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 = malloc(buffer->size);
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
- free(buffer->ptr);
28
+ ruby_xfree(buffer->ptr);
28
29
  buffer->ptr = NULL;
29
30
  }
30
- free(buffer);
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 = realloc(buffer->ptr, size);
38
+ buffer->ptr = ruby_xrealloc(buffer->ptr, size);
38
39
  buffer->size = size;
39
40
  buffer->expand_count += 1;
40
41
  return buffer;
@@ -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/
@@ -1,3 +1,3 @@
1
1
  module Brotli
2
- VERSION = '0.5.0'
2
+ VERSION = '0.7.0'
3
3
  end
data/test/brotli_test.rb CHANGED
@@ -21,9 +21,10 @@ class BrotliTest < Test::Unit::TestCase
21
21
  end
22
22
  end
23
23
 
24
- sub_test_case ".version" do
25
- test "returns string" do
26
- assert_equal "1.1.0", Brotli.version
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
@@ -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
@@ -6,3 +6,4 @@ require "brotli"
6
6
  require "test-unit"
7
7
  require "test/unit/rr"
8
8
  require "rantly/testunit_extensions"
9
+ require "stringio"
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.5.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: 2024-01-31 00:00:00.000000000 Z
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.5.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: