octopusci 0.2.3 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +132 -0
- data/bin/octopusci-reset-redis +26 -0
- data/bin/octopusci-skel +2 -7
- data/bin/octopusci-tentacles +2 -2
- data/config.ru +1 -1
- data/lib/octopusci.rb +3 -7
- data/lib/octopusci/config.rb +63 -49
- data/lib/octopusci/errors.rb +2 -0
- data/lib/octopusci/helpers.rb +16 -15
- data/lib/octopusci/io.rb +70 -0
- data/lib/octopusci/job.rb +145 -34
- data/lib/octopusci/job_store.rb +67 -0
- data/lib/octopusci/notifier.rb +7 -17
- data/lib/octopusci/notifier/job_complete.html.erb +76 -3
- data/lib/octopusci/queue.rb +14 -10
- data/lib/octopusci/server.rb +17 -20
- data/lib/octopusci/server/views/index.erb +3 -4
- data/lib/octopusci/server/views/job.erb +3 -3
- data/lib/octopusci/server/views/job_summary.erb +18 -18
- data/lib/octopusci/server/views/layout.erb +6 -5
- data/lib/octopusci/stage_locker.rb +11 -7
- data/lib/octopusci/version.rb +1 -1
- data/lib/octopusci/worker_launcher.rb +1 -1
- data/spec/lib/octopusci/config_spec.rb +195 -0
- data/spec/lib/octopusci/io_spec.rb +64 -0
- data/spec/lib/octopusci/job_spec.rb +122 -0
- data/spec/lib/octopusci/job_store_spec.rb +155 -0
- data/spec/lib/octopusci/notifier_spec.rb +0 -15
- data/spec/lib/octopusci/queue_spec.rb +122 -0
- data/spec/lib/octopusci/server_spec.rb +92 -1
- data/spec/lib/octopusci/stage_locker_spec.rb +94 -0
- data/spec/spec_helper.rb +8 -0
- metadata +39 -58
- data/README +0 -63
- data/bin/octopusci-db-migrate +0 -10
- data/db/migrate/0001_init.rb +0 -29
- data/db/migrate/0002_add_status_job.rb +0 -19
- data/lib/octopusci/notifier/job_complete.text.erb +0 -5
- data/lib/octopusci/schema.rb +0 -140
@@ -1,8 +1,7 @@
|
|
1
1
|
<script type="text/javascript" charset="utf-8">
|
2
2
|
$(document).ready(function() {
|
3
3
|
$('#jobs').delegate("a.show_hide_job_output", 'click', function() {
|
4
|
-
$('.
|
5
|
-
var job_cont = $(this).closest('.job_summary').next('.job_content')
|
4
|
+
var job_cont = $(this).closest('.job_summary').next('.job_content');
|
6
5
|
var job_out = job_cont.find('.job_output').first();
|
7
6
|
job_out.toggle();
|
8
7
|
return false;
|
@@ -20,12 +19,12 @@
|
|
20
19
|
</script>
|
21
20
|
<div id="jobs">
|
22
21
|
<% @jobs.each do |j| %>
|
23
|
-
<div id="job_<%= j
|
22
|
+
<div id="job_<%= j['id']%>" class="job status_<%= j['status'] %>" data-status-url="/jobs/<%= j['id'] %>/status" data-output-url="/jobs/<%= j['id'] %>/output" data-summary-url="/jobs/<%= j['id'] %>/ajax_summary">
|
24
23
|
<div class="job_summary">
|
25
24
|
<%= erb :job_summary, :locals => { :j => j } %>
|
26
25
|
</div>
|
27
26
|
<div class="job_content">
|
28
|
-
<div class="job_output"><pre><code><%= j.
|
27
|
+
<div class="job_output"><pre><code><%= Octopusci::IO.new(j).read_all_out %></code></pre></div>
|
29
28
|
</div>
|
30
29
|
</div>
|
31
30
|
<% end %>
|
@@ -11,16 +11,16 @@
|
|
11
11
|
}, 5000);
|
12
12
|
});
|
13
13
|
</script>
|
14
|
-
<div id="job_<%= @job
|
14
|
+
<div id="job_<%= @job['id']%>" class="job status_<%= @job['status'] %>" data-status-url="/jobs/<%= @job['id'] %>/status" data-output-url="/jobs/<%= @job['id'] %>/output" data-silent-output-url="/jobs/<%= @job['id'] %>/silent_output" data-summary-url="/jobs/<%= @job['id'] %>/ajax_summary">
|
15
15
|
<div class="job_summary">
|
16
16
|
<%= erb :job_summary, :locals => { :j => @job } %>
|
17
17
|
</div>
|
18
18
|
<div class="job_content">
|
19
19
|
Output:
|
20
|
-
<div class="output job_output" style="display: block;"><pre><code><%= @job.
|
20
|
+
<div class="output job_output" style="display: block;"><pre><code><%= Octopusci::IO.new(@job).read_all_out %></code></pre></div>
|
21
21
|
</div>
|
22
22
|
<div class="job_content">
|
23
23
|
Log:
|
24
|
-
<div class="silent_output job_output" style="display: block;"><pre><code><%= @job.
|
24
|
+
<div class="silent_output job_output" style="display: block;"><pre><code><%= Octopusci::IO.new(@job).read_all_log %></code></pre></div>
|
25
25
|
</div>
|
26
26
|
</div>
|
@@ -1,13 +1,13 @@
|
|
1
1
|
<div class="job_header">
|
2
2
|
<div class="job_title">
|
3
|
-
<span class="status <%= j
|
3
|
+
<span class="status <%= j['status'] %>"></span>
|
4
4
|
<div style="float: left;">
|
5
|
-
<a href="<%= j
|
5
|
+
<a href="<%= j['payload']['repository']['url'] %>"><%= j['repo_name'] %></a> / <a href="/<%= j['repo_name'] %>/<%= j['branch_name'] %>/jobs"><%= j['branch_name'] %></a>
|
6
6
|
</div>
|
7
7
|
<div style="clear: both;"></div>
|
8
8
|
</div>
|
9
9
|
<div class="created_on">
|
10
|
-
<%= j
|
10
|
+
<%= j['created_at'].strftime('%A, %B %d, %Y AT %I:%M%p') unless j['created_at'].nil? %>
|
11
11
|
</div>
|
12
12
|
<div style="clear: both;"></div>
|
13
13
|
</div>
|
@@ -15,11 +15,11 @@
|
|
15
15
|
<table>
|
16
16
|
<tr>
|
17
17
|
<td style="text-align: right;">Job:</td>
|
18
|
-
<td style="width: 200px;"><a href="/jobs/<%= j
|
18
|
+
<td style="width: 200px;"><a href="/jobs/<%= j['id'] %>"><%= j['id'] %></a></td>
|
19
19
|
<td style="text-align: right;">Before Commit:</td>
|
20
20
|
<td>
|
21
|
-
<% if !j
|
22
|
-
<%= j
|
21
|
+
<% if !j['payload']['created'] %>
|
22
|
+
<%= j['payload']['before'] %>
|
23
23
|
<% else %>
|
24
24
|
No before commit, remote branch was created with this push.
|
25
25
|
<% end %>
|
@@ -28,49 +28,49 @@
|
|
28
28
|
<tr>
|
29
29
|
<td style="text-align: right;">Finished:</td>
|
30
30
|
<td>
|
31
|
-
<% if j
|
32
|
-
<%= j
|
31
|
+
<% if j['ended_at'] %>
|
32
|
+
<%= j['ended_at'].ago_in_words %>
|
33
33
|
<% else %>
|
34
34
|
-
|
35
35
|
<% end %>
|
36
36
|
</td>
|
37
37
|
<td style="text-align: right;">After Commit:</td>
|
38
38
|
<td>
|
39
|
-
<%= j
|
39
|
+
<%= j['payload']['after'] %>
|
40
40
|
</td>
|
41
41
|
</tr>
|
42
42
|
<tr>
|
43
43
|
<td style="text-align: right;">Duration:</td>
|
44
44
|
<td>
|
45
|
-
<% if j
|
46
|
-
<%= ((j
|
45
|
+
<% if j['ended_at'] && j['started_at'] %>
|
46
|
+
<%= ((j['ended_at'] - j['started_at'])/60).to_i %> minutes
|
47
47
|
<% else %>
|
48
48
|
-
|
49
49
|
<% end %>
|
50
50
|
</td>
|
51
51
|
<td style="text-align: right;">Compare:</td>
|
52
|
-
<td><a href="<%= j
|
52
|
+
<td><a href="<%= j['compare'] %>"><%= j['compare'] %></a></td>
|
53
53
|
</tr>
|
54
54
|
<tr>
|
55
55
|
<td style="text-align: right;">Stage:</td>
|
56
|
-
<td><%= j
|
56
|
+
<td><%= j['stage'] %></td>
|
57
57
|
<td style="text-align: right;">Pusher:</td>
|
58
58
|
<td>
|
59
|
-
<% if j
|
60
|
-
<a href="mailto:<%= j
|
59
|
+
<% if j['payload']['pusher']['email'] %>
|
60
|
+
<a href="mailto:<%= j['payload']['pusher']['name'] %>"><%= j['payload']['pusher']['name'] %></a>
|
61
61
|
<% else %>
|
62
|
-
<%= j
|
62
|
+
<%= j['payload']['pusher']['name'] %>
|
63
63
|
<% end %>
|
64
64
|
</td>
|
65
65
|
</tr>
|
66
66
|
<tr>
|
67
67
|
<td style="text-align: right;">Output:</td>
|
68
68
|
<td>
|
69
|
-
<a class="show_hide_job_output" style="display: block; float: left;" href="#">toggle</a><div class="output_busy" style="float: left; margin-left: 5px; margin-top: 2px; <% if j
|
69
|
+
<a class="show_hide_job_output" style="display: block; float: left;" href="#">toggle</a><div class="output_busy" style="float: left; margin-left: 5px; margin-top: 2px; <% if j['status'] == 'running' %>display: block;<% else %>display: none;<% end %>"><img src="/images/ajax-loader-1.gif" style="height: 12px;"/></div><div style="clear: both;"></div>
|
70
70
|
</td>
|
71
71
|
<td style="text-align: right;">Log:</td>
|
72
72
|
<td>
|
73
|
-
<a target="_blank" href="/jobs/<%= j
|
73
|
+
<a target="_blank" href="/jobs/<%= j['id'] %>/silent_output">click here</a>
|
74
74
|
</td>
|
75
75
|
</tr>
|
76
76
|
</table>
|
@@ -103,25 +103,26 @@
|
|
103
103
|
height: 26px;
|
104
104
|
display: block;
|
105
105
|
margin: 0 0 0 0;
|
106
|
+
background-position: 0px -74px; /* pending... in the case that no status specific class is provided but this is, the default case */
|
106
107
|
}
|
107
108
|
|
108
|
-
.successful {
|
109
|
+
.status.successful {
|
109
110
|
background-position: 0px 0px;
|
110
111
|
}
|
111
112
|
|
112
|
-
.failed {
|
113
|
+
.status.failed {
|
113
114
|
background-position: 0px -26px;
|
114
115
|
}
|
115
116
|
|
116
|
-
.running {
|
117
|
+
.status.running {
|
117
118
|
background-position: 0px -52px;
|
118
119
|
}
|
119
120
|
|
120
|
-
.pending {
|
121
|
+
.status.pending {
|
121
122
|
background-position: 0px -74px;
|
122
123
|
}
|
123
124
|
|
124
|
-
.error {
|
125
|
+
.status.error {
|
125
126
|
background-position: 0px -26px;
|
126
127
|
}
|
127
128
|
</style>
|
@@ -8,28 +8,32 @@ module Octopusci
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def self.clear
|
11
|
-
|
11
|
+
self.redis.del('octopusci:stagelocker')
|
12
12
|
end
|
13
13
|
|
14
14
|
def self.pop
|
15
|
-
|
15
|
+
self.redis.lpop('octopusci:stagelocker')
|
16
16
|
end
|
17
17
|
|
18
18
|
def self.rem(v)
|
19
|
-
|
19
|
+
self.redis.lrem('octopusci:stagelocker', 1, v)
|
20
20
|
end
|
21
21
|
|
22
22
|
def self.push(v)
|
23
|
-
|
23
|
+
self.redis.rpush('octopusci:stagelocker', v)
|
24
24
|
end
|
25
25
|
|
26
26
|
def self.stages
|
27
|
-
Octopusci::
|
27
|
+
Octopusci::Config['stages']
|
28
28
|
end
|
29
29
|
|
30
30
|
def self.pool
|
31
|
-
len =
|
32
|
-
|
31
|
+
len = self.redis.size('octopusci:stagelocker')
|
32
|
+
self.redis.peek('octopusci:stagelocker', 0, len)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.redis
|
36
|
+
Resque.redis
|
33
37
|
end
|
34
38
|
end
|
35
39
|
end
|
data/lib/octopusci/version.rb
CHANGED
@@ -0,0 +1,195 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Octopusci::ConfigStore do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@config_store = Octopusci::ConfigStore.new
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#initialize" do
|
10
|
+
it "should create an instance of Octopusci::ConfigStore class with an empty options set" do
|
11
|
+
@config_store.options.should == {}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#options" do
|
16
|
+
it "should return a hash of the options" do
|
17
|
+
@config_store.options.class.should == Hash
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "#load" do
|
22
|
+
it "should load a yml file into the options set additively" do
|
23
|
+
tmp_file_path = "/tmp/octopusci_test_config.yml"
|
24
|
+
File.open(tmp_file_path, "w") do |f|
|
25
|
+
f << "jack:\n"
|
26
|
+
f << " jobs_path: \"/etc/octopusci/jobs\"\n"
|
27
|
+
f << " workspace_base_path: \"/Users/adeponte/.octopusci\"\n"
|
28
|
+
end
|
29
|
+
|
30
|
+
@config_store.load(tmp_file_path)
|
31
|
+
|
32
|
+
@config_store.options.should == { 'jack' => { 'jobs_path' => "/etc/octopusci/jobs", "workspace_base_path" => "/Users/adeponte/.octopusci" } }
|
33
|
+
|
34
|
+
tmp_file_path = "/tmp/octopusci_test_config.yml"
|
35
|
+
File.open(tmp_file_path, "w") do |f|
|
36
|
+
f << "joe:\n"
|
37
|
+
f << " hello: \"world\"\n"
|
38
|
+
end
|
39
|
+
|
40
|
+
@config_store.load(tmp_file_path)
|
41
|
+
|
42
|
+
@config_store.options.should == { 'jack' => { 'jobs_path' => "/etc/octopusci/jobs", "workspace_base_path" => "/Users/adeponte/.octopusci" }, 'joe' => { 'hello' => 'world' } }
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should load a provided block into the options set additively" do
|
46
|
+
@config_store.load() do |c|
|
47
|
+
c['jackie'] = 'fool'
|
48
|
+
end
|
49
|
+
|
50
|
+
@config_store.options.should == { 'jackie' => 'fool' }
|
51
|
+
|
52
|
+
@config_store.load() do |c|
|
53
|
+
c['brown'] = 'ace'
|
54
|
+
end
|
55
|
+
|
56
|
+
@config_store.options.should == { 'jackie' => 'fool', 'brown' => 'ace' }
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should load a yml file and a provided block into the options set additively" do
|
60
|
+
tmp_file_path = "/tmp/octopusci_test_config.yml"
|
61
|
+
File.open(tmp_file_path, "w") do |f|
|
62
|
+
f << "alice:\n"
|
63
|
+
f << " hi: \"there\"\n"
|
64
|
+
end
|
65
|
+
|
66
|
+
@config_store.load(tmp_file_path) do |c|
|
67
|
+
c['bob'] = 'hey alice'
|
68
|
+
end
|
69
|
+
|
70
|
+
@config_store.options.should == { 'alice' => { 'hi' => 'there' }, 'bob' => 'hey alice' }
|
71
|
+
|
72
|
+
tmp_file_path = "/tmp/octopusci_test_config.yml"
|
73
|
+
File.open(tmp_file_path, "w") do |f|
|
74
|
+
f << "cindy:\n"
|
75
|
+
f << " good: \"bye\"\n"
|
76
|
+
end
|
77
|
+
|
78
|
+
@config_store.load(tmp_file_path) do |c|
|
79
|
+
c['rachel'] = 'cya'
|
80
|
+
end
|
81
|
+
|
82
|
+
@config_store.options.should == { 'alice' => { 'hi' => 'there' }, 'bob' => 'hey alice', 'cindy' => { 'good' => 'bye' }, 'rachel' => 'cya' }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "#reset" do
|
87
|
+
it "should reset the options set back to an empty set" do
|
88
|
+
@config_store.load() do |c|
|
89
|
+
c['kitty'] = 'little'
|
90
|
+
end
|
91
|
+
|
92
|
+
@config_store.options.should == { 'kitty' => 'little' }
|
93
|
+
|
94
|
+
@config_store.reset()
|
95
|
+
|
96
|
+
@config_store.options.should == {}
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe "#reload" do
|
101
|
+
it "should reset the options set to an empty set and then perform a normal additive load" do
|
102
|
+
@config_store.load() do |c|
|
103
|
+
c['big'] = 'dog'
|
104
|
+
end
|
105
|
+
|
106
|
+
@config_store.options.should == { 'big' => 'dog' }
|
107
|
+
|
108
|
+
@config_store.reload() do |c|
|
109
|
+
c['albino'] = 'zebra'
|
110
|
+
end
|
111
|
+
|
112
|
+
@config_store.options.should == { 'albino' => 'zebra' }
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe "hash style getter" do
|
117
|
+
it "should return the value of the option with the given key" do
|
118
|
+
@config_store.load() do |c|
|
119
|
+
c['test_getter'] = 'winning'
|
120
|
+
end
|
121
|
+
|
122
|
+
@config_store['test_getter'].should == 'winning'
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should raise Octopusci::ConfigStore::MissingConfigField if try to get a key that doesn't exist" do
|
126
|
+
lambda { @config_store['blowup'] }.should raise_error(Octopusci::ConfigStore::MissingConfigField)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe "hash style setter" do
|
131
|
+
it "should set the value of the option with the given key to the given value" do
|
132
|
+
@config_store['test_setter'] = 'bi-winning'
|
133
|
+
@config_store.options.should == { 'test_setter' => 'bi-winning' }
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
describe "method style getter" do
|
138
|
+
it "should return the value of the option with the give key as the method name" do
|
139
|
+
@config_store.load() do |c|
|
140
|
+
c['pirate'] = 'hords'
|
141
|
+
end
|
142
|
+
|
143
|
+
@config_store.pirate.should == 'hords'
|
144
|
+
end
|
145
|
+
|
146
|
+
it "should raise Octopusci::ConfigStore::MissingConfigField if try to get a key that doesn't exist" do
|
147
|
+
lambda { @config_store.hippy }.should raise_error(Octopusci::ConfigStore::MissingConfigField)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
describe "method style setter" do
|
152
|
+
it "should set the value of the option with the given key as the method name to the given value" do
|
153
|
+
@config_store.ship = 'it'
|
154
|
+
|
155
|
+
@config_store.options.should == { 'ship' => 'it' }
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
describe "#has_key?" do
|
160
|
+
it "should return a boolean value indicating whether or not there is an option in the option set with the given key" do
|
161
|
+
@config_store['foo'] = 1
|
162
|
+
|
163
|
+
@config_store.has_key?('foo').should be_true
|
164
|
+
@config_store.has_key?('bar').should be_false
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
describe "#after_load" do
|
169
|
+
it "should set the after load callback when given a block" do
|
170
|
+
@config_store.after_load do
|
171
|
+
@config_store['after_load_cb_test'] = 'wootwoot'
|
172
|
+
end
|
173
|
+
|
174
|
+
@config_store.options.should == {}
|
175
|
+
|
176
|
+
@config_store.load() do |c|
|
177
|
+
@config_store['feet'] = 'wet'
|
178
|
+
end
|
179
|
+
|
180
|
+
@config_store.options.should == { 'feet' => 'wet', 'after_load_cb_test' => 'wootwoot' }
|
181
|
+
end
|
182
|
+
|
183
|
+
it "should execute the after load callback when NOT given a block and the callback has been previously set" do
|
184
|
+
@config_store.after_load do
|
185
|
+
@config_store['after_load_cb_test'] = 'wootwoot'
|
186
|
+
end
|
187
|
+
|
188
|
+
@config_store.load() do |c|
|
189
|
+
@config_store['feet'] = 'wet'
|
190
|
+
end
|
191
|
+
|
192
|
+
@config_store.options.should == { 'feet' => 'wet', 'after_load_cb_test' => 'wootwoot' }
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Octopusci::IO do
|
4
|
+
describe "#initialize" do
|
5
|
+
it "should construct an instance of Octopusci::IO" do
|
6
|
+
job = stub('job')
|
7
|
+
Octopusci::IO.new(job).should be_a(Octopusci::IO)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "#read_all_out" do
|
12
|
+
it "should open the output file if it exists" do
|
13
|
+
file = stub('file').as_null_object
|
14
|
+
File.stub(:exists?).and_return(true)
|
15
|
+
File.should_receive(:open).and_return(file)
|
16
|
+
Octopusci::IO.new({ 'id' => 23 }).read_all_out
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should return all the file content if the output file exists" do
|
20
|
+
file = stub('file', :read => 'file content', :close => 0)
|
21
|
+
File.stub(:exists?).and_return(true)
|
22
|
+
File.stub(:open).and_return(file)
|
23
|
+
Octopusci::IO.new({ 'id' => 23 }).read_all_out.should == 'file content'
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should return an empty string if the file does not exist" do
|
27
|
+
File.stub(:exists?).and_return(false)
|
28
|
+
Octopusci::IO.new({ 'id' => 23 }).read_all_out.should == ""
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "#read_all_log" do
|
33
|
+
it "should open the log file if it exists" do
|
34
|
+
file = stub('file').as_null_object
|
35
|
+
File.stub(:exists?).and_return(true)
|
36
|
+
File.should_receive(:open).and_return(file)
|
37
|
+
Octopusci::IO.new({ 'id' => 23 }).read_all_log
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should return all the file content if the log file exists" do
|
41
|
+
file = stub('file', :read => 'file content')
|
42
|
+
File.stub(:exists?).and_return(true)
|
43
|
+
File.stub(:open).and_return(file)
|
44
|
+
Octopusci::IO.new({ 'id' => 23 }).read_all_log.should == 'file content'
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should return an empty string if the file does not exist" do
|
48
|
+
File.stub(:exists?).and_return(false)
|
49
|
+
Octopusci::IO.new({ 'id' => 23 }).read_all_log.should == ""
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "#open_log_for_writing" do
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "#open_out_for_writing" do
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "#puts" do
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "#log" do
|
63
|
+
end
|
64
|
+
end
|