bootscript 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +17 -0
- data/.rspec +1 -0
- data/ERB_VARS.md +30 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +22 -0
- data/README.md +176 -0
- data/Rakefile +8 -0
- data/bootscript.gemspec +31 -0
- data/lib/bootscript.rb +80 -0
- data/lib/bootscript/chef.rb +70 -0
- data/lib/bootscript/script.rb +178 -0
- data/lib/bootscript/uu_writer.rb +17 -0
- data/lib/bootscript/version.rb +3 -0
- data/lib/templates/bootscript.ps1.erb +91 -0
- data/lib/templates/bootscript.sh.erb +154 -0
- data/lib/templates/chef/attributes.json.erb +7 -0
- data/lib/templates/chef/chef-install.ps1.erb +126 -0
- data/lib/templates/chef/chef-install.sh.erb +72 -0
- data/lib/templates/chef/chef_client.conf.erb +9 -0
- data/lib/templates/chef/json_attributes.rb.erb +4 -0
- data/lib/templates/chef/ramdisk_secrets.rb.erb +2 -0
- data/lib/templates/windows_footer.bat.erb +4 -0
- data/lib/templates/windows_header.bat.erb +18 -0
- data/spec/bootscript/chef_spec.rb +48 -0
- data/spec/bootscript/script_spec.rb +126 -0
- data/spec/bootscript/uu_writer_spec.rb +30 -0
- data/spec/bootscript_spec.rb +71 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/unpacker.rb +40 -0
- metadata +208 -0
@@ -0,0 +1,126 @@
|
|
1
|
+
echo "Starting Chef installation..."
|
2
|
+
|
3
|
+
$ChefVers = "<%= (defined? chef_version) ? chef_version : '' %>"
|
4
|
+
$ChefPath = "C:\Chef"
|
5
|
+
$ChefLog = "$ChefPath\log\bootscript_log.txt"
|
6
|
+
$CreateRAMDisk = $<%= create_ramdisk.to_s.upcase %>
|
7
|
+
$RAMDiskMount = "<%= ramdisk_mount %>"
|
8
|
+
|
9
|
+
function main()
|
10
|
+
{
|
11
|
+
try
|
12
|
+
{
|
13
|
+
Create-Chef-Directory-Structure
|
14
|
+
if ($createRAMDisk) { Move-Chef-Secrets-To-Ramdisk }
|
15
|
+
Install-Chef-Client
|
16
|
+
Execute-Chef-Client
|
17
|
+
}
|
18
|
+
catch{
|
19
|
+
write-error $error[0]
|
20
|
+
exit 1
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
function Create-Chef-Directory-Structure()
|
25
|
+
{
|
26
|
+
try
|
27
|
+
{
|
28
|
+
if (!(Test-Path $ChefPath)) {New-Item $ChefPath -type directory | out-null}
|
29
|
+
foreach($f in @("etc", "bin", "log", "tmp", "var"))
|
30
|
+
{
|
31
|
+
$subdir = "$($ChefPath)\$f"
|
32
|
+
if (!(Test-Path $subdir)) {New-Item $subdir -type directory | out-null}
|
33
|
+
}
|
34
|
+
}
|
35
|
+
catch {throw $error[0]}
|
36
|
+
}
|
37
|
+
|
38
|
+
function Move-Chef-Secrets-To-Ramdisk()
|
39
|
+
{
|
40
|
+
echo "Moving Chef secrets to $RAMDiskMount"
|
41
|
+
Move-Item c:\chef\validation.pem $RAMDiskMount
|
42
|
+
Move-Item c:\chef\encrypted_data_bag_secret $RAMDiskMount
|
43
|
+
}
|
44
|
+
|
45
|
+
function Install-Chef-Client()
|
46
|
+
{
|
47
|
+
echo "Downloading Chef installer..."
|
48
|
+
$sourceFile = "C:\chef-client.msi"
|
49
|
+
$wc = new-object System.Net.WebClient
|
50
|
+
try
|
51
|
+
{
|
52
|
+
$installerURL = Chef-URL
|
53
|
+
echo "Downloading $installerURL -> $sourceFile"
|
54
|
+
$wc.DownloadFile($installerURL, $sourceFile)
|
55
|
+
if ((test-path $sourceFile) -ne $true){ throw "File not found: $sourceFile" }
|
56
|
+
echo "Installing Chef installer ($sourceFile) with msiexec..."
|
57
|
+
Execute-Command("msiexec /qn /i $sourceFile")
|
58
|
+
}
|
59
|
+
catch{
|
60
|
+
throw $error[0]
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
function Chef-Url()
|
65
|
+
{
|
66
|
+
$major_rev = [System.Environment]::OSVersion.Version.Major
|
67
|
+
$minor_rev = [System.Environment]::OSVersion.Version.Minor
|
68
|
+
$winrev = "$major_rev.$minor_rev"
|
69
|
+
$arch = "x86_64"
|
70
|
+
if ($winrev -eq "5.1") { $machineos = "2003" }
|
71
|
+
elseif ($winrev -eq "6.0") { $machineos = "2008" }
|
72
|
+
elseif ($winrev -eq "6.1") { $machineos = "2008r2" }
|
73
|
+
elseif ($winrev -eq "6.2") { $machineos = "2012" }
|
74
|
+
else { throw "ERROR: Windows Server 2003, 2008 or 2012 required" }
|
75
|
+
$url = "https://www.opscode.com/chef/download?p=windows&pv=$machineos&m=$arch"
|
76
|
+
if ($ChefVers -ne "") { $url = "$url&v=$ChefVers" }
|
77
|
+
return $url
|
78
|
+
}
|
79
|
+
|
80
|
+
function Execute-Chef-Client()
|
81
|
+
{
|
82
|
+
try
|
83
|
+
{
|
84
|
+
$Env:Path = "$Env:Path;C:\opscode\chef\bin;C:\opscode\chef\embedded\bin"
|
85
|
+
|
86
|
+
echo "Performing initial convergence..."
|
87
|
+
$tinyRunlist = "recipe[chef-client::config],recipe[chef-client::service]"
|
88
|
+
$logOptions = "-l info -L $ChefLog"
|
89
|
+
Execute-Command("chef-client --once --no-color $logOptions -o $tinyRunlist")
|
90
|
+
|
91
|
+
echo "Performing full convergence..."
|
92
|
+
Execute-Command("chef-client --once --no-color $logOptions")
|
93
|
+
|
94
|
+
echo "Initial Chef runs completed - see $ChefLog"
|
95
|
+
}
|
96
|
+
catch
|
97
|
+
{
|
98
|
+
echo "Chef client execution failed"
|
99
|
+
echo "Error was: $error[0]"
|
100
|
+
throw $error[0]
|
101
|
+
}
|
102
|
+
}
|
103
|
+
|
104
|
+
function Execute-Command($cmd)
|
105
|
+
{
|
106
|
+
if ($cmd -ne "")
|
107
|
+
{
|
108
|
+
Try {
|
109
|
+
echo "Running: $cmd"
|
110
|
+
$Env:_THIS_CMD = $cmd
|
111
|
+
$proc = Start-Process -FilePath c:\windows\system32\cmd.exe `
|
112
|
+
-ArgumentList "/C", "%_THIS_CMD%" `
|
113
|
+
-Verbose -Debug -Wait -Passthru
|
114
|
+
do { start-sleep -Milliseconds 500 }
|
115
|
+
until ($proc.HasExited)
|
116
|
+
echo "Finished: $cmd"
|
117
|
+
}
|
118
|
+
Catch {
|
119
|
+
echo "Failed: $cmd"
|
120
|
+
echo "Error was: $error[0]"
|
121
|
+
throw $error[0]
|
122
|
+
}
|
123
|
+
}
|
124
|
+
}
|
125
|
+
|
126
|
+
main
|
@@ -0,0 +1,72 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
# Installs chef-client via the ombnibus installer (with minimal dependencies),
|
3
|
+
# and tries to converge twice: first, with just this runlist -
|
4
|
+
# recipe[chef-client::config],recipe[chef-client::service]
|
5
|
+
# ; and then again with the full runlist.
|
6
|
+
set -e
|
7
|
+
test $UID == 0 || (echo "Error: must run as root"; exit 1)
|
8
|
+
echo "Installing Chef with ${0}..."
|
9
|
+
|
10
|
+
######### STEP 0: CONFIG, rendered by bootscript gem
|
11
|
+
NODE_NAME="<%= chef_attributes['chef_client']['config']['node_name'] %>"
|
12
|
+
CHEF_URL="<%= chef_attributes['chef_client']['config']['chef_server_url'] %>"
|
13
|
+
VALIDATION_PEM='<%= ramdisk_mount %>/chef/validation.pem'
|
14
|
+
DATABAG_SECRET='<%= ramdisk_mount %>/chef/encrypted_data_bag_secret'
|
15
|
+
CHEF_VERSION="<%= (defined? chef_version) ? chef_version : '' %>"
|
16
|
+
CHEF_INITIAL_RUNLIST="<%=
|
17
|
+
(defined? chef_initial_runlist) ? chef_initial_runlist : '' %>"
|
18
|
+
OMNIBUS_INSTALLER_URL="http://opscode.com/chef/install.sh"
|
19
|
+
CHEF_BIN="/usr/bin/chef-client" # Unix Chef omnibus always symlinks here
|
20
|
+
PID_FILE="/var/run/chef/client.pid"
|
21
|
+
|
22
|
+
######### STEP 1: MOVE CHEF SECRETS INTO PLACE
|
23
|
+
chmod 0600 "$VALIDATION_PEM"
|
24
|
+
if ! [ "$VALIDATION_PEM" == "/etc/chef/validation.pem" ] ; then
|
25
|
+
ln -sf "$VALIDATION_PEM" "/etc/chef/validation.pem"
|
26
|
+
fi
|
27
|
+
if [ -e "$DATABAG_SECRET" ] ; then
|
28
|
+
chmod 0600 "$DATABAG_SECRET"
|
29
|
+
if ! [ "$DATABAG_SECRET" == "/etc/chef/encrypted_data_bag_secret" ] ; then
|
30
|
+
ln -sf "$DATABAG_SECRET" "/etc/chef/encrypted_data_bag_secret"
|
31
|
+
fi
|
32
|
+
fi
|
33
|
+
|
34
|
+
######### STEP 2: CONFIGURE OPERATING SYSTEM AND INSTALL DEPENDENCIES
|
35
|
+
echo "Performing package update..."
|
36
|
+
export DEBIAN_FRONTEND=noninteractive
|
37
|
+
apt-get update -y
|
38
|
+
echo "Installing build-essential and curl..."
|
39
|
+
apt-get --force-yes -y install build-essential curl
|
40
|
+
|
41
|
+
######### STEP 3: INSTALL CHEF VIA OMNIBUS INSTALLER
|
42
|
+
echo "Downloading Chef..."
|
43
|
+
installer=$(basename $OMNIBUS_INSTALLER_URL)
|
44
|
+
if ! (which $CHEF_BIN >/dev/null 2>&1) ; then
|
45
|
+
if (which curl >/dev/null 2>&1); then
|
46
|
+
curl -L -o $installer $OMNIBUS_INSTALLER_URL
|
47
|
+
else
|
48
|
+
echo "Cannot find curl - cannot download Chef!"
|
49
|
+
fi
|
50
|
+
fi
|
51
|
+
cmd="bash $installer"
|
52
|
+
if ! [ "$CHEF_VERSION" == "" ] ; then
|
53
|
+
cmd="$cmd -v ${CHEF_VERSION}"
|
54
|
+
fi
|
55
|
+
echo "Installing Chef..."
|
56
|
+
$cmd
|
57
|
+
rm -f $installer
|
58
|
+
|
59
|
+
######### STEP 4: INITIAL, ONE-PASS CHEF CONVERGENCE
|
60
|
+
echo "Performing initial convergence..."
|
61
|
+
cmd="$CHEF_BIN --once --no-color"
|
62
|
+
if [[ "$CHEF_INITIAL_RUNLIST" != "" ]] ; then
|
63
|
+
cmd="$cmd -o $CHEF_INITIAL_RUNLIST"
|
64
|
+
fi
|
65
|
+
echo $cmd
|
66
|
+
$cmd
|
67
|
+
|
68
|
+
######### STEP 5: SIGNAL SERVICE TO CONVERGE
|
69
|
+
echo "Signalling chef service to converge..."
|
70
|
+
killall -USR1 chef-client || echo " (...no chef client service detected)"
|
71
|
+
|
72
|
+
echo "Done."
|
@@ -0,0 +1,9 @@
|
|
1
|
+
<% %w{node_name chef_server_url validation_client_name environment}.each do |param| %>
|
2
|
+
<% if chef_attributes['chef_client']['config'][param] %>
|
3
|
+
<%= param %> '<%= chef_attributes['chef_client']['config'][param] %>'
|
4
|
+
<% end %>
|
5
|
+
<% end %>
|
6
|
+
chef_dir = "<%= platform =~ /windows/i ? '/chef' : '/etc/chef' %>"
|
7
|
+
Dir.glob(File.join(chef_dir, "client.d", "*.rb")).each do |conf|
|
8
|
+
Chef::Config.from_file(conf)
|
9
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
<% if add_script_tags %><script>
|
2
|
+
<% end -%>@@echo off
|
3
|
+
:CheckPowerShellExecutionPolicy
|
4
|
+
@@FOR /F "tokens=*" %%i IN ('powershell -noprofile -command Get-ExecutionPolicy') DO Set PSExecMode=%%i
|
5
|
+
@@if /I "%PSExecMode%"=="unrestricted" goto :RunPowerShellScript
|
6
|
+
|
7
|
+
@@NET FILE 1>NUL 2>NUL
|
8
|
+
@@if not "%ERRORLEVEL%"=="0" (
|
9
|
+
@@echo Elevation required to change PowerShell execution policy from [%PSExecMode%] to Unrestricted
|
10
|
+
@@powershell -NoProfile -Command "start-process -Wait -Verb 'RunAs' -FilePath 'powershell.exe' -ArgumentList '-NoProfile Set-ExecutionPolicy Unrestricted'" ) else (
|
11
|
+
@@powershell -NoProfile Set-ExecutionPolicy Unrestricted )
|
12
|
+
|
13
|
+
:RunPowerShellScript
|
14
|
+
@@set POWERSHELL_BAT_ARGS=%*
|
15
|
+
@@if defined POWERSHELL_BAT_ARGS set POWERSHELL_BAT_ARGS=%POWERSHELL_BAT_ARGS:"=\"%
|
16
|
+
@@PowerShell -Command Invoke-Expression $('$args=@(^&{$args} %POWERSHELL_BAT_ARGS%);'+[String]::Join([Environment]::NewLine,$((Get-Content '%~f0') -notmatch '^^@@^|^^:'))) > c:\bootscript.log 2>&1 & goto :EOF
|
17
|
+
|
18
|
+
{
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'bootscript'
|
2
|
+
include Bootscript
|
3
|
+
|
4
|
+
describe Chef do
|
5
|
+
|
6
|
+
describe :files do
|
7
|
+
context "given a set of ERB template vars" do
|
8
|
+
erb_vars = {
|
9
|
+
ramdisk_mount: '/mount/myramdisk',
|
10
|
+
chef_validation_pem: 'MYPEM',
|
11
|
+
chef_databag_secret: 'SECRET',
|
12
|
+
}
|
13
|
+
it "returns a Hash mapping locations on the boot target to local data" do
|
14
|
+
Chef.files(erb_vars).should be_a Hash
|
15
|
+
end
|
16
|
+
it "maps the Chef Validation data into place on the target's RAMdisk" do
|
17
|
+
Chef.files(erb_vars)[
|
18
|
+
"#{erb_vars[:ramdisk_mount]}/chef/validation.pem"
|
19
|
+
].should be erb_vars[:chef_validation_pem]
|
20
|
+
end
|
21
|
+
it "maps the Chef data bag secret into place on the target's RAMdisk" do
|
22
|
+
Chef.files(erb_vars)[
|
23
|
+
"#{erb_vars[:ramdisk_mount]}/chef/encrypted_data_bag_secret"
|
24
|
+
].should be erb_vars[:chef_databag_secret]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe :included? do
|
30
|
+
desired_key = :chef_validation_pem
|
31
|
+
context "given a set of ERB template vars with key :#{desired_key}" do
|
32
|
+
it "returns true" do
|
33
|
+
Chef.included?(chef_validation_pem: 'SOME DATA').should be true
|
34
|
+
end
|
35
|
+
end
|
36
|
+
context "given a set of ERB template vars without key :#{desired_key}" do
|
37
|
+
it "returns false" do
|
38
|
+
Chef.included?({}).should be false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
context "given nothing" do
|
42
|
+
it "returns false" do
|
43
|
+
Chef.included?().should be false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'bootscript'
|
2
|
+
require 'logger' # for testing logging functionality
|
3
|
+
require 'tmpdir' # for unpacking Script archives
|
4
|
+
include Bootscript # for brevity
|
5
|
+
|
6
|
+
describe Script do
|
7
|
+
|
8
|
+
#### TEST SETUP
|
9
|
+
before :each do
|
10
|
+
@script = Script.new()
|
11
|
+
end
|
12
|
+
|
13
|
+
#### TEST PUBLIC INSTANCE MEMBER VARIABLES
|
14
|
+
|
15
|
+
it "has a public @data_map Hash, for mapping local data to the boot target" do
|
16
|
+
Script.new().should respond_to(:data_map)
|
17
|
+
Script.new().data_map.should be_a Hash
|
18
|
+
end
|
19
|
+
|
20
|
+
it "exposes a Ruby Logger as its public @log member, to adjust log level" do
|
21
|
+
Script.new().should respond_to(:log)
|
22
|
+
Script.new().log.should be_a Logger
|
23
|
+
end
|
24
|
+
|
25
|
+
#### TEST PUBLIC METHODS
|
26
|
+
|
27
|
+
describe :initialize do
|
28
|
+
context "when invoked with a logger" do
|
29
|
+
it "sets the BootScript's @log to the passed Logger object" do
|
30
|
+
my_logger = Logger.new(STDOUT)
|
31
|
+
Script.new(my_logger).log.should be my_logger
|
32
|
+
end
|
33
|
+
end
|
34
|
+
context "when invoked with no logger" do
|
35
|
+
it "assigns a default Logger the BootScript's @log" do
|
36
|
+
Script.new().log.should be_a Logger
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe :generate do
|
42
|
+
# test arguments / arity
|
43
|
+
it "accepts an (optional) Hash of template vars" do
|
44
|
+
expect{@script.generate}.to_not raise_error
|
45
|
+
expect{@script.generate({some_key: :some_value})}.to_not raise_error
|
46
|
+
end
|
47
|
+
it "accepts an (optional) destination for the generated text" do
|
48
|
+
File.open('/dev/null', 'w') do |outfile| # write to nowhere!
|
49
|
+
expect{@script.generate({}, outfile)}.to_not raise_error
|
50
|
+
end
|
51
|
+
end
|
52
|
+
# test output format
|
53
|
+
it "produces a Bash script" do
|
54
|
+
@script.generate.lines.first.chomp.should eq '#!/usr/bin/env bash'
|
55
|
+
end
|
56
|
+
# test stripping of empty lines and comments
|
57
|
+
context "when invoked with :strip_comments = true (the default)" do
|
58
|
+
it "strips all empty lines and comments from the output" do
|
59
|
+
lines = @script.generate.lines.to_a
|
60
|
+
lines[1..lines.count].each do |line|
|
61
|
+
line.should_not match /^#/
|
62
|
+
line.should_not match /^\s+$/
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
context "when invoked with :strip_comments = false" do
|
67
|
+
it "leaves empty lines and comments in the output" do
|
68
|
+
lines = @script.generate(strip_comments: false).lines.to_a
|
69
|
+
lines.select{|l| l =~ /^\s+$/}.count.should be > 0 # check empty lines
|
70
|
+
lines.select{|l| l =~ /^#/}.count.should be > 1 # check comments
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# test rendering of built-in variables into built-in templates
|
75
|
+
vars = {create_ramdisk: false, ramdisk_size: 5,
|
76
|
+
ramdisk_mount: '/secrets', update_os: false}
|
77
|
+
vars.keys.each do |var|
|
78
|
+
it "renders template variable :#{var} as Bash variable #{var.upcase}" do
|
79
|
+
rendered_config = Unpacker.new(Script.new.generate(vars)).config
|
80
|
+
vars[var].to_s.should eq rendered_config[var.upcase.to_s]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
# test rendering of custom templates
|
84
|
+
it "renders custom templates into place with correct ERB values" do
|
85
|
+
@script.data_map = {'/hello.sh' => 'echo Hello, <%= my_name %>.'}
|
86
|
+
text = @script.generate(my_name: 'H. L. Mencken')
|
87
|
+
Dir.mktmpdir do |tmp_dir| # do unarchiving in a temp dir
|
88
|
+
Unpacker.new(text).unpack_to tmp_dir
|
89
|
+
File.exists?("#{tmp_dir}/hello.sh").should == true
|
90
|
+
File.read("#{tmp_dir}/hello.sh").should eq 'echo Hello, H. L. Mencken.'
|
91
|
+
end
|
92
|
+
end
|
93
|
+
# test raw file copying
|
94
|
+
it "copies non-template files directly into the generated archive" do
|
95
|
+
# insert this test file itself into the BootScript's archive! :-/
|
96
|
+
@script.data_map = {File.basename(__FILE__) => File.new(__FILE__)}
|
97
|
+
Dir.mktmpdir do |tmp_dir| # do unarchiving in a temp dir
|
98
|
+
target_file = "#{tmp_dir}/#{File.basename(__FILE__)}"
|
99
|
+
Unpacker.new(@script.generate).unpack_to tmp_dir
|
100
|
+
File.exists?(target_file).should == true
|
101
|
+
File.read(target_file).should eq File.read(__FILE__)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
# test return values
|
105
|
+
context "when invoked without any output destination" do
|
106
|
+
it "returns the rendered text of the BootScript" do
|
107
|
+
rendered_text = @script.generate
|
108
|
+
rendered_text.should be_a String
|
109
|
+
rendered_text.lines.first.chomp.should eq '#!/usr/bin/env bash'
|
110
|
+
rendered_text.lines.should include("__ARCHIVE_FOLLOWS__\n")
|
111
|
+
end
|
112
|
+
end
|
113
|
+
context "when invoked with a custom output destination" do
|
114
|
+
it "returns the number of bytes written to the destination" do
|
115
|
+
bytes_written, script_size = 0, @script.generate.bytes.count
|
116
|
+
File.open('/dev/null', 'w') do |outfile|
|
117
|
+
bytes_written = @script.generate({}, outfile)
|
118
|
+
end
|
119
|
+
bytes_written.should be_a Fixnum
|
120
|
+
bytes_written.should == script_size
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'bootscript'
|
2
|
+
include Bootscript # for brevity
|
3
|
+
|
4
|
+
describe UUWriter do
|
5
|
+
|
6
|
+
#### TEST PUBLIC INSTANCE MEMBER VARIABLES
|
7
|
+
|
8
|
+
it "exposes a the number of bytes written as an integer" do
|
9
|
+
UUWriter.new(nil).should respond_to(:bytes_written)
|
10
|
+
UUWriter.new(nil).bytes_written.should be_a Fixnum
|
11
|
+
end
|
12
|
+
|
13
|
+
#### TEST PUBLIC METHODS
|
14
|
+
|
15
|
+
describe :initialize do
|
16
|
+
it "sets bytes_written to zero" do
|
17
|
+
UUWriter.new(nil).bytes_written.should == 0
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe :write do
|
22
|
+
it "writes the uuencoded version of its argument to its output member" do
|
23
|
+
destination = StringIO.open("", 'w')
|
24
|
+
UUWriter.new(destination).write("Encode me!")
|
25
|
+
destination.close
|
26
|
+
destination.string.should == ["Encode me!"].pack('m')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|