diff --git a/lib/vagrant/util/file_checksum.rb b/lib/vagrant/util/file_checksum.rb new file mode 100644 index 000000000..b1c00e600 --- /dev/null +++ b/lib/vagrant/util/file_checksum.rb @@ -0,0 +1,38 @@ +# This is an "interface" that should be implemented by any digest class +# passed into FileChecksum. Note that this isn't strictly enforced at +# the moment, and this class isn't directly used. It is merely here for +# documentation of structure of the class. +class DigestClass + def update(string); end + def hexdigest; end +end + +class FileChecksum + BUFFER_SIZE = 1024 + + # Initializes an object to calculate the checksum of a file. The given + # ``digest_klass`` should implement the ``DigestClass`` interface. Note + # that the built-in Ruby digest classes duck type this properly: + # Digest::MD5, Digest::SHA1, etc. + def initialize(path, digest_klass) + @digest_klass = digest_klass + @path = path + end + + # This calculates the checksum of the file and returns it as a + # string. + # + # @return [String] + def checksum + digest= @digest_klass.new + + File.open(@path, "r") do |f| + while !f.eof + buf = f.readpartial(BUFFER_SIZE) + digest.update(buf) + end + end + + return digest.hexdigest + end +end diff --git a/tasks/acceptance.rake b/tasks/acceptance.rake index d807666d5..93eeaafdd 100644 --- a/tasks/acceptance.rake +++ b/tasks/acceptance.rake @@ -1,9 +1,65 @@ +require 'digest/sha1' +require 'pathname' +require 'yaml' + +require 'posix-spawn' + +require 'vagrant/util/file_checksum' + namespace :acceptance do + desc "Downloads the boxes required for running the acceptance tests." + task :boxes, :directory do |t, args| + # Create the directory where the boxes will be downloaded + box_dir = Pathname.new(args[:directory] || File.expand_path("../../test/tmp/boxes", __FILE__)) + box_dir.mkpath + puts "Boxes will be placed in: #{box_dir}" + + # Load the required boxes + boxes = YAML.load_file(File.expand_path("../../test/config/acceptance_boxes.yml", __FILE__)) + + boxes.each do |box| + puts "Box: #{box["name"]}" + box_file = box_dir.join("#{box["name"]}.box") + checksum = FileChecksum.new(box_file, Digest::SHA1) + + # If the box exists, we need to check the checksum and determine if we need + # to redownload the file. + if box_file.exist? + print "Box exists, checking SHA1 sum... " + if checksum.checksum == box["checksum"] + puts "OK" + next + else + puts "FAIL" + end + end + + # Download the file + puts "Downloading: #{box["url"]}" + + # TODO: This isn't Windows friendly yet. Move to a OS-independent + # download. + pid, _stdin, stdout, stderr = + POSIX::Spawn.popen4("wget", box["url"], "-O", box_file.to_s) + pid, status = Process.waitpid2(pid) + if status.exitstatus != 0 + puts "Download failed!" + puts stdout.read + puts stderr.read + abort + end + + # Check the checksum of the new file to verify that it + # downloaded properly. This shouldn't happen, but it can! + if checksum.checksum != box["checksum"] + puts "Checksum didn't match! Download was corrupt!" + abort + end + end + end + desc "Generates the configuration for acceptance tests from current source." task :config do - require 'yaml' - require 'posix-spawn' - require File.expand_path("../lib/vagrant/version", __FILE__) require File.expand_path('../test/acceptance/support/tempdir', __FILE__) diff --git a/test/config/acceptance_boxes.yml b/test/config/acceptance_boxes.yml new file mode 100644 index 000000000..2555407aa --- /dev/null +++ b/test/config/acceptance_boxes.yml @@ -0,0 +1,7 @@ +# This is the list of required boxes for acceptance tests, and +# their locations. The locations are used to download the boxes +# on the fly, if necessary. Additionally, a SHA1 checksum is +# given to determine if the box needs to be updated. +- name: default + url: http://files.vagrantup.com/test/boxes/default.box + checksum: 1b0a7eb6e152c5b43f45485654ff4965a7f9f604