From aebd303e267a4b830592ffe3551bd80647802a20 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Thu, 14 Feb 2008 19:45:47 -0600 Subject: Enhancing the stand-alone checksums utility module with the rest of the checksums we're likely to use, and adding tests, which I somehow missed when I wrote this file. --- lib/puppet/util/checksums.rb | 57 ++++++++++++++++++----- spec/unit/util/checksums.rb | 105 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+), 10 deletions(-) create mode 100755 spec/unit/util/checksums.rb diff --git a/lib/puppet/util/checksums.rb b/lib/puppet/util/checksums.rb index 6f6ea59b5..0d45c6d5a 100644 --- a/lib/puppet/util/checksums.rb +++ b/lib/puppet/util/checksums.rb @@ -1,37 +1,74 @@ +# A stand-alone module for calculating checksums +# in a generic way. module Puppet::Util::Checksums + # Calculate a checksum using Digest::MD5. def md5(content) require 'digest/md5' Digest::MD5.hexdigest(content) end + # Calculate a checksum of the first 500 chars of the content using Digest::MD5. + def md5lite(content) + md5(content[0..499]) + end + + # Calculate a checksum of a file's content using Digest::MD5. def md5_file(filename) require 'digest/md5' - incr_digest = Digest::MD5.new() - File.open(filename, 'r') do |file| - file.each_line do |line| - incr_digest << line - end - end + digest = Digest::MD5.new() + return checksum_file(digest, filename) + end + + # Calculate a checksum of the first 500 chars of a file's content using Digest::MD5. + def md5lite_file(filename) + File.open(filename, "r") { |f| return md5(f.read(500)) } + end - return incr_digest.hexdigest + # Return the :mtime timestamp of a file. + def mtime_file(filename) + File.stat(filename).send(:mtime) end + # Calculate a checksum using Digest::SHA1. def sha1(content) require 'digest/sha1' Digest::SHA1.hexdigest(content) end + # Calculate a checksum of the first 500 chars of the content using Digest::SHA1. + def sha1lite(content) + sha1(content[0..499]) + end + + # Calculate a checksum of a file's content using Digest::SHA1. def sha1_file(filename) require 'digest/sha1' - incr_digest = Digest::SHA1.new() + digest = Digest::SHA1.new() + return checksum_file(digest, filename) + end + + # Calculate a checksum of the first 500 chars of a file's content using Digest::SHA1. + def sha1lite_file(filename) + File.open(filename, "r") { |f| return sha1(f.read(500)) } + end + + # Return the :ctime of a file. + def timestamp_file(filename) + File.stat(filename).send(:ctime) + end + + private + + # Perform an incremental checksum on a file. + def checksum_file(digest, filename) File.open(filename, 'r') do |file| file.each_line do |line| - incr_digest << line + digest << line end end - return incr_digest.hexdigest + return digest.hexdigest end end diff --git a/spec/unit/util/checksums.rb b/spec/unit/util/checksums.rb new file mode 100755 index 000000000..50c213538 --- /dev/null +++ b/spec/unit/util/checksums.rb @@ -0,0 +1,105 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2007-9-22. +# Copyright (c) 2007. All rights reserved. + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/util/checksums' + +describe Puppet::Util::Checksums do + before do + @summer = Object.new + @summer.extend(Puppet::Util::Checksums) + end + + class LineYielder + def initialize(content) + @content = content + end + + def each_line + @content.split("\n").each { |line| yield line } + end + end + + content_sums = [:md5, :md5lite, :sha1, :sha1lite] + file_only = [:timestamp, :mtime] + + content_sums.each do |sumtype| + it "should be able to calculate %s sums from strings" % sumtype do + @summer.should be_respond_to(sumtype) + end + end + + [content_sums, file_only].flatten.each do |sumtype| + it "should be able to calculate %s sums from files" % sumtype do + @summer.should be_respond_to(sumtype.to_s + "_file") + end + end + + {:md5 => Digest::MD5, :sha1 => Digest::SHA1}.each do |sum, klass| + describe("when using %s" % sum) do + it "should use #{klass} to calculate string checksums" do + klass.expects(:hexdigest).with("mycontent").returns "whatever" + @summer.send(sum, "mycontent").should == "whatever" + end + + it "should use incremental #{klass} sums to calculate file checksums" do + digest = mock 'digest' + klass.expects(:new).returns digest + + file = "/path/to/my/file" + + # Mocha doesn't seem to be able to mock multiple yields, yay. + fh = LineYielder.new("firstline\nsecondline") + + File.expects(:open).with(file, "r").yields(fh) + + digest.expects(:<<).with "firstline" + digest.expects(:<<).with "secondline" + digest.expects(:hexdigest).returns :mydigest + + @summer.send(sum.to_s + "_file", file).should == :mydigest + end + end + end + + {:md5lite => Digest::MD5, :sha1lite => Digest::SHA1}.each do |sum, klass| + describe("when using %s" % sum) do + it "should use #{klass} to calculate string checksums from the first 500 characters of the string" do + content = "this is a test" * 100 + klass.expects(:hexdigest).with(content[0..499]).returns "whatever" + @summer.send(sum, content).should == "whatever" + end + + it "should use #{klass} to calculate a sum from the first 500 characters in the file" do + digest = mock 'digest' + + file = "/path/to/my/file" + + fh = mock 'filehandle' + File.expects(:open).with(file, "r").yields(fh) + + fh.expects(:read).with(500).returns('my content') + + klass.expects(:hexdigest).with("my content").returns :mydigest + + @summer.send(sum.to_s + "_file", file).should == :mydigest + end + end + end + + {:timestamp => :ctime, :mtime => :mtime}.each do |sum, method| + describe("when using %s" % sum) do + it "should use the '#{method}' on the file to determine the timestamp" do + file = "/my/file" + stat = mock 'stat', method => "mysum" + + File.expects(:stat).with(file).returns(stat) + + @summer.send(sum.to_s + "_file", file).should == "mysum" + end + end + end +end -- cgit From 6013b2500d9e799f8aba9f614de15d4eb477860a Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Thu, 14 Feb 2008 20:13:44 -0600 Subject: Refactoring the incremental checksum generation slightly based on the code in type/file/checksum.rb. --- lib/puppet/util/checksums.rb | 23 ++++++++++++----------- spec/unit/util/checksums.rb | 30 ++++++++++++------------------ 2 files changed, 24 insertions(+), 29 deletions(-) diff --git a/lib/puppet/util/checksums.rb b/lib/puppet/util/checksums.rb index 0d45c6d5a..598b3adfa 100644 --- a/lib/puppet/util/checksums.rb +++ b/lib/puppet/util/checksums.rb @@ -9,20 +9,20 @@ module Puppet::Util::Checksums # Calculate a checksum of the first 500 chars of the content using Digest::MD5. def md5lite(content) - md5(content[0..499]) + md5(content[0..511]) end # Calculate a checksum of a file's content using Digest::MD5. - def md5_file(filename) + def md5_file(filename, lite = false) require 'digest/md5' digest = Digest::MD5.new() - return checksum_file(digest, filename) + return checksum_file(digest, filename, lite) end # Calculate a checksum of the first 500 chars of a file's content using Digest::MD5. def md5lite_file(filename) - File.open(filename, "r") { |f| return md5(f.read(500)) } + md5_file(filename, true) end # Return the :mtime timestamp of a file. @@ -38,20 +38,20 @@ module Puppet::Util::Checksums # Calculate a checksum of the first 500 chars of the content using Digest::SHA1. def sha1lite(content) - sha1(content[0..499]) + sha1(content[0..511]) end # Calculate a checksum of a file's content using Digest::SHA1. - def sha1_file(filename) + def sha1_file(filename, lite = false) require 'digest/sha1' digest = Digest::SHA1.new() - return checksum_file(digest, filename) + return checksum_file(digest, filename, lite) end # Calculate a checksum of the first 500 chars of a file's content using Digest::SHA1. def sha1lite_file(filename) - File.open(filename, "r") { |f| return sha1(f.read(500)) } + sha1_file(filename, true) end # Return the :ctime of a file. @@ -62,10 +62,11 @@ module Puppet::Util::Checksums private # Perform an incremental checksum on a file. - def checksum_file(digest, filename) + def checksum_file(digest, filename, lite = false) File.open(filename, 'r') do |file| - file.each_line do |line| - digest << line + while content = file.read(512) + digest << content + break if lite end end diff --git a/spec/unit/util/checksums.rb b/spec/unit/util/checksums.rb index 50c213538..31cf24f5b 100755 --- a/spec/unit/util/checksums.rb +++ b/spec/unit/util/checksums.rb @@ -13,16 +13,6 @@ describe Puppet::Util::Checksums do @summer.extend(Puppet::Util::Checksums) end - class LineYielder - def initialize(content) - @content = content - end - - def each_line - @content.split("\n").each { |line| yield line } - end - end - content_sums = [:md5, :md5lite, :sha1, :sha1lite] file_only = [:timestamp, :mtime] @@ -51,8 +41,10 @@ describe Puppet::Util::Checksums do file = "/path/to/my/file" - # Mocha doesn't seem to be able to mock multiple yields, yay. - fh = LineYielder.new("firstline\nsecondline") + fh = mock 'filehandle' + fh.expects(:read).with(512).times(3).returns("firstline").then.returns("secondline").then.returns(nil) + #fh.expects(:read).with(512).returns("secondline") + #fh.expects(:read).with(512).returns(nil) File.expects(:open).with(file, "r").yields(fh) @@ -67,23 +59,25 @@ describe Puppet::Util::Checksums do {:md5lite => Digest::MD5, :sha1lite => Digest::SHA1}.each do |sum, klass| describe("when using %s" % sum) do - it "should use #{klass} to calculate string checksums from the first 500 characters of the string" do + it "should use #{klass} to calculate string checksums from the first 512 characters of the string" do content = "this is a test" * 100 - klass.expects(:hexdigest).with(content[0..499]).returns "whatever" + klass.expects(:hexdigest).with(content[0..511]).returns "whatever" @summer.send(sum, content).should == "whatever" end - it "should use #{klass} to calculate a sum from the first 500 characters in the file" do + it "should use #{klass} to calculate a sum from the first 512 characters in the file" do digest = mock 'digest' + klass.expects(:new).returns digest file = "/path/to/my/file" fh = mock 'filehandle' - File.expects(:open).with(file, "r").yields(fh) + fh.expects(:read).with(512).returns('my content') - fh.expects(:read).with(500).returns('my content') + File.expects(:open).with(file, "r").yields(fh) - klass.expects(:hexdigest).with("my content").returns :mydigest + digest.expects(:<<).with "my content" + digest.expects(:hexdigest).returns :mydigest @summer.send(sum.to_s + "_file", file).should == :mydigest end -- cgit