psched 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.
- checksums.yaml +7 -0
- data/.document +3 -0
- data/.gitignore +6 -0
- data/.rspec +1 -0
- data/.yardopts +1 -0
- data/ChangeLog.md +4 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +23 -0
- data/README.md +44 -0
- data/Rakefile +25 -0
- data/lib/psched/version.rb +4 -0
- data/lib/psched.rb +192 -0
- data/psched.gemspec +38 -0
- data/spec/psched_spec.rb +8 -0
- data/spec/spec_helper.rb +4 -0
- metadata +144 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 58946f4a0c8a49837d18022f99a4f98c99cf3117
|
4
|
+
data.tar.gz: c1d675d5dba069199d49420407694171855261e1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 47cbf71e0f1f5c47c58980105761bfd0d65dd68a3d9b1fe39f5b54efbbe4ec7e72b10d897e9151bcee797c24319653b916b1a8de07591ac0d8473cb9f2e7c103
|
7
|
+
data.tar.gz: a0e0a9d007693cda71f86ae85b8c6280f4fc873fa2f4288520e9382d25eaae0682b532dda4c77a1dbf58824d2b009bceb1d76c3f680172bb56e73fada7b62d69
|
data/.document
ADDED
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour --format documentation
|
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--markup markdown --title "psched Documentation" --protected
|
data/ChangeLog.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
Copyright (c) 2017, Paolo Bosetti
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without modification,
|
5
|
+
are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
8
|
+
list of conditions and the following disclaimer.
|
9
|
+
|
10
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
this list of conditions and the following disclaimer in the documentation
|
12
|
+
and/or other materials provided with the distribution.
|
13
|
+
|
14
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
15
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
16
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
17
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
18
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
19
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
20
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
21
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
22
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
23
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# psched
|
2
|
+
|
3
|
+
- [Homepage](https://rubygems.org/gems/psched)
|
4
|
+
- [Documentation](http://rubydoc.info/gems/psched/frames)
|
5
|
+
- [Email](mailto:paolo.bosetti at unitn.it)
|
6
|
+
|
7
|
+
## Description
|
8
|
+
|
9
|
+
TODO: Description
|
10
|
+
|
11
|
+
## Features
|
12
|
+
|
13
|
+
TODO: Features
|
14
|
+
|
15
|
+
## Examples
|
16
|
+
|
17
|
+
require 'psched'
|
18
|
+
op = PSched::Operation.new(0.5)
|
19
|
+
op.start(10) do |i|
|
20
|
+
puts "Ping #{i}"
|
21
|
+
end
|
22
|
+
|
23
|
+
Signal.trap("SIGINT") do
|
24
|
+
print "Stopping recurring process..."
|
25
|
+
op.stop
|
26
|
+
puts "done!"
|
27
|
+
end
|
28
|
+
|
29
|
+
sleep(0.2) while op.active? # waits for scheduling to complete
|
30
|
+
|
31
|
+
## Requirements
|
32
|
+
FFI gem and an OS supporting semaphores.
|
33
|
+
|
34
|
+
## Install
|
35
|
+
|
36
|
+
```
|
37
|
+
$ gem install psched
|
38
|
+
```
|
39
|
+
|
40
|
+
## Copyright
|
41
|
+
|
42
|
+
Copyright (c) 2017 Paolo Bosetti
|
43
|
+
|
44
|
+
See {file:LICENSE.txt} for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'bundler/setup'
|
7
|
+
rescue LoadError => e
|
8
|
+
abort e.message
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'rake'
|
12
|
+
|
13
|
+
|
14
|
+
require 'rubygems/tasks'
|
15
|
+
Gem::Tasks.new
|
16
|
+
|
17
|
+
require 'rspec/core/rake_task'
|
18
|
+
RSpec::Core::RakeTask.new
|
19
|
+
|
20
|
+
task :test => :spec
|
21
|
+
task :default => :spec
|
22
|
+
|
23
|
+
require 'yard'
|
24
|
+
YARD::Rake::YardocTask.new
|
25
|
+
task :doc => :yard
|
data/lib/psched.rb
ADDED
@@ -0,0 +1,192 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# psched.rb
|
3
|
+
|
4
|
+
# Created by Paolo Bosetti on 2011-04-21.
|
5
|
+
# Copyright (c) 2011 University of Trento. All rights reserved.
|
6
|
+
|
7
|
+
require 'ffi'
|
8
|
+
require 'psched/version'
|
9
|
+
|
10
|
+
|
11
|
+
# A module and a class ({PSched::Operation}) for implementing a recurring
|
12
|
+
# call as deterministic as possible.
|
13
|
+
#
|
14
|
+
# See {PSched::Operation} for usage details. Module functions are FFI
|
15
|
+
# wrappers and are not intended to be used directly.
|
16
|
+
# @author Paolo Bosetti
|
17
|
+
# @todo Are there better ways?
|
18
|
+
module PSched
|
19
|
+
extend FFI::Library
|
20
|
+
ffi_lib FFI::Library::LIBC
|
21
|
+
# @!method ualarm(useconds, interval)
|
22
|
+
# The `ualarm()` function waits a count of useconds before asserting the
|
23
|
+
# terminating signal `SIGALRM`. System activity or time used in processing
|
24
|
+
# the call may cause a slight delay.
|
25
|
+
# If the interval argument is non-zero, the `SIGALRM` signal will be sent to
|
26
|
+
# the process every interval microseconds after the timer expires
|
27
|
+
# (e.g.,after useconds number of microseconds have passed).
|
28
|
+
# @param [Fixnum] useconds initial delay in microseconds
|
29
|
+
# @param [Fixnum] interval recurring interval in microseconds
|
30
|
+
# @return [Fixnum] the amount of time left on the clock
|
31
|
+
attach_function :ualarm, [:uint, :uint], :uint
|
32
|
+
|
33
|
+
# @!method alarm(seconds)
|
34
|
+
# Set a timer to deliver the signal `SIGALRM` to the calling process after
|
35
|
+
# the specified number of seconds
|
36
|
+
# @param [Fixnum] seconds delay in microseconds
|
37
|
+
# @return [Fixnum] the amount of time left on the clock from a previous
|
38
|
+
# call to alarm()
|
39
|
+
attach_function :alarm, [:uint], :uint
|
40
|
+
|
41
|
+
enum :which, [
|
42
|
+
:prio_process, 0,
|
43
|
+
:prio_pgrp,
|
44
|
+
:prio_user,
|
45
|
+
:prio_darwin_thread,
|
46
|
+
:prio_darwin_process
|
47
|
+
]
|
48
|
+
|
49
|
+
# @!method setpriority(which, who, prio)
|
50
|
+
# Set the scheduling priority of the current process
|
51
|
+
# @param [Fixnum] which The type of priority, as for the enum `which`
|
52
|
+
# @param [Fixnum] who The process, group, user or thread
|
53
|
+
# @param [Fixnum] prio The priority level, from -20 to 20
|
54
|
+
# @return [0|-1] 0 if no error, -1 if error
|
55
|
+
attach_function :setpriority, [:which, :uint, :int], :int
|
56
|
+
|
57
|
+
# Implements a recurring operation.
|
58
|
+
# @example General usage. Note the `sleep` call.
|
59
|
+
# op = PSched::Operation.new(0.5)
|
60
|
+
# op.start(10) do |i|
|
61
|
+
# puts "Ping #{i}"
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
# Signal.trap("SIGINT") do
|
65
|
+
# print "Stopping recurring process..."
|
66
|
+
# op.stop
|
67
|
+
# puts "done!"
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
# sleep(0.2) while op.active? # THIS IS IMPORTANT!!!
|
71
|
+
#
|
72
|
+
# @author Paolo Bosetti
|
73
|
+
class Operation
|
74
|
+
# Changes scheduling priority of the current process
|
75
|
+
# @param [Fixnum] nice sets the nice level (-20..+20)
|
76
|
+
# @todo better return message and error management
|
77
|
+
def self.prioritize(nice = -20)
|
78
|
+
result = PSched.setpriority(:prio_process,0,nice)
|
79
|
+
puts "Setting max priority: #{result == 0 ? 'success' : 'failure (missing sudo?)'}"
|
80
|
+
end
|
81
|
+
|
82
|
+
# @!attribute [rw] strict_timing
|
83
|
+
# If true, raise a {RealTimeError} when `TET >= step`
|
84
|
+
# (default to `false`)
|
85
|
+
attr_accessor :strict_timing
|
86
|
+
|
87
|
+
# @!attribute [r] tet
|
88
|
+
# The Task Execution Time
|
89
|
+
# @!attribute [r] step
|
90
|
+
# The time step in seconds
|
91
|
+
attr_reader :step, :tet
|
92
|
+
# Initializer
|
93
|
+
# @param [Numeric] step the timestep in seconds
|
94
|
+
def initialize(step)
|
95
|
+
@step = step
|
96
|
+
@active = false
|
97
|
+
@lock = false
|
98
|
+
@strict_timing = false
|
99
|
+
@tet = 0
|
100
|
+
end
|
101
|
+
|
102
|
+
# Sets the time step (in seconds) and reschedule pending alarms.
|
103
|
+
# @param [Numeric] secs Timestep in seconds
|
104
|
+
def step=(secs)
|
105
|
+
@step = secs
|
106
|
+
self.schedule
|
107
|
+
end
|
108
|
+
|
109
|
+
# Updates scheduling of pending alarms.
|
110
|
+
# @note Usually, there's no need to call this, since {#step=} automatically
|
111
|
+
# calls it after having set the +@step+ attribute.
|
112
|
+
def schedule
|
113
|
+
usecs = @step * 1E6
|
114
|
+
PSched::ualarm(usecs, usecs)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Tells id the recurring operation is active or not.
|
118
|
+
# @return [Boolean] the status of the recurring alarm operation
|
119
|
+
def active?; @active; end
|
120
|
+
|
121
|
+
# Starts a recurring operation, described by the passed block.
|
122
|
+
# If the block returns the symbol :stop, the recurrin operation gets
|
123
|
+
# disabled.
|
124
|
+
# @param [Fixnum,nil] n_iter the maximum number of iterations.
|
125
|
+
# If nil it loops indefinitedly
|
126
|
+
# @yieldparam [Fixnum] i the number of elapsed iterations
|
127
|
+
# @yieldparam [Numeric] tet the Task Execution Time of previous step
|
128
|
+
# @raise [ArgumentError] unless a block is given
|
129
|
+
# @raise [RealTimeError] if TET exceeds @step
|
130
|
+
def start(n_iter=nil)
|
131
|
+
@active = true
|
132
|
+
i = 0
|
133
|
+
raise ArgumentError, "Need a block!" unless block_given?
|
134
|
+
Signal.trap(:ALRM) do
|
135
|
+
# If there is still a pending step, raises an error containing
|
136
|
+
# information about the CURRENT step
|
137
|
+
if @lock then
|
138
|
+
if @strict_timing
|
139
|
+
@lock = false
|
140
|
+
raise RealTimeError.new({:tet => @tet, :step => @step, :i => i, :time => Time.now})
|
141
|
+
end
|
142
|
+
else
|
143
|
+
start = Time.now
|
144
|
+
@lock = true
|
145
|
+
result = yield(i, @tet)
|
146
|
+
i += 1
|
147
|
+
self.stop if (n_iter and i >= n_iter)
|
148
|
+
self.stop if (result.kind_of? Symbol and result == :stop)
|
149
|
+
@tet = Time.now - start
|
150
|
+
@lock = false
|
151
|
+
end
|
152
|
+
end
|
153
|
+
self.schedule
|
154
|
+
end
|
155
|
+
|
156
|
+
# Stops the recurring process by resetting alarm and disabling management
|
157
|
+
# of `SIGALRM` signal.
|
158
|
+
def stop
|
159
|
+
PSched::ualarm(0, 0)
|
160
|
+
Signal.trap(:ALRM, "DEFAULT")
|
161
|
+
@active = false
|
162
|
+
@lock = false
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
class RealTimeError < RuntimeError
|
168
|
+
attr_reader :status
|
169
|
+
def initialize(status)
|
170
|
+
@status = status
|
171
|
+
end
|
172
|
+
def ratio
|
173
|
+
(@status[:tet] / @status[:step]) * 100
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
if $0 == __FILE__ then
|
178
|
+
PSched::Operation.prioritize
|
179
|
+
|
180
|
+
op = PSched::Operation.new(0.01)
|
181
|
+
op.start(10) do |i|
|
182
|
+
puts "Ping #{i}"
|
183
|
+
end
|
184
|
+
|
185
|
+
Signal.trap("SIGINT") do
|
186
|
+
print "Stopping recurring process..."
|
187
|
+
op.stop
|
188
|
+
puts "done!"
|
189
|
+
end
|
190
|
+
|
191
|
+
sleep while op.active?
|
192
|
+
end
|
data/psched.gemspec
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'psched/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |gem|
|
8
|
+
gem.name = "psched"
|
9
|
+
gem.version = Psched::VERSION
|
10
|
+
gem.summary = %q{Precise scheduling of recurring tasks}
|
11
|
+
gem.description = %q{Precise scheduling of recurring tasks using semaphores (not supported on Windows!)}
|
12
|
+
gem.license = "0BSD"
|
13
|
+
gem.authors = ["Paolo Bosetti"]
|
14
|
+
gem.email = "paolo.bosetti@unitn.it"
|
15
|
+
gem.homepage = "https://rubygems.org/gems/psched"
|
16
|
+
|
17
|
+
gem.files = `git ls-files`.split($/)
|
18
|
+
|
19
|
+
`git submodule --quiet foreach --recursive pwd`.split($/).each do |submodule|
|
20
|
+
submodule.sub!("#{Dir.pwd}/",'')
|
21
|
+
|
22
|
+
Dir.chdir(submodule) do
|
23
|
+
`git ls-files`.split($/).map do |subpath|
|
24
|
+
gem.files << File.join(submodule,subpath)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
29
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
30
|
+
gem.require_paths = ['lib']
|
31
|
+
gem.add_dependency 'ffi', '~>1.9'
|
32
|
+
|
33
|
+
gem.add_development_dependency 'bundler', '~> 1.10'
|
34
|
+
gem.add_development_dependency 'rake', '~> 10.0'
|
35
|
+
gem.add_development_dependency 'rspec', '~> 3.0'
|
36
|
+
gem.add_development_dependency 'rubygems-tasks', '~> 0.2'
|
37
|
+
gem.add_development_dependency 'yard', '~> 0.8'
|
38
|
+
end
|
data/spec/psched_spec.rb
ADDED
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: psched
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Paolo Bosetti
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-12-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ffi
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.9'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.9'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.10'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.10'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rubygems-tasks
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.2'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.2'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: yard
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.8'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.8'
|
97
|
+
description: Precise scheduling of recurring tasks using semaphores (not supported
|
98
|
+
on Windows!)
|
99
|
+
email: paolo.bosetti@unitn.it
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- ".document"
|
105
|
+
- ".gitignore"
|
106
|
+
- ".rspec"
|
107
|
+
- ".yardopts"
|
108
|
+
- ChangeLog.md
|
109
|
+
- Gemfile
|
110
|
+
- LICENSE.txt
|
111
|
+
- README.md
|
112
|
+
- Rakefile
|
113
|
+
- lib/psched.rb
|
114
|
+
- lib/psched/version.rb
|
115
|
+
- psched.gemspec
|
116
|
+
- spec/psched_spec.rb
|
117
|
+
- spec/spec_helper.rb
|
118
|
+
homepage: https://rubygems.org/gems/psched
|
119
|
+
licenses:
|
120
|
+
- 0BSD
|
121
|
+
metadata: {}
|
122
|
+
post_install_message:
|
123
|
+
rdoc_options: []
|
124
|
+
require_paths:
|
125
|
+
- lib
|
126
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - ">="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '0'
|
136
|
+
requirements: []
|
137
|
+
rubyforge_project:
|
138
|
+
rubygems_version: 2.6.13
|
139
|
+
signing_key:
|
140
|
+
specification_version: 4
|
141
|
+
summary: Precise scheduling of recurring tasks
|
142
|
+
test_files:
|
143
|
+
- spec/psched_spec.rb
|
144
|
+
- spec/spec_helper.rb
|