spreadsheet_agent 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.
- data/lib/spreadsheet_agent.rb +418 -0
- data/lib/spreadsheet_agent/db.rb +55 -0
- data/lib/spreadsheet_agent/error.rb +12 -0
- data/lib/spreadsheet_agent/runner.rb +306 -0
- data/test/agent_bin/othergoal_agent.rb +17 -0
- data/test/agent_bin/testgoal_agent.rb +18 -0
- data/test/spreadsheet_agent_db_test.rb +13 -0
- data/test/spreadsheet_agent_runner_test.rb +468 -0
- data/test/spreadsheet_agent_test.rb +411 -0
- metadata +157 -0
@@ -0,0 +1,12 @@
|
|
1
|
+
# Author: Darin London
|
2
|
+
# The license of this source is "MIT Licence"
|
3
|
+
|
4
|
+
module SpreadsheetAgent
|
5
|
+
|
6
|
+
# SpreadsheetAgent::Error is an extension of Error that SpreadsheetAgent classes throw
|
7
|
+
# when critical errors are encountered
|
8
|
+
class Error < RuntimeError
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
@@ -0,0 +1,306 @@
|
|
1
|
+
# Author: Darin London
|
2
|
+
# The license of this source is "MIT Licence"
|
3
|
+
|
4
|
+
require 'spreadsheet_agent/db'
|
5
|
+
|
6
|
+
module SpreadsheetAgent
|
7
|
+
|
8
|
+
|
9
|
+
# SpreadsheetAgent::Runner is a class designed to facilitate the automated traversal of all, or some
|
10
|
+
# defined set of pages, entries, and goals defined in a SpreadsheetAgent compatible Google Spreadsheet,
|
11
|
+
# and run agents or processes on them. By placing a SpreadsheetAgent::Runner script into the scheduling
|
12
|
+
# system (cron, etc.) on one or more compute nodes, desired pages, entries, and goals can be processed
|
13
|
+
# efficiently over a period of time, and new pages, entries, or goals can be automatically picked up
|
14
|
+
# as they are introduced. Runners can be designed to automate the submission of agent scripts, check
|
15
|
+
# the status of jobs, aggregate information about job status, or automate cleanup tasks.
|
16
|
+
class Runner < SpreadsheetAgent::Db
|
17
|
+
|
18
|
+
# Boolean. Optional (default false). If true, run will generate the commands that
|
19
|
+
# it would run for all runnable entry-goals, print them to STDERR, but not actually
|
20
|
+
# run the commands. Automatically sets debug to 1.
|
21
|
+
# Note, if the process_entries_with coderef is overridden, dry_run is ignored.
|
22
|
+
attr_accessor :dry_run
|
23
|
+
|
24
|
+
# Boolean, Optional (default false). If true, the default process_entries_with PROC
|
25
|
+
# runs each agent_script executable in the foreground, rather than in the background,
|
26
|
+
# thus in serial. If false, all agent_script executables are run in parallel, in the
|
27
|
+
# background. This is not used when process_entries_with is set to a different PROC.
|
28
|
+
attr_accessor :run_in_serial
|
29
|
+
|
30
|
+
# Boolean, Optional (default false). If true, information about pages, entries, and
|
31
|
+
# goals that are checked and filtered is printed to STDERR.
|
32
|
+
attr_accessor :debug
|
33
|
+
|
34
|
+
# Integer, Optional (default 5). The number of seconds that the runner sleeps between
|
35
|
+
# each call to process an entry-goal.
|
36
|
+
attr_accessor :sleep_between
|
37
|
+
|
38
|
+
# String Path, Optional. The path to the directory containing agent executable programs that
|
39
|
+
# the default process PROC executes. The default is the ../agent_bin directory relative
|
40
|
+
# to the directory containing the calling script, $0.
|
41
|
+
attr_accessor :agent_bin
|
42
|
+
|
43
|
+
# Readonly access to the Hash of key_fields, as defined in :config. The runner uses this to construct
|
44
|
+
# the commandline for each agent on each entry in a page that gets run, with the value of the
|
45
|
+
# GoogleDrive::List entry for the given 'key' passed as argument in the order specified by the
|
46
|
+
# 'rank' field for each key in the config.
|
47
|
+
attr_reader :query_fields
|
48
|
+
|
49
|
+
# Readonly access to the array of pages to be processed. Only pages will only be defined
|
50
|
+
# when :only_pages or :skip_pages are defined in the constructor params, or when the skip_pages_if,
|
51
|
+
# or only_pages_if methods are called.
|
52
|
+
attr_reader :only_pages
|
53
|
+
|
54
|
+
@skip_entry_code = nil
|
55
|
+
@skip_goal_code = nil
|
56
|
+
|
57
|
+
# Create a new SpreadsheetAgent::Runner. Can be created with any of the following optional attributes:
|
58
|
+
# * :skip_pages - raises SpreadsheetAgentError if passed along with :only_pages
|
59
|
+
# * :only_pages - raises SpreadsheetAgentError if passed along with :skip_pages
|
60
|
+
# * :dry_run
|
61
|
+
# * :run_in_serial
|
62
|
+
# * :debug
|
63
|
+
# * :config_file (see SpreadsheetAgent::Db)
|
64
|
+
# * :sleep_between
|
65
|
+
# * :agent_bin
|
66
|
+
#
|
67
|
+
def initialize(attributes = { })
|
68
|
+
if (!attributes[:skip_pages].nil? && !attributes[:only_pages].nil?)
|
69
|
+
raise SpreadsheetAgentError, "You cannot construct a runner with both only_pages and skip_pages"
|
70
|
+
end
|
71
|
+
|
72
|
+
@dry_run = attributes[:dry_run]
|
73
|
+
@run_in_serial = attributes[:run_in_serial]
|
74
|
+
@debug = attributes[:debug]
|
75
|
+
@config_file = attributes[:config_file]
|
76
|
+
|
77
|
+
@sleep_between = 5
|
78
|
+
unless attributes[:sleep_between].nil?
|
79
|
+
@sleep_between = attributes[:sleep_between]
|
80
|
+
end
|
81
|
+
|
82
|
+
@agent_bin = find_bin() + '../agent_bin'
|
83
|
+
unless attributes[:agent_bin].nil?
|
84
|
+
@agent_bin = attributes[:agent_bin]
|
85
|
+
end
|
86
|
+
|
87
|
+
if attributes[:skip_pages]
|
88
|
+
@skip_pages = attributes[:skip_pages].clone
|
89
|
+
end
|
90
|
+
|
91
|
+
if attributes[:only_pages]
|
92
|
+
@only_pages = attributes[:only_pages].clone
|
93
|
+
end
|
94
|
+
|
95
|
+
build_db()
|
96
|
+
@query_fields = build_query_fields()
|
97
|
+
|
98
|
+
if @skip_pages
|
99
|
+
skip_pages_if do |page|
|
100
|
+
@skip_pages.include? page
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
if @dry_run
|
105
|
+
@debug = true
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Provide a PROC designed to intelligently filter out pages that are not to be processed.
|
110
|
+
# If not called, all pages not defined in :only_pages, or :skip_pages parameters in the constructor,
|
111
|
+
# or a previous call to only_pages_if will be processed.
|
112
|
+
# This will override only_pages, or skip_pages passed as arguments to the constructor, and
|
113
|
+
# any previous call to skip_pages_if, or only_pages_if. The PROC should take the title of
|
114
|
+
# a page as a string, and return true if a process decides to skip the page, false otherwise.
|
115
|
+
# Must be called before the process! method to affect the pages it processes. Returns the runner
|
116
|
+
# self to facilitate chained processing with skip_goal, skip_entry, and/or process! if desired.
|
117
|
+
#
|
118
|
+
# skip pages whose title contains 'skip'
|
119
|
+
# runner.skip_pages_if {|title| title.match(/skip/) }.process!
|
120
|
+
#
|
121
|
+
# Same, but without calling process so that skip_entry or skip_goal can be called on the runner
|
122
|
+
# runner.skip_pages_if do |title|
|
123
|
+
# title.match(/skip/)
|
124
|
+
# end
|
125
|
+
# ... can call skip_entry, skip_goal, etc
|
126
|
+
# runner.process!
|
127
|
+
#
|
128
|
+
def skip_pages_if(&skip_code)
|
129
|
+
@only_pages = @db.worksheets.collect{ |p| p.title }.reject{ |ptitle| skip_code.call(ptitle) }
|
130
|
+
self
|
131
|
+
end
|
132
|
+
|
133
|
+
# Provide a PROC desinged to intelligently determine pages to process. If not called, all pages
|
134
|
+
# not affected by the :skip_pages, or :only_pages constructor params, or a previous call to
|
135
|
+
# skip_pages_if will be processed.
|
136
|
+
# This will override only_pages, or skip_pages passed as arguments to the constructor, and
|
137
|
+
# any previous call to skip_pages_if, or only_pages_if. The PROC should take the title of
|
138
|
+
# a page as a string, and return true if a process decides to include the page, false otherwise.
|
139
|
+
# Must be called before the process! method to affect the pages it processes. Returns the runner
|
140
|
+
# self to facilitate chained processing with skip_goal, skip_entry, and/or process! if desired.
|
141
|
+
#
|
142
|
+
# include only pages whose title begins with 'foo'
|
143
|
+
# runner.only_pages_if {|title| title.match(/^foo/)}.process!
|
144
|
+
#
|
145
|
+
# Same, but without calling process so that skip_entry or skip_goal can be called on the runner
|
146
|
+
# runner.only_pages_if do |title|
|
147
|
+
# title.match(/^foo/)
|
148
|
+
# end
|
149
|
+
# ... can call skip_entry, skip_goal
|
150
|
+
# runner.process!
|
151
|
+
#
|
152
|
+
def only_pages_if(&include_code)
|
153
|
+
@only_pages = @db.worksheets.collect{ |p| p.title }.select { |ptitle| include_code.call(ptitle) }
|
154
|
+
self
|
155
|
+
end
|
156
|
+
|
157
|
+
# Provide a PROC desinged to intelligently determine entries on any page to skip. If not called,
|
158
|
+
# all entries on processed pages will be processed.
|
159
|
+
# The PROC should take a GoogleDrive::List representing the record in the spreadsheet, which can be accessed
|
160
|
+
# as a Hash with fields as key and that fields value as value. It should return true if the code decides to
|
161
|
+
# skip processing the entry, false otherwise. Must be called before the process! method to affect the entries
|
162
|
+
# on each page that it processes. Returns the runner self to facilitate chained processing with skip_pages_if,
|
163
|
+
# only_pages_if, skip_goal, and/or process! if desired.
|
164
|
+
#
|
165
|
+
# skip entries which have run foo or bar
|
166
|
+
# runner.only_pages_if {|entry| entry['foo'] == 1 || entry['bar'] == 1 }.process!
|
167
|
+
#
|
168
|
+
# skip entries that a human reading the spreadsheet has annotated with less than 3.5 in the 'threshold' field
|
169
|
+
# runner.only_pages_if do |entry|
|
170
|
+
# entry['threshold'] < 3.5
|
171
|
+
# end
|
172
|
+
# ... can call skip_pages_if, only_pages_if, skip_goal
|
173
|
+
# runner.process!
|
174
|
+
#
|
175
|
+
def skip_entry(&skip_code)
|
176
|
+
@skip_entry_code = skip_code
|
177
|
+
self
|
178
|
+
end
|
179
|
+
|
180
|
+
# Provide a PROC desinged to skip a specific goal in any entry on all pages processed. If not called,
|
181
|
+
# all goals of each entry and page to be processed by the runner will be processed.
|
182
|
+
# [note!] Ignored when a PROC is passed to the process! method, e.g. it is only used when process! executes
|
183
|
+
# agent scripts for the goal.
|
184
|
+
# The PROC should take a string, which will be one of the header fields in the spreadsheet. It should return
|
185
|
+
# true if that goal is to be skipped, falsed otherwise.
|
186
|
+
# Returns the runner self to facilitate chained processing with skip_pages_if, only_pages_if, skip_entry,
|
187
|
+
# and/or process! if desired.
|
188
|
+
#
|
189
|
+
# skip the 'post_process' goal on each entry of each page processed
|
190
|
+
# runner.skip_goal{|goal| goal == 'post_process' }.process!
|
191
|
+
#
|
192
|
+
# This is best when used in conjunction with skip_entry to skip_goals for particular entries
|
193
|
+
# runner.skip_entry{|entry| entry['threshold'] < 2.5 }.skip_goal{|goal| goal == 'post_process' }.process!
|
194
|
+
#
|
195
|
+
def skip_goal(&skip_code)
|
196
|
+
@skip_goal_code = skip_code
|
197
|
+
self
|
198
|
+
end
|
199
|
+
|
200
|
+
# Processes configured pages, entries, and goals with a PROC. The default PROC takes the entry, iterates
|
201
|
+
# over each goal not skipped by skip_goal, and:
|
202
|
+
# * determines if an executable #{ @agent_bin }/#{ goal }_agent.rb script exists
|
203
|
+
# * if so, executes the goal_agent script with commandline arguments constructed from the values in the entry for each field in the query_fields array defined in config.
|
204
|
+
# If run_in_serial is false, the default PROC runs each agent in the background, in parallel.
|
205
|
+
# Otherwise, it runs each serially in the foreground. If dry_run is true, the command is printed to STDERR,
|
206
|
+
# but is not run.
|
207
|
+
# A PROC supplied to override the default PROC should take an GoogleDrive::List, and GoogleDrive::Worksheet as arguments.
|
208
|
+
# This allows the process to query the entry for information using its hash access, and/or update the entry on the
|
209
|
+
# spreadsheet. In order for changes to the GoogleDrive::List to take effect, the GoogleDrive::Worksheet must be saved in the PROC.
|
210
|
+
# The process sleeps @sleep_between between each call to the PROC (default or otherwise). If dry_run is true
|
211
|
+
# when a PROC is supplied, the page.title and runnable_entry hash inspection are printed to STDERR but the PROC
|
212
|
+
# is not called.
|
213
|
+
#
|
214
|
+
# # call each goal agent script in agent_bin on each entry in each page
|
215
|
+
# runner = SpreadsheetAgent::Runer.new
|
216
|
+
# runner.process!
|
217
|
+
#
|
218
|
+
# # find entries with a threshold > 5 and update the 'threshold_exceeded' field
|
219
|
+
# runner.skip_entry{|entry| entry['threshold'] <= 5 }.process! do |entry,page|
|
220
|
+
# entry.update 'threshold_exceeded', "1"
|
221
|
+
# page.save
|
222
|
+
#
|
223
|
+
# # only process entries on the 'main' page where the threshold has not been exceeded
|
224
|
+
# runner.only_pages = ['main']
|
225
|
+
# runner.skip_entry{|entry| entry['threshold'] != 1 }.process!
|
226
|
+
#
|
227
|
+
def process!(&runner_code)
|
228
|
+
get_runnable_entries().each do |entry_info|
|
229
|
+
entry_page, runnable_entry = entry_info
|
230
|
+
if runner_code.nil?
|
231
|
+
default_process(runnable_entry)
|
232
|
+
elsif @dry_run
|
233
|
+
$stderr.print "Would run #{ entry_page.title } #{ runnable_entry.inspect }"
|
234
|
+
else
|
235
|
+
runner_code.call(runnable_entry, entry_page)
|
236
|
+
end
|
237
|
+
sleep @sleep_between
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
private
|
242
|
+
|
243
|
+
def get_runnable_entries
|
244
|
+
runnable_entries = []
|
245
|
+
get_pages_to_process().each do |page|
|
246
|
+
$stderr.puts "Processing page " + page.title if @debug
|
247
|
+
runnable_entries += page.list.reject { |check_entry | @skip_entry_code.nil? ? false : @skip_entry_code.call(check_entry) }.collect{ |entry| [page, entry] }
|
248
|
+
end
|
249
|
+
|
250
|
+
return runnable_entries
|
251
|
+
end
|
252
|
+
|
253
|
+
def run_entry_goal(entry, goal)
|
254
|
+
$stderr.puts "Running goal #{goal}" if @debug
|
255
|
+
|
256
|
+
goal_agent = [@agent_bin, "#{ goal }_agent.rb"].join('/')
|
257
|
+
cmd = [goal_agent]
|
258
|
+
|
259
|
+
@query_fields.each do |query_field|
|
260
|
+
if entry[query_field]
|
261
|
+
cmd.push entry[query_field]
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
command = cmd.join(' ')
|
266
|
+
command += '&' unless @run_in_serial
|
267
|
+
$stderr.puts command if @debug
|
268
|
+
if File.executable?(goal_agent)
|
269
|
+
unless @dry_run
|
270
|
+
system(command)
|
271
|
+
end
|
272
|
+
else
|
273
|
+
$stderr.puts "AGENT RUNNER DOES NOT EXIST!" if @debug
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
def default_process(runnable_entry)
|
278
|
+
title = query_fields.collect { |field| runnable_entry[field] }.join(' ')
|
279
|
+
$stderr.puts "Checking goals for #{ title }" if @debug
|
280
|
+
|
281
|
+
runnable_entry.keys.reject { |key|
|
282
|
+
if @skip_goal_code.nil?
|
283
|
+
false
|
284
|
+
else
|
285
|
+
( @skip_goal_code.call(key) )
|
286
|
+
end
|
287
|
+
}.each do |goal|
|
288
|
+
run_entry_goal(runnable_entry, goal)
|
289
|
+
sleep @sleep_between
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
def build_query_fields
|
294
|
+
@config['key_fields'].keys.sort { |a,b| @config['key_fields'][a]['rank'] <=> @config['key_fields'][b]['rank'] }
|
295
|
+
end
|
296
|
+
|
297
|
+
def get_pages_to_process
|
298
|
+
if @only_pages.nil?
|
299
|
+
@db.worksheets
|
300
|
+
else
|
301
|
+
@db.worksheets.select { |page| @only_pages.include? page.title }
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end #Runner
|
305
|
+
end #SpreadsheetAgent
|
306
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$:.unshift( File.expand_path( File.dirname(__FILE__) + '/../../lib' ) )
|
3
|
+
require 'spreadsheet_agent'
|
4
|
+
|
5
|
+
page, entry = ARGV
|
6
|
+
|
7
|
+
google_agent = SpreadsheetAgent::Agent.new({
|
8
|
+
:agent_name => File.basename($0).sub('_agent.rb',''),
|
9
|
+
:config_file => File.expand_path( File.dirname(__FILE__) + '/../../config/agent.conf.yml' ),
|
10
|
+
:page_name => page,
|
11
|
+
:keys => { 'testentry' => entry, 'testpage' => page }
|
12
|
+
})
|
13
|
+
|
14
|
+
google_agent.process! do |entry|
|
15
|
+
true
|
16
|
+
end
|
17
|
+
exit
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift( File.expand_path( File.dirname(__FILE__) + '/../../lib' ) )
|
4
|
+
require 'spreadsheet_agent'
|
5
|
+
|
6
|
+
page, entry = ARGV
|
7
|
+
|
8
|
+
google_agent = SpreadsheetAgent::Agent.new({
|
9
|
+
:agent_name => File.basename($0).sub('_agent.rb',''),
|
10
|
+
:config_file => File.expand_path( File.dirname(__FILE__) + '/../../config/agent.conf.yml' ),
|
11
|
+
:page_name => page,
|
12
|
+
:keys => { 'testentry' => entry, 'testpage' => page }
|
13
|
+
})
|
14
|
+
|
15
|
+
google_agent.process! do |entry|
|
16
|
+
true
|
17
|
+
end
|
18
|
+
exit
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'spreadsheet_agent/db'
|
3
|
+
|
4
|
+
class TC_SpreadsheetAgentDbTest < Test::Unit::TestCase
|
5
|
+
def test_instantiate_db
|
6
|
+
conf_file = File.expand_path(File.dirname( __FILE__ )) + '/../config/agent.conf.yml'
|
7
|
+
assert File.exists?( conf_file ), "You must create a valid test Google Spreadsheet and a valid #{ conf_file } configuration file pointing to it to run the tests. See README.txt file for more information on how to run the tests."
|
8
|
+
|
9
|
+
google_db = SpreadsheetAgent::Db.new()
|
10
|
+
assert_not_nil google_db
|
11
|
+
assert_instance_of(SpreadsheetAgent::Db, google_db)
|
12
|
+
end
|
13
|
+
end #TC_SpreadsheetAgentDbTest
|
@@ -0,0 +1,468 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'shoulda/context'
|
3
|
+
require 'spreadsheet_agent'
|
4
|
+
require 'spreadsheet_agent/runner'
|
5
|
+
require 'capture_io'
|
6
|
+
|
7
|
+
class TC_SpreadsheetAgentRunnerTest < Test::Unit::TestCase
|
8
|
+
|
9
|
+
context 'Runner' do
|
10
|
+
|
11
|
+
setup do
|
12
|
+
@config_file = File.expand_path(File.dirname( __FILE__ )) + '/../config/agent.conf.yml'
|
13
|
+
|
14
|
+
unless File.exists? @config_file
|
15
|
+
$stderr.puts "You must create a valid test Google Spreadsheet and a valid #{ @config_file } configuration file pointing to it to run the tests. See README.txt file for more information on how to run the tests."
|
16
|
+
exit(1)
|
17
|
+
end
|
18
|
+
@test_agent_bin = find_bin() + 'agent_bin'
|
19
|
+
@testing_pages = nil
|
20
|
+
@agent_scripts = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
teardown do
|
24
|
+
runner = SpreadsheetAgent::Runner.new(:agent_bin => @test_agent_bin, :config_file => @config_file)
|
25
|
+
if runner.db.worksheet_by_title('testing').nil?
|
26
|
+
tpage = runner.db.add_worksheet('testing')
|
27
|
+
tpage.max_rows = 2
|
28
|
+
tpage.save
|
29
|
+
end
|
30
|
+
unless @testing_pages.nil?
|
31
|
+
@testing_pages.each do |testing_page|
|
32
|
+
testing_page.delete
|
33
|
+
end
|
34
|
+
end
|
35
|
+
agent_scripts_executable(false)
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'instantiated' do
|
39
|
+
|
40
|
+
should 'be a SpreadsheetAgent::Runner' do
|
41
|
+
runner = SpreadsheetAgent::Runner.new(:config_file => @config_file)
|
42
|
+
assert_not_nil runner, 'runner is nil!'
|
43
|
+
assert_instance_of(SpreadsheetAgent::Runner, runner)
|
44
|
+
end
|
45
|
+
|
46
|
+
end #instantiated
|
47
|
+
|
48
|
+
context 'agent_bin' do
|
49
|
+
|
50
|
+
should 'have a sensible default' do
|
51
|
+
expected_agent_bin = File.expand_path(File.dirname( $0 )) + '/../agent_bin'
|
52
|
+
runner = SpreadsheetAgent::Runner.new(:config_file => @config_file)
|
53
|
+
assert_equal expected_agent_bin, runner.agent_bin
|
54
|
+
end
|
55
|
+
|
56
|
+
should 'be overridable on construction' do
|
57
|
+
runner = SpreadsheetAgent::Runner.new(:agent_bin => @test_agent_bin, :config_file => @config_file)
|
58
|
+
assert_equal @test_agent_bin, runner.agent_bin
|
59
|
+
end
|
60
|
+
|
61
|
+
end #agent_bin
|
62
|
+
|
63
|
+
context 'agent scripts' do
|
64
|
+
|
65
|
+
setup do
|
66
|
+
@entries = {'dotest' => false, 'donottest' => false }
|
67
|
+
@headers = ['testentry', 'testpage', 'ready', 'testgoal', 'othergoal', 'complete']
|
68
|
+
@runner = prepare_runner(
|
69
|
+
['foo_page', 'bar_page'],
|
70
|
+
@headers,
|
71
|
+
@entries
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
should 'pass tests' do
|
76
|
+
#should 'not run if scripts in agent_bin are not executable' do
|
77
|
+
reset_testing_pages(@testing_pages,true)
|
78
|
+
@testing_pages.each do |page|
|
79
|
+
page.reload
|
80
|
+
page.rows(1).each do |entry|
|
81
|
+
assert_equal 1, entry[2].to_i
|
82
|
+
end
|
83
|
+
end
|
84
|
+
agent_scripts_executable(false)
|
85
|
+
|
86
|
+
Dir.glob("#{ @test_agent_bin }/*.rb") do |rb_file|
|
87
|
+
assert !File.executable?(rb_file), "#{ rb_file } should not be executable"
|
88
|
+
end
|
89
|
+
|
90
|
+
@runner.process!
|
91
|
+
|
92
|
+
@testing_pages.each do |page|
|
93
|
+
page.reload
|
94
|
+
page.rows(1).each do |entry|
|
95
|
+
assert entry[3].nil? || entry[3].empty?, "entry #{ entry[0] } should not have run!: #{ entry.inspect }"
|
96
|
+
assert entry[4].nil? || entry[3].empty?, "entry #{ entry[0] } should not have run!: #{ entry.inspect }"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
#should not run on entries that are not ready
|
101
|
+
reset_testing_pages(@testing_pages)
|
102
|
+
agent_scripts_executable(true)
|
103
|
+
@testing_pages.each do |page|
|
104
|
+
page.rows(1).each do |entry|
|
105
|
+
assert entry[2].to_i != 1, "entry #{ entry[0] } should not be ready!: #{ entry.inspect }"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
@runner.process!
|
110
|
+
sleep 2
|
111
|
+
|
112
|
+
@testing_pages.each do |page|
|
113
|
+
page.reload
|
114
|
+
page.rows(1).each do |entry|
|
115
|
+
assert entry[3].to_i != 1, "entry #{ entry[0] } should not have run!: #{ entry.inspect }"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# should 'if executable, run for each goal of each ready entry by default' do
|
120
|
+
reset_testing_pages(@testing_pages,true)
|
121
|
+
|
122
|
+
@runner.process!
|
123
|
+
sleep 5
|
124
|
+
@testing_pages.each do |page|
|
125
|
+
page.reload
|
126
|
+
page.rows(1).each do |entry|
|
127
|
+
assert_equal "1", entry[4]
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# should 'allow code to be passed to override default process! function' do
|
132
|
+
reset_testing_pages(@testing_pages, true)
|
133
|
+
expected_to_run = {}
|
134
|
+
@testing_pages.each do |page|
|
135
|
+
expected_to_run[page.title] = {}
|
136
|
+
(2..page.num_rows).to_a.each do |rownum|
|
137
|
+
expected_to_run[page.title][page[rownum,1]] = false
|
138
|
+
end
|
139
|
+
page.save
|
140
|
+
end
|
141
|
+
|
142
|
+
@runner.process! do |ran_entry, ran_page|
|
143
|
+
ran_entry.update 'testgoal' => "1"
|
144
|
+
ran_page.save
|
145
|
+
expected_to_run[ran_page.title][ran_entry['testentry']] = true
|
146
|
+
end
|
147
|
+
sleep 2
|
148
|
+
|
149
|
+
@testing_pages.each do |page|
|
150
|
+
page.reload
|
151
|
+
page.rows(1).each do |entry|
|
152
|
+
assert_equal "1", entry[2]
|
153
|
+
assert_equal "1", entry[3]
|
154
|
+
assert expected_to_run[page.title][entry[0]], "#{ page.title } should have been processed on #{ entry[0] }"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# should 'allow code passed to override default process! function to update fields on each entry' do
|
159
|
+
reset_testing_pages(@testing_pages, true)
|
160
|
+
|
161
|
+
@runner.process! do |ran_entry, ran_page|
|
162
|
+
ran_entry.update 'complete' => "1"
|
163
|
+
ran_page.save
|
164
|
+
end
|
165
|
+
sleep 2
|
166
|
+
|
167
|
+
@testing_pages.each do |page|
|
168
|
+
page.reload
|
169
|
+
page.rows(1).each do |entry|
|
170
|
+
assert_equal "1", entry[5]
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# should 'not print anything to STDERR if debug is not set to true' do
|
175
|
+
reset_testing_pages(@testing_pages, true)
|
176
|
+
|
177
|
+
nothing_output = CaptureIO.new
|
178
|
+
nothing_output.start
|
179
|
+
@runner.process! do |re, rp|
|
180
|
+
re.update 'othergoal' => "1"
|
181
|
+
rp.save
|
182
|
+
end
|
183
|
+
nothing_captured = nothing_output.stop
|
184
|
+
assert nothing_captured[:stderr].length < 1, 'should not have captured any stderr'
|
185
|
+
@testing_pages.each do |page|
|
186
|
+
page.reload
|
187
|
+
page.rows(1).each do |entry|
|
188
|
+
assert_equal "1", entry[4]
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# should 'allow debug to be set and print output to STDERR' do
|
193
|
+
reset_testing_pages(@testing_pages, true)
|
194
|
+
@runner = SpreadsheetAgent::Runner.new(:agent_bin => @test_agent_bin, :debug => true, :config_file => @config_file)
|
195
|
+
assert @runner.debug, 'debug should be true'
|
196
|
+
debug_output = CaptureIO.new
|
197
|
+
debug_output.start
|
198
|
+
@runner.process! do |re, rp|
|
199
|
+
re.update 'testgoal' => "1"
|
200
|
+
rp.save
|
201
|
+
end
|
202
|
+
debugged_output = debug_output.stop
|
203
|
+
assert debugged_output[:stderr].length > 0, "should have captured stderr in debug mode"
|
204
|
+
sleep 2
|
205
|
+
@testing_pages.each do |page|
|
206
|
+
page.reload
|
207
|
+
page.rows(1).each do |entry|
|
208
|
+
assert_equal "1", entry[3]
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
# should 'allow dry_run to be set, and not run any entries, but set debug to true' do
|
213
|
+
reset_testing_pages(@testing_pages, true)
|
214
|
+
@runner = SpreadsheetAgent::Runner.new(:agent_bin => @test_agent_bin, :dry_run => true, :config_file => @config_file)
|
215
|
+
assert @runner.debug, 'debug should be true with dry run'
|
216
|
+
|
217
|
+
dry_output = CaptureIO.new
|
218
|
+
dry_output.start
|
219
|
+
@runner.process! do |re, rp|
|
220
|
+
re.update 'testgoal' => "1"
|
221
|
+
rp.save
|
222
|
+
end
|
223
|
+
captured_dry_output = dry_output.stop
|
224
|
+
assert captured_dry_output[:stderr].length > 0, 'should have captured stderr in debug mode'
|
225
|
+
sleep 2
|
226
|
+
@testing_pages.each do |page|
|
227
|
+
page.reload
|
228
|
+
page.rows(1).each do |entry|
|
229
|
+
assert entry[3].nil? || entry[3].empty?, "#{ page.title } should not have run #{ entry[0] } #{ entry.inspect }"
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# should 'allow sleep_between to be set, and sleep that amount between process for each entry' do
|
234
|
+
reset_testing_pages(@testing_pages, true)
|
235
|
+
expected_time_between = 10
|
236
|
+
@runner = SpreadsheetAgent::Runner.new(:agent_bin => @test_agent_bin, :sleep_between => expected_time_between,:config_file => @config_file)
|
237
|
+
|
238
|
+
beginning_time = Time.now
|
239
|
+
first_time = true
|
240
|
+
@runner.process! do |re, rp|
|
241
|
+
if first_time
|
242
|
+
first_time = false
|
243
|
+
else
|
244
|
+
elapsed_time = Time.now - beginning_time
|
245
|
+
assert_equal expected_time_between, elapsed_time.to_i
|
246
|
+
end
|
247
|
+
re.update 'testgoal' => "1"
|
248
|
+
rp.save
|
249
|
+
beginning_time = Time.now
|
250
|
+
end
|
251
|
+
sleep 2
|
252
|
+
|
253
|
+
@testing_pages.each do |page|
|
254
|
+
page.reload
|
255
|
+
page.rows(1).each do |entry|
|
256
|
+
assert_equal "1", entry[3]
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
#should 'allow skip_entry to be set with code, and only process entries that return false from the code when passed the entry' do
|
261
|
+
reset_testing_pages(@testing_pages, true)
|
262
|
+
|
263
|
+
@runner.skip_entry do |entry|
|
264
|
+
(entry['testentry'] == 'donottest')
|
265
|
+
end
|
266
|
+
|
267
|
+
@runner.process! do |ran_entry, ran_page|
|
268
|
+
ran_entry.update 'testgoal' => "1"
|
269
|
+
ran_page.save
|
270
|
+
end
|
271
|
+
sleep 2
|
272
|
+
@testing_pages.each do |page|
|
273
|
+
page.reload
|
274
|
+
page.rows(1).each do |entry|
|
275
|
+
if entry[0] == 'donottest'
|
276
|
+
assert entry[3].nil? || entry[3].empty?, "#{ page.title } #{ entry[0]} should not have run"
|
277
|
+
else
|
278
|
+
assert_equal "1", entry[3]
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
# should 'allow skip_goal to be set with code, and only process goals for each entry that return false from the code when passed the goal' do
|
284
|
+
reset_testing_pages(@testing_pages, true)
|
285
|
+
|
286
|
+
@runner.skip_goal { |goal|
|
287
|
+
if goal == 'othergoal'
|
288
|
+
return true
|
289
|
+
else
|
290
|
+
return false
|
291
|
+
end
|
292
|
+
}.process!
|
293
|
+
sleep 2
|
294
|
+
@testing_pages.each do |page|
|
295
|
+
page.reload
|
296
|
+
page.rows(1).each do |entry|
|
297
|
+
assert entry[3] == "1", "entry #{ entry[0] } should have run testgoal #{ entry.inspect }"
|
298
|
+
assert entry[4].nil? || entry[4].empty?, "othergoal should not have run for any entry #{ entry.inspect }"
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
#should 'allow only_pages to be set and only process @only_pages' do
|
303
|
+
@test_only_pages = ['foo_page', 'baz_page']
|
304
|
+
@runner = SpreadsheetAgent::Runner.new(:agent_bin => @test_agent_bin, :only_pages => @test_only_pages, :config_file => @config_file)
|
305
|
+
@testing_pages.push(add_page_to_runner(@runner, 'baz', @headers, @entries))
|
306
|
+
|
307
|
+
|
308
|
+
expected_to_run = {}
|
309
|
+
@testing_pages.each do |page|
|
310
|
+
expected_to_run[page.title] = @test_only_pages.include? page.title
|
311
|
+
end
|
312
|
+
|
313
|
+
@runner.process! do |ran_entry, ran_page|
|
314
|
+
ran_entry.update 'complete' => "1"
|
315
|
+
ran_page.save
|
316
|
+
assert expected_to_run[ran_page.title], "#{ ran_page.title } is not in #{ @test_only_pages.inspect }!"
|
317
|
+
end
|
318
|
+
|
319
|
+
@testing_pages.select { |page| @test_only_pages.include? page.title }.each do |page|
|
320
|
+
page.reload
|
321
|
+
page.rows(1).each do |entry|
|
322
|
+
assert_equal "1", entry[5]
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
#should 'allow only_pages_if to be set and only process those pages' do
|
327
|
+
@runner = SpreadsheetAgent::Runner.new(:agent_bin => @test_agent_bin,:config_file => @config_file)
|
328
|
+
reset_testing_pages(@testing_pages, true)
|
329
|
+
|
330
|
+
@runner.only_pages_if{ |title|
|
331
|
+
if title.match('ba*')
|
332
|
+
return true
|
333
|
+
else
|
334
|
+
return false
|
335
|
+
end
|
336
|
+
}.process! do |ran_entry, ran_page|
|
337
|
+
ran_entry.update 'complete' => "1"
|
338
|
+
ran_page.save
|
339
|
+
assert ran_page.title.match('ba*'), "#{ ran_page.title } was not expected to run!"
|
340
|
+
end
|
341
|
+
|
342
|
+
@testing_pages.select { |page| page.title.match('ba*') }.each do |page|
|
343
|
+
page.reload
|
344
|
+
page.rows(1).each do |entry|
|
345
|
+
assert_equal "1", entry[5]
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
#should 'allow skip_pages to be set and only process pages not in @skip_pages' do
|
350
|
+
@test_skip_pages = ['foo_page', 'baz_page']
|
351
|
+
@runner = SpreadsheetAgent::Runner.new(:agent_bin => @test_agent_bin, :skip_pages => @test_skip_pages,:config_file => @config_file)
|
352
|
+
reset_testing_pages(@testing_pages, true)
|
353
|
+
expected_to_run = {}
|
354
|
+
@testing_pages.each do |page|
|
355
|
+
expected_to_run[page.title] = !( @test_skip_pages.include? page.title )
|
356
|
+
end
|
357
|
+
|
358
|
+
@runner.process! do |ran_entry, ran_page|
|
359
|
+
ran_entry.update 'complete' => "1"
|
360
|
+
ran_page.save
|
361
|
+
assert expected_to_run[ran_page.title], "#{ ran_page.title } is in #{ @test_skip_pages.inspect }!"
|
362
|
+
end
|
363
|
+
|
364
|
+
@testing_pages.each do |page|
|
365
|
+
page.reload
|
366
|
+
if @test_skip_pages.include?(page.title)
|
367
|
+
should_equal = nil
|
368
|
+
else
|
369
|
+
should_equal = "1"
|
370
|
+
end
|
371
|
+
page.rows(1).each do |entry|
|
372
|
+
assert_equal should_equal, entry[4]
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
#should 'allow skip_pages_if to be set and only process pages not skipped by the code' do
|
377
|
+
@runner = SpreadsheetAgent::Runner.new(:agent_bin => @test_agent_bin,:config_file => @config_file)
|
378
|
+
reset_testing_pages(@testing_pages, true)
|
379
|
+
|
380
|
+
@runner.skip_pages_if { |title|
|
381
|
+
title.match('ba*')
|
382
|
+
}.process! do |ran_entry, ran_page|
|
383
|
+
ran_entry.update 'complete' => "1"
|
384
|
+
ran_page.save
|
385
|
+
assert !( ran_page.title.match('ba*') ), "#{ ran_page.title } was not expected to run!"
|
386
|
+
end
|
387
|
+
|
388
|
+
@testing_pages.reject { |page| page.title.match('ba*') }.each do |page|
|
389
|
+
page.reload
|
390
|
+
page.rows(1).each do |entry|
|
391
|
+
assert_equal "1", entry[5]
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
end #pass tests
|
396
|
+
|
397
|
+
end #agent script executable
|
398
|
+
|
399
|
+
end #Runner
|
400
|
+
|
401
|
+
def find_bin()
|
402
|
+
File.expand_path(File.dirname( __FILE__ )) + '/'
|
403
|
+
end
|
404
|
+
|
405
|
+
def prepare_runner(pages, headers, entries)
|
406
|
+
runner = SpreadsheetAgent::Runner.new(:agent_bin => @test_agent_bin,:config_file => @config_file)
|
407
|
+
@testing_pages = []
|
408
|
+
|
409
|
+
pages.each do |page|
|
410
|
+
@testing_pages.push(add_page_to_runner(runner, page, headers, entries))
|
411
|
+
end
|
412
|
+
testing_page = runner.db.worksheet_by_title('testing')
|
413
|
+
unless testing_page.nil?
|
414
|
+
testing_page.delete
|
415
|
+
end
|
416
|
+
|
417
|
+
runner
|
418
|
+
end
|
419
|
+
|
420
|
+
def add_page_to_runner(runner, page, headers, entries)
|
421
|
+
testing_page = runner.db.worksheet_by_title(page)
|
422
|
+
if testing_page.nil?
|
423
|
+
testing_page = runner.db.add_worksheet(page)
|
424
|
+
else
|
425
|
+
testing_page.max_rows = 1
|
426
|
+
end
|
427
|
+
|
428
|
+
colnum = 1
|
429
|
+
headers.each do |field|
|
430
|
+
testing_page[1,colnum] = field
|
431
|
+
colnum += 1
|
432
|
+
end
|
433
|
+
entry_row = 2
|
434
|
+
entries.each do |entry, ready|
|
435
|
+
testing_page[entry_row,1] = entry
|
436
|
+
testing_page[entry_row,2] = page
|
437
|
+
testing_page[entry_row,3] = "1" if ready
|
438
|
+
entry_row += 1
|
439
|
+
|
440
|
+
end
|
441
|
+
testing_page.max_rows = entry_row
|
442
|
+
testing_page.save
|
443
|
+
testing_page
|
444
|
+
end
|
445
|
+
|
446
|
+
def agent_scripts_executable(set_executable)
|
447
|
+
if set_executable
|
448
|
+
`chmod 700 #{ @test_agent_bin }/*.rb`
|
449
|
+
else
|
450
|
+
`chmod 600 #{ @test_agent_bin }/*.rb`
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
def reset_testing_pages(testing_pages, ready = false)
|
455
|
+
ready_value = ready ? "1" : nil
|
456
|
+
|
457
|
+
testing_pages.each do |page|
|
458
|
+
(2..page.num_rows).to_a.each do |rownum|
|
459
|
+
page[rownum,3] = ready_value
|
460
|
+
page[rownum,4] = nil
|
461
|
+
page[rownum,5] = nil
|
462
|
+
page[rownum,6] = nil
|
463
|
+
end
|
464
|
+
page.save
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
end #TC_SpreadsheetAgentRunnerTest
|