oklahoma_mixer 0.1.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.
- data/AUTHORS.rdoc +1 -0
- data/CHANGELOG.rdoc +7 -0
- data/INSTALL.rdoc +22 -0
- data/MIT-LICENSE +19 -0
- data/README.rdoc +48 -0
- data/Rakefile +61 -0
- data/TODO.rdoc +11 -0
- data/lib/oklahoma_mixer/array_list/c.rb +15 -0
- data/lib/oklahoma_mixer/array_list.rb +23 -0
- data/lib/oklahoma_mixer/error.rb +6 -0
- data/lib/oklahoma_mixer/extensible_string/c.rb +18 -0
- data/lib/oklahoma_mixer/extensible_string.rb +27 -0
- data/lib/oklahoma_mixer/hash_database/c.rb +128 -0
- data/lib/oklahoma_mixer/hash_database.rb +335 -0
- data/lib/oklahoma_mixer/utilities.rb +88 -0
- data/lib/oklahoma_mixer.rb +33 -0
- data/test/binary_data_test.rb +41 -0
- data/test/file_system_test.rb +74 -0
- data/test/getting_and_setting_keys_test.rb +218 -0
- data/test/iteration_test.rb +94 -0
- data/test/test_helper.rb +51 -0
- data/test/top_level_interface_test.rb +47 -0
- data/test/transactions_test.rb +66 -0
- data/test/tuning_test.rb +173 -0
- metadata +115 -0
@@ -0,0 +1,218 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class TestGettingAndSettingKeys < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@db = hdb
|
6
|
+
end
|
7
|
+
|
8
|
+
def teardown
|
9
|
+
@db.close
|
10
|
+
remove_db_files
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_a_key_value_pair_can_be_stored_and_fetched_from_the_database
|
14
|
+
assert_equal("value", @db.store("key", "value"))
|
15
|
+
assert_equal("value", @db.fetch("key")) # later
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_both_keys_and_values_are_converted_to_strings
|
19
|
+
assert_equal(42, @db.store(:num, 42))
|
20
|
+
assert_equal("42", @db.fetch("num")) # effectively the same key
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_fetching_a_missing_value_fails_with_an_index_error
|
24
|
+
assert_raise(IndexError) do
|
25
|
+
@db.fetch(:missing)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_fetch_can_return_a_default_for_a_missing_value
|
30
|
+
assert_equal(42, @db.fetch(:missing, 42))
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_fetch_can_run_a_block_returning_its_result_for_a_missing_value
|
34
|
+
assert_equal(:missing, @db.fetch(:missing) { |key| key })
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_fetching_with_a_block_overrides_a_default_and_triggers_a_warning
|
38
|
+
warning = capture_stderr do
|
39
|
+
assert_equal(42, @db.fetch(:missing, 13) { 42 })
|
40
|
+
end
|
41
|
+
assert(!warning.empty?, "A warning was not issued for a default and block")
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_storing_with_keep_mode_adds_a_value_only_if_it_didnt_already_exist
|
45
|
+
assert(@db.store(:key, :new, :keep), "Failed to store a new key")
|
46
|
+
assert_equal("new", @db.fetch(:key))
|
47
|
+
assert(!@db.store(:key, :replace, :keep), "Replaced an existing key")
|
48
|
+
assert_equal("new", @db.fetch(:key))
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_storing_with_cat_mode_concatenates_content_onto_an_existing_value
|
52
|
+
assert_equal("One", @db.store(:list, "One", :cat))
|
53
|
+
assert_equal("One", @db.fetch(:list))
|
54
|
+
assert_equal(", Two", @db.store(:list, ", Two", :cat))
|
55
|
+
assert_equal("One, Two", @db.fetch(:list))
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_storing_with_async_mode_is_buffered_writing
|
59
|
+
args = capture_args(OKMixer::HashDatabase::C, :putasync) do
|
60
|
+
assert_equal(:buffered, @db.store(:key, :buffered, :async))
|
61
|
+
end
|
62
|
+
assert_instance_of(FFI::Pointer, args[0])
|
63
|
+
assert_equal(["key", 3, "buffered", 8], args[1..-1])
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_storing_with_add_mode_adds_to_an_existing_value
|
67
|
+
assert_equal(0, @db.store(:i, 0, :add))
|
68
|
+
assert_equal(1, @db.store(:i, 1, :add))
|
69
|
+
assert_equal(2, @db.store(:i, 1, :add))
|
70
|
+
assert_equal(1, @db.store(:i, -1, :add))
|
71
|
+
|
72
|
+
assert_in_delta(1.5, @db.store(:f, 1.5, :add), 2 ** -20)
|
73
|
+
assert_in_delta(3.5, @db.store(:f, 2.0, :add), 2 ** -20)
|
74
|
+
assert_in_delta(2.5, @db.store(:f, -1.0, :add), 2 ** -20)
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_storing_with_a_block_allows_duplicate_resolution
|
78
|
+
@db[:key] = :old
|
79
|
+
assert_equal( :new, @db.store(:key, :new) { |key, old, new|
|
80
|
+
"#{key}=#{old}&#{new}" } )
|
81
|
+
assert_equal("key=old&new", @db[:key])
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_storing_with_a_block_overrides_a_mode_and_triggers_a_warning
|
85
|
+
warning = capture_stderr do
|
86
|
+
assert_equal(:new, @db.store(:key, :new, :async) { |key, old, new| })
|
87
|
+
end
|
88
|
+
assert(!warning.empty?, "A warning was not issued for a mode and block")
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_store_and_fetch_can_also_be_used_through_the_indexing_brackets
|
92
|
+
assert_equal(42, @db[:num] = 42)
|
93
|
+
assert_equal("42", @db[:num])
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_indexing_returns_nil_instead_of_failing_with_index_error
|
97
|
+
assert_nil(@db[:missing])
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_a_default_can_be_set_for_indexing_to_return
|
101
|
+
@db.default = 42
|
102
|
+
assert_equal(42, @db[:missing])
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_the_indexing_default_will_be_run_if_it_is_a_proc
|
106
|
+
@db.default = lambda { |key| "is #{key}" }
|
107
|
+
assert_equal("is missing", @db[:missing])
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_the_indexing_default_for_a_given_key_can_be_retrieved
|
111
|
+
@db.default = lambda { |key| "is #{key}" }
|
112
|
+
assert_equal("is ", @db.default)
|
113
|
+
assert_equal("is missing", @db.default(:missing))
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_the_indexing_default_can_be_changed
|
117
|
+
assert_nil(@db[:missing])
|
118
|
+
assert_equal(42, @db.default = 42)
|
119
|
+
assert_equal(42, @db[:missing])
|
120
|
+
proc = lambda { |key| fail RuntimeError, "%p not found" % [key]}
|
121
|
+
assert_equal(proc, @db.default = proc)
|
122
|
+
error = assert_raise(RuntimeError) { @db[:missing] }
|
123
|
+
assert_equal(":missing not found", error.message)
|
124
|
+
end
|
125
|
+
|
126
|
+
def test_include_and_aliases_can_be_used_to_check_for_the_existance_of_a_key
|
127
|
+
@db[:exist] = true
|
128
|
+
%w[include? has_key? key? member?].each do |query|
|
129
|
+
assert(@db.send(query, :exist), "Failed to detect an existing key")
|
130
|
+
assert(!@db.send(query, :missing), "Failed to detect an missing key")
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def test_update_sets_multiple_values_at_once_overwriting_old_values
|
135
|
+
@db[:b] = "old_b"
|
136
|
+
assert_equal(@db, @db.update(:a => "new_a", :b => "new_b", :c => "new_c"))
|
137
|
+
assert_equal(%w[new_a new_b new_c], @db.values_at(:a, :b, :c))
|
138
|
+
end
|
139
|
+
|
140
|
+
def test_update_can_be_passed_a_block_for_handling_duplicates
|
141
|
+
@db[:b] = "old"
|
142
|
+
assert_equal( @db, @db.update( :a => "new",
|
143
|
+
:b => "new",
|
144
|
+
:c => "new") { |key, old, new|
|
145
|
+
"#{key}=#{old}&#{new}" } )
|
146
|
+
assert_equal(%w[new b=old&new new], @db.values_at(:a, :b, :c))
|
147
|
+
end
|
148
|
+
|
149
|
+
def test_values_at_can_be_used_to_retrieve_multiple_values_at_once
|
150
|
+
@db[:a] = 1
|
151
|
+
@db[:c] = 2
|
152
|
+
assert_equal([ ], @db.values_at)
|
153
|
+
assert_equal(["1", nil, "2"], @db.values_at(:a, :b, :c))
|
154
|
+
end
|
155
|
+
|
156
|
+
def test_values_at_supports_defaults
|
157
|
+
@db.default = 42
|
158
|
+
@db[:a] = 1
|
159
|
+
@db[:c] = 2
|
160
|
+
assert_equal(["1", 42, "2"], @db.values_at(:a, :b, :c))
|
161
|
+
end
|
162
|
+
|
163
|
+
def test_keys_returns_all_keys_in_the_database
|
164
|
+
@db.update(:a => 1, :b => 2, :c => 3)
|
165
|
+
assert_equal(%w[a b c], @db.keys.sort)
|
166
|
+
end
|
167
|
+
|
168
|
+
def test_keys_can_take_a_prefix_which_all_returned_keys_must_start_with
|
169
|
+
@db.update( "names:1:first" => "James",
|
170
|
+
"names:1:last" => "Gray",
|
171
|
+
"names:2:first" => "Other",
|
172
|
+
"names:2:last" => "Guy" )
|
173
|
+
assert_equal( %w[names:1:first names:1:last],
|
174
|
+
@db.keys(:prefix => "names:1:").sort )
|
175
|
+
end
|
176
|
+
|
177
|
+
def test_keys_can_take_a_limit_of_keys_to_return
|
178
|
+
@db.update(:a => 1, :b => 2, :c => 3)
|
179
|
+
keys = @db.keys(:limit => 2)
|
180
|
+
assert_equal(2, keys.size)
|
181
|
+
keys.each do |key|
|
182
|
+
assert(%w[a b c].include?(key), "Key wasn't in known keys")
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def test_values_returns_all_values_in_the_database
|
187
|
+
@db.update(:a => 1, :b => 2, :c => 3)
|
188
|
+
assert_equal(%w[1 2 3], @db.values.sort)
|
189
|
+
end
|
190
|
+
|
191
|
+
def test_delete_removes_a_key_from_the_database
|
192
|
+
@db[:key] = :value
|
193
|
+
assert_equal("value", @db.delete(:key))
|
194
|
+
assert_nil(@db[:key])
|
195
|
+
end
|
196
|
+
|
197
|
+
def test_delete_returns_nil_for_a_missing_key
|
198
|
+
assert_nil(@db.delete(:missing))
|
199
|
+
end
|
200
|
+
|
201
|
+
def test_delete_can_be_passed_a_block_to_handle_missing_keys
|
202
|
+
assert_equal(42, @db.delete(:missing) { 42 })
|
203
|
+
end
|
204
|
+
|
205
|
+
def test_clear_removes_all_keys_from_the_database
|
206
|
+
@db.update(:a => 1, :b => 2, :c => 3)
|
207
|
+
assert_equal(@db, @db.clear)
|
208
|
+
assert_equal([nil, nil, nil], @db.values_at(:a, :b, :c))
|
209
|
+
end
|
210
|
+
|
211
|
+
def test_size_and_length_return_the_count_of_key_value_pairs_in_the_database
|
212
|
+
assert_equal(0, @db.size)
|
213
|
+
assert_equal(0, @db.length)
|
214
|
+
@db.update(:a => 1, :b => 2, :c => 3)
|
215
|
+
assert_equal(3, @db.size)
|
216
|
+
assert_equal(3, @db.length)
|
217
|
+
end
|
218
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class TestIteration < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@db = hdb
|
6
|
+
@keys = %w[a b c]
|
7
|
+
@values = @keys.map { |key| key * 2 }
|
8
|
+
@keys.zip(@values) do |key, value|
|
9
|
+
@db[key] = value
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def teardown
|
14
|
+
@db.close
|
15
|
+
remove_db_files
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_each_key_iterates_over_all_keys_in_the_database
|
19
|
+
@db.each_key do |key|
|
20
|
+
@keys.delete(key)
|
21
|
+
end
|
22
|
+
assert(@keys.empty?, "All keys were not iterated over")
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_each_iterates_over_all_key_value_pairs_in_arrays
|
26
|
+
@db.each do |key_value_array|
|
27
|
+
assert_instance_of(Array, key_value_array)
|
28
|
+
assert_equal(2, key_value_array.size)
|
29
|
+
key, value = key_value_array
|
30
|
+
assert_equal(key * 2, value)
|
31
|
+
@keys.delete(key)
|
32
|
+
@values.delete(value)
|
33
|
+
end
|
34
|
+
assert( @keys.empty? && @values.empty?,
|
35
|
+
"All key/value pairs were not iterated over" )
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_the_arrays_passed_to_each_can_be_split
|
39
|
+
@db.each do |key, value|
|
40
|
+
@keys.delete(key)
|
41
|
+
@values.delete(value)
|
42
|
+
end
|
43
|
+
assert( @keys.empty? && @values.empty?,
|
44
|
+
"All key/value pairs were not iterated over" )
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_each_pair_is_an_alias_for_each
|
48
|
+
each_arrays = [ ]
|
49
|
+
@db.each do |array|
|
50
|
+
each_arrays << array
|
51
|
+
end
|
52
|
+
@db.each_pair do |array|
|
53
|
+
each_arrays.delete(array)
|
54
|
+
end
|
55
|
+
each_keys_and_values = [ ]
|
56
|
+
@db.each do |key, value|
|
57
|
+
each_keys_and_values << [key, value]
|
58
|
+
end
|
59
|
+
@db.each_pair do |key, value|
|
60
|
+
each_keys_and_values.delete([key, value])
|
61
|
+
end
|
62
|
+
assert( each_arrays.empty? && each_keys_and_values.empty?,
|
63
|
+
"The iterations did not match" )
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_each_value_iterates_over_all_values_in_the_database
|
67
|
+
@db.each_value do |value|
|
68
|
+
@values.delete(value)
|
69
|
+
end
|
70
|
+
assert(@values.empty?, "All values were not iterated over")
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_the_standard_iterators_are_supported
|
74
|
+
assert_kind_of(Enumerable, @db)
|
75
|
+
|
76
|
+
# examples
|
77
|
+
assert_equal(%w[b bb], @db.find { |_, value| value == "bb" })
|
78
|
+
assert_nil(@db.find { |_, value| value == "dd" })
|
79
|
+
assert_equal(%w[aaa bbb ccc], @db.map { |key, value| key + value }.sort)
|
80
|
+
assert( @db.any? { |_, value| value.include? "c" },
|
81
|
+
"A value was not found during iteration" )
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_delete_if_removes_all_keys_for_which_the_block_returns_true
|
85
|
+
@db.delete_if { |key, _| key != "a" }
|
86
|
+
assert_equal([%w[a aa]], @db.to_a)
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_iterators_return_self_to_match_hash_interface
|
90
|
+
%w[each_key each each_pair each_value delete_if].each do |iterator|
|
91
|
+
assert_equal(@db, @db.send(iterator) { })
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require "stringio"
|
2
|
+
require "test/unit"
|
3
|
+
|
4
|
+
require "rubygems" # for FFI
|
5
|
+
|
6
|
+
require "oklahoma_mixer"
|
7
|
+
|
8
|
+
module TestHelper
|
9
|
+
def capture_stderr
|
10
|
+
$stderr = StringIO.new
|
11
|
+
yield
|
12
|
+
$stderr.string
|
13
|
+
ensure
|
14
|
+
$stderr = STDERR
|
15
|
+
end
|
16
|
+
|
17
|
+
def capture_args(receiver, method)
|
18
|
+
called_with = nil
|
19
|
+
singleton = class << receiver; self end
|
20
|
+
singleton.send(:define_method, method) do |*args|
|
21
|
+
called_with = args
|
22
|
+
end
|
23
|
+
yield
|
24
|
+
called_with
|
25
|
+
end
|
26
|
+
|
27
|
+
def db_path(ext)
|
28
|
+
File.join( File.dirname(__FILE__),
|
29
|
+
"#{self.class.to_s.sub(/\ATest/, '').downcase}.#{ext}" )
|
30
|
+
end
|
31
|
+
|
32
|
+
def hdb(*args)
|
33
|
+
db = OKMixer::HashDatabase.new(db_path("tch"), *args)
|
34
|
+
if block_given?
|
35
|
+
begin
|
36
|
+
yield db
|
37
|
+
ensure
|
38
|
+
db.close
|
39
|
+
end
|
40
|
+
else
|
41
|
+
db
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def remove_db_files
|
46
|
+
Dir.glob(db_path("*")) do |file|
|
47
|
+
File.unlink(file)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
Test::Unit::TestCase.send(:include, TestHelper)
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class TestTopLevelInterface < Test::Unit::TestCase
|
4
|
+
def teardown
|
5
|
+
remove_db_files
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_the_version_constant_contains_the_current_version_number
|
9
|
+
assert_match(/\A\d\.\d\.\d\z/, OKMixer::VERSION)
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_open_of_a_tch_extension_file_creates_a_hash_database
|
13
|
+
OKMixer.open(db_path("tch")) do |db|
|
14
|
+
assert_instance_of(OKMixer::HashDatabase, db)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_open_of_an_unrecognized_extension_fails_with_an_error
|
19
|
+
assert_raise(ArgumentError) do
|
20
|
+
OKMixer.open("unrecognized")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_open_with_a_block_returns_the_value_of_the_last_expression
|
25
|
+
assert_equal(42, OKMixer.open(db_path("tch")) { 42 })
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_open_with_a_block_automatically_closes_the_database
|
29
|
+
OKMixer.open(db_path("tch")) do |db|
|
30
|
+
db[:data] = :saved
|
31
|
+
end
|
32
|
+
OKMixer.open(db_path("tch")) do |db| # we can reopen since the lock is gone
|
33
|
+
assert_equal("saved", db[:data])
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_open_without_a_block_returns_the_still_open_database
|
38
|
+
db = OKMixer.open(db_path("tch"))
|
39
|
+
assert_nil(db[:unset]) # we can fetch a value since it's still open
|
40
|
+
ensure
|
41
|
+
db.close if db
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_ok_mixer_is_a_shortcut_for_oklahoma_mixer
|
45
|
+
assert_same(OklahomaMixer, OKMixer)
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class TestTransactions < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@db = hdb
|
6
|
+
@db[:data] = "before transaction"
|
7
|
+
end
|
8
|
+
|
9
|
+
def teardown
|
10
|
+
@db.close
|
11
|
+
remove_db_files
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_a_transaction_returns_the_value_of_the_last_expression
|
15
|
+
assert_equal(42, @db.transaction { 42 })
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_a_transaction_is_automatically_committed_if_the_block_finishes
|
19
|
+
@db.transaction do
|
20
|
+
@db[:data] = "after transaction"
|
21
|
+
end
|
22
|
+
assert_equal("after transaction", @db[:data])
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_failing_with_an_error_aborts_the_transaction
|
26
|
+
assert_raise(RuntimeError) do
|
27
|
+
@db.transaction do
|
28
|
+
@db[:data] = "after transaction"
|
29
|
+
fail "Testing abort"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
assert_equal("before transaction", @db[:data])
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_a_transaction_can_be_committed_before_the_end_of_the_block
|
36
|
+
@db.transaction do
|
37
|
+
@db[:data] = "after transaction"
|
38
|
+
@db.commit
|
39
|
+
@db[:other] = "set" # not executed
|
40
|
+
end
|
41
|
+
assert_equal("after transaction", @db[:data])
|
42
|
+
assert_nil(@db[:other])
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_a_transaction_can_be_aborted_before_the_end_of_the_block
|
46
|
+
@db.transaction do
|
47
|
+
@db[:data] = "after transaction"
|
48
|
+
@db.abort
|
49
|
+
@db[:other] = "set" # not executed
|
50
|
+
end
|
51
|
+
assert_equal("before transaction", @db[:data])
|
52
|
+
assert_nil(@db[:other])
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_commit_fails_with_an_error_if_called_outside_a_transaction
|
56
|
+
assert_raise(OKMixer::Error::TransactionError) do
|
57
|
+
@db.commit
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_abort_fails_with_an_error_if_called_outside_a_transaction
|
62
|
+
assert_raise(OKMixer::Error::TransactionError) do
|
63
|
+
@db.abort
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/test/tuning_test.rb
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class TestTuning < Test::Unit::TestCase
|
4
|
+
def teardown
|
5
|
+
remove_db_files
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_a_mutex_can_be_activated_as_the_database_is_created
|
9
|
+
assert_option_calls([:setmutex], :mutex => true)
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_a_bucket_array_size_can_be_set_with_other_tuning_defaults
|
13
|
+
size = rand(1_000) + 1
|
14
|
+
assert_option_calls([:tune, size, -1, -1, 0xFF], :bnum => size)
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_bucket_array_size_is_converted_to_an_int
|
18
|
+
assert_option_calls([:tune, 42, -1, -1, 0xFF], :bnum => "42")
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_a_record_alignment_power_can_be_set_with_other_tuning_defaults
|
22
|
+
pow = rand(10) + 1
|
23
|
+
assert_option_calls([:tune, 0, pow, -1, 0xFF], :apow => pow)
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_record_alignment_power_is_converted_to_an_int
|
27
|
+
assert_option_calls([:tune, 0, 42, -1, 0xFF], :apow => "42")
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_a_max_free_block_power_can_be_set_with_other_tuning_defaults
|
31
|
+
pow = rand(10) + 1
|
32
|
+
assert_option_calls([:tune, 0, -1, pow, 0xFF], :fpow => pow)
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_max_free_block_power_is_converted_to_an_int
|
36
|
+
assert_option_calls([:tune, 0, -1, 42, 0xFF], :fpow => "42")
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_options_can_be_set_with_other_tuning_defaults
|
40
|
+
assert_option_calls([:tune, 0, -1, -1, 1 | 2], :opts => "ld")
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_options_is_a_string_of_characters_mapped_to_enums_and_ored_together
|
44
|
+
opts = { "l" => OKMixer::HashDatabase::C::OPTS[:HDBTLARGE],
|
45
|
+
"b" => OKMixer::HashDatabase::C::OPTS[:HDBTBZIP] }
|
46
|
+
assert_option_calls( [ :tune, 0, -1, -1,
|
47
|
+
opts.values.inject(0) { |o, v| o | v } ],
|
48
|
+
:opts => opts.keys.join )
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_the_options_string_is_not_case_sensative
|
52
|
+
assert_option_calls([:tune, 0, -1, -1, 1 | 2], :opts => "ld")
|
53
|
+
assert_option_calls([:tune, 0, -1, -1, 1 | 2], :opts => "LD")
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_unknown_options_are_ignored_with_a_warning
|
57
|
+
warning = capture_stderr do
|
58
|
+
assert_option_calls([:tune, 0, -1, -1, 1 | 8], :opts => "ltu")
|
59
|
+
end
|
60
|
+
assert(!warning.empty?, "A warning was not issued for an unknown option")
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_multiple_tuning_parameters_can_be_set_at_the_same_time
|
64
|
+
size = rand(1_000) + 1
|
65
|
+
assert_option_calls( [:tune, size, -1, -1, 1 | 2],
|
66
|
+
:bnum => size, :opts => "ld" )
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_optimize_allows_the_adjustment_of_tune_options_for_an_open_database
|
70
|
+
hdb do |db|
|
71
|
+
args = capture_args(OKMixer::HashDatabase::C, :optimize) do
|
72
|
+
db.optimize(:apow => "42", :opts => "ld")
|
73
|
+
end
|
74
|
+
assert_instance_of(FFI::Pointer, args[0])
|
75
|
+
assert_equal([0, 42, -1, 1 | 2], args[1..-1])
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_limit_for_cached_records_can_be_set
|
80
|
+
limit = rand(1_000) + 1
|
81
|
+
assert_option_calls([:setcache, limit], :rcnum => limit)
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_limit_for_cached_records_is_converted_to_an_int
|
85
|
+
assert_option_calls([:setcache, 42], :rcnum => "42")
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_a_size_can_be_set_for_extra_mapped_memory
|
89
|
+
size = rand(1_000) + 1
|
90
|
+
assert_option_calls([:setxmsiz, size], :xmsiz => size)
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_extra_mapped_memory_size_is_converted_to_an_int
|
94
|
+
assert_option_calls([:setxmsiz, 42], :xmsiz => "42")
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_a_step_unit_can_be_set_for_auto_defragmentation
|
98
|
+
unit = rand(1_000) + 1
|
99
|
+
assert_option_calls([:setdfunit, unit], :dfunit => unit)
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_auto_defragmentation_step_unit_is_converted_to_an_int
|
103
|
+
assert_option_calls([:setdfunit, 42], :dfunit => "42")
|
104
|
+
end
|
105
|
+
|
106
|
+
def test_nested_transactions_can_be_ignored
|
107
|
+
hdb(:nested_transactions => :ignore) do |db|
|
108
|
+
result = db.transaction {
|
109
|
+
db.transaction { # ignored
|
110
|
+
41
|
111
|
+
} + 1 # ignored
|
112
|
+
}
|
113
|
+
assert_equal(42, result)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def test_nested_transactions_can_be_set_to_fail_with_an_error
|
118
|
+
[:fail, :raise].each do |setting|
|
119
|
+
hdb(:nested_transactions => setting) do |db|
|
120
|
+
db.transaction do
|
121
|
+
assert_raise(OKMixer::Error::TransactionError) do
|
122
|
+
db.transaction { } # nested fails with error
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def test_a_mode_string_can_be_passed
|
130
|
+
assert_raise(OKMixer::Error::CabinetError) do # file not found
|
131
|
+
hdb("r")
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_the_mode_can_be_passed_as_as_option
|
136
|
+
assert_raise(OKMixer::Error::CabinetError) do # file not found
|
137
|
+
hdb(:mode => "r")
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def test_an_option_mode_overrides_the_mode_argument_and_triggers_a_warning
|
142
|
+
warning = capture_stderr do
|
143
|
+
hdb("r", :mode => "wc") do
|
144
|
+
# just open and close
|
145
|
+
end
|
146
|
+
end
|
147
|
+
assert( !warning.empty?,
|
148
|
+
"A warning was not issued for an option mode with a mode argument" )
|
149
|
+
end
|
150
|
+
|
151
|
+
def test_an_unknown_mode_triggers_a_warning
|
152
|
+
warning = capture_stderr do
|
153
|
+
hdb("wcu") do
|
154
|
+
# just open and close
|
155
|
+
end
|
156
|
+
end
|
157
|
+
assert(!warning.empty?, "A warning was not issued for an unknown mode")
|
158
|
+
end
|
159
|
+
|
160
|
+
#######
|
161
|
+
private
|
162
|
+
#######
|
163
|
+
|
164
|
+
def assert_option_calls(c_call, options)
|
165
|
+
args = capture_args(OKMixer::HashDatabase::C, c_call[0]) do
|
166
|
+
hdb(options) do
|
167
|
+
# just open and close
|
168
|
+
end
|
169
|
+
end
|
170
|
+
assert_instance_of(FFI::Pointer, args[0])
|
171
|
+
assert_equal(c_call[1..-1], args[1..-1])
|
172
|
+
end
|
173
|
+
end
|