octopusci 0.2.3 → 0.3.0
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.
- 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
|