crazy_ivan 0.3.3 → 1.0.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.
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