/
tryouts.rb
307 lines (270 loc) · 9.46 KB
/
tryouts.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
require 'time'
require 'sysinfo'
require 'digest/sha1'
require 'ostruct'
require 'yaml'
begin; require 'json'; rescue LoadError; end # json may not be installed
GYMNASIUM_HOME = File.join(Dir.pwd, '{tryouts,try}') ## also check try (for rye)
GYMNASIUM_GLOB = File.join(GYMNASIUM_HOME, '**', '*_tryouts.rb')
# = Tryouts
#
# This class has three purposes:
# * It represents the Tryouts object which is a group of Tryout objects.
# * The tryouts and dreams DSLs are executed within its namespace. In general the
# class methods are the handlers for the DSL syntax (some instance getter methods
# are modified to support DSL syntax by acting like setters when given arguments)
# * It stores all known instances of Tryouts objects in a class variable @@instances.
#
# ==== Are you ready to run some drills?
#
# May all your dreams come true!
#
class Tryouts
# = Exception
# A generic exception which all other Tryouts exceptions inherit from.
class Exception < RuntimeError; end
# = BadDreams
# Raised when there is a problem loading or parsing a Tryouts::Drill::Dream object
class BadDream < Exception; end
class NoDrillType < Exception
attr_accessor :tname
def initialize(t); @tname = t; end
def message
vdt = Tryouts::Drill.valid_dtypes
"Tryout '#{@tname}' has no drill type. Should be: #{vdt.join(', ')}"
end
end
VERSION = "0.7.2"
require 'tryouts/mixins'
require 'tryouts/tryout'
require 'tryouts/drill'
require 'tryouts/stats'
require 'tryouts/orderedhash'
HASH_TYPE = (RUBY_VERSION =~ /1.9/) ? ::Hash : Tryouts::OrderedHash
# An Array of +_tryouts.rb+ file paths that have been loaded.
@@loaded_files = []
# An Hash of Tryouts instances stored under the name of the Tryouts subclass.
@@instances = HASH_TYPE.new
# An instance of SysInfo
@@sysinfo = SysInfo.new
@@debug = false
@@verbose = 0
# This will be true if any error occurred during any of the drills or parsing.
@@failed = false
def self.debug?; @@debug; end
def self.enable_debug; @@debug = true; end
def self.disable_debug; @@debug = false; end
def self.verbose; @@verbose; end
def self.verbose=(v); @@verbose = (v == true) ? 1 : v; end
def self.failed?; @@failed; end
def self.failed=(v); @@failed = v; end
# Returns +@@instances+
def self.instances; @@instances; end
# Returns +@@sysinfo+
def self.sysinfo; @@sysinfo; end
# The name of this group of Tryout objects
attr_accessor :group
# A Symbol representing the default drill type. One of: :cli, :api
attr_accessor :dtype
# An Array of file paths which populated this instance of Tryouts
attr_accessor :paths
# An Array of Tryout objects
attr_accessor :tryouts
# A Symbol representing the command taking part in the tryouts. For @dtype :cli only.
attr_accessor :command
# A Symbol representing the name of the library taking part in the tryouts. For @dtype :api only.
attr_accessor :library
# An Array of exceptions that were raised during the tryouts that were not captured by a drill.
attr_reader :errors
def initialize(group=nil)
@group = group || "Default Group"
@tryouts = HASH_TYPE.new
@paths, @errors = [], []
@command = nil
end
# Populate this Tryouts from a block. The block should contain calls to
# the external DSL methods: tryout, command, library, group
def from_block(b, &inline)
instance_eval &b
end
# Execute Tryout#report for each Tryout in +@tryouts+
def report
successes = []
@tryouts.each_pair { |n,to| successes << to.report }
puts $/, "All your dreams came true" unless successes.member?(false)
end
# Execute Tryout#run for each Tryout in +@tryouts+
def run; @tryouts.each_pair { |n,to| to.run }; end
# Add a shell command to Rye::Cmd and save the command name
# in @@commands so it can be used as the default for drills
def command(name=nil, path=nil)
raise "command testing is temporarily disabled"
return @command if name.nil?
@command = name.to_sym
@dtype = :cli
Rye::Cmd.module_eval do
define_method(name) do |*args|
cmd(path || name, *args)
end
end
@command
end
# Calls Tryouts#command on the current instance of Tryouts
#
# NOTE: this is a standalone DSL-syntax method.
def self.command(*args)
@@instances.last.command(*args)
end
# Require +name+. If +path+ is supplied, it will "require path".
# * +name+ The name of the library in question (required). Stored as a Symbol to +@library+.
# * +path+ Add a path to the front of $LOAD_PATH (optional). Use this if you want to load
# a specific copy of the library. Otherwise, it loads from the system path.
def library(name=nil, path=nil)
return @library if name.nil?
@library = name.to_sym
@dtype = :api
$LOAD_PATH.unshift path unless path.nil?
begin
require @library.to_s
rescue SyntaxError, LoadError, Exception, TypeError,
RuntimeError, NoMethodError, NameError => ex
@errors << ex
Tryouts.failed = true
end
end
# Calls Tryouts#library on the current instance of Tryouts
#
# NOTE: this is a standalone DSL-syntax method.
def self.library(*args)
@@instances.last.library(*args)
end
def group(name=nil)
return @group if name.nil?
@group = name unless name.nil?
@group
end
# Raises a Tryouts::Exception. +group+ is not support in the standalone syntax
# because the group name is taken from the name of the class. See inherited.
#
# NOTE: this is a standalone DSL-syntax method.
def self.group(*args)
raise "Group is already set: #{@@instances.last.group}"
end
# Create a new Tryout object and add it to the list for this Tryouts class.
# * +name+ is the name of the Tryout
# * +dtype+ is the default drill type for the Tryout.
# * +command+ when type is :cli, this is the name of the Rye::Box method that we're testing. Otherwise ignored.
# * +b+ is a block definition for the Tryout. See Tryout#from_block
#
# NOTE: This is a DSL-only method and is not intended for OO use.
def tryout(name, dtype=nil, command=nil, &block)
return if name.nil?
dtype ||= @dtype
command ||= @command if dtype == :cli
raise NoDrillType, name if dtype.nil?
to = find_tryout(name, dtype)
if to.nil?
to = Tryouts::Tryout.new(name, dtype, command)
@tryouts[name] = to
end
# Process the rest of the DSL
begin
to.from_block block if block
rescue SyntaxError, LoadError, Exception, TypeError,
RuntimeError, NoMethodError, NameError => ex
@errors << ex
Tryouts.failed = true
end
to
end
# Calls Tryouts#tryout on the current instance of Tryouts
#
# NOTE: this is a standalone DSL-syntax method.
def self.tryout(*args, &block)
@@instances.last.tryout(*args, &block)
end
# Find matching Tryout objects by +name+ and filter by
# +dtype+ if specified. Returns a Tryout object or nil.
def find_tryout(name, dtype=nil)
by_name = @tryouts.values.select { |t| t.name == name }
by_name = by_name.select { |t| t.dtype == dtype } if dtype
by_name.first # by_name is an Array. We just want the Object.
end
# This method does nothing. It provides a quick way to disable a tryout.
#
# NOTE: This is a DSL-only method and is not intended for OO use.
def xtryout(*args, &block); end
# This method does nothing. It provides a quick way to disable a tryout.
#
# NOTE: this is a standalone DSL-syntax method.
def self.xtryout(*args, &block); end
# Returns +@tryouts+.
#
# Also acts as a stub for Tryouts#tryout in case someone
# specifies "tryouts 'name' do ..." in the DSL.
def tryouts(*args, &block)
return tryout(*args, &block) unless args.empty?
@tryouts
end
# An alias for Tryouts.tryout.
def self.tryouts(*args, &block)
tryout(args, &block)
end
# Parse a +_tryouts.rb+ file. See Tryouts::CLI::Run for an example.
#
# NOTE: this is an OO syntax method
def self.parse_file(fpath)
raise "No such file: #{fpath}" unless File.exists?(fpath)
file_content = File.read(fpath)
to = Tryouts.new
begin
to.paths << fpath
to.instance_eval file_content, fpath
# After parsing the DSL, we'll know the group name.
# If a Tryouts object already exists for that group
# we'll use that instead and re-parse the DSL.
if @@instances.has_key? to.group
to = @@instances[to.group]
to.instance_eval file_content, fpath
end
rescue SyntaxError, LoadError, Exception, TypeError,
RuntimeError, NoMethodError, NameError => ex
to.errors << ex
Tryouts.failed = true
end
@@instances[to.group] = to
to
end
# Run all Tryout objects in +@tryouts+
#
# NOTE: this is an OO syntax method
def self.run
@@instances.each_pair do |group, inst|
inst.tryouts.each_pair do |name,to|
to.run
to.report
STDOUT.flush
end
end
end
# Called when a new class inherits from Tryouts. This creates a new instance
# of Tryouts, sets group to the name of the new class, and adds the instance
# to +@@instances+.
#
# NOTE: this is a standalone DSL-syntax method.
def self.inherited(klass)
to = @@instances[ klass ]
to ||= Tryouts.new
to.paths << __FILE__
to.group = klass
@@instances[to.group] = to
end
##---
## Is this wacky syntax useful for anything?
## t2 :set .
## run = "poop"
## def self.t2(*args)
## OpenStruct.new
## end
##+++
end