bogus 0.1.5 → 0.1.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/.travis.yml +1 -0
- data/Gemfile +0 -2
- data/Guardfile +1 -1
- data/bogus.gemspec +1 -1
- data/features/changelog.md +8 -0
- data/features/contract_tests/contract_tests_mocks.feature +4 -4
- data/features/contract_tests/contract_tests_stubs.feature +4 -4
- data/features/contract_tests/custom_overwritten_class.feature +3 -3
- data/features/fakes/fake_objects.feature +2 -2
- data/lib/bogus/core_ext.rb +5 -1
- data/lib/bogus/rspec/syntax.rb +3 -0
- data/lib/bogus/version.rb +1 -1
- data/spec/bogus/bugs/rbx_instance_eval_bug_spec.rb +20 -0
- data/spec/bogus/bugs/rbx_jruby_stub_on_class_spec.rb +45 -0
- data/spec/bogus/contracts/adds_contract_verification_spec.rb +10 -10
- data/spec/bogus/contracts/adds_recording_spec.rb +9 -9
- data/spec/bogus/contracts/interactions_repository_spec.rb +13 -13
- data/spec/bogus/contracts/records_double_interactions_spec.rb +9 -7
- data/spec/bogus/contracts/verifies_contracts_spec.rb +9 -9
- data/spec/bogus/fakes/copies_classes_spec.rb +5 -5
- data/spec/bogus/fakes/creates_fakes_spec.rb +13 -13
- data/spec/bogus/fakes/creates_fakes_with_stubbed_methods_spec.rb +21 -19
- data/spec/bogus/fakes/fake_ar_attributes_spec.rb +3 -1
- data/spec/bogus/fakes/fake_configuration_spec.rb +3 -3
- data/spec/bogus/fakes/fakes_classes_spec.rb +6 -6
- data/spec/bogus/fakes/faking_factories_spec.rb +3 -1
- data/spec/bogus/fakes/frozen_fakes_spec.rb +3 -1
- data/spec/bogus/fakes/registers_created_fakes_spec.rb +8 -8
- data/spec/bogus/fakes/resets_overwritten_classes_spec.rb +8 -8
- data/spec/bogus/fakes/stubbing_new_method_on_fake_class_spec.rb +3 -1
- data/spec/bogus/mocking_dsl_spec.rb +3 -1
- data/spec/bogus/rspec/syntax_spec.rb +16 -0
- data/spec/bogus/ruby_2_1_support_spec.rb +4 -2
- data/spec/bogus/ruby_2_support_spec.rb +4 -2
- data/spec/bogus/stubbing/double_spec.rb +18 -18
- data/spec/bogus/stubbing/have_received_matcher_spec.rb +13 -14
- data/spec/bogus/stubbing/interaction_spec.rb +7 -7
- data/spec/bogus/stubbing/multi_stubber_spec.rb +5 -5
- data/spec/bogus/stubbing/record_interactions_spec.rb +2 -2
- data/spec/bogus/stubbing/resets_stubbed_methods_spec.rb +5 -5
- data/spec/bogus/stubbing/shadow_spec.rb +6 -6
- data/spec/bogus/stubbing/stubbing_existing_methods_on_fakes_spec.rb +1 -1
- data/spec/spec_helper.rb +6 -13
- data/spec/support/ruby_features.rb +19 -0
- metadata +10 -5
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Bogus::RSpecSyntax do
|
4
|
+
context = self
|
5
|
+
let(:syntax) { Bogus::RSpecSyntax.new(context) }
|
6
|
+
|
7
|
+
it "gets the described class" do
|
8
|
+
expect(syntax.described_class).to eq(Bogus::RSpecSyntax)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "can set the described class" do
|
12
|
+
syntax.described_class = Object
|
13
|
+
|
14
|
+
expect(described_class).to eq(Object)
|
15
|
+
end
|
16
|
+
end
|
@@ -1,13 +1,15 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
if
|
3
|
+
if RubyFeatures.required_keyword_arguments?
|
4
4
|
describe "Ruby 2.1 required keyword arguments" do
|
5
5
|
class ExampleForRequiredKeywordArgs
|
6
6
|
eval "def foo(x:); end"
|
7
7
|
eval "def bar(x:, **rest); end"
|
8
8
|
end
|
9
9
|
|
10
|
-
|
10
|
+
before do
|
11
|
+
extend Bogus::MockingDSL
|
12
|
+
end
|
11
13
|
|
12
14
|
context "with regular objects" do
|
13
15
|
subject { ExampleForRequiredKeywordArgs.new }
|
@@ -1,13 +1,15 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
if
|
3
|
+
if RubyFeatures.keyword_arguments?
|
4
4
|
describe "Ruby 2.0 keyword arguments" do
|
5
5
|
class ExampleForKeywordArgs
|
6
6
|
eval "def foo(x: 1); end"
|
7
7
|
eval "def bar(x: 1, **rest); end"
|
8
8
|
end
|
9
9
|
|
10
|
-
|
10
|
+
before do
|
11
|
+
extend Bogus::MockingDSL
|
12
|
+
end
|
11
13
|
|
12
14
|
context "with regular objects" do
|
13
15
|
subject { ExampleForKeywordArgs.new }
|
@@ -4,59 +4,59 @@ module Bogus
|
|
4
4
|
describe Double do
|
5
5
|
shared_examples_for "double behavior" do
|
6
6
|
it "tracks existence of test doubles" do
|
7
|
-
|
7
|
+
expect(double_tracker).to receive(:track).with(object)
|
8
8
|
|
9
|
-
|
9
|
+
double_instance.stub.foo("a", "b") { "the result" }
|
10
10
|
end
|
11
11
|
|
12
12
|
it "does not track existence of the double if verify fails" do
|
13
|
-
|
13
|
+
allow(verifies_stub_definition).to receive(:verify!).with(object, :foo, ["a", "b"]) { raise NameError }
|
14
14
|
|
15
15
|
expect {
|
16
|
-
|
16
|
+
double_instance.stub.foo("a", "b") { "the result" }
|
17
17
|
}.to raise_error
|
18
18
|
|
19
|
-
expect(double_tracker).not_to have_received.
|
19
|
+
expect(double_tracker).not_to have_received(:track).with(object)
|
20
20
|
end
|
21
21
|
|
22
22
|
it "verifies stub definition" do
|
23
|
-
|
23
|
+
expect(verifies_stub_definition).to receive(:verify!).with(object, :foo, ["a", "b"])
|
24
24
|
|
25
|
-
|
25
|
+
double_instance.stub.foo("a", "b") { "the result" }
|
26
26
|
end
|
27
27
|
|
28
28
|
it "stubs shadow methods" do
|
29
29
|
object.extend RecordInteractions
|
30
|
-
|
30
|
+
expect(object.__shadow__).to receive(:stubs).with(:foo, "a", "b")
|
31
31
|
|
32
|
-
|
32
|
+
double_instance.stub.foo("a", "b") { "the result" }
|
33
33
|
end
|
34
34
|
|
35
35
|
it "mocks shadow methods" do
|
36
36
|
object.extend RecordInteractions
|
37
|
-
|
37
|
+
expect(object.__shadow__).to receive(:mocks).with(:foo, "a", "b")
|
38
38
|
|
39
|
-
|
39
|
+
double_instance.mock.foo("a", "b") { "the result" }
|
40
40
|
end
|
41
41
|
|
42
42
|
it "adds method overwriting" do
|
43
|
-
|
43
|
+
double_instance.stub.foo("a", "b") { "the result" }
|
44
44
|
|
45
45
|
expect(overwrites_methods.overwrites).to eq([[object, :foo]])
|
46
46
|
end
|
47
47
|
|
48
48
|
it "records double interactions" do
|
49
|
-
|
49
|
+
expect(records_double_interactions).to receive(:record).with(object, :foo, ["a", "b"])
|
50
50
|
|
51
|
-
|
51
|
+
double_instance.stub.foo("a", "b") { "the result" }
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
55
|
-
let(:double_tracker) {
|
56
|
-
let(:verifies_stub_definition) {
|
57
|
-
let(:records_double_interactions) {
|
55
|
+
let(:double_tracker) { double(:double_tracker, track: nil) }
|
56
|
+
let(:verifies_stub_definition) { double(:verifies_stub_definition, verify!: nil) }
|
57
|
+
let(:records_double_interactions) { double(:records_double_interactions, record: nil) }
|
58
58
|
let(:overwrites_methods) { FakeMethodOverwriter.new }
|
59
|
-
let(:
|
59
|
+
let(:double_instance) { isolate(Double) }
|
60
60
|
|
61
61
|
context "with regular objects" do
|
62
62
|
let(:object) { Samples::Foo.new }
|
@@ -1,10 +1,9 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Bogus::HaveReceivedMatcher do
|
4
|
-
let(:verifies_stub_definition) {
|
5
|
-
let(:records_double_interactions) {
|
4
|
+
let(:verifies_stub_definition) { double(:verifies_stub_definition, verify!: nil) }
|
5
|
+
let(:records_double_interactions) { double(:records_double_interactions, record: nil) }
|
6
6
|
let(:have_received_matcher) { isolate(Bogus::HaveReceivedMatcher) }
|
7
|
-
let(:have_received) { have_received_matcher.method_call }
|
8
7
|
let(:fake) { Samples::FooFake.new }
|
9
8
|
|
10
9
|
before do
|
@@ -13,33 +12,33 @@ describe Bogus::HaveReceivedMatcher do
|
|
13
12
|
|
14
13
|
shared_examples_for "have_received_matcher" do
|
15
14
|
it "matches when the spy has received the message" do
|
16
|
-
expect(fake).to
|
15
|
+
expect(fake).to bogus_have_received(:foo, "a", "b")
|
17
16
|
end
|
18
17
|
|
19
18
|
it "does not match if the spy hasn't received the message" do
|
20
|
-
expect(fake).not_to
|
19
|
+
expect(fake).not_to bogus_have_received(:foo, "a", "c")
|
21
20
|
end
|
22
21
|
|
23
22
|
it "verifies that the method call has the right signature" do
|
24
|
-
|
23
|
+
expect(verifies_stub_definition).to receive(:verify!).with(fake, :foo, ["a", "b"])
|
25
24
|
|
26
|
-
|
25
|
+
bogus_have_received(:foo, "a", "b")
|
27
26
|
|
28
27
|
have_received_matcher.matches?(fake)
|
29
28
|
end
|
30
29
|
|
31
30
|
it "records the interaction so that it can be checked by contract tests" do
|
32
|
-
|
31
|
+
expect(records_double_interactions).to receive(:record).with(fake, :foo, ["a", "b"])
|
33
32
|
|
34
|
-
|
33
|
+
bogus_have_received(:foo, "a", "b")
|
35
34
|
|
36
35
|
have_received_matcher.matches?(fake)
|
37
36
|
end
|
38
37
|
|
39
38
|
it "returns a readable error message for object with no shadow" do
|
40
|
-
|
39
|
+
bogus_have_received(:upcase)
|
41
40
|
|
42
|
-
expect(have_received_matcher.matches?("foo")).to
|
41
|
+
expect(have_received_matcher.matches?("foo")).to be(false)
|
43
42
|
expect(have_received_matcher.failure_message_for_should).to eq(Bogus::HaveReceivedMatcher::NO_SHADOW)
|
44
43
|
expect(have_received_matcher.failure_message).to eq(Bogus::HaveReceivedMatcher::NO_SHADOW)
|
45
44
|
expect(have_received_matcher.failure_message_for_should_not).to eq(Bogus::HaveReceivedMatcher::NO_SHADOW)
|
@@ -47,7 +46,7 @@ describe Bogus::HaveReceivedMatcher do
|
|
47
46
|
end
|
48
47
|
|
49
48
|
it "returns a readable error message for fakes" do
|
50
|
-
|
49
|
+
bogus_have_received(:foo, "a", "c")
|
51
50
|
|
52
51
|
have_received_matcher.matches?(fake)
|
53
52
|
|
@@ -62,7 +61,7 @@ describe Bogus::HaveReceivedMatcher do
|
|
62
61
|
end
|
63
62
|
|
64
63
|
context "with method_missing builder" do
|
65
|
-
def
|
64
|
+
def bogus_have_received(method, *args)
|
66
65
|
have_received_matcher.build.__send__(method, *args)
|
67
66
|
end
|
68
67
|
|
@@ -70,7 +69,7 @@ describe Bogus::HaveReceivedMatcher do
|
|
70
69
|
end
|
71
70
|
|
72
71
|
context "with method call builder" do
|
73
|
-
def
|
72
|
+
def bogus_have_received(*args)
|
74
73
|
have_received_matcher.build(*args)
|
75
74
|
end
|
76
75
|
|
@@ -52,7 +52,7 @@ module Bogus
|
|
52
52
|
first = create_interaction(first_interaction)
|
53
53
|
second = create_interaction(second_interaction)
|
54
54
|
|
55
|
-
expect(Interaction.same?(recorded: first, stubbed: second)).to
|
55
|
+
expect(Interaction.same?(recorded: first, stubbed: second)).to be(true)
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
@@ -61,7 +61,7 @@ module Bogus
|
|
61
61
|
first = create_interaction(first_interaction)
|
62
62
|
second = create_interaction(second_interaction)
|
63
63
|
|
64
|
-
expect(Interaction.same?(recorded: first, stubbed: second)).to
|
64
|
+
expect(Interaction.same?(recorded: first, stubbed: second)).to be(false)
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
@@ -69,21 +69,21 @@ module Bogus
|
|
69
69
|
first = Interaction.new(:foo, [:bar]) { raise SomeError }
|
70
70
|
second = Interaction.new(:foo, [:bar]) { nil }
|
71
71
|
|
72
|
-
expect(Interaction.same?(recorded: first, stubbed: second)).to
|
72
|
+
expect(Interaction.same?(recorded: first, stubbed: second)).to be(false)
|
73
73
|
end
|
74
74
|
|
75
75
|
it "differs raised exceptions from ones just returned from the block" do
|
76
76
|
first = Interaction.new(:foo, [:bar]) { raise SomeError }
|
77
77
|
second = Interaction.new(:foo, [:bar]) { SomeError }
|
78
78
|
|
79
|
-
expect(Interaction.same?(recorded: first, stubbed: second)).to
|
79
|
+
expect(Interaction.same?(recorded: first, stubbed: second)).to be(false)
|
80
80
|
end
|
81
81
|
|
82
82
|
it "considers exceptions of the same type as equal" do
|
83
83
|
first = Interaction.new(:foo, [:bar]) { raise SomeError }
|
84
84
|
second = Interaction.new(:foo, [:bar]) { raise SomeError }
|
85
85
|
|
86
|
-
expect(Interaction.same?(recorded: first, stubbed: second)).to
|
86
|
+
expect(Interaction.same?(recorded: first, stubbed: second)).to be(true)
|
87
87
|
end
|
88
88
|
|
89
89
|
context 'when comparing arguments with custom #== implementations' do
|
@@ -97,14 +97,14 @@ module Bogus
|
|
97
97
|
first = Interaction.new(:with, [Dev.new(:psyho)])
|
98
98
|
second = Interaction.new(:with, [Dev.new(:psyho)])
|
99
99
|
|
100
|
-
expect(Interaction.same?(recorded: first, stubbed: second)).to
|
100
|
+
expect(Interaction.same?(recorded: first, stubbed: second)).to be(true)
|
101
101
|
end
|
102
102
|
|
103
103
|
it "considers two interactions != when the arguments are !=" do
|
104
104
|
first = Interaction.new(:with, [Dev.new(:wrozka)])
|
105
105
|
second = Interaction.new(:with, [Dev.new(:yundt)])
|
106
106
|
|
107
|
-
expect(Interaction.same?(recorded: first, stubbed: second)).to
|
107
|
+
expect(Interaction.same?(recorded: first, stubbed: second)).to be(false)
|
108
108
|
end
|
109
109
|
end
|
110
110
|
end
|
@@ -1,22 +1,22 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Bogus::MultiStubber do
|
4
|
-
let(:
|
5
|
-
let(:
|
6
|
-
let(:
|
4
|
+
let(:fake_double) { FakeDouble.new }
|
5
|
+
let(:bogus_any_args) { Bogus::AnyArgs }
|
6
|
+
let(:create_double) { proc{ fake_double } }
|
7
7
|
|
8
8
|
let(:multi_stubber) { isolate(Bogus::MultiStubber) }
|
9
9
|
|
10
10
|
it "stubs all the given methods with any args returning the given value" do
|
11
11
|
multi_stubber.stub_all(Object.new, foo: 1, bar: 2)
|
12
12
|
|
13
|
-
expect(
|
13
|
+
expect(fake_double.stubbed).to eq([[:foo, [bogus_any_args], 1], [:bar, [bogus_any_args], 2]])
|
14
14
|
end
|
15
15
|
|
16
16
|
it "uses passed procs as the return value block" do
|
17
17
|
multi_stubber.stub_all(Object.new, foo: proc{ 1 })
|
18
18
|
|
19
|
-
expect(
|
19
|
+
expect(fake_double.stubbed).to eq([[:foo, [bogus_any_args], 1]])
|
20
20
|
end
|
21
21
|
|
22
22
|
class FakeDouble
|
@@ -11,13 +11,13 @@ describe Bogus::RecordInteractions do
|
|
11
11
|
it "allows verifying that interactions happened" do
|
12
12
|
sample.__record__(:foo, 1, 2, 3)
|
13
13
|
|
14
|
-
expect(sample.__shadow__.has_received(:foo, [1,2,3])).to
|
14
|
+
expect(sample.__shadow__.has_received(:foo, [1,2,3])).to be(true)
|
15
15
|
end
|
16
16
|
|
17
17
|
it "allows verifying that interactions didn't happen" do
|
18
18
|
sample.__record__(:bar)
|
19
19
|
|
20
|
-
expect(sample.__shadow__.has_received(:foo, [1,2,3])).to
|
20
|
+
expect(sample.__shadow__.has_received(:foo, [1,2,3])).to be(false)
|
21
21
|
end
|
22
22
|
|
23
23
|
it "returns self from record by default" do
|
@@ -1,15 +1,15 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Bogus::ResetsStubbedMethods do
|
4
|
-
let(:double_tracker) {
|
5
|
-
let(:overwrites_methods) {
|
4
|
+
let(:double_tracker) { double }
|
5
|
+
let(:overwrites_methods) { double }
|
6
6
|
|
7
7
|
let(:resets_stubbed_methods) { isolate(Bogus::ResetsStubbedMethods) }
|
8
8
|
|
9
9
|
it "resets all stubbed objects back to previous implementation" do
|
10
|
-
foo =
|
11
|
-
|
12
|
-
|
10
|
+
foo = double
|
11
|
+
allow(double_tracker).to receive(:doubles) { [foo] }
|
12
|
+
expect(overwrites_methods).to receive(:reset).with(foo)
|
13
13
|
|
14
14
|
resets_stubbed_methods.reset_all_doubles
|
15
15
|
end
|
@@ -11,12 +11,12 @@ describe Bogus::Shadow do
|
|
11
11
|
end
|
12
12
|
|
13
13
|
it "returns the called methods" do
|
14
|
-
expect(shadow.has_received(:foo, ["a", "b"])).to
|
14
|
+
expect(shadow.has_received(:foo, ["a", "b"])).to be(true)
|
15
15
|
end
|
16
16
|
|
17
17
|
it "does not return true for interactions that did not happen" do
|
18
|
-
expect(shadow.has_received(:foo, ["a", "c"])).to
|
19
|
-
expect(shadow.has_received(:bar, ["a", "c"])).to
|
18
|
+
expect(shadow.has_received(:foo, ["a", "c"])).to be(false)
|
19
|
+
expect(shadow.has_received(:bar, ["a", "c"])).to be(false)
|
20
20
|
end
|
21
21
|
end
|
22
22
|
end
|
@@ -81,7 +81,7 @@ describe Bogus::Shadow do
|
|
81
81
|
it "allows spying on calls using any args" do
|
82
82
|
shadow.run(:foo, "a", "c")
|
83
83
|
|
84
|
-
expect(shadow.has_received(:foo, [Bogus::AnyArgs])).to
|
84
|
+
expect(shadow.has_received(:foo, [Bogus::AnyArgs])).to be(true)
|
85
85
|
end
|
86
86
|
end
|
87
87
|
|
@@ -103,7 +103,7 @@ describe Bogus::Shadow do
|
|
103
103
|
it "allows spying on calls using anything in args" do
|
104
104
|
shadow.run(:foo, "a", "b")
|
105
105
|
|
106
|
-
expect(shadow.has_received(:foo, [Bogus::Anything, "b"])).to
|
106
|
+
expect(shadow.has_received(:foo, [Bogus::Anything, "b"])).to be(true)
|
107
107
|
end
|
108
108
|
end
|
109
109
|
|
@@ -177,7 +177,7 @@ describe Bogus::Shadow do
|
|
177
177
|
|
178
178
|
it "contributes towards unsatisfied interactions" do
|
179
179
|
interactions = shadow.unsatisfied_interactions
|
180
|
-
expect(interactions).to
|
180
|
+
expect(interactions.size).to eq(1)
|
181
181
|
expect(interactions.first.method).to eq(:foo)
|
182
182
|
expect(interactions.first.args).to eq(["a", "b"])
|
183
183
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
require 'simplecov'
|
2
2
|
begin
|
3
3
|
require "coveralls"
|
4
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
5
|
+
SimpleCov::Formatter::HTMLFormatter,
|
6
|
+
Coveralls::SimpleCov::Formatter]
|
4
7
|
rescue LoadError
|
5
8
|
warn "warning: coveralls gem not found; skipping Coveralls"
|
9
|
+
SimpleCov.formatter = SimpleCov::Formatter::HTMLFormatter
|
6
10
|
end
|
7
11
|
|
8
|
-
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
9
|
-
SimpleCov::Formatter::HTMLFormatter,
|
10
|
-
Coveralls::SimpleCov::Formatter]
|
11
|
-
|
12
12
|
SimpleCov.start do
|
13
13
|
add_filter "/spec/"
|
14
14
|
end
|
@@ -16,19 +16,12 @@ end
|
|
16
16
|
require 'bogus'
|
17
17
|
require 'dependor/rspec'
|
18
18
|
|
19
|
-
require 'rr'
|
20
|
-
|
21
19
|
require_relative 'support/sample_fake'
|
22
20
|
require_relative 'support/fake_creator_of_fakes'
|
23
21
|
require_relative 'support/matchers'
|
24
22
|
require_relative 'support/shared_examples_for_keyword_arguments'
|
23
|
+
require_relative 'support/ruby_features'
|
25
24
|
|
26
25
|
RSpec.configure do |config|
|
27
|
-
config.
|
28
|
-
config.mock_framework = :rr
|
29
|
-
end
|
30
|
-
|
31
|
-
# this should not be necessary...
|
32
|
-
def have_received(method = nil)
|
33
|
-
RR::Adapters::Rspec::InvocationMatcher.new(method)
|
26
|
+
config.mock_with :rspec
|
34
27
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module RubyFeatures
|
2
|
+
module_function
|
3
|
+
|
4
|
+
def keyword_arguments?
|
5
|
+
ruby?('2.0') && !rbx?
|
6
|
+
end
|
7
|
+
|
8
|
+
def required_keyword_arguments?
|
9
|
+
ruby?('2.1') && keyword_arguments?
|
10
|
+
end
|
11
|
+
|
12
|
+
def ruby?(version)
|
13
|
+
RUBY_VERSION >= version
|
14
|
+
end
|
15
|
+
|
16
|
+
def rbx?
|
17
|
+
RUBY_ENGINE == 'rbx'
|
18
|
+
end
|
19
|
+
end
|