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