kaede 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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"