wyrm 0.2.1 → 0.3.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/lib/wyrm/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Wyrm
2
- VERSION = "0.2.1"
2
+ VERSION = "0.3.0"
3
3
  end
data/snippets/console.rb CHANGED
@@ -3,8 +3,10 @@ require 'sqlite3'
3
3
  require 'pathname'
4
4
  require 'wyrm/dump_schema.rb'
5
5
 
6
- # pump = DbPump.new db, :positions, codec: :yaml
7
- dumper = DumpSchema.new db, '/tmp/test', pump: lambda{|_| DbPump.new db, nil, codec: :yaml}
8
- dumper = DumpSchema.new db, '/tmp/test', pump: ->(dump_schema){ DbPump.new dump_schema.src_db, nil, codec: :yaml}
6
+ include Wyrm
7
+
8
+ # pump = Wyrm::Pump.new db, :positions, codec: :yaml
9
+ dumper = DumpSchema.new db, '/tmp/test', pump: lambda{|_| Pump.new db, nil, codec: :yaml}
10
+ dumper = DumpSchema.new db, '/tmp/test', pump: ->(dump_schema){ Pump.new dump_schema.src_db, nil, codec: :yaml}
9
11
  dumper.dump_tables
10
12
 
@@ -0,0 +1,50 @@
1
+ require 'rspec'
2
+
3
+ require Pathname(__dir__) + '../lib/wyrm/core_extensions.rb'
4
+
5
+ describe Method do
6
+ describe '#kwargs_as_hash' do
7
+ it 'empty for no keywords' do
8
+ inst = Class.new do
9
+ def without_kwargs( one, two )
10
+ @kwargs = method(__method__).kwargs_as_hash(binding)
11
+ end
12
+ end.new
13
+ inst.without_kwargs :one, :two
14
+ inst.instance_variable_get('@kwargs').should == {}
15
+ end
16
+
17
+ it 'gives back hash of keywords' do
18
+ inst = Class.new do
19
+ def with_kwargs( one: 'one', two: 'two')
20
+ @kwargs = method(__method__).kwargs_as_hash(binding)
21
+ end
22
+ end.new
23
+
24
+ inst.with_kwargs
25
+ inst.instance_variable_get('@kwargs').should == {one: 'one', two: 'two'}
26
+ end
27
+
28
+ it 'has correct values' do
29
+ inst = Class.new do
30
+ def with_kwargs( one: 'one', two: 'two')
31
+ @kwargs = method(__method__).kwargs_as_hash(binding)
32
+ end
33
+ end.new
34
+
35
+ inst.with_kwargs( one: 1, two: 2 )
36
+ inst.instance_variable_get('@kwargs').should == {one: 1, two: 2}
37
+ end
38
+
39
+ it 'gets some default values' do
40
+ inst = Class.new do
41
+ def with_kwargs( one: 'one', two: 'two')
42
+ @kwargs = method(__method__).kwargs_as_hash(binding)
43
+ end
44
+ end.new
45
+
46
+ inst.with_kwargs( one: 1 )
47
+ inst.instance_variable_get('@kwargs').should == {one: 1, two: 'two'}
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,176 @@
1
+ require 'rspec'
2
+
3
+ require Pathname(__dir__) + '../lib/wyrm/hole.rb'
4
+
5
+ describe Wyrm::Hole::Mouth do
6
+ describe '#flush' do
7
+ it 'calls poison_queue' do
8
+ subject.should_receive(:poison_queue)
9
+ subject.flush
10
+ end
11
+
12
+ it 'sets flag' do
13
+ subject.instance_variable_get('@flushed').should_not == true
14
+ subject.flush
15
+ subject.instance_variable_get('@flushed').should == true
16
+ end
17
+
18
+ describe 'queue empty with waiters' do
19
+ THREADS = rand(1..7)
20
+ def waiters
21
+ @waiters ||= THREADS.times.map do
22
+ Thread.new do
23
+ values = []
24
+ begin
25
+ until subject.eof?
26
+ values << subject.deq
27
+ sleep( rand * 0.05 )
28
+ end
29
+ [:eoffed, values]
30
+ rescue StopIteration
31
+ [:poisoned, values]
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ it 'poisons queue' do
38
+ waiters
39
+ # wait for thread setup to finish
40
+ sleep 0.1
41
+ subject.flush
42
+ thread_values = waiters.map {|waiter| waiter.join(4).andand.value }
43
+ thread_values.map(&:first).should == THREADS.times.map{:poisoned}
44
+ end
45
+
46
+ it 'eof queue' do
47
+ 50.times{subject.enq 'hello'}
48
+ waiters
49
+ subject.flush
50
+ thread_values = waiters.map {|waiter| waiter.join(4).andand.value }
51
+ thread_values.map(&:first).should == THREADS.times.map{:eoffed}
52
+ end
53
+ end
54
+ end
55
+
56
+ describe '#eof?' do
57
+ describe 'flushed' do
58
+ before :each do
59
+ subject.flush
60
+ end
61
+
62
+ it 'true when queue empty' do
63
+ subject.queue.should be_empty
64
+ subject.should be_eof
65
+ end
66
+
67
+ it 'false when queue empty' do
68
+ subject.enq( :arg )
69
+ subject.should_not be_eof
70
+ end
71
+ end
72
+
73
+ describe 'not flushed' do
74
+ it 'false when queue empty' do
75
+ subject.queue.should be_empty
76
+ subject.should_not be_eof
77
+ end
78
+
79
+ it 'false when queue has items' do
80
+ rand(25).times{ subject.enq( :arg ) }
81
+ subject.should_not be_eof
82
+ end
83
+ end
84
+ end
85
+
86
+ describe '#reset' do
87
+ it 'resets flushed' do
88
+ subject.instance_variable_set '@flushed', true
89
+ subject.reset
90
+ subject.instance_variable_get('@flushed').should == false
91
+ end
92
+
93
+ it 'clears queue' do
94
+ rand(1..10).times{subject.enq :some_value}
95
+ subject.queue.should_not be_empty
96
+ subject.reset
97
+ subject.queue.should be_empty
98
+ end
99
+ end
100
+
101
+ describe '#deq' do
102
+ it 'gets value' do
103
+ subject.enq :montagne
104
+ subject.deq.should == :montagne
105
+ end
106
+
107
+ it 'blocks for no values' do
108
+ subject.queue.should be_empty
109
+ th = Thread.new{subject.deq}
110
+ sleep 0.05
111
+ th.status.should == 'sleep'
112
+ th.kill
113
+ sleep 0.05
114
+ th.status.should == false
115
+ end
116
+
117
+ it 'raise StopIteration on poison' do
118
+ subject.queue.should be_empty
119
+ waiter = Thread.new { subject.deq }
120
+ sleep(0.01) while subject.queue.num_waiting != 1
121
+ subject.poison_queue
122
+
123
+ # this is so cool. Thread#value will re-raise the exception it caught
124
+ ->{waiter.value}.should raise_error(StopIteration)
125
+
126
+ # queue should be empty now
127
+ subject.queue.should be_empty
128
+ end
129
+
130
+ it 're-queues poison' do
131
+ subject.queue << :poison
132
+ subject.should_receive(:poison_queue)
133
+ ->{subject.deq}.should raise_error(StopIteration)
134
+ end
135
+ end
136
+
137
+ describe '#poison_queue' do
138
+ it 'poisons when queue empty with waiters' do
139
+ subject.queue.should be_empty
140
+
141
+ # there has to be a thread waiting for the poison to be added
142
+ waiter = Thread.new { subject.queue.deq }
143
+ sleep(0.01) while subject.queue.num_waiting != 1
144
+ subject.poison_queue
145
+
146
+ waiter.value.should == :poison
147
+
148
+ # queue should be empty now
149
+ subject.queue.should be_empty
150
+ end
151
+
152
+ it 'no poison when queue empty' do
153
+ subject.queue.should be_empty
154
+ subject.poison_queue
155
+ subject.queue.should be_empty
156
+ end
157
+
158
+ it 'no poison for no waiters' do
159
+ subject.queue << :hello
160
+ subject.queue << :there
161
+ subject.poison_queue
162
+ subject.queue.size.should == 2
163
+ end
164
+ end
165
+
166
+ describe '#logger' do
167
+ # this is here because the 2.1.0 without SizeQueue branch
168
+ # has a logger which nothing else uses
169
+ it 'works' do
170
+ Wyrm::Hole::Mouth::RUBY_VERSION = '2.1.0'
171
+ subject.should_receive(:logger).and_call_original
172
+ subject.queue
173
+ Wyrm::Hole::Mouth.send :remove_const, :RUBY_VERSION
174
+ end
175
+ end
176
+ end
data/spec/pump_spec.rb ADDED
@@ -0,0 +1,62 @@
1
+ require 'rspec'
2
+
3
+ require Pathname(__dir__) + '../lib/wyrm/pump.rb'
4
+
5
+ include Wyrm
6
+
7
+ describe Pump do
8
+ describe '.quacks_like' do
9
+ it 'recognises method' do
10
+ threequal = Pump.quacks_like( :tap )
11
+ (threequal === Object.new).should be_true
12
+ end
13
+
14
+ it 'recognises two methods' do
15
+ threequal = Pump.quacks_like( :tap, :instance_eval )
16
+ (threequal === Object.new).should be_true
17
+ end
18
+ end
19
+
20
+ describe '#table_name=' do
21
+ it 'invalidates caches'
22
+ end
23
+
24
+ describe '#db=' do
25
+ it 'invalidates caches'
26
+ end
27
+
28
+ describe '#codec=' do
29
+ it ':yaml' do
30
+ subject.codec = :yaml
31
+ subject.codec.should be_a(Pump::YamlCodec)
32
+ end
33
+
34
+ it ':marshal' do
35
+ subject.codec = :marshal
36
+ subject.codec.should be_a(Pump::MarshalCodec)
37
+ end
38
+
39
+ def codec_class
40
+ @codec_class ||=
41
+ Class.new do
42
+ def encode; end
43
+ def decode; end
44
+ end
45
+ end
46
+
47
+ it 'codec instance' do
48
+ inst = codec_class.new
49
+ subject.codec = inst
50
+ subject.codec.should == inst
51
+ end
52
+
53
+ it 'codec class' do
54
+ subject.codec = codec_class
55
+ subject.codec.should be_a(codec_class)
56
+ end
57
+
58
+ it 'raise for unknown' do
59
+ ->{subject.codec = Object.new}.should raise_error(/unknown codec/)
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,201 @@
1
+ require 'rspec'
2
+ require 'sequel'
3
+ require 'sqlite3'
4
+
5
+ require Pathname(__dir__) + '../lib/wyrm/schema_tools.rb'
6
+ require Pathname(__dir__) + '../lib/wyrm/logger.rb'
7
+
8
+ include Wyrm
9
+
10
+ describe SchemaTools do
11
+ after :each do
12
+ @src_dst = nil
13
+ end
14
+
15
+ class Includer
16
+ include SchemaTools
17
+ include Wyrm::Logger
18
+ def logger
19
+ lgr = super
20
+ # silence most logging
21
+ lgr.level = ::Logger::FATAL
22
+ lgr
23
+ end
24
+ def initialize( src_db, dst_db )
25
+ @src_db = src_db.andand.extension :schema_dumper
26
+ @dst_db = dst_db
27
+ end
28
+ attr_reader :src_db, :dst_db
29
+ end
30
+
31
+ def src_dst( src_db: Sequel.sqlite, dst_db: Sequel.sqlite )
32
+ @src_dst ||= Includer.new src_db, dst_db
33
+ end
34
+
35
+ def with_src
36
+ src_dst dst_db: nil
37
+ end
38
+
39
+ def with_dst
40
+ src_dst src_db: nil
41
+ end
42
+
43
+ describe '#same_db' do
44
+ it 'for same db' do
45
+ src_dst.same_db.should be_true
46
+ end
47
+
48
+ it 'for different db' do
49
+ src_dst( dst_db: Sequel.postgres ).same_db.should be_false
50
+ end
51
+ end
52
+
53
+ describe 'src_db dependencies' do
54
+ describe '#schema_migration' do
55
+ it 'executes' do
56
+ String.should === with_src.schema_migration
57
+ with_src.schema_migration.should =~ /Sequel.migration/
58
+ with_src.schema_migration.should =~ /change/
59
+ end
60
+
61
+ it 'recreates table' do
62
+ with_src.src_db.create_table(:things){|t| primary_key :id; t.String :name}
63
+ with_src.schema_migration.should =~ /create_table/
64
+ end
65
+ end
66
+
67
+ describe '#index_migration' do
68
+ it 'executes' do
69
+ String.should === with_src.index_migration
70
+ with_src.index_migration.should =~ /Sequel.migration/
71
+ with_src.index_migration.should =~ /change/
72
+ end
73
+
74
+ it 'recreates table' do
75
+ with_src.src_db.create_table(:things){|t| primary_key :id; t.String :name}
76
+ with_src.src_db.add_index(:things, :name)
77
+ with_src.index_migration.should =~ /add_index/
78
+ end
79
+ end
80
+
81
+ describe '#fk_migration' do
82
+ it 'executes' do
83
+ String.should === with_src.fk_migration
84
+ with_src.fk_migration.should =~ /Sequel.migration/
85
+ with_src.fk_migration.should =~ /change/
86
+ end
87
+
88
+ it 'recreates table' do
89
+ with_src.src_db.create_table(:things){|t| primary_key :id; t.String :name}
90
+ with_src.src_db.create_table(:times){|t| primary_key :id; t.foreign_key :thing_id, :things}
91
+ with_src.fk_migration.should =~ /add_foreign_key/
92
+ end
93
+ end
94
+ end
95
+
96
+ describe '#drop_table_options' do
97
+ it 'empty for non-postgres' do
98
+ with_dst.drop_table_options.should == {}
99
+ end
100
+
101
+ it 'cascade for postgres' do
102
+ src_dst(dst_db: Sequel.postgres, src_db: nil).drop_table_options.should == {cascade: true}
103
+ end
104
+ end
105
+
106
+ describe '#drop_tables' do
107
+ #( tables )
108
+ it 'removes tables with no foreign keys' do
109
+ with_dst.dst_db.create_table(:things){|t| primary_key :id; t.String :name}
110
+ with_dst.dst_db.create_table(:times){|t| primary_key :id}
111
+
112
+ with_dst.dst_db.tables.should == %i[things times]
113
+ with_dst.drop_tables with_dst.dst_db.tables
114
+ with_dst.dst_db.tables.should be_empty
115
+ end
116
+
117
+ it 'removes tables with some foreign keys' do
118
+ with_dst.dst_db.create_table(:things){|t| primary_key :id; t.String :name}
119
+ with_dst.dst_db.create_table(:times){|t| primary_key :id; t.foreign_key :thing_id, :things}
120
+
121
+ with_dst.dst_db.tables.should == %i[things times]
122
+ with_dst.drop_tables with_dst.dst_db.tables
123
+ with_dst.dst_db.tables.should be_empty
124
+ end
125
+
126
+ if ::SQLite3::SQLITE_VERSION >= "3.6.19"
127
+ it 'sqlite mutual foreign keys' do
128
+ with_dst.dst_db.create_table(:things){|t| primary_key :id; t.String :name}
129
+ with_dst.dst_db.create_table(:times){|t| primary_key :id; t.foreign_key :thing_id, :things}
130
+ with_dst.dst_db.alter_table :things do
131
+ add_foreign_key :times_id, :times
132
+ end
133
+
134
+ thing_id = with_dst.dst_db[:things].insert name: 'Gruffalo'
135
+ time_id = with_dst.dst_db[:times].insert thing_id: thing_id
136
+ with_dst.dst_db[:things].update times_id: time_id
137
+
138
+ with_dst.dst_db.tables.sort.should == %i[things times]
139
+ ->{with_dst.drop_tables with_dst.dst_db.tables}.should raise_error(/mutual foreign keys/)
140
+ end
141
+ else
142
+ it "Can't test foreign keys with sqlite < 3.6.19"
143
+ end
144
+
145
+ it 'handles mysql foreign key exception'
146
+ end
147
+
148
+ describe '#create_tables' do
149
+ MIGRATION = <<-EOF
150
+ Sequel.migration do
151
+ change do
152
+ create_table(:things) do
153
+ primary_key :id
154
+ String :name, :size=>255
155
+ Integer :times_id
156
+ end
157
+
158
+ create_table(:times) do
159
+ primary_key :id
160
+ foreign_key :thing_id, :things
161
+ end
162
+
163
+ alter_table(:things) do
164
+ add_foreign_key [:times_id], :times, :key=>nil
165
+ end
166
+ end
167
+ end
168
+ EOF
169
+
170
+ it 'creates table from schema_migration' do
171
+ with_dst.stub( :schema_migration ) do
172
+ MIGRATION
173
+ end
174
+ with_dst.dst_db.tables.should be_empty
175
+ with_dst.create_tables
176
+ with_dst.dst_db.tables.sort.should == %i[things times]
177
+ end
178
+ end
179
+
180
+ # just receive the methods
181
+ describe '#create_indexes' do
182
+ it 'creates indexes and foreign keys' do
183
+ with_dst.should_receive(:index_migration){ 'Sequel.migration{}' }
184
+ with_dst.should_receive(:fk_migration){ 'Sequel.migration{}' }
185
+ with_dst.create_indexes
186
+ end
187
+
188
+ it 'resets key sequences for postgres' do
189
+ with_dst.stub(:index_migration){ 'Sequel.migration{}' }
190
+ with_dst.stub(:fk_migration){ 'Sequel.migration{}' }
191
+
192
+ with_dst.dst_db.stub(:database_type){Sequel.postgres.database_type}
193
+
194
+ with_obj = Object.new
195
+ with_dst.dst_db.should_receive(:tables){[with_obj]}
196
+ with_dst.dst_db.should_receive(:reset_primary_key_sequence).with(with_obj)
197
+ with_dst.create_indexes
198
+ end
199
+ end
200
+
201
+ end