rbuilder 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/Rakefile +47 -0
  2. data/bin/rbuilder +65 -0
  3. data/conf/example.conf +2 -0
  4. data/rbuilder/build.rb +50 -0
  5. data/rbuilder/build_command.rb +33 -0
  6. data/rbuilder/build_configuration.rb +21 -0
  7. data/rbuilder/build_description.rb +46 -0
  8. data/rbuilder/build_report.rb +40 -0
  9. data/rbuilder/build_status.rb +39 -0
  10. data/rbuilder/builder.rb +45 -0
  11. data/rbuilder/gui.rb +6 -0
  12. data/rbuilder/gui/build_details_pane.rb +105 -0
  13. data/rbuilder/gui/build_list_view_entry.rb +33 -0
  14. data/rbuilder/gui/builder_list_view.rb +92 -0
  15. data/rbuilder/gui/builder_monitor_window.rb +112 -0
  16. data/rbuilder/gui/images.rb +5 -0
  17. data/rbuilder/gui/images/broken_builder.gif +0 -0
  18. data/rbuilder/gui/images/building.gif +0 -0
  19. data/rbuilder/gui/images/failed.gif +0 -0
  20. data/rbuilder/gui/images/ok.gif +0 -0
  21. data/rbuilder/gui/images/ok_builder.gif +0 -0
  22. data/rbuilder/lib/observer.rb +7 -0
  23. data/rbuilder/libs.rb +4 -0
  24. data/rbuilder/revision_committed.rb +21 -0
  25. data/rbuilder/source_control.rb +3 -0
  26. data/rbuilder/source_control/status.rb +23 -0
  27. data/rbuilder/source_control/svn.rb +64 -0
  28. data/rbuilder/source_control/tracker.rb +36 -0
  29. data/tests/gui/test_build_details_pane.rb +118 -0
  30. data/tests/gui/test_builder_list_view.rb +202 -0
  31. data/tests/gui/test_builder_list_view_entry.rb +79 -0
  32. data/tests/gui/test_builder_monitor_window.rb +81 -0
  33. data/tests/source_control/test_source_control.rb +52 -0
  34. data/tests/source_control/test_svn.rb +60 -0
  35. data/tests/test_build.rb +111 -0
  36. data/tests/test_build_command.rb +57 -0
  37. data/tests/test_build_configuration.rb +31 -0
  38. data/tests/test_build_description.rb +106 -0
  39. data/tests/test_build_report.rb +56 -0
  40. data/tests/test_builder.rb +54 -0
  41. data/tests/test_revision_comitted.rb +30 -0
  42. metadata +104 -0
@@ -0,0 +1,33 @@
1
+ module Gui
2
+ class BuildListViewEntry
3
+ attr_reader :iterator, :build
4
+ def self.create(build, iterator)
5
+ entry = BuildListViewEntry.new(build)
6
+ entry.attach_to(iterator)
7
+ return entry
8
+ end
9
+ def initialize(build)
10
+ @build = build
11
+ build.add_observer(self)
12
+ end
13
+
14
+ def attach_to(iterator)
15
+ @iterator = iterator
16
+ update
17
+ end
18
+
19
+ def update
20
+ iterator[0] = self
21
+ iterator[1] = build.archive
22
+ iterator[2] = build.revision.to_s
23
+ iterator[3] = build.committer.to_s
24
+ iterator[4] = build.status.to_s
25
+ iterator[5] = build.summary.to_s
26
+ end
27
+ Colors = {BuildStatus.ok => 'green', BuildStatus.failed => 'red', BuildStatus.building => 'yellow'}
28
+ def update_renderer_style(renderer)
29
+ color = Colors[build.status]
30
+ renderer.background=color if color
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,92 @@
1
+ module Gui
2
+ class BuilderListView
3
+ attr_reader :gtk_container,:tree_store, :tree_view
4
+ attr_accessor :monitor_window
5
+
6
+ def initialize(builder)
7
+ initialize_ui_elements
8
+ builder.each_description {|build_description| create_build_description_list_view(build_description)}
9
+ end
10
+
11
+ def initialize_ui_elements
12
+ @tree_store = Gtk::TreeStore.new(BuildListViewEntry,String,String,String,String,String)
13
+ @tree_view = Gtk::TreeView.new(tree_store)
14
+ @gtk_container = Gtk::ScrolledWindow.new
15
+ gtk_container << tree_view
16
+
17
+ renderer = Gtk::CellRendererText.new
18
+ %w{Archive Revision Committer Status Summary}.each_with_index do |column, index|
19
+ col = Gtk::TreeViewColumn.new(column, renderer, :text => index+1)
20
+ col.resizable=true
21
+ col.set_cell_data_func(renderer) do |col, renderer, model, iter|
22
+ iter[0].update_renderer_style(renderer)
23
+ end
24
+
25
+ tree_view.append_column(col)
26
+ end
27
+ tree_view.selection.set_select_function do |selection, model, path, currently_selected|
28
+ self.select_build(self.build_for_iterator(model.get_iter(path)))
29
+ true
30
+ end
31
+ end
32
+
33
+ def create_build_description_list_view(build_description)
34
+ BuildDescriptionListView.new(build_description, self, tree_store)
35
+ end
36
+
37
+ def select_build(build)
38
+ @selected_build = build
39
+ monitor_window.build_selected(build)
40
+ end
41
+
42
+ def replace_build_selection(new_selected_build, build_selection_to_replace)
43
+ select_build(new_selected_build) if build_selection_to_replace == selected_build || selected_build == nil
44
+ end
45
+
46
+ def build_for_iterator(iterator)
47
+ iterator[0].build
48
+ end
49
+
50
+ private
51
+ attr_reader :selected_build
52
+ end
53
+
54
+ class BuildDescriptionListView
55
+ attr_reader :tree_store, :current_build_view_entry, :build_description, :builder_list_view
56
+ def initialize(builder, builder_list_view, tree_store = nil)
57
+ @listeners = []
58
+ @build_description = @builder = builder
59
+ @builder_list_view = builder_list_view
60
+ @builder.add_observer(self)
61
+ @tree_store = tree_store
62
+ end
63
+
64
+ def update(options = {})
65
+ replace_build_selection(options[:new_build])
66
+ new_build_list_view_entry(options[:new_build])
67
+ end
68
+
69
+ def replace_build_selection(new_build)
70
+ return unless builder_list_view
71
+ builder_list_view.replace_build_selection(new_build, current_build)
72
+ end
73
+
74
+ def current_build
75
+ return current_build_view_entry.build if current_build_view_entry
76
+ end
77
+
78
+ def new_build_list_view_entry(build)
79
+ if current_build_view_entry
80
+ current_build_view_entry
81
+ new_build_list_entry = BuildListViewEntry.create(build,current_build_view_entry.iterator)
82
+ current_build_view_entry.attach_to(tree_store.prepend(current_build_view_entry.iterator))
83
+ @current_build_view_entry = new_build_list_entry
84
+ else
85
+ @current_build_view_entry = BuildListViewEntry.create(build,tree_store.append(nil))
86
+ end
87
+ current_build_view_entry
88
+ end
89
+
90
+ end
91
+
92
+ end
@@ -0,0 +1,112 @@
1
+ require 'gui/images'
2
+ module Gui
3
+ class BuilderMonitorWindow
4
+
5
+ class StatusIcon
6
+ attr_reader :status
7
+ def initialize(menu)
8
+ @icon = Gtk::StatusIcon.new
9
+
10
+ @icon.signal_connect('popup_menu', menu) do |widget,button, time|
11
+ if button == 3
12
+ menu.popup(nil,nil,button, time)
13
+ end
14
+ end
15
+ self.ok
16
+ end
17
+
18
+ def ok
19
+ self.status ='ok_builder'
20
+ end
21
+
22
+ def broken
23
+ self.status ='broken_builder'
24
+ end
25
+
26
+ def status=(new_status)
27
+ @status = new_status
28
+ @icon.file=Images.file("#{status}.gif")
29
+
30
+ end
31
+
32
+ def on_activate(&block)
33
+ @icon.signal_connect('activate',&block)
34
+ end
35
+ end
36
+
37
+
38
+ attr_reader :window, :status_icon, :builder
39
+
40
+ def initialize(builder, builder_list_view, build_details_pane)
41
+ @builder = builder
42
+ builder.add_observer(self)
43
+ @build_details_pane = build_details_pane
44
+ builder_list_view.monitor_window = self
45
+ initialize_widgets(builder_list_view)
46
+ end
47
+
48
+ def initialize_widgets(builder_list_view)
49
+ initialize_status_icon initialize_menu
50
+ @window = Gtk::Window.new(Gtk::Window::TOPLEVEL)
51
+ panes = Gtk::VPaned.new
52
+ panes.pack1(builder_list_view.gtk_container,false, false)
53
+ panes.pack2(build_details_pane.gtk_container,false, false)
54
+ window.signal_connect("delete_event") { self.hide }
55
+ window.add(panes)
56
+ window.title = "rbuilder monitor"
57
+ window.width_request = 600
58
+ window.height_request = 500
59
+ window.icon_list = [Gdk::Pixbuf.new(Images.file('ok_builder.gif'))]
60
+ end
61
+
62
+ def initialize_menu
63
+ menu = Gtk::Menu.new
64
+ menu.append(show = Gtk::MenuItem.new("Show"))
65
+ menu.append(hide = Gtk::MenuItem.new("Hide"))
66
+ menu.append(exit = Gtk::MenuItem.new("Exit"))
67
+ menu.show_all
68
+ show.signal_connect('activate') {self.show}
69
+ hide.signal_connect('activate') {self.hide}
70
+ exit.signal_connect('activate') {Gtk.main_quit}
71
+ menu
72
+ end
73
+
74
+ def initialize_status_icon(menu)
75
+ @status_icon = StatusIcon.new(menu)
76
+ status_icon.on_activate do
77
+ toggle
78
+ end
79
+ end
80
+
81
+ def build_selected(build)
82
+ @selected_build = build
83
+ @build_details_pane.attach_to build
84
+ end
85
+
86
+ def show
87
+ window.show_all
88
+ end
89
+
90
+ def hide
91
+ window.hide_all
92
+ end
93
+
94
+ def toggle
95
+ if window.visible?
96
+ window.hide_all
97
+ else
98
+ window.show_all
99
+ end
100
+ end
101
+
102
+ def update
103
+ if builder.status == BuilderState.ok
104
+ status_icon.ok
105
+ else
106
+ status_icon.broken
107
+ end
108
+ end
109
+
110
+ attr_reader :build_details_pane
111
+ end
112
+ end
@@ -0,0 +1,5 @@
1
+ module Gui
2
+ module Images
3
+ def self.file(image_name); File.expand_path(File.join(File.dirname(__FILE__),'images',image_name)); end
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ require 'observer'
2
+ module Observable
3
+ def changed!(*args)
4
+ changed
5
+ notify_observers(*args)
6
+ end
7
+ end
@@ -0,0 +1,4 @@
1
+ $: << File.expand_path(File.dirname(__FILE__))
2
+ require 'builder'
3
+ require 'build_configuration'
4
+ require 'build'
@@ -0,0 +1,21 @@
1
+
2
+ class RevisionCommitted
3
+ attr_reader :committer, :revision
4
+ def initialize(revision, committer)
5
+ @revision = revision
6
+ @committer = committer
7
+ end
8
+
9
+ def self.from_source_control_status(status)
10
+ self.new(status.revision, status.committer)
11
+ end
12
+
13
+ def ==(other)
14
+ return false unless other.class == self.class
15
+ self.revision == other.revision && self.committer == other.committer
16
+ end
17
+
18
+ def to_s
19
+ "Revision #{revision} committed by #{committer}"
20
+ end
21
+ end
@@ -0,0 +1,3 @@
1
+ require 'source_control/status'
2
+ require 'source_control/tracker'
3
+ require 'source_control/svn'
@@ -0,0 +1,23 @@
1
+ module SourceControl
2
+ class Status
3
+ attr_reader :revision, :committer
4
+ def initialize(revision, committer)
5
+ @revision = revision
6
+ @committer = committer
7
+ end
8
+
9
+
10
+ def ==(other)
11
+ other.class == self.class &&
12
+ other.revision == self.revision &&
13
+ other.committer == self.committer
14
+ end
15
+
16
+ def to_s
17
+ "(#{revision},#{committer})"
18
+ end
19
+ def inspect
20
+ "Status#{to_s}"
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,64 @@
1
+ require 'fileutils'
2
+ module SourceControl
3
+ def self.svn(working_dir)
4
+ new(svn_backend(working_dir))
5
+ end
6
+
7
+ def self.svn_backend(working_dir)
8
+ Svn.new(working_dir)
9
+ end
10
+
11
+
12
+ class Svn
13
+ attr_reader :working_dir
14
+ def initialize(working_dir)
15
+ @working_dir = working_dir
16
+ validate_working_dir
17
+ end
18
+
19
+ def validate_working_dir
20
+ unless File.directory?(working_dir)
21
+ raise DirectoryInvalidException.new("directory does not exist please create a valid checkout first")
22
+ end
23
+ end
24
+
25
+ def archive
26
+ return @archive if @archive
27
+ @archive = parse_archive(run_info)
28
+ end
29
+
30
+ def parse_archive(repo_info)
31
+ repo_info =~ /^(Repository Root: )(.*)$/
32
+ line = $2
33
+ line = strip_server(line)
34
+ return strip_trunk(line)
35
+ end
36
+
37
+ def get_info
38
+ info_output = run_info
39
+ revision = info_output[/^Revision:(.*)$/].split(':')[1]
40
+ committer = info_output[/^Last Changed Author:(.*)$/].split(':')[1].strip
41
+ Status.new(revision.to_i, committer)
42
+ end
43
+
44
+ def strip_server(line)
45
+ return line unless line =~ /(svn.*:\/\/[^\/]*\/)(.*)/
46
+ return $2
47
+ end
48
+
49
+ def strip_trunk(line)
50
+ return line unless line =~ /(.*)(\/trunk)/
51
+ return $1
52
+ end
53
+
54
+ protected
55
+ def run_info
56
+ FileUtils.cd @working_dir do
57
+ `svn up`
58
+ return `svn info`
59
+ end
60
+ end
61
+
62
+
63
+ end
64
+ end
@@ -0,0 +1,36 @@
1
+ module SourceControl
2
+ class DirectoryInvalidException < Exception; end
3
+
4
+ class Tracker
5
+ attr_reader :backend
6
+
7
+ def initialize(backend)
8
+ @backend = backend
9
+ @current_revision = nil
10
+ end
11
+
12
+ def report_change_to(builder)
13
+ source_control_info = backend.get_info
14
+ if source_control_info.revision != current_revision
15
+ builder.new_revision_committed(source_control_info)
16
+ @current_revision = source_control_info.revision
17
+ end
18
+ end
19
+
20
+ def working_dir
21
+ backend.working_dir
22
+ end
23
+
24
+ def archive
25
+ backend.archive
26
+ end
27
+
28
+ private
29
+ attr_reader :current_revision
30
+ end
31
+
32
+ def self.new(source_control_backend)
33
+ Tracker.new(source_control_backend)
34
+ end
35
+
36
+ end
@@ -0,0 +1,118 @@
1
+ require 'test/unit'
2
+ require 'rubygems'
3
+ require 'mocha'
4
+ require File.join(File.dirname(__FILE__), '..', 'builder_test_env')
5
+ require 'rbuilder/gui'
6
+
7
+ module Gui
8
+ class TestBuildDetailsPane < Test::Unit::TestCase
9
+ attr_reader :pane, :build, :build_report, :status_widget, :log_widget
10
+
11
+ def setup
12
+ @build_report = stub('initial_build_report', :delete_observer => nil, :add_observer => nil, :log => '')
13
+ @build = stub_everything('initial_build')
14
+ build.stubs(:report).returns(build_report)
15
+ build.stubs(:archive).returns "the_archive"
16
+ build.stubs(:status).returns BuildStatus.ok
17
+ build.stubs(:reason).returns "some_reason"
18
+ @status_widget = stub('status_widget', :gtk_container => Gtk::Label.new, :status= => nil)
19
+ @log_widget = stub('log_widget', :attach_to => nil, :gtk_container => Gtk::Label.new)
20
+ @pane = BuildDetailsPane.new(build, status_widget, log_widget)
21
+ end
22
+
23
+ def test_contains_label_with_archive_name
24
+ assert_equal 'the_archive', pane.archive_label.text
25
+ end
26
+
27
+ def test_contains_label_with_reason
28
+ assert_equal 'some_reason', pane.reason_label.text
29
+ end
30
+
31
+ def test_observes_build_upon_creation
32
+ build.expects(:add_observer).with(instance_of(BuildDetailsPane))
33
+ pane = BuildDetailsPane.new(build)
34
+ end
35
+
36
+ def test_observes_build_report_and_build_upon_attach_to
37
+ build.expects(:delete_observer).with(pane)
38
+ other_build_report = mock('other_build_report')
39
+ other_build = stub('other_build', :report => other_build_report, :archive => 'other_archive', :reason => 'some_other_reason')
40
+
41
+ other_build.expects(:add_observer).with(pane)
42
+ log_widget.expects(:attach_to).with(other_build_report)
43
+ pane.expects :update
44
+ pane.attach_to(other_build)
45
+ assert_equal 'other_archive', pane.archive_label.text
46
+ assert_equal 'some_other_reason', pane.reason_label.text
47
+ end
48
+
49
+
50
+ def test_update_pushes_status_information_to_attached_widgets
51
+ status_widget.expects(:status=).with(BuildStatus.ok)
52
+ pane.update
53
+ end
54
+
55
+ end
56
+
57
+ class TestStatusWidget < Test::Unit::TestCase
58
+ attr_reader :widget
59
+ def setup
60
+ @widget = StatusWidget.new(BuildStatus.ok)
61
+ end
62
+
63
+ def test_sets_status_in_image
64
+ assert_equal Gtk::Image, widget.gtk_container.class
65
+ assert_equal "ok.gif", File.basename(widget.gtk_container.file)
66
+ end
67
+
68
+ def test_changing_status_changes_the_file_not_the_label
69
+ expected_gtk_container = widget.gtk_container
70
+
71
+ widget.status = BuildStatus.failed
72
+ assert_equal "failed.gif", File.basename(widget.gtk_container.file)
73
+ assert_equal expected_gtk_container, widget.gtk_container
74
+ end
75
+ end
76
+
77
+ class TestLogWidget < Test::Unit::TestCase
78
+ attr_reader :widget, :build_report
79
+ def setup
80
+ @build_report = stub_everything
81
+ @widget = LogWidget.new(build_report)
82
+ end
83
+
84
+ def test_obseves_build_erport_upon_creation
85
+ build_report.expects(:add_observer).with(instance_of(LogWidget))
86
+ widget = LogWidget.new(build_report)
87
+ end
88
+
89
+ def test_obseves_build_erport_upon_attach
90
+ new_build_report = mock
91
+ build_report.expects(:delete_observer).with(widget)
92
+ new_build_report.expects(:add_observer).with(widget)
93
+ widget.expects :update
94
+ widget.attach_to(new_build_report)
95
+ end
96
+
97
+ def test_update_log_line_is_added_to_text
98
+ new_log_line = "bla di bla"
99
+ another_log_line = "rob is gek"
100
+
101
+ widget.update(:log_line => new_log_line)
102
+ assert_match new_log_line, widget.text
103
+ assert_no_match(/#{another_log_line}/, widget.text)
104
+
105
+ widget.update(:log_line => another_log_line)
106
+ assert_match another_log_line, widget.text
107
+ end
108
+
109
+ def test_update_sets_complete_text
110
+ the_log = "some\nlog\nlines"
111
+ build_report.stubs :log => the_log
112
+ widget.update
113
+ assert_equal the_log, widget.text
114
+ end
115
+ end
116
+
117
+
118
+ end