semantic_puppet 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "semantic_puppet"
4
+
5
+ spec = Gem::Specification.new do |s|
6
+ # Metadata
7
+ s.name = "semantic_puppet"
8
+ s.version = SemanticPuppet::VERSION
9
+ s.authors = ["Puppet Labs"]
10
+ s.email = ["info@puppetlabs.com"]
11
+ s.homepage = "https://github.com/puppetlabs/semantic_puppet-gem"
12
+ s.summary = "Useful tools for working with Semantic Versions."
13
+ s.description = %q{Tools used by Puppet to parse, validate, and compare Semantic Versions and Version Ranges and to query and resolve module dependencies.}
14
+
15
+ # Manifest
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {spec}/*_spec.rb`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # Dependencies
22
+ s.required_ruby_version = '>= 1.8.7'
23
+
24
+ s.add_development_dependency "rake"
25
+ s.add_development_dependency "rspec"
26
+ s.add_development_dependency "simplecov"
27
+ s.add_development_dependency "cane"
28
+ s.add_development_dependency "yard"
29
+ s.add_development_dependency "redcarpet"
30
+ end
@@ -0,0 +1,24 @@
1
+ PROJECT_ROOT = File.join(File.dirname(__FILE__), '..')
2
+
3
+ if ENV['COVERAGE']
4
+ require 'simplecov'
5
+ SimpleCov.start do
6
+ add_filter "/spec/"
7
+ end
8
+ end
9
+
10
+ RSpec.configure do |config|
11
+ config.treat_symbols_as_metadata_keys_with_true_values = true
12
+ config.run_all_when_everything_filtered = true
13
+ config.filter_run :focus
14
+
15
+ # Run specs in random order to surface order dependencies. If you find an
16
+ # order dependency and want to debug it, you can fix the order by providing
17
+ # the seed, which is printed after each run.
18
+ # --seed 1234
19
+ config.order = 'random'
20
+
21
+ config.before do
22
+ SemanticPuppet::Dependency.instance_variable_set(:@sources, nil)
23
+ end
24
+ end
@@ -0,0 +1,141 @@
1
+ require 'spec_helper'
2
+ require 'semantic_puppet/dependency/graph_node'
3
+
4
+ describe SemanticPuppet::Dependency::GraphNode do
5
+ let(:klass) do
6
+ Class.new do
7
+ include SemanticPuppet::Dependency::GraphNode
8
+
9
+ attr_accessor :name
10
+
11
+ def initialize(name, *satisfying)
12
+ @name = name
13
+ @satisfying = satisfying
14
+ @satisfying.each { |x| add_dependency(x.name) }
15
+ end
16
+
17
+ # @override
18
+ def satisfies_dependency?(node)
19
+ @satisfying.include?(node)
20
+ end
21
+ end
22
+ end
23
+
24
+ def instance(*args)
25
+ name = args.first.name unless args.empty?
26
+ klass.new(name || 'unnamed', *args)
27
+ end
28
+
29
+ context 'dependencies' do
30
+ subject { instance() }
31
+
32
+ example 'are added by #add_dependency' do
33
+ subject.add_dependency('foo')
34
+ subject.add_dependency('bar')
35
+ subject.add_dependency('baz')
36
+ expect(subject.dependency_names).to match_array %w[ foo bar baz ]
37
+ end
38
+
39
+ example 'are maintained in the #dependencies Hash' do
40
+ expect(subject.dependencies).to be_empty
41
+ subject.add_dependency('foo')
42
+ expect(subject.dependencies).to have_key 'foo'
43
+ expect(subject.dependencies).to respond_to :to_a
44
+ end
45
+ end
46
+
47
+ describe '#<<' do
48
+ let(:foo) { double('Node', :name => 'foo') }
49
+ let(:bar1) { double('Node', :name => 'bar', :'<=>' => 0) }
50
+ let(:bar2) { double('Node', :name => 'bar', :'<=>' => 0) }
51
+ let(:bar3) { double('Node', :name => 'bar') }
52
+ let(:baz) { double('Node', :name => 'baz') }
53
+
54
+ subject { instance(foo, bar1, bar2) }
55
+
56
+ it 'appends satisfying nodes to the dependencies' do
57
+ subject << foo << bar1 << bar2
58
+ expect(Array(subject.dependencies['foo'])).to match_array [ foo ]
59
+ expect(Array(subject.dependencies['bar'])).to match_array [ bar1, bar2 ]
60
+ end
61
+
62
+ it 'does not append nodes with unknown names' do
63
+ subject << baz
64
+ expect(Array(subject.dependencies['baz'])).to be_empty
65
+ end
66
+
67
+ it 'does not append unsatisfying nodes' do
68
+ subject << bar3
69
+ expect(Array(subject.dependencies['bar'])).to be_empty
70
+ end
71
+ end
72
+
73
+ describe '#satisfied' do
74
+ let(:foo) { double('Node', :name => 'foo') }
75
+ let(:bar) { double('Node', :name => 'bar') }
76
+
77
+ subject { instance(foo, bar) }
78
+
79
+ it 'is unsatisfied when no nodes have been appended' do
80
+ expect(subject).to_not be_satisfied
81
+ end
82
+
83
+ it 'is unsatisfied when any dependencies are missing' do
84
+ subject << foo
85
+ expect(subject).to_not be_satisfied
86
+ end
87
+
88
+ it 'is satisfied when all dependencies are fulfilled' do
89
+ subject << foo << bar
90
+ expect(subject).to be_satisfied
91
+ end
92
+ end
93
+
94
+ describe '#populate_children' do
95
+ let(:foo) { double('Node', :name => 'foo') }
96
+ let(:bar1) { double('Node', :name => 'bar', :'<=>' => 0) }
97
+ let(:bar2) { double('Node', :name => 'bar', :'<=>' => 0) }
98
+ let(:baz1) { double('Node', :name => 'baz', :'<=>' => 0) }
99
+ let(:baz2) { double('Node', :name => 'baz', :'<=>' => 0) }
100
+ let(:quxx) { double('Node', :name => 'quxx') }
101
+
102
+ subject do
103
+ graph = instance(foo, bar1, bar2, baz1, baz2)
104
+ graph << foo << bar1 << bar2 << baz1 << baz2
105
+ end
106
+
107
+ it 'saves all relevant nodes as its children' do
108
+ nodes = [ foo, bar2, baz1, quxx ]
109
+ nodes.each do |node|
110
+ allow(node).to receive(:populate_children)
111
+ end
112
+
113
+ subject.populate_children(nodes)
114
+
115
+ expected = { 'foo' => foo, 'bar' => bar2, 'baz' => baz1 }
116
+ expect(subject.children).to eql expected
117
+ end
118
+
119
+ it 'accepts a graph solution and populates it across all nodes' do
120
+ nodes = [ foo, bar2, baz1 ]
121
+ nodes.each do |node|
122
+ expect(node).to receive(:populate_children).with(nodes)
123
+ end
124
+
125
+ subject.populate_children(nodes)
126
+ end
127
+ end
128
+
129
+ describe '#<=>' do
130
+ it 'can be compared' do
131
+ a = instance(double('Node', :name => 'a'))
132
+ b = instance(double('Node', :name => 'b'))
133
+
134
+ expect(a).to be < b
135
+ expect(b).to be > a
136
+ expect([b, a].sort).to eql [a, b]
137
+ expect([a, b].sort).to eql [a, b]
138
+ end
139
+ end
140
+
141
+ end
@@ -0,0 +1,162 @@
1
+ require 'spec_helper'
2
+ require 'semantic_puppet/dependency/graph'
3
+
4
+ describe SemanticPuppet::Dependency::Graph do
5
+ Graph = SemanticPuppet::Dependency::Graph
6
+ GraphNode = SemanticPuppet::Dependency::GraphNode
7
+ ModuleRelease = SemanticPuppet::Dependency::ModuleRelease
8
+ Version = SemanticPuppet::Version
9
+ VersionRange = SemanticPuppet::VersionRange
10
+
11
+ describe '#initialize' do
12
+ it 'can be called without arguments' do
13
+ expect { Graph.new }.to_not raise_error
14
+ end
15
+
16
+ it 'implements the GraphNode protocol' do
17
+ expect(Graph.new).to be_a GraphNode
18
+ end
19
+
20
+ it 'adds constraints for every key in the passed hash' do
21
+ graph = Graph.new('foo' => 1, 'bar' => 2, 'baz' => 3)
22
+ expect(graph.constraints.keys).to match_array %w[ foo bar baz ]
23
+ end
24
+
25
+ it 'adds the named dependencies for every key in the passed hash' do
26
+ graph = Graph.new('foo' => 1, 'bar' => 2, 'baz' => 3)
27
+ expect(graph.dependency_names).to match_array %w[ foo bar baz ]
28
+ end
29
+ end
30
+
31
+ describe '#add_constraint' do
32
+ let(:graph) { Graph.new }
33
+
34
+ it 'can create a new constraint on a module' do
35
+ expect(graph.constraints.keys).to be_empty
36
+
37
+ graph.add_constraint('test', 'module-name', 'nil') { }
38
+ expect(graph.constraints.keys).to match_array %w[ module-name ]
39
+ end
40
+
41
+ it 'permits multiple constraints against the same module name' do
42
+ expect(graph.constraints.keys).to be_empty
43
+
44
+ graph.add_constraint('test', 'module-name', 'nil') { }
45
+ graph.add_constraint('test', 'module-name', 'nil') { }
46
+
47
+ expect(graph.constraints.keys).to match_array %w[ module-name ]
48
+ end
49
+ end
50
+
51
+ describe '#satisfies_dependency?' do
52
+ it 'is not satisfied by modules it does not depend on' do
53
+ graph = Graph.new('foo' => VersionRange.parse('1.x'))
54
+ release = ModuleRelease.new(nil, 'bar', Version.parse('1.0.0'))
55
+
56
+ expect(graph.satisfies_dependency?(release)).to_not be true
57
+ end
58
+
59
+ it 'is not satisfied by modules that do not fulfill the constraint' do
60
+ graph = Graph.new('foo' => VersionRange.parse('1.x'))
61
+ release = ModuleRelease.new(nil, 'foo', Version.parse('2.3.1'))
62
+
63
+ expect(graph.satisfies_dependency?(release)).to_not be true
64
+ end
65
+
66
+ it 'is not satisfied by modules that do not fulfill all the constraints' do
67
+ graph = Graph.new('foo' => VersionRange.parse('1.x'))
68
+ graph.add_constraint('me', 'foo', '1.2.3') do |node|
69
+ node.version.to_s == '1.2.3'
70
+ end
71
+
72
+ release = ModuleRelease.new(nil, 'foo', Version.parse('1.2.1'))
73
+
74
+ expect(graph.satisfies_dependency?(release)).to_not be true
75
+ end
76
+
77
+ it 'is satisfied by modules that do fulfill all the constraints' do
78
+ graph = Graph.new('foo' => VersionRange.parse('1.x'))
79
+ graph.add_constraint('me', 'foo', '1.2.3') do |node|
80
+ node.version.to_s == '1.2.3'
81
+ end
82
+
83
+ release = ModuleRelease.new(nil, 'foo', Version.parse('1.2.3'))
84
+
85
+ expect(graph.satisfies_dependency?(release)).to be true
86
+ end
87
+ end
88
+
89
+ describe '#add_graph_constraint' do
90
+ let(:graph) { Graph.new }
91
+
92
+ it 'can create a new constraint on a graph' do
93
+ expect(graph.constraints.keys).to be_empty
94
+
95
+ graph.add_graph_constraint('test') { }
96
+ expect(graph.constraints.keys).to match_array [ :graph ]
97
+ end
98
+
99
+ it 'permits multiple graph constraints' do
100
+ expect(graph.constraints.keys).to be_empty
101
+
102
+ graph.add_graph_constraint('test') { }
103
+ graph.add_graph_constraint('test') { }
104
+
105
+ expect(graph.constraints.keys).to match_array [ :graph ]
106
+ end
107
+ end
108
+
109
+ describe '#satisfies_graph?' do
110
+ it 'returns false if the solution violates a graph constraint' do
111
+ graph = Graph.new
112
+ graph.add_graph_constraint('me') do |nodes|
113
+ nodes.none? { |node| node.name =~ /z/ }
114
+ end
115
+
116
+ releases = [
117
+ double('Node', :name => 'foo'),
118
+ double('Node', :name => 'bar'),
119
+ double('Node', :name => 'baz'),
120
+ ]
121
+
122
+ expect(graph.satisfies_graph?(releases)).to_not be true
123
+ end
124
+
125
+ it 'returns false if the solution violates any graph constraint' do
126
+ graph = Graph.new
127
+ graph.add_graph_constraint('me') do |nodes|
128
+ nodes.all? { |node| node.name.length < 5 }
129
+ end
130
+ graph.add_graph_constraint('me') do |nodes|
131
+ nodes.none? { |node| node.name =~ /z/ }
132
+ end
133
+
134
+ releases = [
135
+ double('Node', :name => 'foo'),
136
+ double('Node', :name => 'bar'),
137
+ double('Node', :name => 'bangerang'),
138
+ ]
139
+
140
+ expect(graph.satisfies_graph?(releases)).to_not be true
141
+ end
142
+
143
+ it 'returns true if the solution violates no graph constraints' do
144
+ graph = Graph.new
145
+ graph.add_graph_constraint('me') do |nodes|
146
+ nodes.all? { |node| node.name.length < 5 }
147
+ end
148
+ graph.add_graph_constraint('me') do |nodes|
149
+ nodes.none? { |node| node.name =~ /z/ }
150
+ end
151
+
152
+ releases = [
153
+ double('Node', :name => 'foo'),
154
+ double('Node', :name => 'bar'),
155
+ double('Node', :name => 'boom'),
156
+ ]
157
+
158
+ expect(graph.satisfies_graph?(releases)).to be true
159
+ end
160
+ end
161
+
162
+ end
@@ -0,0 +1,143 @@
1
+ require 'spec_helper'
2
+ require 'semantic_puppet/dependency/module_release'
3
+
4
+ describe SemanticPuppet::Dependency::ModuleRelease do
5
+ def source
6
+ @source ||= SemanticPuppet::Dependency::Source.new
7
+ end
8
+
9
+ def make_release(name, version, deps = {})
10
+ source.create_release(name, version, deps)
11
+ end
12
+
13
+ let(:no_dependencies) do
14
+ make_release('module', '1.2.3')
15
+ end
16
+
17
+ let(:one_dependency) do
18
+ make_release('module', '1.2.3', 'foo' => '1.0.0')
19
+ end
20
+
21
+ let(:three_dependencies) do
22
+ dependencies = { 'foo' => '1.0.0', 'bar' => '2.0.0', 'baz' => '3.0.0' }
23
+ make_release('module', '1.2.3', dependencies)
24
+ end
25
+
26
+ describe '#dependency_names' do
27
+
28
+ it "lists the names of all the release's dependencies" do
29
+ expect(no_dependencies.dependency_names).to match_array %w[]
30
+ expect(one_dependency.dependency_names).to match_array %w[foo]
31
+ expect(three_dependencies.dependency_names).to match_array %w[foo bar baz]
32
+ end
33
+
34
+ end
35
+
36
+ describe '#to_s' do
37
+
38
+ let(:name) { 'foobarbaz' }
39
+ let(:version) { '1.2.3' }
40
+
41
+ subject { make_release(name, version).to_s }
42
+
43
+ it { should =~ /#{name}/ }
44
+ it { should =~ /#{version}/ }
45
+
46
+ end
47
+
48
+ describe '#<<' do
49
+
50
+ it 'marks matching dependencies as satisfied' do
51
+ one_dependency << make_release('foo', '1.0.0')
52
+ expect(one_dependency).to be_satisfied
53
+ end
54
+
55
+ it 'does not mark mis-matching dependency names as satisfied' do
56
+ one_dependency << make_release('WAT', '1.0.0')
57
+ expect(one_dependency).to_not be_satisfied
58
+ end
59
+
60
+ it 'does not mark mis-matching dependency versions as satisfied' do
61
+ one_dependency << make_release('foo', '0.0.1')
62
+ expect(one_dependency).to_not be_satisfied
63
+ end
64
+
65
+ end
66
+
67
+ describe '#<=>' do
68
+
69
+ it 'considers releases with greater version numbers greater' do
70
+ expect(make_release('foo', '1.0.0')).to be > make_release('foo', '0.1.0')
71
+ end
72
+
73
+ it 'considers releases with lesser version numbers lesser' do
74
+ expect(make_release('foo', '0.1.0')).to be < make_release('foo', '1.0.0')
75
+ end
76
+
77
+ it 'orders releases with different names lexographically' do
78
+ expect(make_release('bar', '1.0.0')).to be < make_release('foo', '1.0.0')
79
+ end
80
+
81
+ it 'orders releases by name first' do
82
+ expect(make_release('bar', '2.0.0')).to be < make_release('foo', '1.0.0')
83
+ end
84
+
85
+ end
86
+
87
+ describe '#satisfied?' do
88
+
89
+ it 'returns true when there are no dependencies to satisfy' do
90
+ expect(no_dependencies).to be_satisfied
91
+ end
92
+
93
+ it 'returns false when no dependencies have been satisified' do
94
+ expect(one_dependency).to_not be_satisfied
95
+ end
96
+
97
+ it 'returns false when not all dependencies have been satisified' do
98
+ releases = %w[ 0.9.0 1.0.0 1.0.1 ].map { |ver| make_release('foo', ver) }
99
+ three_dependencies << releases
100
+
101
+ expect(three_dependencies).to_not be_satisfied
102
+ end
103
+
104
+ it 'returns false when not all dependency versions have been satisified' do
105
+ releases = %w[ 0.9.0 1.0.1 ].map { |ver| make_release('foo', ver) }
106
+ one_dependency << releases
107
+
108
+ expect(one_dependency).to_not be_satisfied
109
+ end
110
+
111
+ it 'returns true when all dependencies have been satisified' do
112
+ releases = %w[ 0.9.0 1.0.0 1.0.1 ].map { |ver| make_release('foo', ver) }
113
+ one_dependency << releases
114
+
115
+ expect(one_dependency).to be_satisfied
116
+ end
117
+
118
+ end
119
+
120
+ describe '#satisfies_dependency?' do
121
+
122
+ it 'returns false when there are no dependencies to satisfy' do
123
+ release = make_release('foo', '1.0.0')
124
+ expect(no_dependencies.satisfies_dependency?(release)).to_not be true
125
+ end
126
+
127
+ it 'returns false when the release does not match the dependency name' do
128
+ release = make_release('bar', '1.0.0')
129
+ expect(one_dependency.satisfies_dependency?(release)).to_not be true
130
+ end
131
+
132
+ it 'returns false when the release does not match the dependency version' do
133
+ release = make_release('foo', '4.0.0')
134
+ expect(one_dependency.satisfies_dependency?(release)).to_not be true
135
+ end
136
+
137
+ it 'returns true when the release matches the dependency' do
138
+ release = make_release('foo', '1.0.0')
139
+ expect(one_dependency.satisfies_dependency?(release)).to be true
140
+ end
141
+
142
+ end
143
+ end