battman 0.0.1

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,232 @@
1
+ require 'battman/acpi_battery'
2
+
3
+ module Battman
4
+
5
+ describe AcpiBattery do
6
+ it 'is a subclass of Battery' do
7
+ expect(AcpiBattery.new).to be_a Battery
8
+ end
9
+
10
+ describe '.new' do
11
+ it 'accepts an precision option and sets the corresponding instance variable' do
12
+ battery = AcpiBattery.new(0, precision: 100)
13
+
14
+ expect(battery.instance_variable_get(:@precision)).to eq 100
15
+ end
16
+
17
+ it 'sets precision to 1000 by default' do
18
+ expect(AcpiBattery.new.instance_variable_get(:@precision)).to eq 1000
19
+ end
20
+ end
21
+
22
+ describe '#path' do
23
+
24
+ let(:path_prefix) { '/sys/class/power_supply' }
25
+
26
+ it 'builds the path for a smapi battery with the correct index' do
27
+ battery = AcpiBattery.new
28
+
29
+ expect(battery.path).to eq File.join(path_prefix, 'BAT0')
30
+
31
+ battery = AcpiBattery.new(1)
32
+
33
+ expect(battery.path).to eq File.join(path_prefix, 'BAT1')
34
+ end
35
+
36
+ it 'sets the corresponding instance variable' do
37
+ battery = AcpiBattery.new
38
+ path = battery.path
39
+
40
+ expect(battery.instance_variable_get(:@path)).to eq path
41
+ end
42
+ end
43
+
44
+ let(:battery) { AcpiBattery.new }
45
+
46
+ before(:each) do
47
+ allow(File).to receive(:join).and_call_original
48
+ end
49
+
50
+ describe '#remaining_percent' do
51
+
52
+ let(:energy_full_file) { File.join(battery.path, 'energy_full') }
53
+ let(:energy_now_file) { File.join(battery.path, 'energy_now') }
54
+
55
+ before(:each) do
56
+ allow(File).to receive(:read).with(energy_full_file).and_return("1000\n")
57
+ allow(File).to receive(:read).with(energy_now_file).and_return("100\n")
58
+ end
59
+
60
+ it 'reads the value from the correct files' do
61
+ battery.remaining_percent
62
+
63
+ expect(File).to have_received(:read).with(energy_full_file)
64
+ expect(File).to have_received(:read).with(energy_now_file)
65
+ end
66
+
67
+ it 'calculates the percentage from the read file contents' do
68
+ expect(battery.remaining_percent).to eq 10
69
+ end
70
+
71
+ end
72
+
73
+ describe '#power' do
74
+
75
+ let(:power_file) { File.join(battery.path, 'power_now') }
76
+
77
+ it 'reads the value from the correct file' do
78
+ allow(File).to receive(:read).with(power_file).and_return("1000000\n")
79
+ allow(battery).to receive(:state).and_return(:charging)
80
+ battery.power
81
+
82
+ expect(File).to have_received(:read).with(power_file)
83
+ end
84
+
85
+ it 'returns the power used in watt' do
86
+ allow(File).to receive(:read).with(power_file).and_return("1000000\n")
87
+ allow(battery).to receive(:state).and_return(:charging)
88
+
89
+ expect(battery.power).to eq(1.0)
90
+ end
91
+
92
+ it 'returns a negative value if state is :discharging' do
93
+ allow(File).to receive(:read).with(power_file).and_return("1000000\n")
94
+ allow(battery).to receive(:state).and_return(:discharging)
95
+
96
+ expect(battery.power).to be < 0
97
+ end
98
+
99
+ it 'returns a positive value if state is either :charging or :idle' do
100
+ allow(File).to receive(:read).with(power_file).and_return("1000000\n")
101
+ allow(battery).to receive(:state).and_return(:charging)
102
+
103
+ expect(battery.power).to be >= 0
104
+
105
+ allow(battery).to receive(:state).and_return(:idle)
106
+
107
+ expect(battery.power).to be >= 0
108
+ end
109
+
110
+ end
111
+
112
+ describe "#state" do
113
+ let(:state_file) { File.join(battery.path, 'status') }
114
+
115
+ it 'reads the state from the correct file' do
116
+ allow(File).to receive(:read).with(state_file).and_return("Discharging\n")
117
+ battery.state
118
+
119
+ expect(File).to have_received(:read).with(state_file)
120
+ end
121
+
122
+ it 'returns the correct state as a symbol' do
123
+ allow(File).to receive(:read).with(state_file).and_return("Discharging\n")
124
+
125
+ expect(battery.state).to eq :discharging
126
+ end
127
+
128
+ it 'returns :idle if state read from file is Unknown' do
129
+ allow(File).to receive(:read).with(state_file).and_return("Unknown\n")
130
+
131
+ expect(battery.state).to eq :idle
132
+ end
133
+ end
134
+
135
+ describe "#remaining_energy" do
136
+ let(:energy_file) { File.join(battery.path, 'energy_now') }
137
+
138
+ it 'reads the value from the correct file' do
139
+ allow(File).to receive(:read).with(energy_file).and_return("100000000\n")
140
+ battery.remaining_energy
141
+
142
+ expect(File).to have_received(:read).with(energy_file)
143
+ end
144
+
145
+ it 'returns the remaining energy in Wh' do
146
+ allow(File).to receive(:read).with(energy_file).and_return("100000000\n")
147
+
148
+ expect(battery.remaining_energy).to eq 100.0
149
+ end
150
+ end
151
+
152
+ describe '#remaining_running_time' do
153
+ it 'calculates the value from the current power and remaining energy' do
154
+ allow(battery).to receive(:state).and_return(:discharging)
155
+ allow(battery).to receive(:power).and_return(10.0)
156
+ allow(battery).to receive(:remaining_energy).and_return(100.0)
157
+ battery.remaining_running_time
158
+
159
+ expect(battery).to have_received(:power)
160
+ expect(battery).to have_received(:remaining_energy)
161
+
162
+ expect(battery.remaining_running_time).to eq 600
163
+ end
164
+
165
+ it 'raises a WrongStateError if the battery is not discharging' do
166
+ allow(battery).to receive(:power).and_return(10.0)
167
+ allow(battery).to receive(:remaining_energy).and_return(100.0)
168
+
169
+ allow(battery).to receive(:state).and_return(:discharging)
170
+ battery.remaining_running_time
171
+
172
+ allow(battery).to receive(:state).and_return(:idle)
173
+
174
+ expect { battery.remaining_running_time }.to raise_error(WrongStateError)
175
+
176
+ allow(battery).to receive(:state).and_return(:charging)
177
+
178
+ expect { battery.remaining_running_time }.to raise_error(WrongStateError)
179
+ end
180
+
181
+ end
182
+
183
+ describe "#full_energy" do
184
+ let(:energy_file) { File.join(battery.path, 'energy_full') }
185
+
186
+ it 'reads the value from the correct file' do
187
+ allow(File).to receive(:read).with(energy_file).and_return("100000000\n")
188
+ battery.full_energy
189
+
190
+ expect(File).to have_received(:read).with(energy_file)
191
+ end
192
+
193
+ it 'returns the remaining energy in Wh' do
194
+ allow(File).to receive(:read).with(energy_file).and_return("100000000\n")
195
+
196
+ expect(battery.full_energy).to eq 100.0
197
+ end
198
+ end
199
+
200
+ describe '#remaining_charging_time' do
201
+ it 'calculates the value from the current power and remaining energy' do
202
+ allow(battery).to receive(:state).and_return(:charging)
203
+ allow(battery).to receive(:power).and_return(10.0)
204
+ allow(battery).to receive(:remaining_energy).and_return(90.0)
205
+ allow(battery).to receive(:full_energy).and_return(100.0)
206
+ battery.remaining_charging_time
207
+
208
+ expect(battery).to have_received(:power)
209
+ expect(battery).to have_received(:remaining_energy)
210
+
211
+ expect(battery.remaining_charging_time).to eq 60.0
212
+ end
213
+
214
+ it 'raises a WrongStateError if the battery is not charging' do
215
+ allow(battery).to receive(:remaining_energy).and_return(90.0)
216
+ allow(battery).to receive(:power).and_return(10.0)
217
+ allow(battery).to receive(:full_energy).and_return(100.0)
218
+
219
+ allow(battery).to receive(:state).and_return(:charging)
220
+ battery.remaining_charging_time
221
+
222
+ allow(battery).to receive(:state).and_return(:discharging)
223
+
224
+ expect { battery.remaining_charging_time }.to raise_error(WrongStateError)
225
+
226
+ allow(battery).to receive(:state).and_return(:idle)
227
+
228
+ expect { battery.remaining_charging_time }.to raise_error(WrongStateError)
229
+ end
230
+ end
231
+ end
232
+ end
@@ -0,0 +1,111 @@
1
+ require 'battman/battery'
2
+ module Battman
3
+ describe Battery do
4
+
5
+ describe '.new' do
6
+
7
+ it 'throws an AbstractError if directly instantiated' do
8
+ expect { Battery.new }.to raise_error AbstractError
9
+ end
10
+
11
+ let(:subclass) { Class.new(Battery) }
12
+
13
+ it 'allows instantiation of subclasses' do
14
+ subclass.new
15
+ end
16
+
17
+ it 'accepts an index and sets the corresponding instance variable' do
18
+ battery = subclass.new(1)
19
+
20
+ expect(battery.instance_variable_get(:@index)).to eq 1
21
+ end
22
+
23
+ it 'sets index to 0 by default' do
24
+ battery = subclass.new
25
+
26
+ expect(battery.instance_variable_get(:@index)).to eq 0
27
+ end
28
+
29
+ end
30
+
31
+ let(:battery) { Class.new(Battery).new }
32
+
33
+ [:remaining_percent, :power, :remaining_running_time,
34
+ :remaining_charging_time, :state, :remaining_energy,
35
+ :full_energy].each do |method|
36
+
37
+ describe "##{method}" do
38
+
39
+ it 'throws an NotImplementedError' do
40
+ expect { battery.send(method) }.to raise_error NotImplementedError
41
+ end
42
+ end
43
+ end
44
+
45
+
46
+ describe '#power_in' do
47
+ it 'requires a unit argument' do
48
+ expect { battery.power_in }.to raise_error(ArgumentError)
49
+ end
50
+
51
+ it 'only accepts units from CONVERSIONS[:power]' do
52
+ expect { battery.power_in(:unsupported_unit) }.to raise_error(UnsupportedUnitError)
53
+
54
+ allow(battery).to receive(:power).and_return(10.0)
55
+ battery.power_in(Battery::CONVERSIONS[:power].keys.sample)
56
+ end
57
+
58
+ it 'converts the power from watts to the given unit' do
59
+ allow(battery).to receive(:power).and_return(10.0)
60
+
61
+ expect(battery.power_in(:watts)).to eq 10.0
62
+ expect(battery.power_in(:milliwatts)).to eq 10_000.0
63
+ end
64
+ end
65
+
66
+ [:remaining_running_time, :remaining_charging_time].each do |method|
67
+ describe "#{method}_in" do
68
+ it 'requires a unit argument' do
69
+ expect { battery.send(:"#{method}_in") }.to raise_error(ArgumentError)
70
+ end
71
+
72
+ it 'only accepts units from CONVERSIONS[:time]' do
73
+ expect { battery.send(:"#{method}_in", :unsupported_unit) }.to raise_error(UnsupportedUnitError)
74
+
75
+ allow(battery).to receive(method).and_return(10.0)
76
+ battery.send(:"#{method}_in", Battery::CONVERSIONS[:time].keys.sample)
77
+ end
78
+
79
+ it 'converts the time from seconds to the given unit' do
80
+ allow(battery).to receive(method).and_return(3600.0)
81
+
82
+ expect(battery.send(:"#{method}_in", :seconds)).to eq 3600.0
83
+ expect(battery.send(:"#{method}_in", :minutes)).to eq 60.0
84
+ expect(battery.send(:"#{method}_in", :hours)).to eq 1.0
85
+ end
86
+ end
87
+ end
88
+
89
+ [:remaining_energy, :full_energy].each do |method|
90
+ describe "#{method}_in" do
91
+ it 'requires a unit argument' do
92
+ expect { battery.send(:"#{method}_in") }.to raise_error(ArgumentError)
93
+ end
94
+
95
+ it 'only accepts units from CONVERSIONS[:energy]' do
96
+ expect { battery.send(:"#{method}_in", :unsupported_unit) }.to raise_error(UnsupportedUnitError)
97
+
98
+ allow(battery).to receive(method).and_return(10.0)
99
+ battery.send(:"#{method}_in", Battery::CONVERSIONS[:energy].keys.sample)
100
+ end
101
+
102
+ it 'converts the time from watt hours to the given unit' do
103
+ allow(battery).to receive(method).and_return(10.0)
104
+
105
+ expect(battery.send(:"#{method}_in", :watt_hours)).to eq 10.0
106
+ expect(battery.send(:"#{method}_in", :milliwatt_hours)).to eq 10_000.0
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,62 @@
1
+ require 'battman/dsl/every_block'
2
+
3
+ module Battman
4
+ module DSL
5
+ describe EveryBlock do
6
+
7
+ describe '#check' do
8
+
9
+ let(:dsl) { double('DSL') }
10
+ let(:battery) { double('Battery') }
11
+ let(:state) { EveryBlock.new(dsl, battery, 10) }
12
+
13
+ before(:each) do
14
+ allow(battery).to receive(:power_in).with(:milliwatts)
15
+ allow(battery).to receive(:remaining_percent)
16
+ allow(dsl).to receive(:register)
17
+ end
18
+
19
+ it 'expects a battery attribute and a block' do
20
+ expect { state.check }.to raise_error(ArgumentError)
21
+ expect { state.check(:remaining_percent) }.to raise_error(ArgumentError)
22
+ state.check(:remaining_percent) {}
23
+ end
24
+
25
+ it 'allows additional arguments to pass methods' do
26
+ state.check(:power_in, :milliwatts) {}
27
+ end
28
+
29
+ it 'correctly registers a block on the dsl instance' do
30
+ task_was_called = false
31
+ value_passed = nil
32
+ last_value_passed = nil
33
+ task = Proc.new do |value, last_value|
34
+ task_was_called = true
35
+ value_passed = value
36
+ last_value_passed = last_value
37
+ end
38
+
39
+ expect(dsl).to receive(:register) do |interval, block|
40
+ expect(interval).to eq 10
41
+
42
+ expect(battery).to receive(:power_in).with(:milliwatts).and_return(10_000.0)
43
+ block.call
44
+
45
+ expect(task_was_called)
46
+ expect(value_passed).to eq 10_000.0
47
+ expect(last_value_passed).to be nil
48
+
49
+ expect(battery).to receive(:power_in).with(:milliwatts).and_return(9_000.0)
50
+ block.call
51
+
52
+ expect(value_passed).to eq 9_000.0
53
+ expect(last_value_passed).to eq 10_000.0
54
+ end
55
+
56
+ state.check(:power_in, :milliwatts, &task)
57
+ end
58
+
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,27 @@
1
+ require 'battman/dsl/watch_block'
2
+
3
+ module Battman
4
+ module DSL
5
+ describe WatchBlock do
6
+ let(:dsl) { double('DSL') }
7
+ let(:battery) { double('Battery') }
8
+ let(:watcher) { WatchBlock.new(dsl, battery) }
9
+
10
+ describe '#every' do
11
+ it 'expects an interval in seconds and a block' do
12
+ expect { watcher.every }.to raise_error(ArgumentError)
13
+ expect { watcher.every(10) }.to raise_error(ArgumentError)
14
+
15
+ watcher.every(10) {}
16
+ end
17
+
18
+ it 'yields an object that responds to check' do
19
+ expect {|b| watcher.every(10, &b) }.to yield_with_args do |arg|
20
+ expect(arg).to respond_to(:check)
21
+ end
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,137 @@
1
+ require 'battman/dsl'
2
+ require 'battman/smapi_battery'
3
+ require 'active_support/core_ext/numeric/time'
4
+
5
+ module Battman
6
+ describe DSL do
7
+
8
+ let(:dsl) { Class.new { include DSL }.new }
9
+
10
+ describe '.new' do
11
+ it 'initializes @blocks correctly' do
12
+ blocks = dsl.instance_variable_get(:@blocks)
13
+ expect(blocks).not_to be nil
14
+ expect(blocks[2]).to eq []
15
+
16
+ blocks[4] << :foo
17
+ blocks[4] << :bar
18
+ expect(blocks[4]).to eq [:foo, :bar]
19
+ end
20
+
21
+ it 'initializes @intervals_due correctly' do
22
+ intervals_due = dsl.instance_variable_get(:@intervals_due)
23
+
24
+ expect(intervals_due[1]).to eq 0
25
+ expect(intervals_due[:anything]).to eq 0
26
+ end
27
+
28
+ it 'yields self if given a block' do
29
+ expect {|b| Class.new { include DSL }.new(&b) }.to yield_with_args(DSL)
30
+ end
31
+ end
32
+
33
+ describe '#watch' do
34
+
35
+ it 'instantiates a battery of given type' do
36
+ expect(SmapiBattery).to receive(:new)
37
+
38
+ dsl.watch(:smapi) {}
39
+ end
40
+
41
+ it 'expects a valid type and a block' do
42
+ expect { dsl.watch }.to raise_error(ArgumentError)
43
+ expect { dsl.watch(:type) }.to raise_error(ArgumentError)
44
+ expect { dsl.watch(:invalid_type) {} }.to raise_error(LoadError)
45
+
46
+ dsl.watch(:smapi) {}
47
+ end
48
+
49
+ it 'yields a WatchBlock' do
50
+ expect {|b| dsl.watch(:smapi, &b) }.to yield_with_args(DSL::WatchBlock)
51
+ end
52
+
53
+ end
54
+
55
+ describe '#register' do
56
+
57
+ it 'adds a given block to the blocks hash' do
58
+ first_block = Proc.new {}
59
+ second_block = Proc.new {}
60
+ dsl.register(1, first_block)
61
+ dsl.register(1, second_block)
62
+
63
+ expect(dsl.instance_variable_get(:@blocks)[1]).to eq [first_block, second_block]
64
+ end
65
+
66
+ it 'sets the greatest common interval correctly' do
67
+ dsl.register(4, Proc.new {})
68
+ expect(dsl.instance_variable_get(:@greatest_common_interval)).to eq 4
69
+
70
+ dsl.register(8, Proc.new {})
71
+ expect(dsl.instance_variable_get(:@greatest_common_interval)).to eq 4
72
+
73
+ dsl.register(10, Proc.new {})
74
+ expect(dsl.instance_variable_get(:@greatest_common_interval)).to eq 2
75
+ end
76
+
77
+ end
78
+
79
+ describe '#run_once' do
80
+ let(:blocks) do
81
+ blocks = Hash.new {|hash, key| hash[key] = []}
82
+ 3.times do |i|
83
+ block = double('proc')
84
+ allow(block).to receive(:call)
85
+ blocks[2**(i+1)] << block
86
+ end
87
+
88
+ blocks
89
+ end
90
+
91
+ let(:greatest_common_interval) { blocks.keys.inject(:gcd) }
92
+
93
+ before(:each) do
94
+ dsl.instance_variable_set(:@blocks, blocks)
95
+ dsl.instance_variable_set(:@greatest_common_interval, greatest_common_interval)
96
+ end
97
+
98
+ it 'calls all registered blocks on the first run' do
99
+ blocks.values.inject(&:+).each do |block|
100
+ expect(block).to receive(:call).once
101
+ end
102
+
103
+ dsl.run_once
104
+ end
105
+
106
+ it 'sets the correct schedules for blocks in @intervals_due' do
107
+ expected_schedule = {}
108
+ blocks.keys.each do |interval|
109
+ expected_schedule[interval] = interval
110
+ end
111
+
112
+ dsl.run_once
113
+ expect(dsl.instance_variable_get(:@intervals_due)).to eq expected_schedule
114
+ end
115
+
116
+ it 'calls only scheduled blocks on consecutive runs' do
117
+ dsl.run_once
118
+ dsl.run_once
119
+
120
+ blocks[greatest_common_interval].each do |block|
121
+ expect(block).to have_received(:call).twice
122
+ end
123
+
124
+ dsl.run_once
125
+
126
+ blocks[greatest_common_interval].each do |block|
127
+ expect(block).to have_received(:call).exactly(3).times
128
+ end
129
+
130
+ blocks[greatest_common_interval * 2].each do |block|
131
+ expect(block).to have_received(:call).twice
132
+ end
133
+ end
134
+ end
135
+
136
+ end
137
+ end