(#13542) New and updated unit tests for the PMT tarball install

This patch adds new and updates the original unit tests for the PMT
install/upgrade code which now uses the ebeded minitar to work with
the module packages instead of calling external tar commands and has
gained the capability to install modules from tarballs.
This commit is contained in:
mruzicka 2013-03-15 19:50:12 +01:00 коммит произвёл Andrew Parker
Родитель 137246fe1e
Коммит 0b75658447
6 изменённых файлов: 1273 добавлений и 163 удалений

Просмотреть файл

@ -74,7 +74,7 @@ module Puppet::ModuleTool::Shared
def get_release(metadata, file)
return nil if metadata.nil?
module_name = metadata['name']
module_name = metadata['name']
version = metadata['version']
release = {
@ -304,10 +304,16 @@ module Puppet::ModuleTool::Shared
release[:has_local_changes] = mod.has_metadata? && mod.has_local_changes?
end
def resolve_constraints(dependencies, selected = {})
#
# Select releases satisfying the constraints specified by the dependencies
# parameter while making sure they are not in conflict with previously
# selected realeases specified by the selected parameter.
#
def get_candidates(dependencies, selected)
candidates = []
preferred = []
dependencies.each { |module_name, constraints|
dependencies.each do |module_name, constraints|
range = constraints.map { |constraint| constraint[:range] }.inject(&:&)
if selected.include?(module_name)
@ -378,7 +384,75 @@ module Puppet::ModuleTool::Shared
end
candidates << releases
end
[candidates, preferred]
end
#
# Perform the following post-resolution checks when applicable:
# - check that the resolution satisfies contraints imposed by already installed module releases
# - check that the resolution is not a downgrade of an already installed module release
# - check that the reoslution is not replacing an already installead module release with local modifications
# Throw an appropriate exception in case any of the checks fails.
#
def check_resolution(release, selected)
# skip the tests if --force was specified or the resolution
# is an already installed module release
return if @force || release[:module]
module_name = release[:module_name]
# verify that constraints imposed by already installed module releases
# are not violated
constraints = @conditions[module_name].select { |constraint|
# ignore constraints from modules which are being upgraded
!selected.include?(constraint[:source][:module_name])
}
range = constraints.map { |constraint| constraint[:range] }.inject(&:&)
unless constraints.empty? || range === release[:semver]
raise NoVersionsSatisfyError,
:requested_name => @module_name,
:requested_version => @version || (@conditions[@module_name].empty? ? :latest : :best),
:installed_version => @installed[@module_name].empty? ? nil : @installed[@module_name].first[:version],
:dependency_name => module_name,
:conditions => constraints,
:action => @action
end
# more checks in case we are to upgrade a module
if module_name != @module_name && previous = release[:previous]
# refuse to downgrade
if previous[:semver] > release[:semver]
raise NewerInstalledError,
:action => @action,
:module_name => module_name,
:requested_version => release[:version] || :best,
:installed_version => previous[:version]
end
# refuse to replace a modified module release
if has_local_changes?(previous)
raise LocalChangesError,
:action => @action,
:module_name => module_name,
:requested_version => release[:version] || :best,
:installed_version => previous[:version]
end
end
end
#
# Resolve constraints described by the dependencies parameter.
# First select all releases which satisfy the constraints, these
# are candidates for the resolution. Then select one particular
# release of each module from the candidates, collect all
# dependencies of these selected releases and try to resolve
# those recursively. If the resolution fails, try to select
# different releases from the candidates and try again.
#
def resolve_constraints(dependencies, selected = {})
candidates, preferred = get_candidates(dependencies, selected)
# we are done if there are no dependencies left to be resolved
return [] if candidates.empty?
@ -417,49 +491,8 @@ module Puppet::ModuleTool::Shared
subresolutions = @ignore_dependencies ? [] : resolve_constraints(subdependencies, selected)
resolutions = candidate_set.map do |release|
# perfrom post-resolution checks on modules installed from Forge (or a tarball)
# (skip the checks if --force was specified)
unless @force || release[:module] # skip already installed modules
module_name = release[:module_name]
# verify that constraints imposed by already installed modules
# are not violated
constraints = @conditions[module_name].select { |constraint|
# ignore constraints from modules which are being upgraded
!selected.include?(constraint[:source][:module_name])
}
range = constraints.map { |constraint| constraint[:range] }.inject(&:&)
unless constraints.empty? || range === release[:semver]
raise NoVersionsSatisfyError,
:requested_name => @module_name,
:requested_version => @version || (@conditions[@module_name].empty? ? :latest : :best),
:installed_version => @installed[@module_name].empty? ? nil : @installed[@module_name].first[:version],
:dependency_name => module_name,
:conditions => constraints,
:action => @action
end
# more checks in case we are to upgrade a module
if module_name != @module_name && previous = release[:previous]
# refuse to downgrade
if previous[:semver] > release[:semver]
raise NewerInstalledError,
:action => @action,
:module_name => module_name,
:requested_version => release[:version] || :best,
:installed_version => previous[:version]
end
# refuse to replace a modified module release
if has_local_changes?(previous)
raise LocalChangesError,
:action => @action,
:module_name => module_name,
:requested_version => release[:version] || :best,
:installed_version => previous[:version]
end
end
end
# perform post resolution checks
check_resolution(release, selected)
# the rosultion consists of the release satisfying the constriants
# and the list of resolutions satisfying its dependencies (to be
@ -472,7 +505,7 @@ module Puppet::ModuleTool::Shared
resolution
end
# link the subresolutions with the resolutions to from the install/upgrade tree
# link the subresolutions with the resolutions to form the install/upgrade tree
subresolutions.each do |subresolution|
resolutions[parentmap[subresolution[:release][:module_name]]][:dependencies] << subresolution
end
@ -500,7 +533,7 @@ module Puppet::ModuleTool::Shared
# fall through to try the next candidate set
end
# the currently selected candidate set is could not be resolved
# the currently selected candidate set could not be resolved
# we need to unselected the respective releaes and try the next set
candidate_set.each do |release|
selected.delete(release[:module_name])
@ -511,8 +544,8 @@ module Puppet::ModuleTool::Shared
# of the NoVersionsSatisfyError so that they are diplayed if/when
# the error is eventually reported
if resolution_exception.is_a? NoVersionsSatisfyError
dependencies = dependencies[resolution_exception.dependency_name]
resolution_exception.add_conditions(dependencies) unless (!dependencies || dependencies.empty?)
dependencies = dependencies[resolution_exception.dependency_name]
resolution_exception.add_conditions(dependencies) unless (!dependencies || dependencies.empty?)
end
# no more combinations to try, re-raise the most recent exception
@ -619,7 +652,7 @@ module Puppet::ModuleTool::Shared
# Long term we should just get rid of this caching behavior and cleanup downloaded modules after they install
# but for now this is a quick fix to disable caching
Puppet::Forge::Cache.clean
download_tarballs(@graph, @options[:target_dir])
download_tarballs(@graph, options[:target_dir])
end
#

Просмотреть файл

@ -181,15 +181,11 @@ describe "puppet module install" do
def expects_installer_run_with(name, options)
installer = mock("Installer")
install_dir = mock("InstallDir")
forge = mock("Forge")
Puppet::Forge.expects(:new).with("PMT", subject.version).returns(forge)
Puppet::ModuleTool::InstallDirectory.expects(:new).
with(Pathname.new(expected_options[:target_dir])).
returns(install_dir)
Puppet::ModuleTool::Applications::Installer.expects(:new).
with("puppetlabs-apache", forge, install_dir, expected_options).
with("puppetlabs-apache", forge, expected_options).
returns(installer)
installer.expects(:run)
end

Просмотреть файл

@ -23,7 +23,7 @@ describe Puppet::ModuleTool::Applications::Installer, :unless => Puppet.features
let(:forge) do
forge = mock("Puppet::Forge")
forge.stubs(:remote_dependency_info).returns(remote_dependency_info)
forge.stubs(:multiple_remote_dependency_info).returns(remote_dependency_info)
forge.stubs(:uri).returns('forge-dev.puppetlabs.com')
remote_dependency_info.each_key do |mod|
remote_dependency_info[mod].each do |release|
@ -37,6 +37,7 @@ describe Puppet::ModuleTool::Applications::Installer, :unless => Puppet.features
let(:install_dir) do
install_dir = mock("Puppet::ModuleTool::InstallDirectory")
install_dir.stubs(:prepare)
Puppet::ModuleTool::InstallDirectory.stubs(:new).returns(install_dir)
install_dir
end
@ -78,26 +79,18 @@ describe Puppet::ModuleTool::Applications::Installer, :unless => Puppet.features
}
end
describe "the behavior of .is_module_package?" do
it "should return true when file is a module package" do
pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do
installer = installer_class.new("foo", forge, install_dir, options)
installer.send(:is_module_package?, stdlib_pkg).should be_true
end
end
def installer_run(*args)
installer = installer_class.new(*args)
installer.instance_exec(fake_env) { |environment|
@environment = environment
}
it "should return false when file is not a module package" do
pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do
installer = installer_class.new("foo", forge, install_dir, options)
installer.send(:is_module_package?, "pmtacceptance-apollo-0.0.2.tar").
should be_false
end
end
installer.run
end
context "when the source is a repository" do
it "should require a valid name" do
lambda { installer_class.run('puppet', install_dir, params) }.should
lambda { installer_class.run('puppet', params) }.should
raise_error(ArgumentError, "Could not install module with invalid name: puppet")
end
@ -106,18 +99,14 @@ describe Puppet::ModuleTool::Applications::Installer, :unless => Puppet.features
Puppet::ModuleTool::Applications::Unpacker.expects(:new).
with('/fake_cache/pmtacceptance-stdlib-1.0.0.tar.gz', options).
returns(unpacker)
results = installer_class.run('pmtacceptance-stdlib', forge, install_dir, options)
results[:installed_modules].length == 1
results = installer_run('pmtacceptance-stdlib', forge, options)
results[:installed_modules].length.should == 1
results[:installed_modules][0][:module].should == "pmtacceptance-stdlib"
results[:installed_modules][0][:version][:vstring].should == "1.0.0"
end
end
context "should check the target directory" do
let(:installer) do
installer_class.new('pmtacceptance-stdlib', forge, install_dir, options)
end
def expect_normal_unpacker
Puppet::ModuleTool::Applications::Unpacker.expects(:new).
with('/fake_cache/pmtacceptance-stdlib-1.0.0.tar.gz', options).
@ -132,10 +121,8 @@ describe Puppet::ModuleTool::Applications::Installer, :unless => Puppet.features
pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do
expect_normal_unpacker
install_dir.expects(:prepare).with("pmtacceptance-stdlib", "latest")
results = installer.run
results[:installed_modules].length.should eq 1
results = installer_run('pmtacceptance-stdlib', forge, options)
results[:installed_modules].length.should == 1
results[:installed_modules][0][:module].should == "pmtacceptance-stdlib"
results[:installed_modules][0][:version][:vstring].should == "1.0.0"
end
@ -145,9 +132,7 @@ describe Puppet::ModuleTool::Applications::Installer, :unless => Puppet.features
pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do
install_dir.expects(:prepare).with("pmtacceptance-stdlib", "latest").
raises(Puppet::ModuleTool::Errors::PermissionDeniedCreateInstallDirectoryError.new("original", :module => "pmtacceptance-stdlib"))
results = installer.run
results = installer_run('pmtacceptance-stdlib', forge, options)
results[:result].should == :failure
results[:error][:oneline].should =~ /Permission is denied/
end
@ -167,7 +152,7 @@ describe Puppet::ModuleTool::Applications::Installer, :unless => Puppet.features
with('/fake_cache/pmtacceptance-java-1.7.1.tar.gz', options).
returns(unpacker)
results = installer_class.run('pmtacceptance-apollo', forge, install_dir, options)
results = installer_run('pmtacceptance-apollo', forge, options)
installed_dependencies = results[:installed_modules][0][:dependencies]
dependencies = installed_dependencies.inject({}) do |result, dep|
@ -187,7 +172,7 @@ describe Puppet::ModuleTool::Applications::Installer, :unless => Puppet.features
Puppet::ModuleTool::Applications::Unpacker.expects(:new).
with('/fake_cache/pmtacceptance-apollo-0.0.2.tar.gz', options).
returns(unpacker)
results = installer_class.run('pmtacceptance-apollo', forge, install_dir, options)
results = installer_run('pmtacceptance-apollo', forge, options)
results[:installed_modules][0][:module].should == "pmtacceptance-apollo"
end
end
@ -198,7 +183,7 @@ describe Puppet::ModuleTool::Applications::Installer, :unless => Puppet.features
Puppet::ModuleTool::Applications::Unpacker.expects(:new).
with('/fake_cache/pmtacceptance-apollo-0.0.2.tar.gz', options).
returns(unpacker)
results = installer_class.run('pmtacceptance-apollo', forge, install_dir, options)
results = installer_run('pmtacceptance-apollo', forge, options)
dependencies = results[:installed_modules][0][:dependencies]
dependencies.should == []
end
@ -210,7 +195,7 @@ describe Puppet::ModuleTool::Applications::Installer, :unless => Puppet.features
Puppet::ModuleTool::Applications::Unpacker.expects(:new).
with('/fake_cache/pmtacceptance-apollo-0.0.2.tar.gz', options).
returns(unpacker)
results = installer_class.run('pmtacceptance-apollo', forge, install_dir, options)
results = installer_run('pmtacceptance-apollo', forge, options)
dependencies = results[:installed_modules][0][:dependencies]
dependencies.should == []
end
@ -219,49 +204,21 @@ describe Puppet::ModuleTool::Applications::Installer, :unless => Puppet.features
it "should set an error if dependencies can't be resolved" do
pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do
options = { :version => '0.0.1', :target_dir => modpath1 }
oneline = "'pmtacceptance-apollo' (v0.0.1) requested; Invalid dependency cycle"
oneline = "Could not install 'pmtacceptance-apollo' (v0.0.1); module 'pmtacceptance-stdlib' cannot satisfy dependencies"
multiline = <<-MSG.strip
Could not install module 'pmtacceptance-apollo' (v0.0.1)
No version of 'pmtacceptance-stdlib' will satisfy dependencies
You specified 'pmtacceptance-apollo' (v0.0.1),
which depends on 'pmtacceptance-java' (v1.7.1),
which depends on 'pmtacceptance-stdlib' (v1.0.0)
Use `puppet module install --force` to install this module anyway
'pmtacceptance-apollo' (v0.0.1) requires 'pmtacceptance-stdlib' (v0.0.1)
'pmtacceptance-java' (v1.7.1) requires 'pmtacceptance-stdlib' (v1.0.0)
Use `puppet module install --ignore-dependencies` to install only this module
MSG
results = installer_class.run('pmtacceptance-apollo', forge, install_dir, options)
results = installer_class.run('pmtacceptance-apollo', forge, options)
results[:result].should == :failure
results[:error][:oneline].should == oneline
results[:error][:multiline].should == multiline
end
end
end
context "when there are modules installed" do
it "should use local version when already exists and satisfies constraints"
it "should reinstall the local version if force is used"
it "should upgrade local version when necessary to satisfy constraints"
it "should error when a local version can't be upgraded to satisfy constraints"
end
context "when a local module needs upgrading to satisfy constraints but has changes" do
it "should error"
it "should warn and continue if force is used"
end
it "should error when a local version of a dependency has no version metadata"
it "should error when a local version of a dependency has a non-semver version"
it "should error when a local version of a dependency has a different forge name"
it "should error when a local version of a dependency has no metadata"
end
context "when the source is a filesystem" do
before do
@sourcedir = tmpdir('sourcedir')
end
it "should error if it can't parse the name"
it "should try to get_release_package_from_filesystem if it has a valid name"
end
end

Просмотреть файл

@ -30,37 +30,20 @@ describe Puppet::ModuleTool::Applications::Unpacker do
end
before :each do
# Mock redhat for most test cases
Facter.stubs(:value).with("osfamily").returns("Redhat")
build_dir.stubs(:mkpath => nil, :rmtree => nil, :children => [])
unpacker.stubs(:build_dir).at_least_once.returns(build_dir)
FileUtils.stubs(:mv)
end
context "on linux" do
it "should attempt to untar file to temporary location using system tar" do
pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do
Puppet::Util::Execution.expects(:execute).with("tar xzf #{filename} -C #{build_dir}").returns(true)
unpacker.run
end
end
end
context "on solaris" do
before :each do
Facter.expects(:value).with("osfamily").returns("Solaris")
end
it "should attempt to untar file to temporary location using gnu tar" do
Puppet::Util.stubs(:which).with('gtar').returns('/usr/sfw/bin/gtar')
Puppet::Util::Execution.expects(:execute).with("gtar xzf #{filename} -C #{build_dir}").returns(true)
it "should attempt to open the file with Zlib::GzipReader and process the yielded stream with Puppet::Util::Archive::Tar::Minitar::Reader" do
pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do
tar = mock('Puppet::Util::Archive::Tar::Minitar::Reader')
tar.expects(:each)
gzip = mock('Zlib::GzipReader')
Puppet::Util::Archive::Tar::Minitar::Reader.expects(:open).with(gzip).yields(tar)
Zlib::GzipReader.expects(:open).with(Pathname.new(filename)).yields(gzip)
unpacker.run
end
it "should throw exception if gtar is not in the path exists" do
Puppet::Util.stubs(:which).with('gtar').returns(nil)
expect { unpacker.run }.to raise_error RuntimeError, "Cannot find the command 'gtar'. Make sure GNU tar is installed, and is in your PATH."
end
end
end

Просмотреть файл

@ -6,29 +6,140 @@ require 'semver'
describe Puppet::ModuleTool::Applications::Upgrader do
include PuppetSpec::Files
it "should update the requested module"
it "should not update dependencies"
it "should fail when updating a dependency to an unsupported version"
it "should fail when updating a module that is not installed"
it "should warn when the latest version is already installed"
it "should warn when the best version is already installed"
let(:unpacker) { stub(:run) }
let(:upgrader_class) { Puppet::ModuleTool::Applications::Upgrader }
let(:modpath) { File.join(tmpdir('upgrader'), 'modpath') }
let(:fake_env) { Puppet::Node::Environment.new('fake_env') }
let(:options) { { :target_dir => modpath } }
context "when using the '--version' option" do
it "should update an installed module to the requested version"
let(:forge) {
forge = mock("Puppet::Forge")
forge.stubs(:multiple_remote_dependency_info).returns(remote_dependency_info)
forge.stubs(:uri).returns('forge-dev.puppetlabs.com')
remote_dependency_info.each_key do |mod|
remote_dependency_info[mod].each do |release|
forge.stubs(:retrieve).with(release['file']).returns("/fake_cache#{release['file']}")
end
end
forge
}
let(:remote_dependency_info) {
{
"pmtacceptance/stdlib" => [
{ "dependencies" => [],
"version" => "0.0.1",
"file" => "/pmtacceptance-stdlib-0.0.1.tar.gz" },
{ "dependencies" => [],
"version" => "0.0.2",
"file" => "/pmtacceptance-stdlib-0.0.2.tar.gz" },
{ "dependencies" => [],
"version" => "1.0.0",
"file" => "/pmtacceptance-stdlib-1.0.0.tar.gz" }
],
"pmtacceptance/java" => [
{ "dependencies" => [["pmtacceptance/stdlib", ">= 0.0.1"]],
"version" => "1.7.0",
"file" => "/pmtacceptance-java-1.7.0.tar.gz" },
{ "dependencies" => [["pmtacceptance/stdlib", "1.0.0"]],
"version" => "1.7.1",
"file" => "/pmtacceptance-java-1.7.1.tar.gz" }
],
"pmtacceptance/apollo" => [
{ "dependencies" => [
["pmtacceptance/java", "1.7.1"],
["pmtacceptance/stdlib", "0.0.1"]
],
"version" => "0.0.1",
"file" => "/pmtacceptance-apollo-0.0.1.tar.gz" },
{ "dependencies" => [
["pmtacceptance/java", ">= 1.7.0"],
["pmtacceptance/stdlib", ">= 1.0.0"]
],
"version" => "0.0.2",
"file" => "/pmtacceptance-apollo-0.0.2.tar.gz" }
]
}
}
before do
FileUtils.mkdir_p(modpath)
fake_env.modulepath = [modpath]
Puppet.settings[:modulepath] = modpath
end
context "when using the '--force' flag" do
it "should ignore missing dependencies"
it "should ignore version constraints"
it "should not update a module that is not installed"
def upgrader_run(*args)
upgrader = upgrader_class.new(*args)
upgrader.instance_exec(fake_env) { |environment|
@environment = environment
}
upgrader.run
end
context "when using the '--env' option" do
it "should use the correct environment"
it "should update the requested module" do
local_module = mock('Puppet::Module')
local_module.stubs(:forge_name).returns('pmtacceptance/stdlib')
local_module.stubs(:name).returns('stdlib')
local_module.stubs(:version).returns('0.5.0')
local_module.stubs(:has_metadata?).returns(true)
local_module.stubs(:has_local_changes?).returns(false)
local_module.stubs(:modulepath).returns(modpath)
local_module.stubs(:dependencies).returns([])
fake_env.stubs(:modules_by_path).returns({
modpath => [ local_module ]
})
Puppet::ModuleTool::Applications::Unpacker.expects(:new).
with('/fake_cache/pmtacceptance-stdlib-1.0.0.tar.gz', options).
returns(unpacker)
results = upgrader_run('pmtacceptance-stdlib', forge, options)
results[:affected_modules].length.should == 1
results[:affected_modules][0][:module].should == 'pmtacceptance-stdlib'
results[:affected_modules][0][:version][:vstring].should == '1.0.0'
end
context "when there are missing dependencies" do
it "should fail to upgrade the original module"
it "should raise an error"
it 'should fail when updating a module that is not installed' do
fake_env.stubs(:modules_by_path).returns({})
results = upgrader_run('pmtacceptance-stdlib', forge, options)
results[:result].should == :failure
results[:error][:oneline].should == "Could not upgrade 'pmtacceptance-stdlib'; module is not installed"
end
it 'should warn when the latest version is already installed' do
local_module = mock('Puppet::Module')
local_module.stubs(:forge_name).returns('pmtacceptance/stdlib')
local_module.stubs(:name).returns('stdlib')
local_module.stubs(:version).returns('1.0.0')
local_module.stubs(:has_metadata?).returns(true)
local_module.stubs(:has_local_changes?).returns(false)
local_module.stubs(:modulepath).returns(modpath)
local_module.stubs(:dependencies).returns([])
fake_env.stubs(:modules_by_path).returns({
modpath => [ local_module ]
})
results = upgrader_run('pmtacceptance-stdlib', forge, options)
results[:result].should == :noop
results[:error][:oneline].should == "Could not upgrade 'pmtacceptance-stdlib'; a better release is already installed"
end
it 'should not update a module that is not installed even when --force is specified' do
options[:force] = true
fake_env.stubs(:modules_by_path).returns({})
results = upgrader_run('pmtacceptance-stdlib', forge, options)
results[:result].should == :failure
results[:error][:oneline].should == "Could not upgrade 'pmtacceptance-stdlib'; module is not installed"
end
end

Разница между файлами не показана из-за своего большого размера Загрузить разницу