Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
SpecChangeSet understands how a new spec differs from an old or locke…
…d resolution.
- Loading branch information
Jay Feldblum
committed
May 27, 2011
1 parent
2a3ccd0
commit 3f2cb98
Showing
3 changed files
with
311 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
require 'librarian/helpers' | ||
require 'librarian/helpers/debug' | ||
|
||
require 'librarian/manifest_set' | ||
require 'librarian/resolution' | ||
require 'librarian/spec' | ||
|
||
module Librarian | ||
class SpecChangeSet | ||
|
||
include Helpers::Debug | ||
|
||
attr_reader :root_module | ||
attr_reader :spec, :lock | ||
|
||
def initialize(root_module, spec, lock) | ||
@root_module = root_module | ||
raise TypeError, "can't convert #{spec.class} into Spec" unless Spec === spec | ||
raise TypeError, "can't convert #{lock.class} into Resolution" unless Resolution === lock | ||
@spec, @lock = spec, lock | ||
end | ||
|
||
def same? | ||
@same ||= spec.dependencies.sort_by{|d| d.name} == lock.dependencies.sort_by{|d| d.name} | ||
end | ||
|
||
def changed? | ||
!same? | ||
end | ||
|
||
def spec_dependencies | ||
@spec_dependencies ||= spec.dependencies | ||
end | ||
def spec_dependency_names | ||
@spec_dependency_names ||= Set.new(spec_dependencies.map{|d| d.name}) | ||
end | ||
def spec_dependency_index | ||
@spec_dependency_index ||= Hash[spec_dependencies.map{|d| [d.name, d]}] | ||
end | ||
|
||
def lock_dependencies | ||
@lock_dependencies ||= lock.dependencies | ||
end | ||
def lock_dependency_names | ||
@lock_dependency_names ||= Set.new(lock_dependencies.map{|d| d.name}) | ||
end | ||
def lock_dependency_index | ||
@lock_dependency_index ||= Hash[lock_dependencies.map{|d| [d.name, d]}] | ||
end | ||
|
||
def lock_manifests | ||
@lock_manifests ||= lock.manifests | ||
end | ||
def lock_manifests_index | ||
@lock_manifests_index ||= ManifestSet.new(lock_manifests).to_hash | ||
end | ||
|
||
def removed_dependency_names | ||
@removed_dependency_names ||= lock_dependency_names - spec_dependency_names | ||
end | ||
|
||
# A dependency which is deleted from the specfile will, in the general case, | ||
# be removed conservatively. This means it might not actually be removed. | ||
# But if the dependency originally declared a source which is now non- | ||
# default, it must be removed, even if another dependency has a transitive | ||
# dependency on the one that was removed (which is the scenario in which | ||
# a conservative removal would not remove it). In this case, we must also | ||
# remove it explicitly so that it can be re-resolved from the default | ||
# source. | ||
def explicit_removed_dependency_names | ||
@explicit_removed_dependency_names ||= removed_dependency_names.reject do |name| | ||
lock_manifest = lock_manifests_index[name] | ||
lock_manifest.source == spec.source | ||
end.to_set | ||
end | ||
|
||
def added_dependency_names | ||
@added_dependency_names ||= spec_dependency_names - lock_dependency_names | ||
end | ||
|
||
def nonmatching_added_dependency_names | ||
@nonmatching_added_dependency_names ||= added_dependency_names.reject do |name| | ||
spec_dependency = spec_dependency_index[name] | ||
lock_manifest = lock_manifests_index[name] | ||
if lock_manifest | ||
matching = true | ||
matching &&= spec_dependency.satisfied_by?(lock_manifest) | ||
matching &&= spec_dependency.source == lock_manifest.source | ||
matching | ||
else | ||
false | ||
end | ||
end.to_set | ||
end | ||
|
||
def common_dependency_names | ||
@common_dependency_names ||= lock_dependency_names & spec_dependency_names | ||
end | ||
|
||
def changed_dependency_names | ||
@changed_dependency_names ||= common_dependency_names.reject do |name| | ||
spec_dependency = spec_dependency_index[name] | ||
lock_dependency = lock_dependency_index[name] | ||
lock_manifest = lock_manifests_index[name] | ||
same = true | ||
same &&= spec_dependency.satisfied_by?(lock_manifest) | ||
same &&= spec_dependency.source == lock_dependency.source | ||
same | ||
end.to_set | ||
end | ||
|
||
def deep_keep_manifest_names | ||
@deep_keep_manifest_names ||= begin | ||
lock_dependency_names - ( | ||
removed_dependency_names + | ||
changed_dependency_names + | ||
nonmatching_added_dependency_names | ||
) | ||
end | ||
end | ||
|
||
def shallow_strip_manifest_names | ||
@shallow_strip_manifest_names ||= begin | ||
explicit_removed_dependency_names + changed_dependency_names | ||
end | ||
end | ||
|
||
def inspect | ||
Helpers.strip_heredoc(<<-INSPECT) | ||
<##{self.class.name}: | ||
Removed: #{removed_dependency_names.to_a.join(", ")} | ||
ExplicitRemoved: #{explicit_removed_dependency_names.to_a.join(", ")} | ||
Added: #{added_dependency_names.to_a.join(", ")} | ||
NonMatchingAdded: #{nonmatching_added_dependency_names.to_a.join(", ")} | ||
Changed: #{changed_dependency_names.to_a.join(", ")} | ||
DeepKeep: #{deep_keep_manifest_names.to_a.join(", ")} | ||
ShallowStrip: #{shallow_strip_manifest_names.to_a.join(", ")} | ||
> | ||
INSPECT | ||
end | ||
|
||
# Returns an array of those manifests from the previous spec which should be kept, | ||
# based on inspecting the new spec against the locked resolution from the previous spec. | ||
def analyze | ||
@analyze ||= begin | ||
debug { "Analyzing spec and lock:" } | ||
|
||
if same? | ||
debug { " Same!" } | ||
return lock.manifests | ||
end | ||
|
||
debug { " Removed:" } ; removed_dependency_names.each { |name| debug { " #{name}" } } | ||
debug { " ExplicitRemoved:" } ; explicit_removed_dependency_names.each { |name| debug { " #{name}" } } | ||
debug { " Added:" } ; added_dependency_names.each { |name| debug { " #{name}" } } | ||
debug { " NonMatchingAdded:" } ; nonmatching_added_dependency_names.each { |name| debug { " #{name}" } } | ||
debug { " Changed:" } ; changed_dependency_names.each { |name| debug { " #{name}" } } | ||
debug { " DeepKeep:" } ; deep_keep_manifest_names.each { |name| debug { " #{name}" } } | ||
debug { " ShallowStrip:" } ; shallow_strip_manifest_names.each { |name| debug { " #{name}" } } | ||
|
||
manifests = ManifestSet.new(lock_manifests) | ||
manifests.deep_keep!(deep_keep_manifest_names) | ||
manifests.shallow_strip!(shallow_strip_manifest_names) | ||
manifests.to_a | ||
end | ||
end | ||
|
||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
require 'librarian' | ||
require 'librarian/mock' | ||
|
||
module Librarian | ||
describe SpecChangeSet do | ||
|
||
context "a simple root removal" do | ||
|
||
it "should work" do | ||
Mock.registry :clear => true do | ||
source 'source-1' do | ||
spec 'butter', '1.0' | ||
spec 'jam', '1.0' | ||
end | ||
end | ||
spec = Mock.dsl do | ||
src 'source-1' | ||
dep 'butter' | ||
dep 'jam' | ||
end | ||
lock = Mock.resolver.resolve(spec) | ||
lock.should be_correct | ||
|
||
spec = Mock.dsl do | ||
src 'source-1' | ||
dep 'jam' | ||
end | ||
changes = Mock.spec_change_set(spec, lock) | ||
changes.should_not be_same | ||
|
||
manifests = ManifestSet.new(changes.analyze).to_hash | ||
manifests.should have_key('jam') | ||
manifests.should_not have_key('butter') | ||
end | ||
|
||
end | ||
|
||
context "a simple root add" do | ||
|
||
it "should work" do | ||
Mock.registry :clear => true do | ||
source 'source-1' do | ||
spec 'butter', '1.0' | ||
spec 'jam', '1.0' | ||
end | ||
end | ||
spec = Mock.dsl do | ||
src 'source-1' | ||
dep 'jam' | ||
end | ||
lock = Mock.resolver.resolve(spec) | ||
lock.should be_correct | ||
|
||
spec = Mock.dsl do | ||
src 'source-1' | ||
dep 'butter' | ||
dep 'jam' | ||
end | ||
changes = Mock.spec_change_set(spec, lock) | ||
changes.should_not be_same | ||
manifests = ManifestSet.new(changes.analyze).to_hash | ||
manifests.should have_key('jam') | ||
manifests.should_not have_key('butter') | ||
end | ||
|
||
end | ||
|
||
context "a simple root change" do | ||
|
||
context "when the change is consistent" do | ||
|
||
it "should work" do | ||
Mock.registry :clear => true do | ||
source 'source-1' do | ||
spec 'butter', '1.0' | ||
spec 'jam', '1.0' | ||
spec 'jam', '1.1' | ||
end | ||
end | ||
spec = Mock.dsl do | ||
src 'source-1' | ||
dep 'butter' | ||
dep 'jam', '= 1.1' | ||
end | ||
lock = Mock.resolver.resolve(spec) | ||
lock.should be_correct | ||
|
||
spec = Mock.dsl do | ||
src 'source-1' | ||
dep 'butter' | ||
dep 'jam', '>= 1.0' | ||
end | ||
changes = Mock.spec_change_set(spec, lock) | ||
changes.should_not be_same | ||
manifests = ManifestSet.new(changes.analyze).to_hash | ||
manifests.should have_key('butter') | ||
manifests.should have_key('jam') | ||
end | ||
|
||
end | ||
|
||
context "when the change is inconsistent" do | ||
|
||
it "should work" do | ||
Mock.registry :clear => true do | ||
source 'source-1' do | ||
spec 'butter', '1.0' | ||
spec 'jam', '1.0' | ||
spec 'jam', '1.1' | ||
end | ||
end | ||
spec = Mock.dsl do | ||
src 'source-1' | ||
dep 'butter' | ||
dep 'jam', '= 1.0' | ||
end | ||
lock = Mock.resolver.resolve(spec) | ||
lock.should be_correct | ||
|
||
spec = Mock.dsl do | ||
src 'source-1' | ||
dep 'butter' | ||
dep 'jam', '>= 1.1' | ||
end | ||
changes = Mock.spec_change_set(spec, lock) | ||
changes.should_not be_same | ||
manifests = ManifestSet.new(changes.analyze).to_hash | ||
manifests.should have_key('butter') | ||
manifests.should_not have_key('jam') | ||
end | ||
|
||
end | ||
|
||
end | ||
|
||
end | ||
end |