Fix up Rubinius specific library specs.
[rbx.git] / tools / cluster_test / ci_cron.rb
blob60e14c79c6146254d11e006ec6aaeed2904456c2
1 #!/usr/bin/env ruby -w
3 # See README for instructions.
5 require 'yaml'
6 require 'fileutils'
7 require 'rubygems'
8 require 'tagz'
10 BASE_DIR  = File.expand_path(ARGV.shift || "/tmp/ci")
11 HTML_DIR  = File.expand_path(ARGV.shift || File.join(BASE_DIR, "html"))
13 CI_DIR    = File.join HTML_DIR, "ci"
14 DATA_DIR  = File.join BASE_DIR, "data"
15 MRI_TRUNK = 'http://svn.ruby-lang.org/repos/ruby/trunk'
16 RBX_REPO  = 'git://git.rubini.us/code'
17 SPEC_REPO = 'git://github.com/rubyspec/rubyspec.git'
18 TEMP_DIR  = File.join BASE_DIR, "tmp"
19 TRIG_DIR  = File.join HTML_DIR, "trigger"
21 SPEC_GIT_URL = "http://github.com/rubyspec/rubyspec/commit/"
22 RBX_GIT_URL = "http://git.rubini.us/?p=code;a=commit;h="
24 STALE   = 14
25 DISPLAY = 50
27 class HashHash < Hash
28   def initialize
29     super { |h,k| h[k] = HashHash.new }
30   end
31 end
33 ############################################################
34 # Main Runner:
36 def _run
37   initialize_dirs
38   archive_data STALE
40   all_data  = HashHash.new
41   flat_data = process_individual_results
42   platforms = flat_data.map { |h| h[:platform] }.uniq
43   hashes    = flat_data.map { |h| h[:hash]     }.uniq
45   flat_data.each do |data|
46     all_data[data[:incremental]][data[:hash]][data[:platform]] = data
47   end
49   hash_times = hashes.map { |hash|
50     # incrementals and fall back to fulls if the sky is falling
51     (all_data[true ][hash].map { |_,run| run[:submitted] }.max ||
52      all_data[false][hash].map { |_,run| run[:submitted] }.max)
53   }
55   hashes = Hash[*hashes.zip(hash_times).flatten]
57   write_index platforms, hashes, all_data
59   update_triggers
60 end
62 ############################################################
63 # Helper Methods
65 def abbreviate_platform(arch)
66   plat = Gem::Platform.new(arch)
67   cpu, os, _ = plat.to_a
68   o, c = os[0..0], cpu[0..0] rescue "?"
70   c = "i" if c == "x"
71   x = "6" if cpu =~ /64/
73   "#{o}#{c}#{x}"
74 end
76 def archive_data days
77   seconds_per_day = 86400
78   days           *= seconds_per_day
79   now             = Time.now
80   results         = Hash.new { |h,k| h[k] = [] }
81   fresh_path      = "html/index.yaml"
83   files = Dir["data/*"].select { |f| File.file? f }.sort_by { |f| File.mtime f }
85   files.map! { |f|
86     h             = YAML.load(File.read(f))
87     log           = h.delete :log
88     h[:id]        = f
89     h[:submitted] = File.mtime f
90     h[:time]      = log[/^Finished in (.*) seconds/, 1].to_f
91     h[:result]    = log[/^\d+ files.*/]
92     h
93   }
95   fresh, stale = files.partition { |h|
96     now - h[:submitted] < days
97   }
99   stale.each do |h|
100     results[h[:submitted].strftime("%Y-%m")] << h
101     File.unlink h[:id]
102   end
104   latest_mtime = fresh.last[:submitted] rescue Time.at(0)
105   safe_write fresh_path, latest_mtime do |f|
106     YAML.dump fresh, f
107   end
109   results.each do |date, data|
110     next if data.empty?
112     path = "html/index.#{date}.yaml"
114     if File.exist? path then
115       old_data = YAML.load_file path
116       data = old_data + data
117     end
119     safe_write path do |f|
120       YAML.dump data, f
121     end
122   end
125 def build_row runs, platforms
126   platforms.each do |platform|
127     run = runs[platform]
128     if run.has_key? :log then
129       status = run[:status]
130       case status
131       when :passed then
132         td_ "&nbsp;", :class => :p
133       when :died then
134         td_ :class => :d do
135           a_("doa", :href => "ci/#{run[:id]}.html")
136         end
137       when String then
138         td_ :class => :f do
139           a_(status, :href => "ci/#{run[:id]}.html")
140         end
141       end
142     else
143       td_ "&nbsp;", :class => :m
144     end
145   end
148 def git_diff repo, n
149   old = YAML.load_file(File.join(TRIG_DIR, "#{n}.yaml"))[:revision] rescue nil
150   new = `git ls-remote -h #{repo} refs/heads/master`.split.first
151   return old, new
154 def initialize_dirs
155   FileUtils.rm_rf CI_DIR
156   FileUtils.mkdir_p [CI_DIR, TEMP_DIR, TRIG_DIR]
157   Dir.chdir BASE_DIR
160 def process_individual_results
161   Dir[File.join(DATA_DIR, "*")].select { |f| File.file? f }.map { |f|
162     h = YAML.load(File.read(f))
163     h[:id] = File.basename(f)
164     h[:submitted] = File.mtime f
166     log = h[:log]
167     h[:status] = if log =~ /(\d+) failures?, (\d+) errors?$/ then
168                    failures, errors = $1.to_i, $2.to_i
169                    if failures + errors == 0 then
170                      :passed
171                    else
172                      "#{failures}/#{errors}"
173                    end
174                  else
175                    :died
176                  end
178     if h[:status] != :passed then
179       html = Tagz do
180         head_ do
181           title_ "Build Result"
182           style_ do
183             tagz << "pre { white-space: pre-wrap;  }"
184             tagz << ".x  { font-size: 0.7em; color: #999; }"
185             tagz << ".b  { color: #009; }"
186             tagz << "th  { text-align: right }"
187           end
188         end
190         body_ do
191           h1_ "Build Result"
193           table_ do
194             [:id, :submitted, :incremental, :hash, :platform].each do |key|
195               tr_ do
196                 th_ key
197                 td_ h[key]
198               end
199             end
200           end
202           log = h[:log]
203           log.gsub!(/^(\*\*.*not_needed.*)/, '<span class=x>\1</span>')
204           log.gsub!(/^(\*\*.*)/, '<span class=x>\1</span>')
205           log.gsub!(/^.*\.{10}.*/) { |l| l.scan(/.{78}/).join("\n") + "\n" }
206           log.gsub!(/<.span>\n/, "\n</span>")
207           pre_ log
208         end
209       end
211       path = File.join(HTML_DIR, "ci", "#{h[:id]}.html")
212       File.open path, 'w' do |out|
213         out.write html
214       end
215       t = File.mtime f
216       File.utime(t, t, path)
217     end
219     h
220   }
223 def safe_write path, mtime = nil
224   new_path = "#{path}.new"
225   File.open new_path, 'w' do |f|
226     yield f
227   end
228   File.utime mtime, mtime, new_path if mtime
229   File.rename new_path, path
232 def svn_diff repo, dir
233   Dir.chdir TEMP_DIR do
234     system "svn co -r 1 -q #{repo} #{dir}" unless File.directory? dir
236     Dir.chdir dir do
237       old = `svnversion .`.chomp
238       system "svn up -q"
239       new = `svnversion .`.chomp
240       return old.to_i, new.to_i
241     end
242   end
245 def update_mri_trigger
246   old, new = svn_diff MRI_TRUNK, "mri_trunk"
248   update_trigger "mri", :revision => new if old != new
251 def update_rbx_trigger
252   old, new = git_diff RBX_REPO, "rbx"
254   update_trigger "rbx", :revision => new if old != new || old.nil?
257 def update_rubyspec_trigger
258   old, new = git_diff SPEC_REPO, "spec"
260   update_trigger "spec", :revision => new if old != new || old.nil?
263 def update_trigger name, data = nil
264   Dir.chdir HTML_DIR do
265     File.open "trigger/#{name}.yaml", "w" do |f|
266       YAML.dump data, f
267     end
268   end
271 def update_triggers
272   update_rbx_trigger
273   update_rubyspec_trigger
274   update_mri_trigger
277 def write_index platforms, hashes, all_data
278   html = Tagz do
279     head_ do
280       title_ "Rubinius CI Dashboard"
281       style_ do
282         tagz << "body { font-family: Optima, Times }"
283         tagz << "a    { color: black; }"
284         tagz << "h1,h3{ color: #339; text-align: center }"
286         tagz << "table.data { border-spacing: 2 0 }"
287         tagz << ".data tr   { border-bottom: 1px solid black }"
288         tagz << ".data th   { text-align: right; font-family: monospace }"
289         tagz << ".data td   { text-align: center; }"
291         tagz << ".data #first th { text-align: center }"
293         tagz << "td.p { background-color: #cfc }"
294         tagz << "td.f { background-color: #fcc }"
295         tagz << "td.d { background-color: #fcc }"
296         tagz << "td.m { background-color: #ccc }"
297       end
298     end
300     body_ do
301       h1_ "Rubinius CI Dashboard"
302       h3_ Time.now.strftime("%m-%d %H:%M")
304       table_ :id => "data" do
305         tr_ :id => "first" do
306           th_ "&nbsp;"
307           th_ "&nbsp;"
308           th_ "Incrs", :colspan => platforms.size
309           th_ "&nbsp;"
310           th_ "Fulls", :colspan => platforms.size
311         end
313         tr_ :id => "first" do
314           th_ "Hash"
315           th_ "Time"
317           platforms.each do |platform|
318             th_ abbreviate_platform(platform)
319           end
321           th_ "&nbsp;"
323           platforms.each do |platform|
324             th_ abbreviate_platform(platform)
325           end
326         end
328         hashes.sort_by {|_,t| -t.to_i }.first(DISPLAY).each do |hash, time|
329           tr_ do
330             th_ do
331               a_(hash[0..7], :href => "#{SPEC_GIT_URL}#{hash}")
332             end
334             th_ "#{time.strftime("%m-%d %H:%M")}"
336             build_row all_data[true][hash], platforms
337             td_ "&nbsp;"
338             build_row all_data[false][hash], platforms
339           end
340         end
341       end
343       table_ do
344         tr_ { th_ "Legend", :colspan => 2 }
345         platforms.each do |plat|
346           tr_ { th_ abbreviate_platform(plat); td_ plat }
347         end
348       end
349     end
350   end
352   File.open File.join(HTML_DIR, "index.html"), "w" do |f|
353     f.puts html
354   end
357 # Done
358 ############################################################
360 _run if $0 == __FILE__