codersdojo 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,645 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "rubygems"
4
+ require "tempfile"
5
+ require "rexml/document"
6
+
7
+ class Scheduler
8
+
9
+ def initialize runner
10
+ @runner = runner
11
+ end
12
+
13
+ def start
14
+ @runner.start
15
+ while true do
16
+ sleep 1
17
+ @runner.execute
18
+ end
19
+ end
20
+
21
+ end
22
+
23
+ class Runner
24
+
25
+ attr_accessor :file, :run_command
26
+
27
+ def initialize shell, session_provider
28
+ @filename_formatter = FilenameFormatter.new
29
+ @shell = shell
30
+ @session_provider = session_provider
31
+ end
32
+
33
+ def start
34
+ init_session
35
+ execute
36
+ end
37
+
38
+ def init_session
39
+ @step = 0
40
+ @session_id = @session_provider.generate_id
41
+ @shell.mkdir_p(@filename_formatter.session_dir @session_id)
42
+ end
43
+
44
+ def execute
45
+ change_time = @shell.ctime @file
46
+ if change_time == @previous_change_time then
47
+ return
48
+ end
49
+ result = @shell.execute "#{@run_command} #{@file}"
50
+ state_dir = @filename_formatter.state_dir @session_id, @step
51
+ @shell.mkdir state_dir
52
+ @shell.cp @file, state_dir
53
+ @shell.write_file @filename_formatter.result_file(state_dir), result
54
+ @step += 1
55
+ @previous_change_time = change_time
56
+ end
57
+
58
+ end
59
+
60
+ class Shell
61
+
62
+ MAX_STDOUT_LENGTH = 100000
63
+
64
+ def cp source, destination
65
+ FileUtils.cp source, destination
66
+ end
67
+
68
+ def mkdir dir
69
+ FileUtils.mkdir dir
70
+ end
71
+
72
+ def mkdir_p dirs
73
+ FileUtils.mkdir_p dirs
74
+ end
75
+
76
+ def execute command
77
+ spec_pipe = IO.popen(command, "r")
78
+ result = spec_pipe.read MAX_STDOUT_LENGTH
79
+ spec_pipe.close
80
+ puts result
81
+ result
82
+ end
83
+
84
+ def write_file filename, content
85
+ file = File.new filename, "w"
86
+ file << content
87
+ file.close
88
+ end
89
+
90
+ def read_file filename
91
+ file = File.new filename
92
+ content = file.read
93
+ file.close
94
+ content
95
+ end
96
+
97
+ def ctime filename
98
+ File.new(filename).ctime
99
+ end
100
+
101
+ def make_os_specific text
102
+ text.gsub('%sh%', shell_extension).gsub('%:%', path_separator).gsub('%rm%', remove_command_name)
103
+ end
104
+
105
+ def remove_command_name
106
+ windows? ? 'delete' : 'rm'
107
+ end
108
+
109
+ def shell_extension
110
+ windows? ? 'cmd' : 'sh'
111
+ end
112
+
113
+ def path_separator
114
+ windows? ? ';' : ':'
115
+ end
116
+
117
+ def windows?
118
+ RUBY_PLATFORM.downcase.include? "windows"
119
+ end
120
+
121
+ end
122
+
123
+ class SessionIdGenerator
124
+
125
+ def generate_id time=Time.new
126
+ year = format_to_length time.year, 4
127
+ month = format_to_length time.month, 2
128
+ day = format_to_length time.day, 2
129
+ hour = format_to_length time.hour, 2
130
+ minute = format_to_length time.min, 2
131
+ second = format_to_length time.sec, 2
132
+ "#{year}-#{month}-#{day}_#{hour}-#{minute}-#{second}"
133
+ end
134
+
135
+ def format_to_length value, len
136
+ value.to_s.rjust len,"0"
137
+ end
138
+
139
+ end
140
+
141
+ class StateReader
142
+
143
+ attr_accessor :session_id, :next_step
144
+
145
+ def initialize shell
146
+ @filename_formatter = FilenameFormatter.new
147
+ @shell = shell
148
+ @next_step = 0
149
+ end
150
+
151
+ def state_count
152
+ Dir.new(@filename_formatter.session_dir @session_id).count - 2
153
+ end
154
+
155
+ def enough_states?
156
+ state_count >= 2
157
+ end
158
+
159
+ def get_state_dir
160
+ @filename_formatter.state_dir(@session_id, @next_step)
161
+ end
162
+
163
+ def has_next_state
164
+ File.exist?(get_state_dir)
165
+ end
166
+
167
+ def read_next_state
168
+ state = State.new
169
+ state_dir = get_state_dir
170
+ state.time = @shell.ctime state_dir
171
+ state.code = @shell.read_file @filename_formatter.source_code_file(state_dir)
172
+ state.result = @shell.read_file @filename_formatter.result_file(state_dir)
173
+ @next_step += 1
174
+ state
175
+ end
176
+
177
+ end
178
+
179
+ class FilenameFormatter
180
+
181
+ CODERSDOJO_WORKSPACE = ".codersdojo"
182
+ RESULT_FILE = "result.txt"
183
+ STATE_DIR_PREFIX = "state_"
184
+
185
+
186
+ def source_code_file state_dir
187
+ Dir.entries(state_dir).each { |file|
188
+ return state_file state_dir, file unless file =='..' || file == '.' ||file == RESULT_FILE }
189
+ end
190
+
191
+ def result_file state_dir
192
+ state_file state_dir, RESULT_FILE
193
+ end
194
+
195
+ def state_file state_dir, file
196
+ "#{state_dir}/#{file}"
197
+ end
198
+
199
+ def state_dir session_id, step
200
+ session_directory = session_dir session_id
201
+ "#{session_directory}/#{STATE_DIR_PREFIX}#{step}"
202
+ end
203
+
204
+ def session_dir session_id
205
+ "#{CODERSDOJO_WORKSPACE}/#{session_id}"
206
+ end
207
+
208
+ end
209
+
210
+ class Progress
211
+
212
+ def self.write_empty_progress states
213
+ STDOUT.print "#{states} states to upload"
214
+ STDOUT.print "["+" "*states+"]"
215
+ STDOUT.print "\b"*(states+1)
216
+ STDOUT.flush
217
+ end
218
+
219
+ def self.next
220
+ STDOUT.print "."
221
+ STDOUT.flush
222
+ end
223
+
224
+ def self.end
225
+ STDOUT.puts
226
+ end
227
+ end
228
+
229
+ class Uploader
230
+
231
+ def initialize hostname, framework, session_dir, state_reader = StateReader.new(Shell.new)
232
+ @hostname = hostname
233
+ @framework = framework
234
+ @state_reader = state_reader
235
+ @state_reader.session_id = session_dir.gsub('.codersdojo/', '')
236
+ end
237
+
238
+ def upload_kata
239
+ RestClient.post "#{@hostname}#{@@kata_path}", {:framework => @framework}
240
+ end
241
+
242
+ def upload_state kata_id
243
+ state = @state_reader.read_next_state
244
+ RestClient.post "#{@hostname}#{@@kata_path}/#{kata_id}#{@@state_path}", {:code => state.code, :result => state.result, :created_at => state.time}
245
+ Progress.next
246
+ end
247
+
248
+ def upload_states kata_id
249
+ Progress.write_empty_progress @state_reader.state_count
250
+ while @state_reader.has_next_state
251
+ upload_state kata_id
252
+ end
253
+ Progress.end
254
+ end
255
+
256
+ def upload_kata_and_states
257
+ kata = upload_kata
258
+ upload_states(XMLElementExtractor.extract('kata/id', kata))
259
+ "This is the link to review and comment your kata #{XMLElementExtractor.extract('kata/short-url', kata)}"
260
+ end
261
+
262
+ def upload
263
+ begin
264
+ require 'rest_client'
265
+ rescue LoadError
266
+ return 'Cant find gem rest-client. Please install it.'
267
+ end
268
+ return upload_kata_and_states if @state_reader.enough_states?
269
+ return "You need at least two states"
270
+ end
271
+
272
+ private
273
+ @@kata_path = '/katas'
274
+ @@state_path = '/states'
275
+
276
+ end
277
+
278
+ class XMLElementExtractor
279
+ def self.extract element, xml_string
280
+ doc = REXML::Document.new xml_string
281
+ return doc.elements.each(element) do |found|
282
+ return found.text
283
+ end
284
+ end
285
+ end
286
+
287
+ class State
288
+
289
+ attr_accessor :time, :code, :result
290
+
291
+ def initialize time=nil, code=nil, result=nil
292
+ @time = time
293
+ @code = code
294
+ @result = result
295
+ end
296
+
297
+ end
298
+
299
+ class ArgumentParser
300
+
301
+ def initialize controller
302
+ @controller = controller
303
+ end
304
+
305
+ def parse params
306
+ command = params[0] ? params[0] : ""
307
+ if command.downcase == "help" then
308
+ @controller.help params[1]
309
+ elsif command.downcase == "show-examples" then
310
+ @controller.show_examples
311
+ elsif command.downcase == "setup" then
312
+ @controller.generate params[1], params[2] ? params[2] : '<kata_file>'
313
+ elsif command.downcase == "upload" then
314
+ @controller.upload params[1], params[2]
315
+ elsif command.downcase == "start" then
316
+ run_command = expand_run_command params[1]
317
+ @controller.start run_command, params[2]
318
+ elsif command.downcase == "spec" then
319
+ # 'spec" is for testing purpose only: do nothing special
320
+ else
321
+ raise ArgumentError
322
+ end
323
+ end
324
+
325
+ def expand_run_command command
326
+ if command.end_with?(".sh") then
327
+ "bash #{command}"
328
+ elsif command.end_with?(".bat") or command.end_with?(".cmd") then
329
+ "start #{command}"
330
+ else
331
+ command
332
+ end
333
+ end
334
+
335
+ end
336
+
337
+ class Controller
338
+
339
+ def initialize view, hostname
340
+ @view = view
341
+ @hostname = hostname
342
+ end
343
+
344
+ def help command=nil
345
+ if command then
346
+ @view.show_detailed_help command.downcase
347
+ else
348
+ @view.show_help
349
+ end
350
+ end
351
+
352
+ def show_examples
353
+ @view.show_examples
354
+ end
355
+
356
+ def generate framework, kata_file
357
+ generator = GeneratorFactory.new.create_generator framework
358
+ shell = Shell.new
359
+ generator_text = generator.generate kata_file
360
+ generator_text = shell.make_os_specific generator_text
361
+ puts generator_text
362
+ end
363
+
364
+ def start command, file
365
+ if not command or not file then
366
+ @view.show_missing_command_argument_error "start"
367
+ return
368
+ end
369
+ @view.show_start_kata command, file
370
+ dojo = Runner.new Shell.new, SessionIdGenerator.new
371
+ dojo.file = file
372
+ dojo.run_command = command
373
+ scheduler = Scheduler.new dojo
374
+ scheduler.start
375
+ end
376
+
377
+ def upload framework, session_directory
378
+ @view.show_upload_start @hostname
379
+ if session_directory then
380
+ uploader = Uploader.new @hostname, framework, session_directory
381
+ p uploader.upload
382
+ else
383
+ @view.show_missing_command_argument_error "upload"
384
+ end
385
+ end
386
+
387
+ end
388
+
389
+
390
+ class ConsoleView
391
+
392
+ @@VERSION = '0.9'
393
+
394
+ def show_help
395
+ puts <<-helptext
396
+ Personal CodersDojo, Version #{@@VERSION}, http://www.codersdojo.com, Copyright by it-agile GmbH (http://www.it-agile.de)
397
+ PersonalCodersDojo automatically runs your tests of a code kata.
398
+
399
+ helptext
400
+ show_usage
401
+ end
402
+
403
+ def show_usage
404
+ puts <<-helptext
405
+ Usage: #{$0} command [options]
406
+ Commands:
407
+ help, -h, --help Print this help text.
408
+ help <command> See the details of the command.
409
+ setup <framework> <kata_file> Setup the environment for running the kata.
410
+ upload <framework> <session_dir> Upload the kata to http://www.codersdojo.org
411
+ show-examples Show some example usages.
412
+
413
+ Report bugs to <codersdojo@it-agile.de>
414
+ helptext
415
+ end
416
+
417
+ def show_examples
418
+ puts <<-helptext
419
+ Examples:
420
+ :/dojo/my_kata% #{$0} setup ruby.test/unit prime
421
+ Show the instructions how to setup the environment for kata execution with ruby.
422
+
423
+ :/dojo/my_kata$ #{$0} start ruby prime.rb
424
+ Run the tests of prime.rb. The test runs automatically every second if prime.rb was modified.
425
+
426
+ :/dojo/my_kata$ #{$0} upload ruby.test/unit .codersdojo/2010-11-02_16-21-53
427
+ Upload the kata (written in Ruby with the test/unit framework) located in directory ".codersdojo/2010-11-02_16-21-53" to codersdojo.com.
428
+ helptext
429
+ end
430
+
431
+ def show_detailed_help command
432
+ if command == 'setup' then
433
+ show_help_setup
434
+ elsif command == 'start' then
435
+ show_help_start
436
+ elsif command == 'upload' then
437
+ show_help_upload
438
+ else
439
+ show_help_unknown command
440
+ end
441
+ end
442
+
443
+ def show_help_setup
444
+ puts <<-helptext
445
+ setup <framework> <kata_file_no_ext> Setup the environment for the kata for the given framework and kata file.
446
+ The kata_file should not have an extension. Use 'prime' and not 'prime.java'.
447
+ By now <framework> is one of clojure.is-test, java.junit, javascript.jspec,
448
+ python.unittest, ruby.test/unit.
449
+ Use ??? as framework if your framework isn't in the list.
450
+ helptext
451
+ end
452
+
453
+ def show_help_start
454
+ puts <<-helptext
455
+ start <shell_command> <kata_file> Start the continuous test runner, that runs <shell-command> whenever <kata_file>
456
+ changes. The <kata_file> has to include the whole source code of the kata.
457
+ Whenever the test runner is started, it creates a new session directory in the
458
+ directory .codersdojo where it logs the steps of the kata.
459
+ helptext
460
+ end
461
+
462
+ def show_help_upload
463
+ puts <<-helptext
464
+ upload <framework> <session_directory> Upload the kata written with <framework> in <session_directory> to codersdojo.com.
465
+ <session_directory> is relative to the working directory.
466
+ Take a look at the generate command for the supported frameworks.
467
+ If you used another framework, use ??? and send an email to codersdojo@it-agile.de
468
+ helptext
469
+ end
470
+
471
+ def show_help_unknown command
472
+ puts <<-helptext
473
+ Command #{command} not known. Try '#{$0} help' to list the supported commands.
474
+ helptext
475
+ end
476
+
477
+ def show_start_kata command, file
478
+ puts "Starting PersonalCodersDojo with command #{command} and kata file #{file}. Use Ctrl+C to finish the kata."
479
+ end
480
+
481
+ def show_missing_command_argument_error command
482
+ puts "Command <#{command}> recognized but no argument was provided (at least one argument is required).\n\n"
483
+ show_usage
484
+ end
485
+
486
+ def show_upload_start hostname
487
+ puts "Start upload to #{hostname}"
488
+ end
489
+
490
+ def show_upload_result result
491
+ puts result
492
+ end
493
+
494
+ def show_socket_error command
495
+ puts "Encountered network error while <#{command}>. Is http://www.codersdojo.com down?"
496
+ end
497
+
498
+ end
499
+
500
+ class GeneratorFactory
501
+
502
+ def initialize
503
+ @frameworks = {"clojure.is-test" => ClosureGenerator,
504
+ "java.junit" => JavaGenerator,
505
+ "javascript.jspec" => JavascriptGenerator,
506
+ "python.unittest" => PythonGenerator,
507
+ "ruby.test/unit" => RubyGenerator}
508
+ end
509
+
510
+ def create_generator framework
511
+ generator_class = @frameworks[framework]
512
+ if generator_class.nil? then
513
+ generator_class = AnyGenerator
514
+ end
515
+ generator_class.new
516
+ end
517
+
518
+ end
519
+
520
+ class AnyGenerator
521
+
522
+ def generate kata_file
523
+ <<-generate_help
524
+ You have to create two shell scripts manually:
525
+ Create a shell script run-once.%sh% that runs the tests of your kata once.
526
+
527
+ Create a second shell script run-endless.sh with this content:
528
+ #{$0} start run-once.%sh% #{kata_file}.<extension>
529
+
530
+ Run run-endless.%sh% and start your kata.
531
+
532
+ Assumptions:
533
+ - The whole kata source code is in the one #{kata_file}.<extension>.
534
+ generate_help
535
+ end
536
+ end
537
+
538
+ class ClosureGenerator
539
+ def generate kata_file
540
+ <<-generate_help
541
+ You have to create two shell scripts manually:
542
+ Create a shell script run-once.%sh% with this content:
543
+ java -cp clojure-contrib.jar%:%clojure.jar clojure.main #{kata_file}.clj
544
+
545
+ Create a second shell script run-endless.sh with this content:
546
+ #{$0} start run-once.%sh% #{kata_file}.clj
547
+
548
+ Run run-endless.%sh% and start your kata.
549
+
550
+ Assumptions:
551
+ - Java is installed on your system and 'java' is in the path.
552
+ - clojure.jar and clojure-contrib.jar are placed in your work directory.
553
+ generate_help
554
+ end
555
+ end
556
+
557
+ class JavaGenerator
558
+ def generate kata_file
559
+ <<-generate_help
560
+ You have to create two shell scripts manually:
561
+ Create a shell script run-once.%sh% with this content:
562
+ %rm% bin/#{kata_file}.class
563
+ javac -cp lib/junit.jar -d bin #{kata_file}.java
564
+ java -cp lib/junit.jar%:%bin #{kata_file}
565
+
566
+ Create a second shell script run-endless.sh with this content:
567
+ #{$0} start run-once.%sh% src/#{kata_file}.java
568
+
569
+ Run run-endless.%sh% and start your kata.
570
+
571
+ Assumptions:
572
+ - A Java JDK is installed on your system and 'java' and 'javac' are in the path.
573
+ - junit.jar is placed in a directory named 'lib'.
574
+ - The kata source file is placed in the 'src' directory.
575
+ - The kata class file is generated into the 'class' directory.
576
+ - In the source file the classes are placed in the default package.
577
+ - The kata source file has a main method that starts the tests.
578
+ - If your IDE (like Eclipse) compiles the source file, you should remove the first two lines
579
+ in the run-once.%sh% file.
580
+ generate_help
581
+ end
582
+ end
583
+
584
+
585
+ class JavascriptGenerator
586
+ def generate kata_file
587
+ <<-generate_help
588
+ You have to create two shell scripts manually:
589
+ Create a shell script run-once.%sh% with this content:
590
+ jspec --rhino run
591
+
592
+ Create a second shell script run-endless.sh with this content:
593
+ #{$0} start run-once.%sh% #{kata_file}.js
594
+
595
+ Run run-endless.%sh% and start your kata.
596
+ generate_help
597
+ end
598
+ end
599
+
600
+
601
+ class PythonGenerator
602
+ def generate kata_file
603
+ <<-generate_help
604
+ You have to create two shell scripts manually:
605
+ Create a shell script run-once.%sh% with this content:
606
+ python #{kata_file}.py
607
+
608
+ Create a second shell script run-endless.sh with this content:
609
+ #{$0} start run-once.%sh% #{kata_file}.py
610
+
611
+ Run run-endless.%sh% and start your kata.
612
+ generate_help
613
+ end
614
+ end
615
+
616
+
617
+ class RubyGenerator
618
+ def generate kata_file
619
+ <<-generate_help
620
+ You have to create one shell script manually:
621
+ Create a shell script run-endless.%sh% with this content:
622
+ #{$0} start ruby #{kata_file}.rb
623
+
624
+ Run run-endless.%sh% and start your kata.
625
+ generate_help
626
+ end
627
+ end
628
+
629
+ def called_from_spec args
630
+ args[0] == "spec"
631
+ end
632
+
633
+ # entry from shell
634
+ if not called_from_spec(ARGV) then
635
+ view = ConsoleView.new
636
+ hostname = "http://www.codersdojo.com"
637
+ #hostname = "http://localhost:3000"
638
+ controller = Controller.new view, hostname
639
+ begin
640
+ arg_parser = ArgumentParser.new controller
641
+ command = arg_parser.parse ARGV
642
+ rescue ArgumentError
643
+ controller.help
644
+ end
645
+ end
@@ -0,0 +1,272 @@
1
+ ARGV[0] = "spec" # to be first line to suppress help text output of shell command
2
+ require 'rubygems'
3
+ require "app/personal_codersdojo"
4
+ require "restclient"
5
+ require "spec"
6
+
7
+
8
+ describe Runner, "in run mode" do
9
+
10
+ WORKSPACE_DIR = ".codersdojo"
11
+ SESSION_ID = "id0815"
12
+ SESSION_DIR = "#{WORKSPACE_DIR}/#{SESSION_ID}"
13
+ STATE_DIR_PREFIX = "#{SESSION_DIR}/state_"
14
+
15
+ before (:each) do
16
+ @shell_mock = mock.as_null_object
17
+ @session_id_provider_mock = mock.as_null_object
18
+ @session_id_provider_mock.should_receive(:generate_id).and_return SESSION_ID
19
+ @runner = Runner.new @shell_mock, @session_id_provider_mock
20
+ @runner.file = "my_file.rb"
21
+ @runner.run_command = "ruby"
22
+ end
23
+
24
+ it "should create codersdojo directory if it doesn't exist with session sub-directory" do
25
+ @shell_mock.should_receive(:mkdir_p).with SESSION_DIR
26
+ @runner.start
27
+ end
28
+
29
+ it "should run ruby command on kata file given as argument" do
30
+ @shell_mock.should_receive(:execute).with "ruby my_file.rb"
31
+ @runner.start
32
+ end
33
+
34
+ it "should create a state directory for every state" do
35
+ @shell_mock.should_receive(:ctime).with("my_file.rb").and_return 1
36
+ @shell_mock.should_receive(:mkdir).with "#{STATE_DIR_PREFIX}0"
37
+ @shell_mock.should_receive(:cp).with "my_file.rb", "#{STATE_DIR_PREFIX}0"
38
+ @runner.start
39
+ @shell_mock.should_receive(:ctime).with("my_file.rb").and_return 2
40
+ @shell_mock.should_receive(:mkdir).with "#{STATE_DIR_PREFIX}1"
41
+ @shell_mock.should_receive(:cp).with "my_file.rb", "#{STATE_DIR_PREFIX}1"
42
+ @runner.execute
43
+ end
44
+
45
+ it "should not run if the kata file wasn't modified" do
46
+ a_time = Time.new
47
+ @shell_mock.should_receive(:ctime).with("my_file.rb").and_return a_time
48
+ @shell_mock.should_receive(:mkdir).with "#{STATE_DIR_PREFIX}0"
49
+ @shell_mock.should_receive(:cp).with "my_file.rb", "#{STATE_DIR_PREFIX}0"
50
+ @runner.start
51
+ @shell_mock.should_receive(:ctime).with("my_file.rb").and_return a_time
52
+ @shell_mock.should_not_receive(:mkdir).with "#{STATE_DIR_PREFIX}1"
53
+ @shell_mock.should_not_receive(:cp).with "my_file.rb", "#{STATE_DIR_PREFIX}1"
54
+ @runner.execute
55
+ end
56
+
57
+ it "should capture run result into state directory" do
58
+ @shell_mock.should_receive(:execute).and_return "spec result"
59
+ @shell_mock.should_receive(:write_file).with "#{STATE_DIR_PREFIX}0/result.txt", "spec result"
60
+ @runner.start
61
+ end
62
+
63
+ end
64
+
65
+
66
+ describe SessionIdGenerator do
67
+
68
+ before (:each) do
69
+ @time_mock = mock
70
+ @generator = SessionIdGenerator.new
71
+ end
72
+
73
+ it "should format id as yyyy-mm-dd_hh-mm-ss" do
74
+ @time_mock.should_receive(:year).and_return 2010
75
+ @time_mock.should_receive(:month).and_return 8
76
+ @time_mock.should_receive(:day).and_return 7
77
+ @time_mock.should_receive(:hour).and_return 6
78
+ @time_mock.should_receive(:min).and_return 5
79
+ @time_mock.should_receive(:sec).and_return 0
80
+ @generator.generate_id(@time_mock).should == "2010-08-07_06-05-00"
81
+ end
82
+
83
+ end
84
+
85
+ describe StateReader do
86
+
87
+ before (:each) do
88
+ @a_time = Time.new
89
+ @shell_mock = mock
90
+ @state_reader = StateReader.new @shell_mock
91
+ @state_reader.session_id = "id0815"
92
+ end
93
+
94
+ it "should read a stored kata state" do
95
+ @shell_mock.should_receive(:ctime).with("#{STATE_DIR_PREFIX}0").and_return @a_time
96
+ Dir.should_receive(:entries).with("#{STATE_DIR_PREFIX}0").and_return(['.','..','file.rb', 'result.txt'])
97
+ @shell_mock.should_receive(:read_file).with("#{STATE_DIR_PREFIX}0/file.rb").and_return "source code"
98
+ @shell_mock.should_receive(:read_file).with("#{STATE_DIR_PREFIX}0/result.txt").and_return "result"
99
+ state = @state_reader.read_next_state
100
+ state.time.should == @a_time
101
+ state.code.should == "source code"
102
+ state.result.should == "result"
103
+ @state_reader.next_step.should == 1
104
+ end
105
+
106
+ end
107
+
108
+ describe Uploader do
109
+
110
+ before (:each) do
111
+ @state_reader_mock = mock StateReader
112
+ end
113
+
114
+ it "should convert session-dir to session-id" do
115
+ @state_reader_mock.should_receive(:session_id=).with("session_id")
116
+ Uploader.new "http://dummy_host", "dummy.framework", ".codersdojo/session_id", @state_reader_mock
117
+ end
118
+
119
+ context'upload' do
120
+
121
+ before (:each) do
122
+ @state_reader_mock = mock StateReader
123
+ @state_reader_mock.should_receive(:session_id=).with("path_to_kata")
124
+ @uploader = Uploader.new "http://dummy_host", "dummy.framework", "path_to_kata", @state_reader_mock
125
+ end
126
+
127
+ it "should return error message if rest-client not installed" do
128
+ @uploader.stub(:require).and_raise(LoadError)
129
+ message = @uploader.upload
130
+ message.should == 'Cant find gem rest-client. Please install it.'
131
+ end
132
+
133
+ it "should upload a kata through a rest-interface" do
134
+ RestClient.should_receive(:post).with('http://dummy_host/katas', {:framework => "dummy.framework"}).and_return '<id>222</id>'
135
+ @uploader.upload_kata
136
+ end
137
+
138
+ it "should upload kata and states" do
139
+ @uploader.stub(:upload_kata).and_return 'kata_xml'
140
+ XMLElementExtractor.should_receive(:extract).with('kata/id', 'kata_xml').and_return 'kata_id'
141
+ @uploader.stub(:upload_states).with 'kata_id'
142
+ XMLElementExtractor.should_receive(:extract).with('kata/short-url', 'kata_xml').and_return 'short_url'
143
+ @uploader.upload_kata_and_states
144
+ end
145
+
146
+ it 'should upload if enugh states are there' do
147
+ @state_reader_mock.should_receive(:enough_states?).and_return 'true'
148
+ @uploader.stub!(:upload_kata_and_states).and_return 'kata_link'
149
+ @uploader.upload
150
+ end
151
+
152
+ it 'should return a helptext if not enught states are there' do
153
+ @state_reader_mock.should_receive(:enough_states?).and_return nil
154
+ help_text = @uploader.upload
155
+ help_text.should == 'You need at least two states'
156
+ end
157
+
158
+ context 'states' do
159
+ it "should read all states and starts/ends progress" do
160
+ @state_reader_mock.should_receive(:state_count).and_return(1)
161
+ Progress.should_receive(:write_empty_progress).with(1)
162
+
163
+ @state_reader_mock.should_receive(:has_next_state).and_return 'true'
164
+ @uploader.should_receive(:upload_state)
165
+ @state_reader_mock.should_receive(:has_next_state).and_return nil
166
+
167
+ Progress.should_receive(:end)
168
+
169
+ @uploader.upload_states "kata_id"
170
+ end
171
+
172
+
173
+ it "through a rest interface and log process" do
174
+ state = mock State
175
+ @state_reader_mock.should_receive(:read_next_state).and_return state
176
+ state.should_receive(:code).and_return 'code'
177
+ state.should_receive(:time).and_return 'time'
178
+ state.should_receive(:result).and_return 'result'
179
+ RestClient.should_receive(:post).with('http://dummy_host/katas/kata_id/states', {:code=> 'code', :result => 'result', :created_at => 'time'})
180
+ Progress.should_receive(:next)
181
+ @uploader.upload_state "kata_id"
182
+ end
183
+
184
+ end
185
+
186
+ end
187
+
188
+ end
189
+
190
+ describe XMLElementExtractor do
191
+
192
+ it "should extract first element from a xml string" do
193
+ xmlString = "<?xml version='1.0' encoding='UTF-8'?>\n<kata>\n <created-at type='datetime'>2010-07-16T16:02:00+02:00</created-at>\n <end-date type='datetime' nil='true'/>\n <id type='integer'>60</id>\n <short-url nil='true'/>\n <updated-at type='datetime'>2010-07-16T16:02:00+02:00</updated-at>\n <uuid>2a5a83dc71b8ad6565bd99f15d01e41ec1a8f3f2</uuid>\n</kata>\n"
194
+ element = XMLElementExtractor.extract 'kata/id', xmlString
195
+ element.should == "60"
196
+ end
197
+
198
+ end
199
+
200
+ describe ArgumentParser do
201
+
202
+ before (:each) do
203
+ @controller_mock = mock.as_null_object
204
+ @parser = ArgumentParser.new @controller_mock
205
+ end
206
+
207
+ it "should reject empty command" do
208
+ lambda{@parser.parse []}.should raise_error
209
+ end
210
+
211
+ it "should reject unknown command" do
212
+ lambda{@parser.parse "unknown command"}.should raise_error
213
+ end
214
+
215
+ it "should accept help command" do
216
+ @controller_mock.should_receive(:help).with(nil)
217
+ @parser.parse ["help"]
218
+ end
219
+
220
+ it "should accept start command" do
221
+ @controller_mock.should_receive(:start).with "aCommand", "aFile"
222
+ @parser.parse ["start", "aCommand","aFile"]
223
+ end
224
+
225
+ it "should prepend *.sh start scripts with 'bash'" do
226
+ @controller_mock.should_receive(:start).with "bash aCommand.sh", "aFile"
227
+ @parser.parse ["start", "aCommand.sh","aFile"]
228
+ end
229
+
230
+ it "should prepend *.bat start scripts with 'start'" do
231
+ @controller_mock.should_receive(:start).with "start aCommand.bat", "aFile"
232
+ @parser.parse ["start", "aCommand.bat","aFile"]
233
+ end
234
+
235
+ it "should prepend *.cmd start scripts with 'start'" do
236
+ @controller_mock.should_receive(:start).with "start aCommand.cmd", "aFile"
237
+ @parser.parse ["start", "aCommand.cmd","aFile"]
238
+ end
239
+
240
+ it "should accept upload command" do
241
+ @controller_mock.should_receive(:upload).with "framework", "dir"
242
+ @parser.parse ["upload", "framework", "dir"]
243
+ end
244
+
245
+ it "should accept uppercase commands" do
246
+ @controller_mock.should_receive(:help).with(nil)
247
+ @parser.parse ["HELP"]
248
+ end
249
+
250
+ end
251
+
252
+ describe Progress do
253
+
254
+ it 'should print infos and empty progress in initialization' do
255
+ STDOUT.should_receive(:print).with("2 states to upload")
256
+ STDOUT.should_receive(:print).with("[ ]")
257
+ STDOUT.should_receive(:print).with("\b\b\b")
258
+ Progress.write_empty_progress 2
259
+ end
260
+
261
+ it 'should print dots and flush in next' do
262
+ STDOUT.should_receive(:print).with(".")
263
+ STDOUT.should_receive(:flush)
264
+ Progress.next
265
+ end
266
+
267
+ it 'should print empty line in end' do
268
+ STDOUT.should_receive(:puts)
269
+ Progress.end
270
+ end
271
+
272
+ end
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: codersdojo
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 9
8
+ - 0
9
+ version: 0.9.0
10
+ platform: ruby
11
+ authors:
12
+ - CodersDojo-Team
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-12-10 00:00:00 +01:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description: Client executes tests in an endless loop and logs source code and test result.
22
+ email: codersdojo@it-agile.de
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files: []
28
+
29
+ files:
30
+ - app/personal_codersdojo.rb
31
+ has_rdoc: true
32
+ homepage: http://www.codersdojo.org/
33
+ licenses: []
34
+
35
+ post_install_message:
36
+ rdoc_options: []
37
+
38
+ require_paths:
39
+ - lib
40
+ required_ruby_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ segments:
52
+ - 0
53
+ version: "0"
54
+ requirements: []
55
+
56
+ rubyforge_project: codersdojo
57
+ rubygems_version: 1.3.6
58
+ signing_key:
59
+ specification_version: 3
60
+ summary: Client for CodersDojo.org
61
+ test_files:
62
+ - spec/personal_codersdojo_spec.rb