crazy_ivan 0.3.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/crazy_ivan.rb CHANGED
@@ -1,73 +1,121 @@
1
+ require 'syslog'
1
2
  require 'fileutils'
2
3
  require 'crazy_ivan/report_assembler'
3
4
  require 'crazy_ivan/test_runner'
4
5
  require 'crazy_ivan/html_asset_crush'
5
6
  require 'crazy_ivan/version'
6
7
  require 'crazy_ivan/vendor/json'
8
+ require 'crazy_ivan/vendor/open4'
7
9
 
8
10
  module CrazyIvan
9
11
  def self.setup
12
+ puts
13
+ puts "Preparing per-project continuous integration configuration scripts"
14
+ puts
15
+
10
16
  Dir['*'].each do |dir|
11
17
  Dir.chdir(dir) do
12
18
  FileUtils.mkdir_p('.ci')
13
19
 
14
20
  Dir.chdir('.ci') do
15
- File.open('version', 'w+') do |f|
16
- f.puts "#!/usr/bin/env ruby"
17
- f.puts
18
- f.puts "# This script grabs a unique hash from your version control system"
19
- f.puts "#"
20
- f.puts "# If you're not able to use a VCS, this script could just generate a timestamp."
21
- f.puts
22
- f.puts "puts `git show`[/^commit (.+)$/, 1]"
21
+ puts " #{dir}/.ci/update"
22
+ if File.exists?('version')
23
+ puts " #{' ' * (dir + "/.ci").size}/version already exists - skipping"
24
+ else
25
+ File.open('version', 'w+') do |f|
26
+ f.puts "#!/usr/bin/env ruby"
27
+ f.puts
28
+ f.puts "# This script grabs a unique version name from your version control system"
29
+ f.puts "#"
30
+ f.puts "# If you're not able to use a VCS, this script could just generate a timestamp."
31
+ f.puts
32
+ f.puts "puts `git show`[/^commit (.+)$/, 1]"
33
+ end
34
+ puts " #{' ' * (dir + "/.ci").size}/version -- created"
23
35
  end
24
36
 
25
- File.open('update', 'w+') do |f|
26
- f.puts "#!/usr/bin/env bash"
27
- f.puts
28
- f.puts "# This script updates your code"
29
- f.puts "#"
30
- f.puts "# If you can’t use a version control system, this script could just do some"
31
- f.puts "# some basic copying commands."
32
- f.puts
33
- f.puts "git pull"
37
+ if File.exists?('update')
38
+ puts " #{' ' * (dir + "/.ci").size}/update already exists - skipping"
39
+ else
40
+ File.open('update', 'w+') do |f|
41
+ f.puts "#!/usr/bin/env bash"
42
+ f.puts
43
+ f.puts "# This script updates your code"
44
+ f.puts "#"
45
+ f.puts "# If you can’t use a version control system, this script could just do some"
46
+ f.puts "# some basic copying commands."
47
+ f.puts
48
+ f.puts "git pull"
49
+ end
50
+ puts " #{' ' * (dir + "/.ci").size}/update -- created"
34
51
  end
35
52
 
36
- File.open('test', 'w+') do |f|
37
- f.puts "#!/usr/bin/env bash"
38
- f.puts
39
- f.puts "# This script runs your testing suite. For a typical Ruby project running"
40
- f.puts "# test-unit this is probably all you need."
41
- f.puts
42
- f.puts "rake"
53
+ if File.exists?('test')
54
+ puts " #{' ' * (dir + "/.ci").size}/test already exists -- skipping"
55
+ else
56
+ File.open('test', 'w+') do |f|
57
+ f.puts "#!/usr/bin/env bash"
58
+ f.puts
59
+ f.puts "# This script runs your testing suite. For a typical Ruby project running"
60
+ f.puts "# test-unit this is probably all you need."
61
+ f.puts
62
+ f.puts "rake"
63
+ end
64
+ puts " #{' ' * (dir + "/.ci").size}/test -- created"
65
+ end
66
+
67
+ if File.exists?('conclusion')
68
+ puts " #{' ' * (dir + "/.ci").size}/conclusion already exists -- skipping"
69
+ else
70
+ File.open('conclusion', 'w+') do |f|
71
+ f.puts "#!/usr/bin/env ruby"
72
+ f.puts
73
+ f.puts "# This script is piped the results of the testing suite run."
74
+ f.puts
75
+ f.puts "# If you're interested in bouncing the message to campfire, "
76
+ f.puts "# emailing, or otherwise sending notifications, this is the place to do it."
77
+ f.puts
78
+ f.puts "# To enable campfire notifications, uncomment the next lines:"
79
+ f.puts "# CAMPFIRE_ROOM_URL = 'http://your-company.campfirenow.com/room/265250'"
80
+ f.puts "# CAMPFIRE_API_KEY = '23b8al234gkj80a3e372133l4k4j34275f80ef8971'"
81
+ f.puts "# CRAZY_IVAN_REPORTS_URL = 'http://ci.your-projects.com'"
82
+ f.puts "# IO.popen(\"test_report2campfire \#{CAMPFIRE_ROOM_URL} \#{CAMPFIRE_API_KEY} \#{CRAZY_IVAN_REPORTS_URL}\", 'w') {|f| f.puts STDIN.read }"
83
+ f.puts
84
+ end
85
+ puts " #{' ' * (dir + "/.ci").size}/conclusion -- created"
43
86
  end
87
+ puts
44
88
 
45
- File.chmod 0755, 'update', 'version', 'test'
89
+ File.chmod 0755, 'update', 'version', 'test', 'conclusion'
46
90
  end
47
-
48
- puts
49
- puts "Created #{dir}/.ci/update"
50
- puts " #{' ' * (dir + "/.ci").size}/version"
51
- puts " #{' ' * (dir + "/.ci").size}/test"
52
- puts
53
- puts "Take a look at those 3 scripts to make sure "
54
- puts "they do the right thing for each case."
55
- puts
56
91
  end
57
92
  end
93
+
94
+ puts "Take a look at those 4 scripts to make sure they each do the right thing."
95
+ puts
96
+ puts "When you're ready, run the following from the projects directory (here):"
97
+ puts
98
+ puts " crazy_ivan /path/to/directory/your/reports/go"
99
+ puts
100
+ puts "then look at index.html in that path to confirm that everything is ok."
101
+ puts
102
+ puts "If things look good, then set up a cron task or other script to run"
103
+ puts "crazy_ivan on a periodic basis."
104
+ puts
58
105
  end
59
106
 
60
107
  def self.generate_test_reports_in(output_directory)
108
+ Syslog.open('crazy_ivan', Syslog::LOG_PID | Syslog::LOG_CONS)
61
109
  FileUtils.mkdir_p(output_directory)
62
- report = ReportAssembler.new(output_directory)
63
-
64
- Dir['*'].each do |dir|
65
- if File.directory?(dir)
66
- report.test_results << TestRunner.new(dir).invoke
67
- end
68
- end
69
110
 
111
+ report = ReportAssembler.new(Dir.pwd, output_directory)
70
112
  report.generate
71
- puts "Generated test reports for #{report.test_results.size} projects"
113
+
114
+ msg = "Generated test reports for #{report.runners.size} projects"
115
+ Syslog.info(msg)
116
+ puts msg
117
+ # REFACTOR to use a logger that spits out to both STDOUT and Syslog
118
+ ensure
119
+ Syslog.close
72
120
  end
73
121
  end
data/templates/css/ci.css CHANGED
@@ -22,6 +22,7 @@ pre { margin: 0;}
22
22
 
23
23
  .results .result { margin: 12px 18px 8px 18px;}
24
24
  .results .result .timestamp { margin-right: 12px;}
25
+ .results .result .version { margin: 6px 0 6px 12px }
25
26
  .results .result .update { margin: 6px 0 6px 12px }
26
27
  .results .result .test { margin: 6px 0 6px 12px }
27
28
 
data/templates/index.html CHANGED
@@ -33,7 +33,7 @@
33
33
  return version_test_report(project_name, version);
34
34
  })
35
35
 
36
- project = {name: project_name, reports: reports};
36
+ project = {name: project_name, reports: reports.reverse(), build_in_progress: build_in_progress(project_name)};
37
37
  projects.push(project);
38
38
  })
39
39
 
@@ -64,6 +64,18 @@
64
64
  return report;
65
65
  }
66
66
 
67
+ var build_in_progress = function(project) {
68
+ var build_in_progress = {};
69
+
70
+ new Ajax.Request(project + '/currently_building.json', {
71
+ asynchronous: false,
72
+ onSuccess: function(transport) {
73
+ build_in_progress = transport.responseText.evalJSON();
74
+ }
75
+ });
76
+ return build_in_progress;
77
+ }
78
+
67
79
  var expand = function(element) {
68
80
  element.siblings().invoke('show');
69
81
  element.remove();
@@ -77,10 +89,15 @@
77
89
  {.repeated section @} \
78
90
  <div class='project'> \
79
91
  <h2>{name}</h2> \
92
+ {.section build_in_progress} \
93
+ {.section timestamp} \
94
+ <div>[build in progress &ndash; started at {start}]</div> \
95
+ {.end} \
96
+ {.end} \
80
97
  <div class='tests'> \
81
98
  {.section reports} \
82
99
  {.repeated section @} \
83
- <a class='test {.section test_error} error {.end} {.section update_error} error {.end}' href='#'>{timestamp}</a> \
100
+ <a class='test {.section test} {.section exit_status} error {.end} {.end} {.section update} {.section exit_status} error {.end} {.end}' href='#'>{timestamp.finish}</a> \
84
101
  {.end} \
85
102
  {.end} \
86
103
  </div> \
@@ -89,27 +106,39 @@
89
106
  {.repeated section @} \
90
107
  <div class='result'> \
91
108
  <div> \
92
- <span class='timestamp'>{timestamp}</span> \
93
- <span class='version'>{version}</span> \
109
+ <span class='timestamp'>{timestamp.finish}</span> \
110
+ <span class='version'>{version.output}</span> \
94
111
  </div> \
95
112
  \
113
+ {.section version} \
114
+ {.section exit_status} \
115
+ <div class='version'> \
116
+ <pre class='error'>{error}</pre> \
117
+ </div> \
118
+ {.end} \
119
+ {.end} \
120
+ \
96
121
  <div class='update'> \
97
- <pre>{update}</pre> \
98
- {.section update_error} \
99
- <pre class='error'>{update_error}</pre> \
122
+ <pre>{update.output}</pre> \
123
+ {.section update} \
124
+ {.section exit_status} \
125
+ <pre class='error'>{update.error}</pre> \
126
+ {.end} \
100
127
  {.end} \
101
128
  </div> \
102
129
  \
103
130
  <div class='test'> \
104
- <pre>{test}</pre> \
105
- {.section test_error} \
106
- <pre class='error'>{test_error}</pre> \
131
+ <pre>{test.output}</pre> \
132
+ {.section test} \
133
+ {.section exit_status} \
134
+ <pre class='error'>{test.error}</pre> \
135
+ {.end} \
107
136
  {.end} \
108
137
  </div> \
109
138
  </div> \
110
139
  {.end} \
111
140
  {.or} \
112
- <p>No test reports found. Please run the `ci` executable.</p> \
141
+ <p>No test reports found. Please run crazy_ivan.</p> \
113
142
  {.end} \
114
143
  </div> \
115
144
  </div> \
@@ -2,10 +2,18 @@ require 'test_helper'
2
2
  require 'tmpdir'
3
3
 
4
4
  class CrazyIvanTest < Test::Unit::TestCase
5
+ def setup
6
+ @results = {:project_name => 'some-project',
7
+ :version => {:output => 'a-valid-version', :error => '', :exit_status => '0'},
8
+ :update => {:output => 'Updated successfully', :error => '', :exit_status => '0'},
9
+ :test => {:output => 'Some valid test results. No fails.', :error => '', :exit_status => '0'},
10
+ :timestamp => {:start => Time.now, :finish => nil}}
11
+ end
12
+
5
13
  def test_setup
6
14
  setup_crazy_ivan do
7
- assert File.exists?('projects/some-project/.ci/version')
8
15
  assert File.exists?('projects/some-project/.ci/update')
16
+ assert File.exists?('projects/some-project/.ci/version')
9
17
  assert File.exists?('projects/some-project/.ci/test')
10
18
  end
11
19
  end
@@ -14,16 +22,12 @@ class CrazyIvanTest < Test::Unit::TestCase
14
22
  setup_external_scripts_to_all_be_successful
15
23
 
16
24
  setup_crazy_ivan do
17
- CrazyIvan.setup
18
-
19
- # crazy_ivan runs from the projects directory
20
- Dir.chdir('projects') do
21
- CrazyIvan.generate_test_reports_in('../test-results')
22
- end
25
+ run_crazy_ivan
23
26
 
24
27
  assert File.exists?('test-results/index.html')
25
28
  assert File.exists?('test-results/projects.json')
26
29
  assert File.exists?('test-results/some-project/recent.json')
30
+ assert File.exists?('test-results/some-project/currently_building.json')
27
31
 
28
32
  projects = JSON.parse(File.open('test-results/projects.json').read)["projects"]
29
33
  recent_versions = JSON.parse(File.open('test-results/some-project/recent.json').read)["recent_versions"]
@@ -34,44 +38,122 @@ class CrazyIvanTest < Test::Unit::TestCase
34
38
  end
35
39
  end
36
40
 
41
+ # FIX Does this test really work? Doesn't look like it
37
42
  def test_external_scripts_not_overwritten
38
43
  setup_external_scripts_to_all_be_successful
39
44
 
40
45
  setup_crazy_ivan do
41
- CrazyIvan.setup
42
-
43
46
  File.open('projects/some-project/.ci/version', 'a') do |file|
44
47
  file << "a change to the script"
45
48
  end
46
49
 
47
50
  FileUtils.copy('projects/some-project/.ci/version', 'projects/some-project/.ci/version_original')
48
51
 
49
- CrazyIvan.setup
50
-
52
+ do_silently { CrazyIvan.setup }
51
53
  assert FileUtils.compare_file('projects/some-project/.ci/version_original', 'projects/some-project/.ci/version')
52
54
  end
53
55
  end
54
56
 
57
+ def test_nil_reports_not_created
58
+ Open4.stubs(:popen4).with('.ci/update').yields(stub(),
59
+ stub(:close),
60
+ stub(:read => @results[:update][:output]),
61
+ stub(:read => @results[:update][:error])).returns(stub(:exitstatus => '0'))
62
+ Open4.stubs(:popen4).with('.ci/version').yields(stub(),
63
+ stub(:close),
64
+ stub(:read => ''),
65
+ stub(:read => 'could not find the command you were looking for')).returns(stub(:exitstatus => '1'))
66
+ Open4.stubs(:popen4).with('.ci/conclusion').yields(stub(),
67
+ stub(:puts => true, :close => true),
68
+ stub(),
69
+ stub(:read => '')).returns(stub(:exitstatus => '0'))
70
+
71
+ setup_crazy_ivan do
72
+ Dir.chdir('projects') do
73
+ do_silently { CrazyIvan.generate_test_reports_in('../test-results') }
74
+ end
75
+
76
+ assert !File.exists?('test-results/some-project/nil.json')
77
+ end
78
+ end
79
+
80
+ def test_conclusion_executed
81
+ Open4.stubs(:popen4).with('.ci/update').yields(stub(),
82
+ stub(:close),
83
+ stub(:read => @results[:update][:output]),
84
+ stub(:read => @results[:update][:error])).returns(stub(:exitstatus => '0'))
85
+ Open4.stubs(:popen4).with('.ci/version').yields(stub(),
86
+ stub(:close),
87
+ stub(:read => @results[:version][:output]),
88
+ stub(:read => @results[:version][:error])).returns(stub(:exitstatus => '0'))
89
+ Open4.stubs(:popen4).with('.ci/test').yields(stub(),
90
+ stub(:close),
91
+ stub(:read => @results[:test][:output]),
92
+ stub(:read => @results[:test][:error])).returns(stub(:exitstatus => '0'))
93
+
94
+ @results[:timestamp][:start] = Time.now
95
+ @results[:timestamp][:finish] = @results[:timestamp][:start]
96
+ Time.stubs(:now => @results[:timestamp][:start])
97
+
98
+ fake_stdin = mock()
99
+
100
+ fake_stdin.expects(:puts).with(@results.to_json).at_least_once
101
+ fake_stdin.expects(:close)
102
+
103
+ Open4.stubs(:popen4).with('.ci/conclusion').yields(stub(), fake_stdin, stub(), stub(:read => '')).returns(stub(:exitstatus => '0'))
104
+
105
+ setup_crazy_ivan(false) do
106
+ run_crazy_ivan
107
+ end
108
+ end
109
+
110
+ # def test_report_in_progress_json_created
111
+ # setup_crazy_ivan do
112
+ # run_crazy_ivan
113
+ # end
114
+ # end
115
+
55
116
  private
56
117
 
57
- def setup_crazy_ivan
118
+ def setup_crazy_ivan(with_multiple_projects = true)
58
119
  Dir.mktmpdir('continuous-integration') do |tmpdir|
59
120
  Dir.chdir(tmpdir)
60
121
 
61
122
  Dir.mkdir('projects')
62
123
  Dir.chdir('projects') do |projects_dir|
63
124
  Dir.mkdir('some-project')
64
- Dir.mkdir('some-other-project')
65
- CrazyIvan.setup
125
+ Dir.mkdir('some-other-project') if with_multiple_projects
126
+ do_silently { CrazyIvan.setup }
66
127
  end
67
128
 
68
- yield
129
+ yield
130
+ end
131
+ end
132
+
133
+ def run_crazy_ivan
134
+ # crazy_ivan runs from the projects directory
135
+ Dir.chdir('projects') do
136
+ do_silently { CrazyIvan.generate_test_reports_in('../test-results') }
69
137
  end
70
138
  end
71
139
 
72
140
  def setup_external_scripts_to_all_be_successful
73
- Open3.stubs(:popen3).with('.ci/version').yields(stub(:close), stub(:read => 'a-valid-version'), stub(:read => ''))
74
- Open3.stubs(:popen3).with('.ci/update').yields(stub(:close), stub(:read => 'Updated successfully.'), stub(:read => ''))
75
- Open3.stubs(:popen3).with('.ci/test').yields(stub(:close), stub(:read => 'Some valid test results. No fails.'), stub(:read => ''))
141
+ Open4.stubs(:popen4).with('.ci/update').yields(stub(),
142
+ stub(:close),
143
+ stub(:read => @results[:update][:output]),
144
+ stub(:read => @results[:update][:error])).returns(stub(:exitstatus => '0'))
145
+ Open4.stubs(:popen4).with('.ci/version').yields(stub(),
146
+ stub(:close),
147
+ stub(:read => @results[:version][:output]),
148
+ stub(:read => @results[:version][:error])).returns(stub(:exitstatus => '0'))
149
+ Open4.stubs(:popen4).with('.ci/test').yields(stub(),
150
+ stub(:close),
151
+ stub(:read => @results[:test][:output]),
152
+ stub(:read => @results[:test][:error])).returns(stub(:exitstatus => '0'))
153
+
154
+ Open4.stubs(:popen4).with('.ci/conclusion').yields(stub(),
155
+ stub(:puts => true, :close => true),
156
+ stub(),
157
+ stub(:read => '')).returns(stub(:exitstatus => '0'))
76
158
  end
77
159
  end
data/test/test_helper.rb CHANGED
@@ -7,4 +7,10 @@ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
7
7
  require 'crazy_ivan'
8
8
 
9
9
  class Test::Unit::TestCase
10
+ def do_silently
11
+ orig_stdout = $stdout
12
+ $stdout = File.new('/dev/null', 'w')
13
+ yield
14
+ $stdout = orig_stdout
15
+ end
10
16
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: crazy_ivan
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Edward Ocampo-Gooding
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-11-30 00:00:00 -05:00
12
+ date: 2010-01-10 00:00:00 -05:00
13
13
  default_executable: crazy_ivan
14
14
  dependencies: []
15
15
 
@@ -19,12 +19,13 @@ description: |-
19
19
  By keeping test reports in json, per-project CI configuration in 3 probably-one-line scripts, things are kept simple, quick, and super extensible.
20
20
 
21
21
  Want to use git, svn, or hg? No problem.
22
- Need to fire off results to Twitter or Campfire? It's one line away.
22
+ Need to fire off results to Campfire? It's built-in.
23
23
 
24
24
  CI depends on cron.
25
25
  email: edward@edwardog.net
26
26
  executables:
27
27
  - crazy_ivan
28
+ - test_report2campfire
28
29
  extensions: []
29
30
 
30
31
  extra_rdoc_files:
@@ -39,6 +40,7 @@ files:
39
40
  - TODO
40
41
  - VERSION
41
42
  - bin/crazy_ivan
43
+ - bin/test_report2campfire
42
44
  - crazy_ivan.gemspec
43
45
  - lib/crazy_ivan.rb
44
46
  - lib/crazy_ivan/html_asset_crush.rb
@@ -159,6 +161,20 @@ files:
159
161
  - lib/crazy_ivan/vendor/json-1.1.7/tools/fuzz.rb
160
162
  - lib/crazy_ivan/vendor/json-1.1.7/tools/server.rb
161
163
  - lib/crazy_ivan/vendor/json.rb
164
+ - lib/crazy_ivan/vendor/open4-1.0.1/README
165
+ - lib/crazy_ivan/vendor/open4-1.0.1/README.erb
166
+ - lib/crazy_ivan/vendor/open4-1.0.1/Rakefile
167
+ - lib/crazy_ivan/vendor/open4-1.0.1/lib/open4.rb
168
+ - lib/crazy_ivan/vendor/open4-1.0.1/open4.gemspec
169
+ - lib/crazy_ivan/vendor/open4-1.0.1/samples/bg.rb
170
+ - lib/crazy_ivan/vendor/open4-1.0.1/samples/block.rb
171
+ - lib/crazy_ivan/vendor/open4-1.0.1/samples/exception.rb
172
+ - lib/crazy_ivan/vendor/open4-1.0.1/samples/simple.rb
173
+ - lib/crazy_ivan/vendor/open4-1.0.1/samples/spawn.rb
174
+ - lib/crazy_ivan/vendor/open4-1.0.1/samples/stdin_timeout.rb
175
+ - lib/crazy_ivan/vendor/open4-1.0.1/samples/timeout.rb
176
+ - lib/crazy_ivan/vendor/open4-1.0.1/white_box/leak.rb
177
+ - lib/crazy_ivan/vendor/open4.rb
162
178
  - lib/crazy_ivan/version.rb
163
179
  - templates/css/ci.css
164
180
  - templates/index.html