hbase-jruby 0.1.2-java → 0.1.3-java

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.
data/CHANGELOG.md CHANGED
@@ -1,6 +1,12 @@
1
1
  Changelog
2
2
  =========
3
3
 
4
+ 0.1.3
5
+ -----
6
+ - Supports Ruby 1.8 compatibility mode
7
+ - Fix: Correct return value from `HBase::resolve_dependency!`
8
+ - Fix: Appropriately close result scanners
9
+
4
10
  0.1.2
5
11
  -----
6
12
 
data/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  *hbase-jruby* is a Ruby-esque interface for accessing HBase from JRuby.
4
4
 
5
- You can of course just use the native Java APIs of HBase,
6
- but doing so requires a lot of keystrokes even for the most basic operations and
7
- can easily lead to overly verbose code that will be frowned upon by Rubyists.
5
+ With JRuby, you can of course just use the native Java APIs of HBase,
6
+ but doing so requires lots of keystrokes even for the most basic operations and
7
+ can lead to having overly verbose code that will be frowned upon by Rubyists.
8
8
  Anyhow, JRuby is Ruby, not Java, right?
9
9
 
10
10
  *hbase-jruby* provides the followings:
@@ -34,9 +34,10 @@ string = row.string('cf1:b')
34
34
  table.range('rowkey1'..'rowkey9').
35
35
  filter('cf1:a' => 100..200, # cf1:a between 100 and 200
36
36
  'cf1:b' => 'Hello', # cf1:b = 'Hello'
37
- 'cf2:c' => /world/i). # cf2:c matches /world/i
38
- 'cf2:d' => ['foo', /^BAR/i], # cf2:d = 'foo' OR matches /^BAR/i
39
- project('cf1:a', 'cf2').each do |row|
37
+ 'cf2:c' => /world/i, # cf2:c matches /world/i
38
+ 'cf2:d' => ['foo', /^BAR/i]). # cf2:d = 'foo' OR matches /^BAR/i
39
+ project('cf1:a', 'cf2').
40
+ each do |row|
40
41
  puts row.fixnum('cf1:a')
41
42
  end
42
43
 
@@ -46,16 +47,6 @@ table.delete(:rowkey9)
46
47
 
47
48
  ## Installation
48
49
 
49
- Add this line to your application's Gemfile:
50
-
51
- gem 'hbase-jruby'
52
-
53
- And then execute:
54
-
55
- $ bundle
56
-
57
- Or install it yourself as:
58
-
59
50
  $ gem install hbase-jruby
60
51
 
61
52
  ## Setting up
@@ -63,13 +54,10 @@ Or install it yourself as:
63
54
  ### Resolving Hadoop/HBase dependency
64
55
 
65
56
  To be able to access HBase from JRuby, Hadoop/HBase dependency must be satisfied.
66
- This can be done by setting up CLASSPATH variable beforehand
67
- or by `require`ing relevant JAR files after launch.
68
- However, downloading all the JAR files and manually putting them in CLASSPATH is a PITA,
69
- especially when HBase is not installed on local system.
70
-
71
- *hbase-jruby* includes `HBase.resolve_dependency!` helper method,
72
- which resolves Hadoop/HBase dependency.
57
+ This can be done by either setting up CLASSPATH variable beforehand
58
+ or by `require`ing relevant JAR files after launching JRuby.
59
+ However, that's a lot of work, so *hbase-jruby* provides `HBase.resolve_dependency!` helper method,
60
+ which automatically resolves Hadoop/HBase dependency.
73
61
 
74
62
  #### Preconfigured dependencies
75
63
 
@@ -88,10 +76,13 @@ require 'hbase-jruby'
88
76
  HBase.resolve_dependency! 'cdh4.1.2'
89
77
  ```
90
78
 
91
- #### Customized dependencies
79
+ (If you're behind an http proxy, set up your ~/.m2/settings.xml file
80
+ as described in [this page](http://maven.apache.org/guides/mini/guide-proxies.html))
81
+
82
+ #### Custom dependency
92
83
 
93
- If you use another version of HBase and Hadoop,
94
- you can use your own Maven pom.xml file with its customized Hadoop/HBase dependency
84
+ If you use other versions of HBase and Hadoop,
85
+ you can use your own Maven pom.xml file with its Hadoop/HBase dependency.
95
86
 
96
87
  ```ruby
97
88
  HBase.resolve_dependency! '/project/my-hbase/pom.xml'
@@ -99,7 +90,7 @@ HBase.resolve_dependency! '/project/my-hbase/pom.xml'
99
90
 
100
91
  #### Using `hbase classpath` command
101
92
 
102
- If you have HBase installed on your system, it's possible to find the JAR files
93
+ If you have HBase installed on your system, it's possible to locate the JAR files
103
94
  for that local installation with `hbase classpath` command.
104
95
  You can tell `resolve_dependency!` method to do so by passing it special `:hbase` parameter.
105
96
 
@@ -569,17 +560,16 @@ scoped.count
569
560
 
570
561
  ## Basic aggregation using coprocessor
571
562
 
572
- *hbase-jruby* provides a few basic aggregation methods using
573
- the built-in coprocessor called
563
+ You can perform some basic aggregation using the built-in coprocessor called
574
564
  `org.apache.hadoop.hbase.coprocessor.AggregateImplementation`.
575
565
 
576
566
  To enable this feature, call `enable_aggregation!` method,
577
- which will first disable the table, add the coprocessor, then enable it.
567
+ which adds the coprocessor to the table.
578
568
 
579
569
  ```ruby
580
570
  table.enable_aggregation!
581
- # Just a shorthand notation for
582
- # table.add_coprocessor! 'org.apache.hadoop.hbase.coprocessor.AggregateImplementation'
571
+ # Just a shorthand notation for
572
+ # table.add_coprocessor! 'org.apache.hadoop.hbase.coprocessor.AggregateImplementation'
583
573
  ```
584
574
 
585
575
  Then you can get the sum, average, minimum, maximum, row count, and standard deviation
@@ -598,8 +588,8 @@ table.project('cf1:a').aggregate(:row_count)
598
588
  table.project('cf1:a', 'cf1:b').aggregate(:sum)
599
589
  ```
600
590
 
601
- By default, aggregate method assumes the column values are 8-byte integers.
602
- For types other than that, you can pass your own ColumnInterpreter.
591
+ By default, aggregate method assumes that the projected values are 8-byte integers.
592
+ For other data types, you can pass your own ColumnInterpreter.
603
593
 
604
594
  ```ruby
605
595
  table.project('cf1:b').aggregate(:sum, MyColumnInterpreter.new)
@@ -685,26 +675,33 @@ table.remove_coprocessor! cp_class_name1
685
675
 
686
676
  You can perform other types of administrative tasks
687
677
  with Native Java [HBaseAdmin object](http://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/HBaseAdmin.html),
688
- which can be obtained by `HBase#admin` method which will automatically close the object at the end of the given block.
678
+ which can be obtained by `HBase#admin` method. Optionally, a block can be given
679
+ so that the HBaseAdmin object is automatically closed at the end of the given block.
689
680
 
690
681
  ```ruby
691
682
  # Advanced table administration with HBaseAdmin object
692
683
  # http://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/HBaseAdmin.html
693
- hbase.admin do |admin|
694
- # ...
695
- end
696
-
697
- # Without the block
698
684
  admin = hbase.admin
699
685
  # ...
700
686
  admin.close
687
+
688
+ # With the block
689
+ hbase.admin do |admin|
690
+ # ...
691
+ end
701
692
  ```
702
693
 
703
694
  ## Test
704
695
 
705
- ```
696
+ ```bash
697
+ # Bash script
706
698
  export HBASE_JRUBY_TEST_ZK='your-hbaase.domain.net'
707
- rake test
699
+
700
+ # Test both for 1.8 and 1.9
701
+ for v in --1.8 --1.9; do
702
+ export JRUBY_OPTS=$v
703
+ rake test
704
+ done
708
705
  ```
709
706
 
710
707
  ## Contributing
@@ -57,11 +57,13 @@ class HBase
57
57
  end
58
58
 
59
59
  # Load jars
60
- jars.select { |jar| File.exists?(jar) && File.extname(jar) == '.jar' }.select do |jar|
60
+ jars_loaded = jars.select { |jar| File.exists?(jar) && File.extname(jar) == '.jar' }.select do |jar|
61
61
  require jar
62
62
  end
63
63
 
64
64
  Util.import_java_classes!
65
+
66
+ return jars_loaded
65
67
  ensure
66
68
  tempfiles.each { |tempfile| tempfile.unlink rescue nil }
67
69
  end
@@ -7,7 +7,8 @@ module Aggregation
7
7
  # Enables aggregation support for the table
8
8
  # @return [nil]
9
9
  def enable_aggregation!
10
- add_coprocessor! 'org.apache.hadoop.hbase.coprocessor.AggregateImplementation'
10
+ cpc = 'org.apache.hadoop.hbase.coprocessor.AggregateImplementation'
11
+ add_coprocessor! cpc unless has_coprocessor?(cpc)
11
12
  end
12
13
  end
13
14
 
@@ -19,14 +19,18 @@ class Scoped
19
19
  # @return [Fixnum, Bignum] The number of rows in the scope
20
20
  def count
21
21
  cnt = 0
22
- if block_given?
23
- htable.getScanner(filtered_scan).each do |result|
24
- cnt += 1 if yield(Result.send(:new, result))
25
- end
26
- else
27
- htable.getScanner(filtered_scan_minimum).each do
28
- cnt += 1
22
+ begin
23
+ if block_given?
24
+ scanner = htable.getScanner(filtered_scan)
25
+ scanner.each do |result|
26
+ cnt += 1 if yield(Result.send(:new, result))
27
+ end
28
+ else
29
+ scanner = htable.getScanner(filtered_scan_minimum)
30
+ scanner.each { cnt += 1 }
29
31
  end
32
+ ensure
33
+ scanner.close if scanner
30
34
  end
31
35
  cnt
32
36
  end
@@ -111,7 +115,7 @@ class Scoped
111
115
  # table.range(:prefix => ['2010', '2012'])
112
116
  def range *key_range
113
117
  if key_range.last.is_a?(Hash)
114
- prefixes = [*key_range.last[:prefix]]
118
+ prefixes = [*key_range.last[:prefix]].compact
115
119
  raise ArgumentError,
116
120
  "Invalid range. Unknown option(s) specified." unless (key_range.last.keys - [:prefix]).empty?
117
121
  key_range = key_range[0...-1]
@@ -328,7 +332,7 @@ private
328
332
  end if range
329
333
 
330
334
  # Prefix filters
331
- filters += [*build_prefix_filter]
335
+ filters += [*build_prefix_filter].compact
332
336
 
333
337
  # RowFilter must precede the others
334
338
  filters += @filters
@@ -365,7 +369,7 @@ private
365
369
  CompareFilter::CompareOp::LESS_OR_EQUAL
366
370
  when :eq, :==
367
371
  CompareFilter::CompareOp::EQUAL
368
- when :ne, :!=
372
+ when :ne # , :!= # Ruby 1.8 compatibility
369
373
  CompareFilter::CompareOp::NOT_EQUAL
370
374
  else
371
375
  raise ArgumentError, "Unknown operator: #{op}"
@@ -375,7 +379,7 @@ private
375
379
  # XXX TODO Undocumented feature
376
380
  FilterList.new(
377
381
  case op
378
- when :ne, :!=
382
+ when :ne # , :!=
379
383
  FilterList::Operator::MUST_PASS_ALL
380
384
  else
381
385
  FilterList::Operator::MUST_PASS_ONE
@@ -428,7 +432,7 @@ private
428
432
  scan.caching = @caching if @caching
429
433
 
430
434
  # Filters
431
- prefix_filter = [*build_prefix_filter]
435
+ prefix_filter = [*build_prefix_filter].compact
432
436
  filters = prefix_filter + @filters
433
437
  filters += process_projection!(scan)
434
438
 
@@ -462,11 +462,11 @@ private
462
462
  end
463
463
 
464
464
  def const_shortcut base, v, message
465
- vs = v.to_s.upcase.to_sym
466
- #if base.constants.map { |c| base.const_get c }.include?(v)
465
+ vs = v.to_s.upcase
466
+ # const_get doesn't work with symbols in 1.8 compatibility mode
467
467
  if base.constants.map { |c| base.const_get c }.any? { |cv| v == cv }
468
468
  v
469
- elsif base.constants.include? vs
469
+ elsif base.constants.map(&:to_s).include?(vs)
470
470
  base.const_get vs
471
471
  else
472
472
  raise ArgumentError, [message, v.to_s].join(': ')
@@ -89,7 +89,7 @@ module Util
89
89
  raise ArgumentError, "Column family not specified"
90
90
  else
91
91
  cf, cq = KeyValue.parseColumn(col.to_s.to_java_bytes)
92
- cq = JAVA_BYTE_ARRAY_EMPTY if cq.nil? && col.to_s[-1] == ':'
92
+ cq = JAVA_BYTE_ARRAY_EMPTY if cq.nil? && col.to_s[-1, 1] == ':'
93
93
  return cf, cq
94
94
  end
95
95
  end
@@ -171,6 +171,8 @@ module Util
171
171
  org.apache.hadoop.hbase.client.coprocessor.LongColumnInterpreter
172
172
  ]
173
173
  end
174
+
175
+ nil
174
176
  end
175
177
  end
176
178
  end#Util
@@ -1,5 +1,5 @@
1
1
  class HBase
2
2
  module JRuby
3
- VERSION = "0.1.2"
3
+ VERSION = "0.1.3"
4
4
  end
5
5
  end
data/test/helper.rb CHANGED
@@ -11,7 +11,10 @@ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
11
11
  require "hbase-jruby"
12
12
 
13
13
  # Required
14
- HBase.resolve_dependency! 'cdh4.1.2'
14
+ unless HBase.resolve_dependency!('cdh4.1.2').all? { |f| File.exists? f }
15
+ puts "Invalid return value from HBase.resolve_dependency!"
16
+ exit 1
17
+ end
15
18
 
16
19
  class TestHBaseJRubyBase < Test::Unit::TestCase
17
20
  TABLE = 'test_hbase_jruby'
@@ -3,22 +3,23 @@
3
3
  $LOAD_PATH.unshift File.expand_path('..', __FILE__)
4
4
  require 'helper'
5
5
 
6
- class TestAggregation < TestHBaseJRubyBase
6
+ class TestAggregation < TestHBaseJRubyBase
7
7
  def test_aggregation
8
8
  (1..100).each do |idx|
9
9
  @table.put idx, 'cf1:a' => idx, 'cf1:b' => idx * 2
10
10
  end
11
11
 
12
- @table.enable_aggregation!
12
+ assert_nil @table.enable_aggregation!
13
+ assert_nil @table.enable_aggregation! # no prob!
13
14
 
14
15
  lci = org.apache.hadoop.hbase.client.coprocessor.LongColumnInterpreter.new
15
16
  [nil, :fixnum, :int, :integer, lci].each do |ci|
16
- assert_equal 100, @table.project('cf1:a').aggregate(:row_count, *[*ci])
17
- assert_equal 5050, @table.project('cf1:a').aggregate(:sum, *[*ci])
18
- assert_equal 1, @table.project('cf1:a').aggregate(:min, *[*ci])
19
- assert_equal 100, @table.project('cf1:a').aggregate(:max, *[*ci])
20
- assert_equal 50.5, @table.project('cf1:a').aggregate(:avg, *[*ci])
21
- assert_equal 28, @table.project('cf1:a').aggregate(:std, *[*ci]).to_i # FIXME: 28 or 29?
17
+ assert_equal 100, @table.project('cf1:a').aggregate(:row_count, *[*ci].compact)
18
+ assert_equal 5050, @table.project('cf1:a').aggregate(:sum, *[*ci].compact)
19
+ assert_equal 1, @table.project('cf1:a').aggregate(:min, *[*ci].compact)
20
+ assert_equal 100, @table.project('cf1:a').aggregate(:max, *[*ci].compact)
21
+ assert_equal 50.5, @table.project('cf1:a').aggregate(:avg, *[*ci].compact)
22
+ assert_equal 28, @table.project('cf1:a').aggregate(:std, *[*ci].compact).to_i # FIXME: 28 or 29?
22
23
  end
23
24
 
24
25
  [%w[cf1:a cf1:b], %w[cf1]].each do |prj|
data/test/test_scoped.rb CHANGED
@@ -45,8 +45,8 @@ class TestScoped < TestHBaseJRubyBase
45
45
 
46
46
  def test_invalid_filter
47
47
  assert_raise(ArgumentError) { @table.filter(3.14) }
48
- assert_raise(ArgumentError) { @table.filter('cf1:a' => { xxx: 50 }) }
49
- assert_raise(ArgumentError) { @table.filter('cf1:a' => { eq: { 1 => 2 } }) }
48
+ assert_raise(ArgumentError) { @table.filter('cf1:a' => { :xxx => 50 }) }
49
+ assert_raise(ArgumentError) { @table.filter('cf1:a' => { :eq => { 1 => 2 } }) }
50
50
  end
51
51
 
52
52
  def test_each_and_count
@@ -132,13 +132,13 @@ class TestScoped < TestHBaseJRubyBase
132
132
  assert_equal 0, table.filter('cf1:a' => 50, 'cf2:b' => 90...100).to_a.length
133
133
  assert_equal 6, table.filter('cf1:a' => 50..60, 'cf2:b' => 100..110).to_a.length
134
134
  assert_equal 10, table.filter('cf1:a' => { :> => 50, :<= => 60 }).to_a.length
135
- assert_equal 9, table.filter('cf1:a' => { :> => 50, :<= => 60, :!= => 55 }).to_a.length
136
- assert_equal 10, table.filter('cf1:a' => { :>= => 50, :<= => 60, :!= => 55 }).to_a.length
137
- assert_equal 9, table.filter('cf1:a' => { :>= => 50, :< => 60, :!= => 55 }).to_a.length
135
+ assert_equal 9, table.filter('cf1:a' => { :> => 50, :<= => 60, :ne => 55 }).to_a.length
136
+ assert_equal 10, table.filter('cf1:a' => { :>= => 50, :<= => 60, :ne => 55 }).to_a.length
137
+ assert_equal 9, table.filter('cf1:a' => { :>= => 50, :< => 60, :ne => 55 }).to_a.length
138
138
  assert_equal 1, table.filter('cf1:a' => { :> => 50, :<= => 60, :== => 55 }).to_a.length
139
139
  assert_equal 2, table.filter('cf1:a' => { :> => 50, :<= => 60, :== => [55, 57] }).to_a.length
140
- assert_equal 9, table.filter('cf1:a' => { gte: 50, lt: 60, ne: 55 }).to_a.length
141
- assert_equal 7, table.filter('cf1:a' => { gte: 50, lt: 60, ne: [55, 57, 59] }).to_a.length
140
+ assert_equal 9, table.filter('cf1:a' => { :gte => 50, :lt => 60, :ne => 55 }).to_a.length
141
+ assert_equal 7, table.filter('cf1:a' => { :gte => 50, :lt => 60, :ne => [55, 57, 59] }).to_a.length
142
142
 
143
143
  # filter: Hash + additive
144
144
  assert_equal 6, table.filter('cf1:a' => 50..60).filter('cf2:b' => 100..110).to_a.length
@@ -149,7 +149,7 @@ class TestScoped < TestHBaseJRubyBase
149
149
  assert_equal 3, table.filter(ColumnPaginationFilter.new(3, 1)).first.to_hash.keys.length
150
150
 
151
151
  # filter: Java filter list
152
- import org.apache.hadoop.hbase.filter.FilterList
152
+ import org.apache.hadoop.hbase.filter.FilterList
153
153
  import org.apache.hadoop.hbase.filter.ColumnRangeFilter
154
154
  assert_equal %w[cf2:b cf3:c],
155
155
  table.filter(FilterList.new [
@@ -216,7 +216,7 @@ class TestScoped < TestHBaseJRubyBase
216
216
 
217
217
  hash = @table.get('rowkey').to_hash(
218
218
  HBase::ColumnKey('cf1', 1) => :fixnum,
219
- HBase::ColumnKey('cf1', 2) => :fixnum,
219
+ HBase::ColumnKey('cf1', 2) => :fixnum
220
220
  )
221
221
  assert_equal 1, hash[HBase::ColumnKey(:cf1, 1)]
222
222
  assert_equal 2, hash[HBase::ColumnKey(:cf1, 2)]
@@ -325,12 +325,12 @@ class TestScoped < TestHBaseJRubyBase
325
325
  @table.put idx, 'cf1:a' => idx % 10, 'cf2:b' => 'Hello'
326
326
  end
327
327
 
328
- assert_equal 20, @table.filter('cf1:a' => { lte: 1 }, 'cf2:b' => 'Hello').count
329
- assert_equal 2, @table.while( 'cf1:a' => { lte: 1 }, 'cf2:b' => 'Hello').count
328
+ assert_equal 20, @table.filter('cf1:a' => { :lte => 1 }, 'cf2:b' => 'Hello').count
329
+ assert_equal 2, @table.while( 'cf1:a' => { :lte => 1 }, 'cf2:b' => 'Hello').count
330
330
 
331
331
  # while == filter for gets
332
- assert_equal 20, @table.filter('cf1:a' => { lte: 1 }, 'cf2:b' => 'Hello').get((0..100).to_a).compact.length
333
- assert_equal 20, @table.while( 'cf1:a' => { lte: 1 }, 'cf2:b' => 'Hello').get((0..100).to_a).compact.length
332
+ assert_equal 20, @table.filter('cf1:a' => { :lte => 1 }, 'cf2:b' => 'Hello').get((0..100).to_a).compact.length
333
+ assert_equal 20, @table.while( 'cf1:a' => { :lte => 1 }, 'cf2:b' => 'Hello').get((0..100).to_a).compact.length
334
334
  end
335
335
 
336
336
  def test_min_max
@@ -356,7 +356,7 @@ class TestScoped < TestHBaseJRubyBase
356
356
  assert_equal 26, @table.filter('cf1:a' => /g$/).count
357
357
  assert_equal 2, @table.filter('cf1:a' => /gg|ff/).count
358
358
  assert_equal 28, @table.filter('cf1:a' => ['aa', 'cc', /^g/]).count
359
- assert_equal 54, @table.filter('cf1:a' => ['aa', 'cc', /^g/, { gte: 'xa', lt: 'y'}]).count
359
+ assert_equal 54, @table.filter('cf1:a' => ['aa', 'cc', /^g/, { :gte => 'xa', :lt => 'y'}]).count
360
360
  end
361
361
  end
362
362
 
data/test/test_table.rb CHANGED
@@ -137,7 +137,7 @@ class TestScoped < TestHBaseJRubyBase
137
137
  'cf1:c' => 3.14,
138
138
  'cf2:d' => :world,
139
139
  'cf2:e' => false,
140
- 'cf3:f' => BigDecimal.new(1234567890123456789012345678901234567890),
140
+ 'cf3:f' => BigDecimal.new('1234567890123456789012345678901234567890'),
141
141
  'cf3' => true
142
142
  }
143
143
  schema = {
@@ -3,7 +3,7 @@
3
3
  $LOAD_PATH.unshift File.expand_path('..', __FILE__)
4
4
  require 'helper'
5
5
 
6
- class TestTableAdmin < TestHBaseJRubyBase
6
+ class TestTableAdmin < TestHBaseJRubyBase
7
7
  def teardown
8
8
  @table.drop! if @table.exists?
9
9
  end
@@ -11,7 +11,7 @@ class TestTableAdmin < TestHBaseJRubyBase
11
11
  def test_create_table_symbol_string
12
12
  t = @hbase.table(:test_hbase_jruby_create_table)
13
13
  t.drop! if t.exists?
14
- [ :cf, 'cf', :cf => {} ].each do |cf|
14
+ [ :cf, 'cf', {:cf => {}} ].each do |cf|
15
15
  assert_false t.exists?
16
16
  t.create! cf
17
17
  assert t.exists?
metadata CHANGED
@@ -2,14 +2,14 @@
2
2
  name: hbase-jruby
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.1.2
5
+ version: 0.1.3
6
6
  platform: java
7
7
  authors:
8
8
  - Junegunn Choi
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-28 00:00:00.000000000 Z
12
+ date: 2012-11-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: test-unit