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