zombie-chaser 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/README.txt +47 -0
- data/Rakefile +24 -0
- data/bin/zombie-chaser +65 -0
- data/lib/chaser.rb +373 -0
- data/lib/human.rb +180 -0
- data/lib/interface.rb +77 -0
- data/lib/test_unit_handler.rb +42 -0
- data/lib/world.rb +93 -0
- data/lib/zombie_test_chaser.rb +133 -0
- data/test/fixtures/chased.rb +56 -0
- data/test/test_chaser.rb +144 -0
- data/test/test_unit.rb +2 -0
- data/test/test_zombie.rb +95 -0
- data/ui/icons/death.png +0 -0
- data/ui/icons/robot.png +0 -0
- data/ui/sprites/robot-attacking.png +0 -0
- data/ui/sprites/robot-dead.png +0 -0
- data/ui/sprites/robot-dying.png +0 -0
- data/ui/sprites/robot-idle.png +0 -0
- data/ui/sprites/robot-moving.png +0 -0
- data/ui/sprites/robot-turning.png +0 -0
- data/ui/sprites/tank-attacking.png +0 -0
- data/ui/sprites/tank-dead.png +0 -0
- data/ui/sprites/tank-idle.png +0 -0
- data/ui/sprites/tank-moving.png +0 -0
- data/ui/sprites/tank-turning.png +0 -0
- data/ui/sprites/witch-attacking.png +0 -0
- data/ui/sprites/witch-dead.png +0 -0
- data/ui/sprites/witch-idle.png +0 -0
- data/ui/sprites/witch-moving.png +0 -0
- data/ui/sprites/witch-turning.png +0 -0
- data/ui/sprites/zombie-attacking.png +0 -0
- data/ui/sprites/zombie-dead.png +0 -0
- data/ui/sprites/zombie-dying.png +0 -0
- data/ui/sprites/zombie-idle.png +0 -0
- data/ui/sprites/zombie-moving.png +0 -0
- data/ui/sprites/zombie-turning.png +0 -0
- data/ui/tiles/grass.png +0 -0
- data/ui/tiles/shrubbery.png +0 -0
- data/ui/ui.rb +117 -0
- metadata +109 -0
data/README.txt
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
== DESCRIPTION:
|
2
|
+
|
3
|
+
Zombie chaser is a graphic(al) interface to mutation testing. Kill off the mutants, or they will eat your brains!
|
4
|
+
|
5
|
+
The human running across the screen represents the normal running of your unit tests. If one of them fails, then the human dies.
|
6
|
+
|
7
|
+
Then the zombies chase after you. Each zombie represents a mutation to your code. If your unit tests detect that something's wrong with your code, then the mutation gets killed. Otherwise, the zombie gets to meet you.
|
8
|
+
|
9
|
+
== FEATURES/PROBLEMS:
|
10
|
+
|
11
|
+
* Code is slightly different to chaser.
|
12
|
+
* Not quite finished.
|
13
|
+
|
14
|
+
== REQUIREMENTS:
|
15
|
+
|
16
|
+
* Gosu
|
17
|
+
* Test-unit (for ruby 1.9)
|
18
|
+
|
19
|
+
== INSTALL:
|
20
|
+
|
21
|
+
* sudo gem install zombie-chaser
|
22
|
+
|
23
|
+
== LICENSE:
|
24
|
+
|
25
|
+
(The MIT License)
|
26
|
+
|
27
|
+
Copyright (c) 2006-2009 Ryan Davis and Kevin Clark and Andrew Grimm,
|
28
|
+
Chris Lloyd, Dave Newman, Carl Woodward & Daniel Bogan.
|
29
|
+
|
30
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
31
|
+
a copy of this software and associated documentation files (the
|
32
|
+
'Software'), to deal in the Software without restriction, including
|
33
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
34
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
35
|
+
permit persons to whom the Software is furnished to do so, subject to
|
36
|
+
the following conditions:
|
37
|
+
|
38
|
+
The above copyright notice and this permission notice shall be
|
39
|
+
included in all copies or substantial portions of the Software.
|
40
|
+
|
41
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
42
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
43
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
44
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
45
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
46
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
47
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
task :default => [:test]
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'jeweler'
|
5
|
+
require File.dirname(__FILE__) + "/lib/zombie_test_chaser.rb"
|
6
|
+
Jeweler::Tasks.new do |gemspec|
|
7
|
+
gemspec.name = "zombie-chaser"
|
8
|
+
gemspec.summary = "Lightweight mutation testing ... with ZOMBIES!!!"
|
9
|
+
gemspec.description = "A zombie-themed graphic(al) user interface for mutation testing"
|
10
|
+
gemspec.email = "andrew.j.grimm@gmail.com"
|
11
|
+
gemspec.authors = ["Andrew Grimm", "Ryan Davis", "Eric Hodel", "Kevin Clark"]
|
12
|
+
#gemspec.add_dependency('test-unit') #FIXME Don't know how to only add the dependency for ruby 1.9
|
13
|
+
gemspec.add_dependency('gosu') #FIXME add option for command-line version, which'll make gosu an optional dependency
|
14
|
+
gemspec.version = ZombieTestChaser::VERSION
|
15
|
+
gemspec.homepage = "http://andrewjgrimm.wordpress.com/2009/11/08/declare-war-on-everything-with-chaser/"
|
16
|
+
end
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler not available. Install it with: sudo gem install jeweler"
|
19
|
+
end
|
20
|
+
|
21
|
+
task :test do
|
22
|
+
ruby "test/test_unit.rb"
|
23
|
+
end
|
24
|
+
|
data/bin/zombie-chaser
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
#!/usr/local/bin/ruby
|
2
|
+
|
3
|
+
$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
|
4
|
+
require 'zombie_test_chaser'
|
5
|
+
require 'optparse'
|
6
|
+
|
7
|
+
force = false
|
8
|
+
target_everything = false
|
9
|
+
|
10
|
+
opts = OptionParser.new do |opts|
|
11
|
+
opts.banner = "Usage: #{File.basename($0)} class_name [method_name]"
|
12
|
+
opts.on("-v", "--verbose", "Loudly explain chaser run") do |opt|
|
13
|
+
ZombieTestChaser.debug = true
|
14
|
+
end
|
15
|
+
|
16
|
+
opts.on("-V", "--version", "Prints zombie-chaser's version number") do |opt|
|
17
|
+
puts "zombie-chaser #{ZombieTestChaser::VERSION}"
|
18
|
+
exit 0
|
19
|
+
end
|
20
|
+
|
21
|
+
opts.on("-t", "--tests TEST_PATTERN",
|
22
|
+
"Location of tests (glob). Unix-style even on Windows, so use forward slashes.") do |pattern|
|
23
|
+
ZombieTestChaser.test_pattern = pattern
|
24
|
+
end
|
25
|
+
|
26
|
+
opts.on("--everything", "Zombie chase all classes") do |opt|
|
27
|
+
puts "You're now facing a plague of zombies!"
|
28
|
+
target_everything = true
|
29
|
+
end
|
30
|
+
|
31
|
+
opts.on("-F", "--force", "Ignore initial test failures") do |opt|
|
32
|
+
force = true
|
33
|
+
end
|
34
|
+
|
35
|
+
opts.on("-T", "--timeout SECONDS", "The maximum time for a test run in seconds",
|
36
|
+
"Used to catch infinite loops") do |timeout|
|
37
|
+
Chaser.timeout = timeout.to_i
|
38
|
+
puts "Setting timeout at #{timeout} seconds."
|
39
|
+
end
|
40
|
+
|
41
|
+
opts.on("-r", "--random-seed SEED", "Random seed number (under development)") do |seed|
|
42
|
+
srand(seed.to_i)
|
43
|
+
end
|
44
|
+
|
45
|
+
opts.on("-h", "--help", "Show this message") do |opt|
|
46
|
+
puts opts
|
47
|
+
exit 0
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
looks_like_rails = test ?f, 'config/environment.rb'
|
52
|
+
ZombieTestChaser.test_pattern = "test/**/*.rb" if looks_like_rails
|
53
|
+
|
54
|
+
opts.parse!
|
55
|
+
|
56
|
+
impl = ARGV.shift
|
57
|
+
meth = ARGV.shift
|
58
|
+
|
59
|
+
unless impl or target_everything then
|
60
|
+
puts opts
|
61
|
+
exit 1
|
62
|
+
end
|
63
|
+
|
64
|
+
exit ZombieTestChaser.validate(impl, meth, force)
|
65
|
+
|
data/lib/chaser.rb
ADDED
@@ -0,0 +1,373 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'timeout'
|
3
|
+
require 'world'
|
4
|
+
|
5
|
+
class String # :nodoc:
|
6
|
+
def to_class
|
7
|
+
split(/::/).inject(Object) { |klass, name| klass.const_get(name) }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
##
|
12
|
+
# Test Unit Sadism
|
13
|
+
|
14
|
+
class Chaser
|
15
|
+
|
16
|
+
class Timeout < Timeout::Error; end
|
17
|
+
|
18
|
+
##
|
19
|
+
# The version of Chaser you are using.
|
20
|
+
|
21
|
+
VERSION = '0.0.4'
|
22
|
+
|
23
|
+
##
|
24
|
+
# Is this platform MS Windows-like?
|
25
|
+
|
26
|
+
WINDOZE = RUBY_PLATFORM =~ /mswin/
|
27
|
+
|
28
|
+
##
|
29
|
+
# Path to the bit bucket.
|
30
|
+
|
31
|
+
NULL_PATH = WINDOZE ? 'NUL:' : '/dev/null'
|
32
|
+
|
33
|
+
##
|
34
|
+
# Class being chased
|
35
|
+
|
36
|
+
attr_accessor :klass
|
37
|
+
|
38
|
+
##
|
39
|
+
# Name of class being chased
|
40
|
+
|
41
|
+
attr_accessor :klass_name
|
42
|
+
|
43
|
+
##
|
44
|
+
# Method being chased
|
45
|
+
|
46
|
+
attr_accessor :method
|
47
|
+
|
48
|
+
##
|
49
|
+
# Name of method being chased
|
50
|
+
|
51
|
+
attr_accessor :method_name
|
52
|
+
|
53
|
+
##
|
54
|
+
# The original version of the method being chased
|
55
|
+
|
56
|
+
attr_reader :old_method
|
57
|
+
|
58
|
+
@@debug = false
|
59
|
+
@@guess_timeout = true
|
60
|
+
@@timeout = 60 # default to something longer (can be overridden by runners)
|
61
|
+
|
62
|
+
def self.debug
|
63
|
+
@@debug
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.debug=(value)
|
67
|
+
@@debug = value
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.timeout=(value)
|
71
|
+
@@timeout = value
|
72
|
+
@@guess_timeout = false # We've set the timeout, don't guess
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.guess_timeout?
|
76
|
+
@@guess_timeout
|
77
|
+
end
|
78
|
+
|
79
|
+
##
|
80
|
+
# Creates a new Chaser that will chase +klass_name+ and +method_name+,
|
81
|
+
# sending results to +reporter+.
|
82
|
+
|
83
|
+
def initialize(klass_name = nil, method_name = nil, reporter = Reporter.new)
|
84
|
+
@klass_name = klass_name
|
85
|
+
@method_name = method_name.intern if method_name
|
86
|
+
|
87
|
+
@klass = klass_name.to_class if klass_name
|
88
|
+
|
89
|
+
@method = nil
|
90
|
+
@reporter = reporter
|
91
|
+
|
92
|
+
@mutated = false
|
93
|
+
|
94
|
+
@failure = false
|
95
|
+
end
|
96
|
+
|
97
|
+
##
|
98
|
+
# Overwrite test_pass? for your own Chaser runner.
|
99
|
+
|
100
|
+
def tests_pass?
|
101
|
+
raise NotImplementedError
|
102
|
+
end
|
103
|
+
|
104
|
+
def run_tests
|
105
|
+
if zombie_survives? then
|
106
|
+
record_passing_mutation
|
107
|
+
else
|
108
|
+
@reporter.report_test_failures
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
############################################################
|
113
|
+
### Running the script
|
114
|
+
|
115
|
+
def validate
|
116
|
+
@reporter.method_loaded(klass_name, method_name)
|
117
|
+
|
118
|
+
begin
|
119
|
+
modify_method
|
120
|
+
timeout(@@timeout, Chaser::Timeout) { run_tests }
|
121
|
+
rescue Chaser::Timeout
|
122
|
+
@reporter.warning "Your tests timed out. Chaser may have caused an infinite loop."
|
123
|
+
rescue Interrupt
|
124
|
+
@reporter.warning 'Mutation canceled, hit ^C again to exit'
|
125
|
+
sleep 2
|
126
|
+
end
|
127
|
+
|
128
|
+
unmodify_method # in case we're validating again. we should clean up.
|
129
|
+
|
130
|
+
if @failure
|
131
|
+
@reporter.report_failure
|
132
|
+
false
|
133
|
+
else
|
134
|
+
@reporter.no_surviving_mutant
|
135
|
+
true
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def record_passing_mutation
|
140
|
+
@failure = true
|
141
|
+
end
|
142
|
+
|
143
|
+
def calculate_proxy_method_name(original_name)
|
144
|
+
result = "__chaser_proxy__#{original_name}"
|
145
|
+
character_renaming = {"[]" => "square_brackets", "^" => "exclusive_or",
|
146
|
+
"=" => "equals", "&" => "ampersand", "*" => "splat", "+" => "plus",
|
147
|
+
"-" => "minus", "%" => "percent", "~" => "tilde", "@" => "at",
|
148
|
+
"/" => "forward_slash", "<" => "less_than", ">" => "greater_than"}
|
149
|
+
character_renaming.each do |characters, renamed_string_portion|
|
150
|
+
result.gsub!(characters, renamed_string_portion)
|
151
|
+
end
|
152
|
+
result
|
153
|
+
end
|
154
|
+
|
155
|
+
def unmodify_instance_method
|
156
|
+
chaser = self
|
157
|
+
@mutated = false
|
158
|
+
chaser_proxy_method_name = calculate_proxy_method_name(@method_name)
|
159
|
+
@klass.send(:define_method, chaser_proxy_method_name) do |block, *args|
|
160
|
+
chaser.old_method.bind(self).call(*args) {|*yielded_values| block.call(*yielded_values)}
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def unmodify_class_method
|
165
|
+
chaser = self
|
166
|
+
@mutated = false
|
167
|
+
chaser_proxy_method_name = calculate_proxy_method_name(clean_method_name)
|
168
|
+
aliasing_class(@method_name).send(:define_method, chaser_proxy_method_name) do |block, *args|
|
169
|
+
chaser.old_method.bind(self).call(*args) {|*yielded_values| block.call(*yielded_values)}
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# Ruby 1.8 doesn't allow define_method to handle blocks.
|
174
|
+
# The blog post http://coderrr.wordpress.com/2008/10/29/using-define_method-with-blocks-in-ruby-18/
|
175
|
+
# show that define_method has problems, and showed how to do workaround_method_code_string
|
176
|
+
def modify_instance_method
|
177
|
+
chaser = self
|
178
|
+
@mutated = true
|
179
|
+
@old_method = @klass.instance_method(@method_name)
|
180
|
+
chaser_proxy_method_name = calculate_proxy_method_name(@method_name)
|
181
|
+
workaround_method_code_string = <<-EOM
|
182
|
+
def #{@method_name}(*args, &block)
|
183
|
+
#{chaser_proxy_method_name}(block, *args)
|
184
|
+
end
|
185
|
+
EOM
|
186
|
+
@klass.class_eval do
|
187
|
+
eval(workaround_method_code_string)
|
188
|
+
end
|
189
|
+
@klass.send(:define_method, chaser_proxy_method_name) do |block, *args|
|
190
|
+
original_value = chaser.old_method.bind(self).call(*args) do |*yielded_values|
|
191
|
+
mutated_yielded_values = yielded_values.map{|value| chaser.mutate_value(value)}
|
192
|
+
block.call(*mutated_yielded_values)
|
193
|
+
end
|
194
|
+
chaser.mutate_value(original_value)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def modify_class_method
|
199
|
+
chaser = self
|
200
|
+
@mutated = true
|
201
|
+
@old_method = aliasing_class(@method_name).instance_method(clean_method_name)
|
202
|
+
chaser_proxy_method_name = calculate_proxy_method_name(clean_method_name)
|
203
|
+
workaround_method_code_string = <<-EOM
|
204
|
+
def #{@method_name}(*args, &block)
|
205
|
+
#{chaser_proxy_method_name}(block, *args)
|
206
|
+
end
|
207
|
+
EOM
|
208
|
+
@klass.class_eval do
|
209
|
+
eval(workaround_method_code_string)
|
210
|
+
end
|
211
|
+
aliasing_class(@method_name).send(:define_method, chaser_proxy_method_name) do |block, *args|
|
212
|
+
original_value = chaser.old_method.bind(self).call(*args) do |*yielded_values|
|
213
|
+
mutated_yielded_values = yielded_values.map{|value| chaser.mutate_value(value)}
|
214
|
+
block.call(*mutated_yielded_values)
|
215
|
+
end
|
216
|
+
chaser.mutate_value(original_value)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def modify_method
|
221
|
+
if method_name.to_s =~ /self\./
|
222
|
+
modify_class_method
|
223
|
+
else
|
224
|
+
modify_instance_method
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def unmodify_method
|
229
|
+
if method_name.to_s =~ /self\./ #TODO fix duplication. Give the test a name
|
230
|
+
unmodify_class_method
|
231
|
+
else
|
232
|
+
unmodify_instance_method
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
|
237
|
+
##
|
238
|
+
# Replaces the value with a random value.
|
239
|
+
|
240
|
+
def mutate_value(value)
|
241
|
+
case value
|
242
|
+
when Fixnum, Float, Bignum
|
243
|
+
value + rand_number
|
244
|
+
when String
|
245
|
+
rand_string
|
246
|
+
when Symbol
|
247
|
+
rand_symbol
|
248
|
+
when Regexp
|
249
|
+
Regexp.new(Regexp.escape(rand_string.gsub(/\//, '\\/')))
|
250
|
+
when Range
|
251
|
+
rand_range
|
252
|
+
when NilClass, FalseClass
|
253
|
+
rand_number
|
254
|
+
when TrueClass
|
255
|
+
false
|
256
|
+
else
|
257
|
+
nil
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
############################################################
|
262
|
+
### Convenience methods
|
263
|
+
|
264
|
+
def aliasing_class(method_name)
|
265
|
+
method_name.to_s =~ /self\./ ? class << @klass; self; end : @klass
|
266
|
+
end
|
267
|
+
|
268
|
+
def clean_method_name
|
269
|
+
method_name.to_s.gsub(/self\./, '')
|
270
|
+
end
|
271
|
+
|
272
|
+
##
|
273
|
+
# Returns a random Fixnum.
|
274
|
+
|
275
|
+
def rand_number
|
276
|
+
(rand(100) + 1)*((-1)**rand(2))
|
277
|
+
end
|
278
|
+
|
279
|
+
##
|
280
|
+
# Returns a random String
|
281
|
+
|
282
|
+
def rand_string
|
283
|
+
size = rand(50)
|
284
|
+
str = ""
|
285
|
+
size.times { str << rand(126).chr }
|
286
|
+
str
|
287
|
+
end
|
288
|
+
|
289
|
+
##
|
290
|
+
# Returns a random Symbol
|
291
|
+
|
292
|
+
def rand_symbol
|
293
|
+
letters = ('a'..'z').to_a + ('A'..'Z').to_a
|
294
|
+
str = ""
|
295
|
+
(rand(50) + 1).times { str << letters[rand(letters.size)] }
|
296
|
+
:"#{str}"
|
297
|
+
end
|
298
|
+
|
299
|
+
##
|
300
|
+
# Returns a random Range
|
301
|
+
|
302
|
+
def rand_range
|
303
|
+
min = rand(50)
|
304
|
+
max = min + rand(50)
|
305
|
+
min..max
|
306
|
+
end
|
307
|
+
|
308
|
+
##
|
309
|
+
# Suppresses output on $stdout and $stderr.
|
310
|
+
|
311
|
+
def silence_stream
|
312
|
+
return yield if @@debug
|
313
|
+
|
314
|
+
begin
|
315
|
+
dead = File.open(Chaser::NULL_PATH, "w")
|
316
|
+
|
317
|
+
$stdout.flush
|
318
|
+
$stderr.flush
|
319
|
+
|
320
|
+
oldstdout = $stdout.dup
|
321
|
+
oldstderr = $stderr.dup
|
322
|
+
|
323
|
+
$stdout.reopen(dead)
|
324
|
+
$stderr.reopen(dead)
|
325
|
+
|
326
|
+
result = yield
|
327
|
+
|
328
|
+
ensure
|
329
|
+
$stdout.flush
|
330
|
+
$stderr.flush
|
331
|
+
|
332
|
+
$stdout.reopen(oldstdout)
|
333
|
+
$stderr.reopen(oldstderr)
|
334
|
+
result
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
class Reporter
|
339
|
+
def method_loaded(klass_name, method_name)
|
340
|
+
info "#{klass_name}\##{method_name} loaded"
|
341
|
+
end
|
342
|
+
|
343
|
+
def warning(message)
|
344
|
+
puts "!" * 70
|
345
|
+
puts "!!! #{message}"
|
346
|
+
puts "!" * 70
|
347
|
+
puts
|
348
|
+
end
|
349
|
+
|
350
|
+
def info(message)
|
351
|
+
puts "*"*70
|
352
|
+
puts "*** #{message}"
|
353
|
+
puts "*"*70
|
354
|
+
puts
|
355
|
+
end
|
356
|
+
|
357
|
+
def report_failure
|
358
|
+
puts
|
359
|
+
puts "The affected method didn't cause test failures."
|
360
|
+
puts
|
361
|
+
end
|
362
|
+
|
363
|
+
def no_surviving_mutant
|
364
|
+
puts "The mutant didn't survive. Cool!\n\n"
|
365
|
+
end
|
366
|
+
|
367
|
+
def report_test_failures
|
368
|
+
puts "Tests failed -- this is good" if Chaser.debug
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
end
|
373
|
+
|
data/lib/human.rb
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__), *%w[.. ui])
|
2
|
+
|
3
|
+
require "test_unit_handler"
|
4
|
+
require "ui" #For actor superclass
|
5
|
+
|
6
|
+
class Human < Actor
|
7
|
+
private_class_method :new
|
8
|
+
attr_reader :successful_step_count
|
9
|
+
|
10
|
+
def self.new_using_test_unit_handler(test_pattern, world)
|
11
|
+
new(test_pattern, world)
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(test_pattern, world)
|
15
|
+
@status = nil #Currently only used by zombie
|
16
|
+
@world = world
|
17
|
+
@successful_step_count = 0
|
18
|
+
@health = :alive
|
19
|
+
@test_handler = TestUnitHandler.new(test_pattern, self)
|
20
|
+
end
|
21
|
+
|
22
|
+
def run
|
23
|
+
notify_world
|
24
|
+
@test_handler.run
|
25
|
+
end
|
26
|
+
|
27
|
+
def current_symbol
|
28
|
+
case @health
|
29
|
+
when :alive
|
30
|
+
"@"
|
31
|
+
when :dying
|
32
|
+
"*"
|
33
|
+
when :dead
|
34
|
+
"+"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def actor_type
|
39
|
+
'robot'
|
40
|
+
end
|
41
|
+
|
42
|
+
def actor_state
|
43
|
+
return "attacking" if @status == :attacking
|
44
|
+
case @health
|
45
|
+
when :alive
|
46
|
+
"moving"
|
47
|
+
when :dying
|
48
|
+
"dying"
|
49
|
+
when :dead
|
50
|
+
"dead"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def actor_direction
|
55
|
+
270.0
|
56
|
+
end
|
57
|
+
|
58
|
+
def notify_passing_step
|
59
|
+
@successful_step_count += 1
|
60
|
+
notify_world
|
61
|
+
end
|
62
|
+
|
63
|
+
def notify_failing_step
|
64
|
+
@health = :dying
|
65
|
+
notify_world
|
66
|
+
end
|
67
|
+
|
68
|
+
def dying?
|
69
|
+
@health == :dying
|
70
|
+
end
|
71
|
+
|
72
|
+
def dead?
|
73
|
+
@health == :dead
|
74
|
+
end
|
75
|
+
|
76
|
+
def finish_dying
|
77
|
+
sleep 0.5
|
78
|
+
raise "I'm not dead yet!" unless dying?
|
79
|
+
@health = :dead
|
80
|
+
notify_world
|
81
|
+
sleep 0.5
|
82
|
+
end
|
83
|
+
|
84
|
+
def notify_world
|
85
|
+
@world.something_happened
|
86
|
+
end
|
87
|
+
|
88
|
+
def get_eaten
|
89
|
+
@health = :dying unless dead?
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
class MockHuman < Human
|
94
|
+
private_class_method :new
|
95
|
+
|
96
|
+
def self.new_using_results(results, world)
|
97
|
+
new(results, world)
|
98
|
+
end
|
99
|
+
|
100
|
+
def initialize(results, world)
|
101
|
+
@world = world
|
102
|
+
@results = results
|
103
|
+
@successful_step_count = 0
|
104
|
+
@health = :alive
|
105
|
+
end
|
106
|
+
|
107
|
+
def run
|
108
|
+
until @successful_step_count == @results.size
|
109
|
+
if @results[@successful_step_count] == :failure
|
110
|
+
@health = :dying
|
111
|
+
notify_world
|
112
|
+
return
|
113
|
+
end
|
114
|
+
notify_world
|
115
|
+
@successful_step_count += 1
|
116
|
+
end
|
117
|
+
notify_world
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class MockZombieList
|
122
|
+
|
123
|
+
def self.new_using_results(zombies_results, world)
|
124
|
+
zombies = zombies_results.map do |zombie_results|
|
125
|
+
MockZombie.new_using_results(zombie_results, world)
|
126
|
+
end
|
127
|
+
new(zombies)
|
128
|
+
end
|
129
|
+
|
130
|
+
def initialize(zombies)
|
131
|
+
@zombies = zombies
|
132
|
+
@current_zombie_number = 0
|
133
|
+
end
|
134
|
+
|
135
|
+
def supply_next_zombie
|
136
|
+
zombie = @zombies[@current_zombie_number]
|
137
|
+
@current_zombie_number += 1
|
138
|
+
zombie
|
139
|
+
end
|
140
|
+
|
141
|
+
def all_slain?
|
142
|
+
@current_zombie_number == @zombies.length
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
module ZombieInterface
|
147
|
+
def current_symbol
|
148
|
+
case @health
|
149
|
+
when :alive
|
150
|
+
"Z"
|
151
|
+
when :dying
|
152
|
+
"*"
|
153
|
+
when :dead
|
154
|
+
"+"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def actor_type
|
159
|
+
'zombie'
|
160
|
+
end
|
161
|
+
|
162
|
+
def actor_direction
|
163
|
+
90.0
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
class Zombie < Human
|
169
|
+
include ZombieInterface
|
170
|
+
|
171
|
+
def eat(human)
|
172
|
+
@status = :attacking #Even if the human's dead, look for leftovers
|
173
|
+
human.get_eaten
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
class MockZombie < MockHuman #Fixme provide a proper hierarchy
|
178
|
+
include ZombieInterface
|
179
|
+
|
180
|
+
end
|