rbuilder 0.0.1

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.
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