crazy_ivan 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,11 +1,12 @@
1
1
  class TestRunner
2
- def initialize(project_path)
2
+ def initialize(project_path, report_assembler)
3
3
  @project_path = project_path
4
4
  @results = {:project_name => File.basename(@project_path),
5
5
  :version => {:output => '', :error => '', :exit_status => ''},
6
6
  :update => {:output => '', :error => '', :exit_status => ''},
7
7
  :test => {:output => '', :error => '', :exit_status => ''},
8
8
  :timestamp => {:start => nil, :finish => nil}}
9
+ @report_assembler = report_assembler
9
10
  end
10
11
 
11
12
  attr_reader :results
@@ -31,26 +32,74 @@ class TestRunner
31
32
  Dir.chdir(@project_path) do
32
33
  if File.exists?(script_path)
33
34
  if !File.stat(script_path).executable?
34
- abort "#{@project_path}/.ci/#{name} script not executable"
35
+ msg = "#{@project_path}/.ci/#{name} script not executable"
36
+ Syslog.warning msg
37
+ abort msg
35
38
  elsif File.open(script_path).read.empty?
36
- abort "#{@project_path}/.ci/#{name} script empty"
39
+ msg = "#{@project_path}/.ci/#{name} script empty"
40
+ Syslog.warning msg
41
+ abort msg
37
42
  end
38
43
  else
39
- abort "#{@project_path}/.ci/#{name} script missing"
44
+ msg = "#{@project_path}/.ci/#{name} script missing"
45
+ Syslog.warning msg
46
+ abort msg
40
47
  end
41
48
  end
42
49
  end
43
50
 
44
- def run_script(name)
51
+ def run_script(name, options = {})
45
52
  output = ''
46
53
  error = ''
47
54
  exit_status = ''
48
55
 
49
56
  Dir.chdir(@project_path) do
57
+ Syslog.debug "Opening up the pipe to #{script_path(name)}"
58
+
50
59
  status = Open4::popen4(script_path(name)) do |pid, stdin, stdout, stderr|
51
60
  stdin.close # Close to prevent hanging if the script wants input
52
- output = stdout.read
53
- error = stderr.read
61
+
62
+ until stdout.eof? && stderr.eof? do
63
+ ready_io_streams = select( [stdout], nil, [stderr], 3600 )
64
+
65
+ script_output = ready_io_streams[0].pop
66
+ script_error = ready_io_streams[2].pop
67
+
68
+ if script_output && !script_output.eof?
69
+ o = script_output.readpartial(4096)
70
+ print o
71
+ output << o
72
+
73
+ if options[:stream_test_results?]
74
+ @results[:test][:output] = output
75
+ @report_assembler.update_project(self)
76
+ end
77
+ end
78
+
79
+ if script_error && !script_error.eof?
80
+ e = script_error.readpartial(4096)
81
+ print e
82
+ error << e
83
+
84
+ if options[:stream_test_results?]
85
+ @results[:test][:error] = error
86
+ @report_assembler.update_project(self)
87
+ end
88
+ end
89
+
90
+ # FIXME - this feels like I'm using IO.select wrong
91
+ if script_output.eof? && script_error.nil?
92
+ # there's no more output to SDOUT, and there aren't any errors
93
+ e = stderr.read
94
+ error << e
95
+ print e
96
+
97
+ if options[:stream_test_results?]
98
+ @results[:test][:error] = error
99
+ @report_assembler.update_project(self)
100
+ end
101
+ end
102
+ end
54
103
  end
55
104
 
56
105
  exit_status = status.exitstatus
@@ -60,7 +109,6 @@ class TestRunner
60
109
  end
61
110
 
62
111
  def run_conclusion_script
63
-
64
112
  # REFACTOR do this asynchronously so the next tests don't wait on running the conclusion
65
113
 
66
114
  Dir.chdir(@project_path) do
@@ -103,7 +151,7 @@ class TestRunner
103
151
  def test!
104
152
  if @results[:version][:exit_status] == '0'
105
153
  Syslog.debug "Testing #{@results[:project_name]} build #{@results[:version][:output]}"
106
- @results[:test][:output], @results[:test][:error], @results[:test][:exit_status] = run_script('test')
154
+ @results[:test][:output], @results[:test][:error], @results[:test][:exit_status] = run_script('test', :stream_test_results? => true)
107
155
  else
108
156
  Syslog.debug "Failed to test #{project_name}; version exit status was #{@results[:version][:exit_status]}"
109
157
  end
@@ -2,5 +2,5 @@ begin
2
2
  require 'open4'
3
3
  rescue LoadError
4
4
  $LOAD_PATH.unshift(File.dirname(__FILE__) + '/open4-1.0.1/lib')
5
- require 'open4.rb'
5
+ require 'open4'
6
6
  end
@@ -1,3 +1,3 @@
1
1
  module CrazyIvan
2
- VERSION = '1.1.1'
2
+ VERSION = '1.2.0'
3
3
  end
data/lib/crazy_ivan.rb CHANGED
@@ -1,29 +1,28 @@
1
1
  require 'syslog'
2
2
  require 'fileutils'
3
+ require 'yaml'
4
+ require 'crazy_ivan/process_manager'
3
5
  require 'crazy_ivan/report_assembler'
4
6
  require 'crazy_ivan/test_runner'
5
- require 'crazy_ivan/html_asset_crush'
6
7
  require 'crazy_ivan/version'
7
8
  require 'crazy_ivan/vendor/json'
8
9
  require 'crazy_ivan/vendor/open4'
9
10
  require 'crazy_ivan/vendor/tmpdir'
10
11
 
11
12
  module CrazyIvan
12
- # VERSION = '1.0.0'
13
-
14
13
  def self.setup
15
14
  puts
16
15
  puts "Preparing per-project continuous integration configuration scripts"
17
16
  puts
18
17
 
19
- Dir['*'].each do |dir|
18
+ Dir['*/'].each do |dir|
20
19
  Dir.chdir(dir) do
21
20
  FileUtils.mkdir_p('.ci')
22
21
 
23
22
  Dir.chdir('.ci') do
24
- puts " #{dir}/.ci"
23
+ puts " #{dir}.ci"
25
24
  if File.exists?('version')
26
- puts " #{' ' * (dir + "/.ci").size}/version already exists - skipping"
25
+ puts " #{' ' * (dir + "/.ci").size}/version already exists - skipping"
27
26
  else
28
27
  File.open('version', 'w+') do |f|
29
28
  f.puts "#!/usr/bin/env ruby"
@@ -34,11 +33,11 @@ module CrazyIvan
34
33
  f.puts
35
34
  f.puts "puts `git show`[/^commit (.+)$/, 1]"
36
35
  end
37
- puts " #{' ' * (dir + "/.ci").size}/version -- created"
36
+ puts " #{' ' * (dir + ".ci").size}/version -- created"
38
37
  end
39
38
 
40
39
  if File.exists?('update')
41
- puts " #{' ' * (dir + "/.ci").size}/update already exists - skipping"
40
+ puts " #{' ' * (dir + "/.ci").size}/update already exists - skipping"
42
41
  else
43
42
  File.open('update', 'w+') do |f|
44
43
  f.puts "#!/usr/bin/env bash"
@@ -50,11 +49,11 @@ module CrazyIvan
50
49
  f.puts
51
50
  f.puts "git pull"
52
51
  end
53
- puts " #{' ' * (dir + "/.ci").size}/update -- created"
52
+ puts " #{' ' * (dir + ".ci").size}/update -- created"
54
53
  end
55
54
 
56
55
  if File.exists?('test')
57
- puts " #{' ' * (dir + "/.ci").size}/test already exists -- skipping"
56
+ puts " #{' ' * (dir + ".ci").size}/test already exists -- skipping"
58
57
  else
59
58
  File.open('test', 'w+') do |f|
60
59
  f.puts "#!/usr/bin/env bash"
@@ -64,11 +63,11 @@ module CrazyIvan
64
63
  f.puts
65
64
  f.puts "rake"
66
65
  end
67
- puts " #{' ' * (dir + "/.ci").size}/test -- created"
66
+ puts " #{' ' * (dir + ".ci").size}/test -- created"
68
67
  end
69
68
 
70
69
  if File.exists?('conclusion')
71
- puts " #{' ' * (dir + "/.ci").size}/conclusion already exists -- skipping"
70
+ puts " #{' ' * (dir + ".ci").size}/conclusion already exists -- skipping"
72
71
  else
73
72
  File.open('conclusion', 'w+') do |f|
74
73
  f.puts "#!/usr/bin/env ruby"
@@ -78,14 +77,14 @@ module CrazyIvan
78
77
  f.puts "# If you're interested in bouncing the message to campfire, "
79
78
  f.puts "# emailing, or otherwise sending notifications, this is the place to do it."
80
79
  f.puts
81
- f.puts "# To enable campfire notifications, uncomment the next lines:"
80
+ f.puts "# To enable campfire notifications, uncomment the next lines and make sure you have the httparty gem installed:"
82
81
  f.puts "# CAMPFIRE_ROOM_URL = 'http://your-company.campfirenow.com/room/265250'"
83
82
  f.puts "# CAMPFIRE_API_KEY = '23b8al234gkj80a3e372133l4k4j34275f80ef8971'"
84
83
  f.puts "# CRAZY_IVAN_REPORTS_URL = 'http://ci.your-projects.com'"
85
84
  f.puts "# IO.popen(\"test_report2campfire \#{CAMPFIRE_ROOM_URL} \#{CAMPFIRE_API_KEY} \#{CRAZY_IVAN_REPORTS_URL}\", 'w') {|f| f.puts STDIN.read }"
86
85
  f.puts
87
86
  end
88
- puts " #{' ' * (dir + "/.ci").size}/conclusion -- created"
87
+ puts " #{' ' * (dir + ".ci").size}/conclusion -- created"
89
88
  end
90
89
  puts
91
90
 
@@ -100,7 +99,10 @@ module CrazyIvan
100
99
  puts
101
100
  puts " crazy_ivan /path/to/directory/your/reports/go"
102
101
  puts
103
- puts "then look at index.html in that path to confirm that everything is ok."
102
+ puts "then look at index.html in that path to confirm that everything is ok. "
103
+ puts
104
+ puts "To force a re-run of the same build version of a project, delete its test "
105
+ puts "results directory from the /path/to/directory/your/reports/go"
104
106
  puts
105
107
  puts "If things look good, then set up a cron job or other script to run"
106
108
  puts "crazy_ivan on a periodic basis."
@@ -119,6 +121,7 @@ module CrazyIvan
119
121
  report = ReportAssembler.new(Dir.pwd, output_directory)
120
122
  report.generate
121
123
 
124
+ # TODO this should really indicate how many projects were tested
122
125
  msg = "Generated test reports for #{report.runners.size} projects"
123
126
  Syslog.info(msg)
124
127
  puts msg
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: 1.1.1
4
+ version: 1.2.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: 2010-01-12 00:00:00 -05:00
12
+ date: 2010-02-01 00:00:00 -05:00
13
13
  default_executable: crazy_ivan
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -52,19 +52,10 @@ extra_rdoc_files: []
52
52
  files:
53
53
  - bin/crazy_ivan
54
54
  - bin/test_report2campfire
55
- - lib/crazy_ivan/html_asset_crush.rb
55
+ - lib/crazy_ivan/process_manager.rb
56
56
  - lib/crazy_ivan/report_assembler.rb
57
- - lib/crazy_ivan/templates/css/ci.css
58
57
  - lib/crazy_ivan/templates/index.html
59
- - lib/crazy_ivan/templates/javascript/builder.js
60
- - lib/crazy_ivan/templates/javascript/controls.js
61
58
  - lib/crazy_ivan/templates/javascript/date.js
62
- - lib/crazy_ivan/templates/javascript/dragdrop.js
63
- - lib/crazy_ivan/templates/javascript/effects.js
64
- - lib/crazy_ivan/templates/javascript/json-template.js
65
- - lib/crazy_ivan/templates/javascript/prototype.js
66
- - lib/crazy_ivan/templates/javascript/scriptaculous.js
67
- - lib/crazy_ivan/templates/javascript/slider.js
68
59
  - lib/crazy_ivan/test_runner.rb
69
60
  - lib/crazy_ivan/vendor/core_ext/tmpdir.rb
70
61
  - lib/crazy_ivan/vendor/json-1.1.7/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkComparison.log
@@ -1,56 +0,0 @@
1
- # Parses an html file's <head> section,
2
- # looks for <script ... > and <link rel="stylesheet" ... >
3
- # and crunches them all together into one file
4
- #
5
- # Warning: this script is super-ghetto and probably not robust.
6
- # If you're concerned, use a real lexer/parser.
7
-
8
- require 'strscan'
9
-
10
- module HtmlAssetCrush
11
- def self.source_for(asset_path)
12
- case asset_path
13
- when /js$/
14
- <<-JS
15
- <script type="text/javascript" charset="utf-8">
16
- #{File.open(asset_path).read}
17
- </script>
18
- JS
19
- when /css$/
20
- <<-CSS
21
- <style type="text/css">
22
- #{File.open(asset_path).read}
23
- </style>
24
- CSS
25
- end
26
- rescue Errno::ENOENT
27
- raise "Could not find #{asset_path} to bring in"
28
- end
29
-
30
- def self.crush(html_filepath)
31
- Dir.chdir(File.dirname(html_filepath)) do
32
- html = File.open(html_filepath).read
33
- crushed_html = ""
34
-
35
- s = StringScanner.new(html)
36
-
37
- js = /<script.+? src=['"](.+)['"].+?\/script>/
38
- css = /<link .+? href=['"](.+?)['"].+?>/
39
- asset = Regexp.union(js, css)
40
-
41
- while result = s.scan_until(asset) do
42
- asset_path = s[2] || s[1]
43
-
44
- # Weird that pre_match doesn't do this
45
- # crushed_html << s.pre_match
46
- crushed_html << result[0...(-s.matched_size)]
47
-
48
- crushed_html << source_for(asset_path) + "\n"
49
- end
50
-
51
- crushed_html << s.rest
52
-
53
- return crushed_html
54
- end
55
- end
56
- end
@@ -1,34 +0,0 @@
1
- body {
2
- margin: 2.5em 3em;
3
- padding: 0;
4
- background: #fff;
5
- color: #333;
6
- font: 100%/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif;
7
- }
8
-
9
- h1 { margin: 0;}
10
-
11
- pre { margin: 0;}
12
-
13
- .error {
14
- color: red;
15
- }
16
-
17
- .project h2 { margin: 12px 0 0 0;}
18
-
19
- .tests { margin: 0 0 0 18px;}
20
- .tests .test { font-size: 80%; margin-right: 8px;}
21
- .tests .latest { font-size: 100%;}
22
-
23
- .results .result .timestamp { margin-right: 12px;}
24
- .results .result .version { margin: 6px 0 6px 12px }
25
- .results .result .output { padding: 5px; color: silver; background:black; margin: 12px 18px 8px 18px;}
26
- .results .result .output .update { margin: 6px 0 6px 12px }
27
- .results .result .output .test { margin: 6px 0 6px 12px }
28
-
29
- .footer {
30
- margin: 24px 0 0 0;
31
- font-size: 60%;
32
- width: 100%;
33
- text-align: center;
34
- }
@@ -1,136 +0,0 @@
1
- // script.aculo.us builder.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009
2
-
3
- // Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4
- //
5
- // script.aculo.us is freely distributable under the terms of an MIT-style license.
6
- // For details, see the script.aculo.us web site: http://script.aculo.us/
7
-
8
- var Builder = {
9
- NODEMAP: {
10
- AREA: 'map',
11
- CAPTION: 'table',
12
- COL: 'table',
13
- COLGROUP: 'table',
14
- LEGEND: 'fieldset',
15
- OPTGROUP: 'select',
16
- OPTION: 'select',
17
- PARAM: 'object',
18
- TBODY: 'table',
19
- TD: 'table',
20
- TFOOT: 'table',
21
- TH: 'table',
22
- THEAD: 'table',
23
- TR: 'table'
24
- },
25
- // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
26
- // due to a Firefox bug
27
- node: function(elementName) {
28
- elementName = elementName.toUpperCase();
29
-
30
- // try innerHTML approach
31
- var parentTag = this.NODEMAP[elementName] || 'div';
32
- var parentElement = document.createElement(parentTag);
33
- try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
34
- parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
35
- } catch(e) {}
36
- var element = parentElement.firstChild || null;
37
-
38
- // see if browser added wrapping tags
39
- if(element && (element.tagName.toUpperCase() != elementName))
40
- element = element.getElementsByTagName(elementName)[0];
41
-
42
- // fallback to createElement approach
43
- if(!element) element = document.createElement(elementName);
44
-
45
- // abort if nothing could be created
46
- if(!element) return;
47
-
48
- // attributes (or text)
49
- if(arguments[1])
50
- if(this._isStringOrNumber(arguments[1]) ||
51
- (arguments[1] instanceof Array) ||
52
- arguments[1].tagName) {
53
- this._children(element, arguments[1]);
54
- } else {
55
- var attrs = this._attributes(arguments[1]);
56
- if(attrs.length) {
57
- try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
58
- parentElement.innerHTML = "<" +elementName + " " +
59
- attrs + "></" + elementName + ">";
60
- } catch(e) {}
61
- element = parentElement.firstChild || null;
62
- // workaround firefox 1.0.X bug
63
- if(!element) {
64
- element = document.createElement(elementName);
65
- for(attr in arguments[1])
66
- element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
67
- }
68
- if(element.tagName.toUpperCase() != elementName)
69
- element = parentElement.getElementsByTagName(elementName)[0];
70
- }
71
- }
72
-
73
- // text, or array of children
74
- if(arguments[2])
75
- this._children(element, arguments[2]);
76
-
77
- return $(element);
78
- },
79
- _text: function(text) {
80
- return document.createTextNode(text);
81
- },
82
-
83
- ATTR_MAP: {
84
- 'className': 'class',
85
- 'htmlFor': 'for'
86
- },
87
-
88
- _attributes: function(attributes) {
89
- var attrs = [];
90
- for(attribute in attributes)
91
- attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) +
92
- '="' + attributes[attribute].toString().escapeHTML().gsub(/"/,'&quot;') + '"');
93
- return attrs.join(" ");
94
- },
95
- _children: function(element, children) {
96
- if(children.tagName) {
97
- element.appendChild(children);
98
- return;
99
- }
100
- if(typeof children=='object') { // array can hold nodes and text
101
- children.flatten().each( function(e) {
102
- if(typeof e=='object')
103
- element.appendChild(e);
104
- else
105
- if(Builder._isStringOrNumber(e))
106
- element.appendChild(Builder._text(e));
107
- });
108
- } else
109
- if(Builder._isStringOrNumber(children))
110
- element.appendChild(Builder._text(children));
111
- },
112
- _isStringOrNumber: function(param) {
113
- return(typeof param=='string' || typeof param=='number');
114
- },
115
- build: function(html) {
116
- var element = this.node('div');
117
- $(element).update(html.strip());
118
- return element.down();
119
- },
120
- dump: function(scope) {
121
- if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope
122
-
123
- var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " +
124
- "BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " +
125
- "FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+
126
- "KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+
127
- "PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+
128
- "TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/);
129
-
130
- tags.each( function(tag){
131
- scope[tag] = function() {
132
- return Builder.node.apply(Builder, [tag].concat($A(arguments)));
133
- };
134
- });
135
- }
136
- };