kaede 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.
@@ -0,0 +1,60 @@
1
+ # coding: utf-8
2
+ require 'spec_helper'
3
+
4
+ require 'kaede/notifier'
5
+ require 'kaede/program'
6
+
7
+ describe Kaede::Notifier do
8
+ let(:notifier) { described_class.new }
9
+ let(:duration) { 30 }
10
+ let(:program) { Kaede::Program.new(1234, 5678, Time.now, Time.now + duration, nil, 19, 9, '6', 0, 'sub', 'title', 'comment') }
11
+
12
+ describe '#notify_before_record' do
13
+ it 'tweets' do
14
+ expect(notifier).to receive(:tweet).with(/title #6 sub/)
15
+ notifier.notify_before_record(program)
16
+ end
17
+ end
18
+
19
+ describe '#notify_after_record' do
20
+ before do
21
+ Kaede.configure do |config|
22
+ config.record_dir = @tmpdir.join('record').tap(&:mkpath)
23
+ tools = @topdir.join('tools')
24
+ config.statvfs = tools.join('statvfs')
25
+ end
26
+
27
+ notifier.record_path(program).open('w') {}
28
+ end
29
+
30
+ it 'tweets' do
31
+ expect(notifier).to receive(:tweet).with(/title #6 sub.*0\.00GB/)
32
+ notifier.notify_after_record(program)
33
+ end
34
+ end
35
+
36
+ describe '#notify_exception' do
37
+ class MyException < Exception
38
+ end
39
+
40
+ it 'tweets' do
41
+ Kaede.configure do |config|
42
+ config.twitter_target = nil
43
+ end
44
+
45
+ expect(notifier).to receive(:tweet).with(/MyException で失敗した/)
46
+ notifier.notify_exception(MyException.new, program)
47
+ end
48
+
49
+ context 'when twitter_target is set' do
50
+ it 'tweets to twitter_target' do
51
+ Kaede.configure do |config|
52
+ config.twitter_target = 'eagletmt'
53
+ end
54
+
55
+ expect(notifier).to receive(:tweet).with(/\A@eagletmt .* MyException で失敗した/)
56
+ notifier.notify_exception(MyException.new, program)
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,176 @@
1
+ require 'spec_helper'
2
+
3
+ require 'kaede'
4
+ require 'kaede/database'
5
+ require 'kaede/recorder'
6
+ require 'kaede/updater'
7
+
8
+ describe Kaede::Recorder do
9
+ let(:notifier) { double('Notifier') }
10
+ let(:recorder) { described_class.new(notifier) }
11
+ let(:db) { Kaede::Database.new(':memory:') }
12
+ let(:job) { db.get_jobs.first }
13
+ let(:program) { db.get_program(job[:pid]) }
14
+ let(:duration) { 30 }
15
+
16
+ let(:formatted_fname) { '5678_1234 title #6 sub (comment) at MX' }
17
+ let(:record_dir) { @tmpdir.join('record').tap(&:mkpath) }
18
+ let(:record_path) { recorder.record_path(program) }
19
+ let(:cache_dir) { @tmpdir.join('cache').tap(&:mkpath) }
20
+ let(:cache_path) { recorder.cache_path(program) }
21
+ let(:cache_ass_path) { recorder.cache_ass_path(program) }
22
+ let(:cabinet_dir) { @tmpdir.join('cabinet').tap(&:mkpath) }
23
+ let(:cabinet_path) { recorder.cabinet_path(program) }
24
+ let(:cabinet_ass_path) { recorder.cabinet_ass_path(program) }
25
+
26
+ before do
27
+ db.add_channel(Kaede::Channel.new(nil, 'MX', 9, 19))
28
+ channel = db.get_channels.first
29
+ program = Kaede::Program.new(1234, 5678, Time.now, Time.now + duration, nil, 19, 9, '6', 0, 'sub', 'title', 'comment')
30
+ db.update_program(program, channel)
31
+ db.update_job(program.pid, Time.now + 5)
32
+
33
+ Kaede.configure do |config|
34
+ config.redis = double('redis')
35
+ tools = @topdir.join('tools')
36
+ config.recpt1 = tools.join('recpt1')
37
+ config.b25 = tools.join('b25')
38
+ config.assdumper = tools.join('assdumper')
39
+ config.statvfs = tools.join('statvfs')
40
+ config.clean_ts = tools.join('clean-ts')
41
+ config.record_dir = record_dir
42
+ config.cache_dir = cache_dir
43
+ config.cabinet_dir = cabinet_dir
44
+ end
45
+ end
46
+
47
+ describe '#record' do
48
+ it 'calls before_record -> do_record -> after_record' do
49
+ expect(recorder).to receive(:before_record).ordered.with(program)
50
+ expect(recorder).to receive(:do_record).ordered.with(program)
51
+ expect(recorder).to receive(:after_record).ordered.with(program)
52
+
53
+ expect { recorder.record(db, job[:pid]) }.to output(/Start #{job[:pid]}.*Done #{job[:pid]}/m).to_stdout
54
+ end
55
+
56
+ it 'notifies exception' do
57
+ e = Class.new(Exception)
58
+ expect(notifier).to receive(:notify_exception).with(e, program)
59
+ allow(recorder).to receive(:before_record)
60
+ allow(recorder).to receive(:do_record).and_raise(e)
61
+ allow(recorder).to receive(:after_record)
62
+
63
+ expect { recorder.record(db, job[:pid]) }.to raise_error(e)
64
+ end
65
+ end
66
+
67
+ describe '#after_record' do
68
+ before do
69
+ record_path.open('w') {}
70
+ cache_path.open('w') {}
71
+ cache_ass_path.open('w') { |f| f.puts 'ass' }
72
+
73
+ allow(Kaede.config.redis).to receive(:rpush)
74
+ allow(notifier).to receive(:notify_after_record).with(program)
75
+ end
76
+
77
+ it 'calls Notifier#notify_after_record' do
78
+ expect(notifier).to receive(:notify_after_record).with(program)
79
+ recorder.after_record(program)
80
+ end
81
+
82
+ it 'cleans cached TS' do
83
+ expect(cache_path).to be_exist
84
+ expect(cabinet_path).to_not be_exist
85
+
86
+ recorder.after_record(program)
87
+
88
+ expect(cache_path).to_not be_exist
89
+ expect(cabinet_path).to be_exist
90
+ end
91
+
92
+ it 'moves ass' do
93
+ expect(cache_ass_path).to be_exist
94
+ expect(cabinet_ass_path).to_not be_exist
95
+
96
+ recorder.after_record(program)
97
+
98
+ expect(cache_ass_path).to_not be_exist
99
+ expect(cabinet_ass_path).to be_exist
100
+ end
101
+
102
+ it 'enqueues to redis' do
103
+ expect(Kaede.config.redis).to receive(:rpush).with(Kaede.config.redis_queue, formatted_fname)
104
+
105
+ recorder.after_record(program)
106
+ end
107
+
108
+ context 'with empty ass' do
109
+ before do
110
+ cache_ass_path.open('w') {}
111
+ end
112
+
113
+ it 'removes ass' do
114
+ expect(cache_ass_path).to be_exist
115
+ expect(cabinet_ass_path).to_not be_exist
116
+
117
+ recorder.after_record(program)
118
+
119
+ expect(cache_ass_path).to_not be_exist
120
+ expect(cabinet_ass_path).to_not be_exist
121
+ end
122
+ end
123
+ end
124
+
125
+ describe '#do_record' do
126
+ it 'creates raw TS in record dir' do
127
+ expect(record_path).to_not be_exist
128
+ recorder.do_record(program)
129
+ expect(record_path).to be_exist
130
+ expect(record_path.read.chomp).to eq("#{program.channel_for_recorder} #{duration - 10}")
131
+ end
132
+
133
+ it 'creates b25-decoded TS in cache dir' do
134
+ expect(cache_path).to_not be_exist
135
+ recorder.do_record(program)
136
+ expect(cache_path).to be_exist
137
+ expect(cache_path.read.chomp).to eq(record_path.read.chomp.reverse)
138
+ end
139
+
140
+ it 'creates ass in cache dir' do
141
+ expect(cache_ass_path).to_not be_exist
142
+ recorder.do_record(program)
143
+ expect(cache_ass_path).to be_exist
144
+ expect(cache_ass_path.read.chomp).to eq(record_path.read.chomp.gsub('1', '2'))
145
+ end
146
+ end
147
+
148
+ describe '#calculate_duration' do
149
+ subject { recorder.calculate_duration(program) }
150
+
151
+ it 'returns duration' do
152
+ is_expected.to eq(duration - 10)
153
+ end
154
+
155
+ context 'with NHK (which has no CM)' do
156
+ before do
157
+ program.channel_name = 'NHK-G'
158
+ end
159
+
160
+ it 'has additional record time' do
161
+ is_expected.to eq(duration + Kaede::Updater::JOB_TIME_GAP)
162
+ end
163
+ end
164
+
165
+ context 'with MX on Sunday 22:00' do
166
+ before do
167
+ program.start_time = Time.local(2014, 4, 6, 22, 00)
168
+ program.end_time = Time.local(2014, 4, 6, 22, 27)
169
+ end
170
+
171
+ it 'has 30min duration even if Syoboi Calendar says 27min' do
172
+ is_expected.to eq(30*60 - 10)
173
+ end
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ require 'tempfile'
4
+ require 'timeout'
5
+ require 'kaede'
6
+ require 'kaede/database'
7
+ require 'kaede/recorder'
8
+ require 'kaede/scheduler'
9
+
10
+ describe Kaede::Scheduler do
11
+ let(:db_file) { Tempfile.open('kaede.db') }
12
+ let(:db) { Kaede::Database.new(db_file.path) }
13
+
14
+ describe '.start' do
15
+ let(:program) { Kaede::Program.new(1234, 5678, Time.now, Time.now + 30, nil, 19, 9, '5.5', 0, 'sub', 'title', '') }
16
+
17
+ before do
18
+ db.add_channel(Kaede::Channel.new(nil, 'MX', 9, 19))
19
+ channel = db.get_channels.first
20
+ db.update_program(program, channel)
21
+ db.update_job(program.pid, Time.now + 5)
22
+ end
23
+
24
+ it 'works' do
25
+ r, w = IO.pipe
26
+ allow_any_instance_of(Kaede::Recorder).to receive(:record) { |recorder, db, pid|
27
+ program = db.get_program(pid)
28
+ puts "Record #{program.pid}"
29
+ }
30
+ pid = fork do
31
+ r.close
32
+ $stdout.reopen(w)
33
+ described_class.setup(db)
34
+ described_class.start
35
+ end
36
+ w.close
37
+
38
+ expect(db.get_jobs.size).to eq(1)
39
+ begin
40
+ Timeout.timeout(10) do
41
+ while s = r.gets.chomp
42
+ if s =~ /\ARecord (\d+)\z/
43
+ expect($1.to_i).to eq(program.pid)
44
+ break
45
+ end
46
+ end
47
+ end
48
+ ensure
49
+ Process.kill(:QUIT, pid)
50
+ Process.waitpid(pid)
51
+ end
52
+ expect(db.get_jobs.size).to eq(0)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+ require 'kaede/syoboi_calendar'
3
+
4
+ describe Kaede::SyoboiCalendar do
5
+ let(:client) { described_class.new }
6
+
7
+ describe '#cal_chk' do
8
+ it 'works' do
9
+ VCR.use_cassette('cal_chk/all') do
10
+ programs = client.cal_chk
11
+ programs.each do |program|
12
+ program.members.each do |attr|
13
+ next if [:channel_name, :channel_for_recorder].include?(attr)
14
+ expect(program[attr]).to_not be_nil
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+ require 'kaede/channel'
3
+ require 'kaede/database'
4
+ require 'kaede/syoboi_calendar'
5
+ require 'kaede/updater'
6
+
7
+ describe Kaede::Updater do
8
+ let(:db) { Kaede::Database.new(':memory:') }
9
+ let(:syobocal) { Kaede::SyoboiCalendar.new }
10
+ let(:updater) { described_class.new(db, syobocal) }
11
+
12
+ describe '#update' do
13
+ let(:channel) { Kaede::Channel.new(nil, 'MX', 9, 19) }
14
+ let(:tracking_tid) { 3225 }
15
+
16
+ let(:expected_pid) { 279855 }
17
+ let(:expected_enqueued_at) { Time.local(2014, 3, 6, 21, 59, 45) }
18
+
19
+ around do |example|
20
+ Timecop.travel(Time.local(2014, 3, 5, 18, 0)) do
21
+ example.run
22
+ end
23
+ end
24
+
25
+ before do
26
+ db.add_channel(channel)
27
+ db.add_tracking_title(tracking_tid)
28
+
29
+ allow(updater).to receive(:reload_scheduler)
30
+ @orig_stderr = $stderr
31
+ $stderr = open(File::NULL, 'w')
32
+ end
33
+
34
+ after do
35
+ $stderr = @orig_stderr
36
+ end
37
+
38
+ it 'inserts new jobs' do
39
+ VCR.use_cassette('cal_chk/days7') do
40
+ updater.update
41
+ end
42
+
43
+ jobs = db.get_jobs
44
+ expect(jobs.size).to eq(1)
45
+ job = jobs[0]
46
+ expect(job[:pid]).to eq(expected_pid)
47
+ expect(job[:enqueued_at]).to eq(expected_enqueued_at)
48
+ end
49
+
50
+ it 'updates existing job' do
51
+ dummy_time = Time.local(2014, 3, 6, 20, 29, 45)
52
+
53
+ VCR.use_cassette('cal_chk/days7') do
54
+ updater.update
55
+ end
56
+ db.update_job(expected_pid, dummy_time)
57
+ job1 = db.get_jobs[0]
58
+ expect(job1[:enqueued_at]).to eq(dummy_time)
59
+ VCR.use_cassette('cal_chk/days7') do
60
+ updater.update
61
+ end
62
+ job2 = db.get_jobs[0]
63
+ expect(job2[:enqueued_at]).to eq(expected_enqueued_at)
64
+ expect(job2[:pid]).to eq(job1[:pid])
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,50 @@
1
+ require 'coveralls'
2
+ require 'simplecov'
3
+ require 'timecop'
4
+ require 'tmpdir'
5
+ require 'vcr'
6
+ require 'webmock/rspec'
7
+
8
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
9
+ SimpleCov::Formatter::HTMLFormatter,
10
+ Coveralls::SimpleCov::Formatter,
11
+ ]
12
+ SimpleCov.start do
13
+ add_filter Bundler.bundle_path.to_s
14
+ add_filter File.dirname(__FILE__)
15
+
16
+ # Kaede::Scheduler is tested in another process.
17
+ add_filter 'lib/kaede/scheduler.rb'
18
+ end
19
+
20
+ VCR.configure do |config|
21
+ config.cassette_library_dir = 'spec/fixtures/vcr'
22
+ config.hook_into :webmock
23
+ end
24
+
25
+ RSpec.configure do |config|
26
+ # config.profile_examples = 10
27
+
28
+ config.order = :random
29
+ Kernel.srand config.seed
30
+
31
+ config.expect_with :rspec do |expectations|
32
+ expectations.syntax = :expect
33
+ end
34
+
35
+ config.mock_with :rspec do |mocks|
36
+ mocks.syntax = :expect
37
+ mocks.verify_partial_doubles = true
38
+ end
39
+
40
+ config.before :each do
41
+ @topdir = Pathname.new(__FILE__).parent
42
+ end
43
+
44
+ config.around :each do |example|
45
+ Dir.mktmpdir('kaede') do |dir|
46
+ @tmpdir = Pathname.new(dir)
47
+ example.run
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,2 @@
1
+ #!/bin/sh
2
+ exec sed -e s/1/2/ "$1"
@@ -0,0 +1,6 @@
1
+ #!/bin/sh
2
+ shift
3
+ shift
4
+ shift
5
+
6
+ exec rev "$1" > "$2"
@@ -0,0 +1,2 @@
1
+ #!/bin/sh
2
+ exec cp "$1" "$2"