codersdojo 0.9.0

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.
@@ -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