chef-resource 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +23 -0
- data/LICENSE +201 -0
- data/README.md +264 -0
- data/Rakefile +8 -0
- data/files/lib/chef_resource.rb +24 -0
- data/files/lib/chef_resource/camel_case.rb +23 -0
- data/files/lib/chef_resource/chef.rb +102 -0
- data/files/lib/chef_resource/chef_dsl/chef_cookbook_compiler.rb +44 -0
- data/files/lib/chef_resource/chef_dsl/chef_recipe.rb +10 -0
- data/files/lib/chef_resource/chef_dsl/chef_recipe_dsl_extensions.rb +84 -0
- data/files/lib/chef_resource/chef_dsl/chef_resource_base.rb +12 -0
- data/files/lib/chef_resource/chef_dsl/chef_resource_class_extensions.rb +30 -0
- data/files/lib/chef_resource/chef_dsl/chef_resource_extensions.rb +224 -0
- data/files/lib/chef_resource/chef_dsl/chef_resource_log.rb +54 -0
- data/files/lib/chef_resource/chef_dsl/resource_container_module.rb +80 -0
- data/files/lib/chef_resource/chef_dsl/resource_definition_dsl.rb +128 -0
- data/files/lib/chef_resource/constants.rb +8 -0
- data/files/lib/chef_resource/errors.rb +31 -0
- data/files/lib/chef_resource/lazy_proc.rb +82 -0
- data/files/lib/chef_resource/output/nested_converge.rb +91 -0
- data/files/lib/chef_resource/output/nested_converge/open_resource.rb +113 -0
- data/files/lib/chef_resource/output/region_stream.rb +145 -0
- data/files/lib/chef_resource/output/simple_output.rb +83 -0
- data/files/lib/chef_resource/resource.rb +428 -0
- data/files/lib/chef_resource/resource/resource_log.rb +197 -0
- data/files/lib/chef_resource/resource/resource_type.rb +74 -0
- data/files/lib/chef_resource/resource/struct_property.rb +39 -0
- data/files/lib/chef_resource/resource/struct_property_type.rb +185 -0
- data/files/lib/chef_resource/resource/struct_resource.rb +410 -0
- data/files/lib/chef_resource/resource/struct_resource_base.rb +11 -0
- data/files/lib/chef_resource/resource/struct_resource_type.rb +275 -0
- data/files/lib/chef_resource/simple_struct.rb +121 -0
- data/files/lib/chef_resource/type.rb +371 -0
- data/files/lib/chef_resource/types.rb +4 -0
- data/files/lib/chef_resource/types/boolean.rb +16 -0
- data/files/lib/chef_resource/types/byte_size.rb +10 -0
- data/files/lib/chef_resource/types/date_time_type.rb +18 -0
- data/files/lib/chef_resource/types/date_type.rb +18 -0
- data/files/lib/chef_resource/types/float_type.rb +28 -0
- data/files/lib/chef_resource/types/integer_type.rb +53 -0
- data/files/lib/chef_resource/types/interval.rb +21 -0
- data/files/lib/chef_resource/types/path.rb +39 -0
- data/files/lib/chef_resource/types/pathname_type.rb +34 -0
- data/files/lib/chef_resource/types/string_type.rb +16 -0
- data/files/lib/chef_resource/types/symbol_type.rb +18 -0
- data/files/lib/chef_resource/types/uri_type.rb +37 -0
- data/files/lib/chef_resource/version.rb +3 -0
- data/spec/integration/chef.rb +81 -0
- data/spec/integration/struct_spec.rb +611 -0
- data/spec/integration/struct_state_spec.rb +538 -0
- data/spec/integration/type_spec.rb +1123 -0
- data/spec/integration/validation_spec.rb +207 -0
- data/spec/support/spec_support.rb +7 -0
- metadata +167 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'chef_resource/type'
|
2
|
+
|
3
|
+
module ChefResource
|
4
|
+
module Types
|
5
|
+
class Boolean
|
6
|
+
extend Type
|
7
|
+
|
8
|
+
must_be_kind_of TrueClass, FalseClass
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
#
|
14
|
+
# Put Boolean, Interval and Path into the top level namespace so they can be used
|
15
|
+
#
|
16
|
+
::Boolean = ChefResource::Types::Boolean
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'chef_resource/type'
|
2
|
+
|
3
|
+
module ChefResource
|
4
|
+
module Types
|
5
|
+
#
|
6
|
+
# Represents a DateTime. Will always be in DateTime format.
|
7
|
+
#
|
8
|
+
# Accepts a number (# of seconds since 1970), a Date, a DateTime, or a
|
9
|
+
# string ("now", "2 hours from now", "second thursday in 1970", "2045/10/27").
|
10
|
+
# Has methods to denote the default date format for printing and disambiguation.
|
11
|
+
#
|
12
|
+
class DateTimeType
|
13
|
+
extend Type
|
14
|
+
|
15
|
+
must_be_kind_of DateTime
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'chef_resource/type'
|
2
|
+
|
3
|
+
module ChefResource
|
4
|
+
module Types
|
5
|
+
#
|
6
|
+
# Represents a Date. Will always be a Date object.
|
7
|
+
#
|
8
|
+
# Accepts a number (# of seconds since 1970), a Date, a DateTime, or a
|
9
|
+
# string ("now", "2 days from now", "second thursday in 1970", "2045/10/27").
|
10
|
+
# Has methods to denote the default date format for printing and disambiguation.
|
11
|
+
#
|
12
|
+
class DateTimeType
|
13
|
+
extend Type
|
14
|
+
|
15
|
+
must_be_kind_of Date
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'chef_resource/type'
|
2
|
+
|
3
|
+
module ChefResource
|
4
|
+
module Types
|
5
|
+
#
|
6
|
+
# Handles Float types.
|
7
|
+
#
|
8
|
+
# Supports strings and number types.
|
9
|
+
#
|
10
|
+
class FloatType
|
11
|
+
extend Type
|
12
|
+
|
13
|
+
must_be_kind_of Float
|
14
|
+
|
15
|
+
def self.coerce(parent, value)
|
16
|
+
if value.is_a?(String)
|
17
|
+
if value !~ /^[+-]?(\d+(\.\d+)?|.\d+)(e[+-]?\d+)?$/i
|
18
|
+
raise ValidationError.new("not a valid floating point string", value)
|
19
|
+
end
|
20
|
+
value = value.to_f
|
21
|
+
elsif value.is_a?(Numeric)
|
22
|
+
value = value.to_f
|
23
|
+
end
|
24
|
+
super
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'chef_resource/type'
|
2
|
+
|
3
|
+
module ChefResource
|
4
|
+
module Types
|
5
|
+
#
|
6
|
+
# Handles Integer types.
|
7
|
+
#
|
8
|
+
# Supports parse / to_s in bases other than 10.
|
9
|
+
#
|
10
|
+
class IntegerType
|
11
|
+
extend Type
|
12
|
+
|
13
|
+
must_be_kind_of Integer
|
14
|
+
|
15
|
+
def self.coerce(parent, value)
|
16
|
+
if value.is_a?(String)
|
17
|
+
if !base_regexp.match(value)
|
18
|
+
raise ValidationError.new("must be a base #{base||10} string", value)
|
19
|
+
end
|
20
|
+
|
21
|
+
if base
|
22
|
+
value = value.to_i(base)
|
23
|
+
else
|
24
|
+
value = value.to_i
|
25
|
+
end
|
26
|
+
end
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.value_to_s(value)
|
31
|
+
str = base ? value.to_s(base) : value.to_s
|
32
|
+
str
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.base_regexp
|
36
|
+
base = self.base || 10
|
37
|
+
if base <= 10
|
38
|
+
/^[-+]?[0-#{base-1}]+$/
|
39
|
+
elsif base <= 36
|
40
|
+
top_char = ('a'.ord + base-11).chr
|
41
|
+
/^[-+]?[0-9a-#{top_char}]+$/i
|
42
|
+
else
|
43
|
+
raise "Base #{base} strings not supported: nothing bigger than 36!"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class <<self
|
48
|
+
extend SimpleStruct
|
49
|
+
property :base
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'chef_resource/type'
|
2
|
+
|
3
|
+
module ChefResource
|
4
|
+
module Types
|
5
|
+
#
|
6
|
+
# Represents a time interval ("3 seconds", "1 day", etc.).
|
7
|
+
#
|
8
|
+
# Stored as an Interval object (no standard representation can accurately
|
9
|
+
# record this, because things like seconds per day and even the length of a
|
10
|
+
# second can vary depending on the exact date and time).
|
11
|
+
#
|
12
|
+
class Interval
|
13
|
+
extend Type
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# Put Boolean, Interval and Path into the top level namespace so they can be used
|
20
|
+
#
|
21
|
+
::Interval = ChefResource::Types::Interval
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'chef_resource/types/pathname_type'
|
2
|
+
|
3
|
+
module ChefResource
|
4
|
+
module Types
|
5
|
+
#
|
6
|
+
# Type for Paths. Always stored as String.
|
7
|
+
#
|
8
|
+
# Allows paths to be specified as Pathname or as String. Can handle
|
9
|
+
# absolutizing relative URLs with #relative_to.
|
10
|
+
#
|
11
|
+
class Path
|
12
|
+
extend Type
|
13
|
+
|
14
|
+
must_be_kind_of String
|
15
|
+
|
16
|
+
class <<self
|
17
|
+
extend SimpleStruct
|
18
|
+
property :relative_to, coerced: "value.is_a?(Pathname) ? value.to_s : value"
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.coerce(parent, path)
|
22
|
+
if path
|
23
|
+
rel = relative_to(parent: parent)
|
24
|
+
if rel
|
25
|
+
path = (Pathname.new(rel) + path).to_s
|
26
|
+
else
|
27
|
+
path = path.to_s if path.is_a?(Pathname)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
super
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# Put Boolean, Interval and Path into the top level namespace so they can be used
|
38
|
+
#
|
39
|
+
::Path = ChefResource::Types::Path
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'chef_resource/type'
|
2
|
+
require 'chef_resource/simple_struct'
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
module ChefResource
|
6
|
+
module Types
|
7
|
+
#
|
8
|
+
# Type for Paths. Always stored as Pathname
|
9
|
+
#
|
10
|
+
# Can handle absolutizing relative URLs with #relative_to.
|
11
|
+
#
|
12
|
+
class PathnameType
|
13
|
+
extend Type
|
14
|
+
must_be_kind_of Pathname
|
15
|
+
|
16
|
+
class <<self
|
17
|
+
extend SimpleStruct
|
18
|
+
property :relative_to, coerced: "value.is_a?(String) ? Pathname.new(value) : value"
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.coerce(parent, path)
|
22
|
+
if path
|
23
|
+
rel = relative_to(parent: parent)
|
24
|
+
if rel
|
25
|
+
path = rel + path if rel
|
26
|
+
else
|
27
|
+
path = Pathname.new(path) if path.is_a?(String)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
super
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'chef_resource/type'
|
2
|
+
|
3
|
+
module ChefResource
|
4
|
+
module Types
|
5
|
+
class SymbolType
|
6
|
+
extend Type
|
7
|
+
|
8
|
+
must_be_kind_of Symbol
|
9
|
+
|
10
|
+
def self.coerce(parent, value)
|
11
|
+
if value.respond_to?(:to_sym)
|
12
|
+
value = value.to_sym
|
13
|
+
end
|
14
|
+
super
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'chef_resource/type'
|
2
|
+
require 'chef_resource/simple_struct'
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
module ChefResource
|
6
|
+
module Types
|
7
|
+
#
|
8
|
+
# Type for URIs.
|
9
|
+
#
|
10
|
+
# Allows URIs to be specified as URI or as String. Can also handle absolutizing
|
11
|
+
# relative URLs with #relative_to.
|
12
|
+
#
|
13
|
+
class URIType
|
14
|
+
extend Type
|
15
|
+
extend SimpleStruct
|
16
|
+
|
17
|
+
must_be_kind_of URI
|
18
|
+
|
19
|
+
def self.coerce(parent, uri)
|
20
|
+
if uri
|
21
|
+
rel = relative_to(parent: parent)
|
22
|
+
if rel
|
23
|
+
uri = rel + uri
|
24
|
+
else
|
25
|
+
uri = URI.parse(uri) if uri.is_a?(String)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
super
|
29
|
+
end
|
30
|
+
|
31
|
+
class <<self
|
32
|
+
extend SimpleStruct
|
33
|
+
property :relative_to, coerced: "value.is_a?(String) ? URI.parse(value) : value"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'cheffish/basic_chef_client'
|
2
|
+
require 'chef_resource/chef'
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
module Cheffish
|
6
|
+
class BasicChefClient
|
7
|
+
prepend ChefResource::ChefDSL::ChefRecipeDSLExtensions
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
Chef.resource :simple_resource do
|
12
|
+
property :hi
|
13
|
+
attr_reader :did_it
|
14
|
+
recipe do
|
15
|
+
@did_it = true
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
Chef.resource :compound_resource do
|
20
|
+
property :lo
|
21
|
+
attr_reader :did_it
|
22
|
+
attr_reader :f
|
23
|
+
recipe do
|
24
|
+
@f = Tempfile.new('foo')
|
25
|
+
file @f.path do
|
26
|
+
content 'hi'
|
27
|
+
end
|
28
|
+
simple_resource do
|
29
|
+
hi 10
|
30
|
+
end
|
31
|
+
@did_it = true
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
Chef.resource :resource_with_error do
|
36
|
+
property :lo
|
37
|
+
recipe do
|
38
|
+
blarghfile 'wow.txt' do
|
39
|
+
content 'hi'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
describe 'Chef integration' do
|
46
|
+
context "When simple_resource is a ChefResource resource" do
|
47
|
+
it "a recipe can run the resource" do
|
48
|
+
x = nil
|
49
|
+
Cheffish::BasicChefClient.converge_block do
|
50
|
+
x = simple_resource do
|
51
|
+
hi 10
|
52
|
+
end
|
53
|
+
end
|
54
|
+
expect(x.did_it).to be_truthy
|
55
|
+
end
|
56
|
+
end
|
57
|
+
context "When compound_resource has a file and a simple_resource in it" do
|
58
|
+
it "a recipe can run the resource and both sub-resources run" do
|
59
|
+
x = nil
|
60
|
+
Cheffish::BasicChefClient.converge_block do
|
61
|
+
x = compound_resource do
|
62
|
+
lo 100
|
63
|
+
end
|
64
|
+
end
|
65
|
+
expect(x.did_it).to be_truthy
|
66
|
+
expect(IO.read(x.f.path)).to eq 'hi'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
context "When resource_with_error has a misspelled resource name" do
|
70
|
+
it "a recipe can run the resource and both sub-resources run" do
|
71
|
+
expect do
|
72
|
+
Cheffish::BasicChefClient.converge_block do
|
73
|
+
resource_with_error do
|
74
|
+
lo 100
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end.to raise_error(NoMethodError)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
# notifications and subscribes, both directions
|
81
|
+
end
|
@@ -0,0 +1,611 @@
|
|
1
|
+
require 'support/spec_support'
|
2
|
+
require 'chef_resource/resource/struct_resource_base'
|
3
|
+
|
4
|
+
describe ChefResource::Resource::StructResource do
|
5
|
+
def self.with_struct(name, &block)
|
6
|
+
before :each do
|
7
|
+
Object.send(:remove_const, name) if Object.const_defined?(name, false)
|
8
|
+
eval "class ::#{name} < ChefResource::Resource::StructResourceBase; end"
|
9
|
+
Object.const_get(name).class_eval(&block)
|
10
|
+
end
|
11
|
+
after :each do
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe :inheritance do
|
16
|
+
context "When A < B, and A has x and B has y" do
|
17
|
+
class A < ChefResource::Resource::StructResourceBase
|
18
|
+
property :x, identity: true do
|
19
|
+
default { y*2 }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
class B < A
|
23
|
+
property :y, identity: true do
|
24
|
+
default { x*2 }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it "A.open(x: 1).y raises an error" do
|
29
|
+
expect { A.open(x: 1).y }.to raise_error
|
30
|
+
end
|
31
|
+
it "B.open(x: 1).y yields 2" do
|
32
|
+
expect(B.open(x: 1).y).to eq 2
|
33
|
+
end
|
34
|
+
it "B.open(y: 1).x yields 2" do
|
35
|
+
expect(B.open(y: 1).x).to eq 2
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe :reset do
|
41
|
+
context "When MyResource has both a set and not-set property" do
|
42
|
+
with_struct(:MyResource) do
|
43
|
+
property :identity_set, identity: true
|
44
|
+
property :normal_set, default: 20
|
45
|
+
property :normal_not_set, default: 30
|
46
|
+
end
|
47
|
+
let(:r) { r = MyResource.open(1); r.normal_set = 2; r }
|
48
|
+
it "explicit_property_values is missing values" do
|
49
|
+
expect(r.to_h(:only_explicit)).to eq({ identity_set: 1, normal_set: 2 })
|
50
|
+
expect(r.normal_set).to eq 2
|
51
|
+
expect(r.normal_not_set).to eq 30
|
52
|
+
end
|
53
|
+
it "reset(:normal_set) succeeds" do
|
54
|
+
r.reset(:normal_set)
|
55
|
+
expect(r.to_h(:only_explicit)).to eq({ identity_set: 1 })
|
56
|
+
expect(r.normal_set).to eq 20
|
57
|
+
expect(r.normal_not_set).to eq 30
|
58
|
+
end
|
59
|
+
it "reset(:normal_not_set) succeeds" do
|
60
|
+
r.reset(:normal_not_set)
|
61
|
+
expect(r.to_h(:only_explicit)).to eq({ identity_set: 1, normal_set: 2 })
|
62
|
+
expect(r.normal_set).to eq 2
|
63
|
+
expect(r.normal_not_set).to eq 30
|
64
|
+
end
|
65
|
+
it "reset(:normal_set) succeeds" do
|
66
|
+
r.reset(:normal_set)
|
67
|
+
expect(r.to_h(:only_explicit)).to eq({ identity_set: 1 })
|
68
|
+
expect(r.normal_set).to eq 20
|
69
|
+
expect(r.normal_not_set).to eq 30
|
70
|
+
end
|
71
|
+
it "reset() resets normal but not identity properties" do
|
72
|
+
r.reset
|
73
|
+
expect(r.to_h(:only_explicit)).to eq({ identity_set: 1 })
|
74
|
+
expect(r.normal_set).to eq 20
|
75
|
+
expect(r.normal_not_set).to eq 30
|
76
|
+
end
|
77
|
+
it "reset() twice in a row succeeds (but second reset does nothing)" do
|
78
|
+
r.reset
|
79
|
+
expect(r.to_h(:only_explicit)).to eq({ identity_set: 1 })
|
80
|
+
expect(r.normal_set).to eq 20
|
81
|
+
expect(r.normal_not_set).to eq 30
|
82
|
+
r.reset
|
83
|
+
expect(r.to_h(:only_explicit)).to eq({ identity_set: 1 })
|
84
|
+
expect(r.normal_set).to eq 20
|
85
|
+
expect(r.normal_not_set).to eq 30
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe :property do
|
91
|
+
context "When MyResource is a ResourceStruct with two properties" do
|
92
|
+
with_struct(:MyResource) do
|
93
|
+
property :x
|
94
|
+
property :y
|
95
|
+
end
|
96
|
+
it "You can create a new MyResource" do
|
97
|
+
expect(MyResource.open).to be_kind_of(MyResource)
|
98
|
+
end
|
99
|
+
it "You can set and get properties" do
|
100
|
+
r = MyResource.open
|
101
|
+
expect(r.x).to be_nil
|
102
|
+
expect(r.y).to be_nil
|
103
|
+
expect(r.x = 10).to eq 10
|
104
|
+
expect(r.y = 20).to eq 20
|
105
|
+
expect(r.x).to eq 10
|
106
|
+
expect(r.y).to eq 20
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe :type do
|
111
|
+
context "When MyResource is a ResourceStruct with property :x, ResourceStruct (resource struct reference)" do
|
112
|
+
with_struct(:MyResource) do
|
113
|
+
property :x, MyResource
|
114
|
+
property :y
|
115
|
+
end
|
116
|
+
it "x and y can be set to a resource" do
|
117
|
+
r = MyResource.open
|
118
|
+
r.y = 10
|
119
|
+
expect(r.x).to be_nil
|
120
|
+
r2 = MyResource.open
|
121
|
+
expect(r2.x = r).to eq r
|
122
|
+
r2.y = 20
|
123
|
+
expect(r2.x).to eq r
|
124
|
+
expect(r2.x.y).to eq 10
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe :identity do
|
130
|
+
context "When MyResource has property :x, identity: true" do
|
131
|
+
with_struct(:MyResource) do
|
132
|
+
property :x, identity: true
|
133
|
+
property :y
|
134
|
+
end
|
135
|
+
it "open() fails with 'x is required'" do
|
136
|
+
expect { MyResource.open() }.to raise_error ArgumentError
|
137
|
+
end
|
138
|
+
it "open(1) creates a MyResource where x = 1" do
|
139
|
+
expect(r = MyResource.open(1)).to be_kind_of(MyResource)
|
140
|
+
expect(r.x).to eq 1
|
141
|
+
expect(r.y).to be_nil
|
142
|
+
end
|
143
|
+
it "open(x: 1) creates a MyResource where x = 1" do
|
144
|
+
expect(r = MyResource.open(x: 1)).to be_kind_of(MyResource)
|
145
|
+
expect(r.x).to eq 1
|
146
|
+
expect(r.y).to be_nil
|
147
|
+
end
|
148
|
+
it "open(1, 2) fails with too many arguments" do
|
149
|
+
expect { MyResource.open(1, 2) }.to raise_error ArgumentError
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
context "When MyResource has property :x, identity: true, default: 10" do
|
154
|
+
with_struct(:MyResource) do
|
155
|
+
property :x, identity: true, default: 10
|
156
|
+
property :y
|
157
|
+
end
|
158
|
+
it "open() succeeds with x = 10" do
|
159
|
+
expect(r = MyResource.open()).to be_kind_of(MyResource)
|
160
|
+
expect(r.x).to eq 10
|
161
|
+
expect(r.y).to be_nil
|
162
|
+
end
|
163
|
+
it "open(1) fails due to non-required argument" do
|
164
|
+
expect { MyResource.open(1) }.to raise_error ArgumentError
|
165
|
+
end
|
166
|
+
it "open(x: 1) creates a MyResource where x = 1" do
|
167
|
+
expect(r = MyResource.open(x: 1)).to be_kind_of(MyResource)
|
168
|
+
expect(r.x).to eq 1
|
169
|
+
expect(r.y).to be_nil
|
170
|
+
end
|
171
|
+
it "open(1, 2) fails with too many arguments" do
|
172
|
+
expect { MyResource.open(1, 2) }.to raise_error ArgumentError
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
context "When MyResource has property :x, identity: true, required: false" do
|
177
|
+
with_struct(:MyResource) do
|
178
|
+
property :x, identity: true, required: false
|
179
|
+
property :y
|
180
|
+
end
|
181
|
+
it "open() creates a MyResource where x = nil" do
|
182
|
+
expect(r = MyResource.open()).to be_kind_of(MyResource)
|
183
|
+
expect(r.x).to be_nil
|
184
|
+
expect(r.y).to be_nil
|
185
|
+
end
|
186
|
+
it "open(1) fails with 'too many arguments'" do
|
187
|
+
expect { MyResource.open(1) }.to raise_error ArgumentError
|
188
|
+
end
|
189
|
+
it "open(x: 1) creates a MyResource where x = 1" do
|
190
|
+
expect(r = MyResource.open(x: 1)).to be_kind_of(MyResource)
|
191
|
+
expect(r.x).to eq 1
|
192
|
+
expect(r.y).to be_nil
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
context "When MyResource has property :x and :y, identity: true" do
|
197
|
+
with_struct(:MyResource) do
|
198
|
+
property :x, identity: true
|
199
|
+
property :y, identity: true
|
200
|
+
property :z
|
201
|
+
end
|
202
|
+
it "open() fails with 'x is required'" do
|
203
|
+
expect { MyResource.open() }.to raise_error ArgumentError
|
204
|
+
end
|
205
|
+
it "open(1) fails with 'y is required'" do
|
206
|
+
expect { MyResource.open(1) }.to raise_error ArgumentError
|
207
|
+
end
|
208
|
+
it "open(y: 1) fails with 'x is required'" do
|
209
|
+
expect { MyResource.open(y: 1) }.to raise_error ArgumentError
|
210
|
+
end
|
211
|
+
it "open(1, 2) creates a MyResource where x = 1 and y = 2" do
|
212
|
+
expect(r = MyResource.open(1, 2)).to be_kind_of(MyResource)
|
213
|
+
expect(r.x).to eq 1
|
214
|
+
expect(r.y).to eq 2
|
215
|
+
expect(r.z).to be_nil
|
216
|
+
end
|
217
|
+
it "open(1, 2, 3) fails with too many arguments" do
|
218
|
+
expect { MyResource.open(1, 2, 3) }.to raise_error ArgumentError
|
219
|
+
end
|
220
|
+
it "open(x: 1, y: 2) creates MyResource.x = 1, y = 2" do
|
221
|
+
expect(r = MyResource.open(x: 1, y: 2)).to be_kind_of(MyResource)
|
222
|
+
expect(r.x).to eq 1
|
223
|
+
expect(r.y).to eq 2
|
224
|
+
expect(r.z).to be_nil
|
225
|
+
end
|
226
|
+
it "open(3, 4, x: 1, y: 2) creates MyResource.x = 3, y = 4" do
|
227
|
+
expect { MyResource.open(3, 4, x: 1, y: 2) }.to raise_error ArgumentError
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
context "When MyResource has identity properties x and y, and x is not required" do
|
232
|
+
with_struct(:MyResource) do
|
233
|
+
property :x, identity: true, required: false
|
234
|
+
property :y, identity: true
|
235
|
+
end
|
236
|
+
it "open() fails with y is required" do
|
237
|
+
expect { MyResource.open() }.to raise_error ArgumentError
|
238
|
+
end
|
239
|
+
it "open(1) creates a MyResource where x = nil and y = 1" do
|
240
|
+
expect(r = MyResource.open(1)).to be_kind_of(MyResource)
|
241
|
+
expect(r.x).to be_nil
|
242
|
+
expect(r.y).to eq 1
|
243
|
+
end
|
244
|
+
it "open(1, 2) fails with 'too many arguments'" do
|
245
|
+
expect { MyResource.open(1, 2) }.to raise_error ArgumentError
|
246
|
+
end
|
247
|
+
it "open(y: 1) creates a MyResource where x = nil and y = 1" do
|
248
|
+
expect(r = MyResource.open(y: 1)).to be_kind_of(MyResource)
|
249
|
+
expect(r.x).to be_nil
|
250
|
+
expect(r.y).to eq 1
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
describe :override_block do
|
256
|
+
context "property overrides" do
|
257
|
+
context "When MyResource has a primitive property that overrides coerce" do
|
258
|
+
with_struct(:MyResource) do
|
259
|
+
property :x, String do
|
260
|
+
def self.coerce(parent, value)
|
261
|
+
"#{value} is awesome"
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
it "MyResource.coerce(nil, { x: 1 }) yields { x: '1 is awesome' }" do
|
266
|
+
expect(MyResource.coerce(nil, { x: 1 }).to_h(:only_explicit)).to eq({ x: "1 is awesome" })
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
context "When MyResource has an untyped property that overrides coerce" do
|
271
|
+
with_struct(:MyResource) do
|
272
|
+
property :x do
|
273
|
+
def self.coerce(parent, value)
|
274
|
+
"#{value} is awesome"
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
it "MyResource.coerce(nil, { x: 1 }) yields { x: '1 is awesome' }" do
|
279
|
+
expect(MyResource.coerce(nil, { x: 1 }).to_h(:only_explicit)).to eq({ x: "1 is awesome" })
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
context "When MyResource has a resource typed property that overrides coerce" do
|
284
|
+
with_struct(:MyResource) do
|
285
|
+
property :x, MyResource do
|
286
|
+
def self.coerce(parent, value)
|
287
|
+
if value.is_a?(Integer)
|
288
|
+
x = value
|
289
|
+
value = MyResource.open
|
290
|
+
value.x "#{x} is awesome"
|
291
|
+
end
|
292
|
+
super
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
it "MyResource.coerce(nil, { x: 1 }) yields MyResource{ x: '1 is awesome' }" do
|
297
|
+
r = MyResource.coerce(nil, { x: 1 })
|
298
|
+
expect(r.x).to be_kind_of(MyResource)
|
299
|
+
expect(r.x.to_h(:only_explicit)).to eq({ x: "1 is awesome" })
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
context "When MyResource has an override that sets must(be between 0 and 10)" do
|
304
|
+
with_struct(:MyResource) do
|
305
|
+
property :x, Integer, nullable: true do
|
306
|
+
def self.run_count
|
307
|
+
@run_count ||= 0
|
308
|
+
end
|
309
|
+
def self.run_count=(value)
|
310
|
+
@run_count = value
|
311
|
+
end
|
312
|
+
must("be between 0 and 10") do
|
313
|
+
MyResource::X.run_count += 1
|
314
|
+
self >= 0 && self <= 10
|
315
|
+
end
|
316
|
+
end
|
317
|
+
property :run_count, Integer, default: 0
|
318
|
+
end
|
319
|
+
it "MyResource.coerce(nil, {x: 1}) succeeds" do
|
320
|
+
expect(MyResource.coerce(nil, { x: 1 }).to_h(:only_explicit)).to eq({ x: 1 })
|
321
|
+
expect(MyResource::X.run_count).to eq 1
|
322
|
+
end
|
323
|
+
it "MyResource.coerce(nil, {x: nil}) succeeds" do
|
324
|
+
expect(MyResource.coerce(nil, { x: nil }).to_h(:only_explicit)).to eq({ x: nil })
|
325
|
+
expect(MyResource::X.run_count).to eq 0
|
326
|
+
end
|
327
|
+
it "MyResource.coerce(nil, {x: 11}) fails" do
|
328
|
+
expect { MyResource.coerce(nil, { x: 11 }).to_h(:all) }.to raise_error(ChefResource::ValidationError)
|
329
|
+
end
|
330
|
+
it "MyResource.coerce(nil, {}) never runs it" do
|
331
|
+
expect(MyResource.coerce(nil, {}).to_h(:only_explicit)).to eq({})
|
332
|
+
expect(MyResource::X.run_count).to eq 0
|
333
|
+
end
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
describe :default do
|
339
|
+
context "When MyResource is a ResourceStruct with property :x, default: 15" do
|
340
|
+
with_struct(:MyResource) do
|
341
|
+
property :x, default: 15
|
342
|
+
end
|
343
|
+
it "x returns the default if not set" do
|
344
|
+
r = MyResource.open
|
345
|
+
expect(r.x).to eq 15
|
346
|
+
end
|
347
|
+
it "x returns the new value if it is set" do
|
348
|
+
r = MyResource.open
|
349
|
+
expect(r.x).to eq 15
|
350
|
+
expect(r.x = 20).to eq 20
|
351
|
+
expect(r.x).to eq 20
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
context "When MyResource is a ResourceStruct with property :x, 15 and property :y { x*2 } (default block)" do
|
356
|
+
with_struct(:MyResource) do
|
357
|
+
property :x, default: 15
|
358
|
+
property :y, default: ChefResource::LazyProc.new { x*2 }
|
359
|
+
end
|
360
|
+
it "x and y return the default if not set" do
|
361
|
+
r = MyResource.open
|
362
|
+
expect(r.x).to eq 15
|
363
|
+
expect(r.y).to eq 30
|
364
|
+
end
|
365
|
+
it "y returns the new value if it is set" do
|
366
|
+
r = MyResource.open
|
367
|
+
expect(r.y).to eq 30
|
368
|
+
expect(r.y = 20).to eq 20
|
369
|
+
expect(r.y).to eq 20
|
370
|
+
end
|
371
|
+
it "y returns a value based on x if x is set" do
|
372
|
+
r = MyResource.open
|
373
|
+
expect(r.y).to eq 30
|
374
|
+
expect(r.x = 20).to eq 20
|
375
|
+
expect(r.y).to eq 40
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
describe :coerce do
|
382
|
+
context "With a struct with x, y and z" do
|
383
|
+
with_struct(:MyResource) do
|
384
|
+
property :a, identity: true
|
385
|
+
property :b, identity: true
|
386
|
+
property :c, nullable: :validate
|
387
|
+
property :d, nullable: :validate
|
388
|
+
nullable :validate
|
389
|
+
end
|
390
|
+
|
391
|
+
context "multi-arg form" do
|
392
|
+
it "coerce(nil, 1, 2) yields a=1,b=2" do
|
393
|
+
expect(MyResource.coerce(nil, 1, 2).to_h(:only_explicit)).to eq({ a: 1, b: 2 })
|
394
|
+
end
|
395
|
+
it "coerce(nil, 1, 2, c: 3, d: 4) yields a=1, b=2, c=3, d=4" do
|
396
|
+
expect(MyResource.coerce(nil, 1, 2, c: 3, d: 4).to_h(:only_explicit)).to eq({ a: 1, b: 2, c: 3, d: 4 })
|
397
|
+
end
|
398
|
+
end
|
399
|
+
context "hash form" do
|
400
|
+
it "coerce(nil, a: 1, b: 2) yields a=1, b=2" do
|
401
|
+
expect(MyResource.coerce(nil, a: 1, b: 2).to_h(:only_explicit)).to eq({ a: 1, b: 2 })
|
402
|
+
end
|
403
|
+
it "coerce(nil, a: 1, b: 2, c: 3, d: 4) yields a=1, b=2, c=3, d=4" do
|
404
|
+
expect(MyResource.coerce(nil, a: 1, b: 2, c: 3, d: 4).to_h(:only_explicit)).to eq({ a: 1, b: 2, c: 3, d: 4 })
|
405
|
+
end
|
406
|
+
it "coerce(nil, c: 3, d: 4) fails" do
|
407
|
+
expect { MyResource.coerce(nil, c: 3, d: 4) }.to raise_error(ArgumentError)
|
408
|
+
end
|
409
|
+
end
|
410
|
+
it "coerce(nil, another resource) yields that resource" do
|
411
|
+
x = MyResource.open(1,2)
|
412
|
+
expect(MyResource.coerce(nil, x).object_id).to eq x.object_id
|
413
|
+
end
|
414
|
+
it "coerce(nil, nil) yields nil" do
|
415
|
+
expect(MyResource.coerce(nil, nil)).to be_nil
|
416
|
+
end
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
describe :load do
|
421
|
+
context "When load sets y to x*2 and z to x*3" do
|
422
|
+
with_struct(:MyResource) do
|
423
|
+
property :x, identity: true
|
424
|
+
property :y
|
425
|
+
property :z
|
426
|
+
property :num_loads
|
427
|
+
def load
|
428
|
+
y x*2
|
429
|
+
z x*3
|
430
|
+
self.num_loads ||= 0
|
431
|
+
self.num_loads += 1
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
it "MyResource.open(1).y == 2 and .z == 3" do
|
436
|
+
r = MyResource.open(1)
|
437
|
+
expect(r.x).to eq 1
|
438
|
+
expect(r.y).to eq 2
|
439
|
+
expect(r.z).to eq 3
|
440
|
+
end
|
441
|
+
|
442
|
+
it "load is only called once" do
|
443
|
+
r = MyResource.open(1)
|
444
|
+
expect(r.x).to eq 1
|
445
|
+
expect(r.y).to eq 2
|
446
|
+
expect(r.z).to eq 3
|
447
|
+
expect(r.x).to eq 1
|
448
|
+
expect(r.y).to eq 2
|
449
|
+
expect(r.z).to eq 3
|
450
|
+
expect(r.num_loads).to eq 1
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
context "When load sets y to x*2 and z has its own load_value that does x*3" do
|
455
|
+
with_struct(:MyResource) do
|
456
|
+
property :x, identity: true
|
457
|
+
property :y
|
458
|
+
property :z, load_value: ChefResource::LazyProc.new { self.num_loads += 1; x*3 }
|
459
|
+
property :num_loads, default: 0
|
460
|
+
def load
|
461
|
+
y x*2
|
462
|
+
self.num_loads += 1
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
466
|
+
it "MyResource.open(1).y == 2 and .z == 3" do
|
467
|
+
r = MyResource.open(1)
|
468
|
+
expect(r.x).to eq 1
|
469
|
+
expect(r.y).to eq 2
|
470
|
+
expect(r.z).to eq 3
|
471
|
+
end
|
472
|
+
|
473
|
+
it "current_resource.z yields x*3" do
|
474
|
+
r = MyResource.open(1)
|
475
|
+
expect(r.current_resource.z).to eq 3
|
476
|
+
end
|
477
|
+
|
478
|
+
it "load is only called twice" do
|
479
|
+
r = MyResource.open(1)
|
480
|
+
expect(r.x).to eq 1
|
481
|
+
expect(r.y).to eq 2
|
482
|
+
expect(r.z).to eq 3
|
483
|
+
expect(r.x).to eq 1
|
484
|
+
expect(r.y).to eq 2
|
485
|
+
expect(r.z).to eq 3
|
486
|
+
expect(r.num_loads).to eq 2
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
context "Primitive values" do
|
491
|
+
context "With a struct with Integers and Strings" do
|
492
|
+
with_struct(:MyResource) do
|
493
|
+
property :s1, String, identity: true, nullable: :validate
|
494
|
+
property :n1, Integer, identity: true, nullable: :validate
|
495
|
+
property :s2, String, nullable: :validate
|
496
|
+
property :n2, Integer, nullable: :validate
|
497
|
+
end
|
498
|
+
|
499
|
+
it "coerce(nil, s1: 'hi', n1: 1, s2: 'lo', n2: 2) succeeds" do
|
500
|
+
expect(MyResource.coerce(nil, s1: 'hi', n1: 1, s2: 'lo', n2: 2).to_h(:only_explicit)).to eq(s1: 'hi', n1: 1, s2: 'lo', n2: 2)
|
501
|
+
end
|
502
|
+
|
503
|
+
it "coerce(nil, s1: nil, n1: nil, s2: nil, n2: nil) succeeds" do
|
504
|
+
expect(MyResource.coerce(nil, s1: nil, n1: nil, s2: nil, n2: nil).to_h(:only_explicit)).to eq(s1: nil, n1: nil, s2: nil, n2: nil)
|
505
|
+
end
|
506
|
+
|
507
|
+
it "coerce(nil, s1: 'hi', n1: 1) succeeds" do
|
508
|
+
expect(MyResource.coerce(nil, s1: 'hi', n1: 1).to_h(:only_explicit)).to eq(s1: 'hi', n1: 1)
|
509
|
+
end
|
510
|
+
|
511
|
+
it "coerce(nil, s1: 'hi', n1: 'lo') fails" do
|
512
|
+
expect { MyResource.coerce(nil, s1: 'hi', n1: 'lo') }.to raise_error(ChefResource::ValidationError)
|
513
|
+
end
|
514
|
+
|
515
|
+
it "coerce(nil, s1: 'hi', n1: 'lo') fails" do
|
516
|
+
expect { MyResource.coerce(nil, s1: 'hi', n1: 'lo') }.to raise_error(ChefResource::ValidationError)
|
517
|
+
end
|
518
|
+
end
|
519
|
+
end
|
520
|
+
|
521
|
+
context "Lazy values" do
|
522
|
+
context "With a struct with x and some default values with instance_eval in various states" do
|
523
|
+
with_struct(:MyResource) do
|
524
|
+
def self.x
|
525
|
+
"outside.x"
|
526
|
+
end
|
527
|
+
property :x, default: "instance.x" do
|
528
|
+
def self.coerce(parent, value)
|
529
|
+
"coerce(#{value})"
|
530
|
+
end
|
531
|
+
end
|
532
|
+
property :default_no_params, default: ChefResource::LazyProc.new { "#{x} lazy_default" } do
|
533
|
+
def self.coerce(parent, value)
|
534
|
+
"coerce(#{value})"
|
535
|
+
end
|
536
|
+
end
|
537
|
+
property :default_instance_eval_symbol, default: ChefResource::LazyProc.new(:should_instance_eval) { "#{x} lazy_default" } do
|
538
|
+
def self.coerce(parent, value)
|
539
|
+
"coerce(#{value})"
|
540
|
+
end
|
541
|
+
end
|
542
|
+
property :default_instance_eval_true, default: ChefResource::LazyProc.new(should_instance_eval: true) { "#{x} lazy_default" } do
|
543
|
+
def self.coerce(parent, value)
|
544
|
+
"coerce(#{value})"
|
545
|
+
end
|
546
|
+
end
|
547
|
+
property :default_instance_eval_false, default: ChefResource::LazyProc.new(should_instance_eval: false) { "#{x} lazy_default" } do
|
548
|
+
def self.coerce(parent, value)
|
549
|
+
"coerce(#{value})"
|
550
|
+
end
|
551
|
+
end
|
552
|
+
property :default_block do
|
553
|
+
default { "#{x} lazy_default" }
|
554
|
+
def self.coerce(parent, value)
|
555
|
+
"coerce(#{value})"
|
556
|
+
end
|
557
|
+
end
|
558
|
+
|
559
|
+
property :z, default: "instance.z"
|
560
|
+
end
|
561
|
+
|
562
|
+
it "lazy default does instance_eval and coerces" do
|
563
|
+
r = MyResource.open
|
564
|
+
expect(r.default_no_params).to eq "coerce(coerce(instance.x) lazy_default)"
|
565
|
+
end
|
566
|
+
it "lazy default with :should_instance_eval does instance_eval and coerces" do
|
567
|
+
r = MyResource.open
|
568
|
+
expect(r.default_instance_eval_symbol).to eq "coerce(coerce(instance.x) lazy_default)"
|
569
|
+
end
|
570
|
+
it "lazy default with should_instance_eval: true does instance_eval and coerces" do
|
571
|
+
r = MyResource.open
|
572
|
+
expect(r.default_instance_eval_true).to eq "coerce(coerce(instance.x) lazy_default)"
|
573
|
+
end
|
574
|
+
it "lazy default with should_instance_eval: false does not do instance_eval, and coerces" do
|
575
|
+
r = MyResource.open
|
576
|
+
expect(r.default_instance_eval_false).to eq "coerce(outside.x lazy_default)"
|
577
|
+
end
|
578
|
+
it "default block does instance_eval and coerces" do
|
579
|
+
r = MyResource.open
|
580
|
+
expect(r.default_block).to eq "coerce(coerce(instance.x) lazy_default)"
|
581
|
+
end
|
582
|
+
|
583
|
+
def z
|
584
|
+
"outside.z"
|
585
|
+
end
|
586
|
+
|
587
|
+
it "lazy on x does not do instance_eval but coerces" do
|
588
|
+
r = MyResource.open
|
589
|
+
r.x ChefResource::LazyProc.new { "#{z} set_lazy" }
|
590
|
+
expect(r.x).to eq "coerce(outside.z set_lazy)"
|
591
|
+
end
|
592
|
+
it "lazy on x with :should_instance_eval does instance_eval and coerces" do
|
593
|
+
r = MyResource.open
|
594
|
+
r.x ChefResource::LazyProc.new(:should_instance_eval) { "#{z} set_lazy" }
|
595
|
+
expect(r.x).to eq "coerce(instance.z set_lazy)"
|
596
|
+
end
|
597
|
+
it "lazy on x should_instance_eval: true does instance_eval and coerces" do
|
598
|
+
r = MyResource.open
|
599
|
+
r.x ChefResource::LazyProc.new(:should_instance_eval) { "#{z} set_lazy" }
|
600
|
+
expect(r.x).to eq "coerce(instance.z set_lazy)"
|
601
|
+
end
|
602
|
+
it "lazy on x with should_instance_eval: false does instance_eval and coerces" do
|
603
|
+
r = MyResource.open
|
604
|
+
r.x ChefResource::LazyProc.new(should_instance_eval: false) { "#{z} set_lazy" }
|
605
|
+
expect(r.x).to eq "coerce(outside.z set_lazy)"
|
606
|
+
end
|
607
|
+
|
608
|
+
end
|
609
|
+
end
|
610
|
+
end
|
611
|
+
end
|