mutant 0.7.4 → 0.7.5
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 +4 -4
- data/.travis.yml +3 -2
- data/Changelog.md +6 -2
- data/Gemfile +1 -0
- data/config/flay.yml +1 -1
- data/config/reek.yml +1 -1
- data/lib/mutant.rb +4 -3
- data/lib/mutant/cli.rb +2 -2
- data/lib/mutant/config.rb +2 -3
- data/lib/mutant/env.rb +29 -132
- data/lib/mutant/env/bootstrap.rb +155 -0
- data/lib/mutant/expression.rb +27 -0
- data/lib/mutant/expression/namespace.rb +2 -2
- data/lib/mutant/matcher/method.rb +1 -1
- data/lib/mutant/matcher/method/instance.rb +1 -1
- data/lib/mutant/mutation.rb +2 -30
- data/lib/mutant/mutator/node/begin.rb +1 -3
- data/lib/mutant/mutator/node/if.rb +2 -2
- data/lib/mutant/reporter/cli/printer.rb +5 -4
- data/lib/mutant/result.rb +1 -1
- data/lib/mutant/runner.rb +3 -3
- data/lib/mutant/runner/sink.rb +86 -53
- data/lib/mutant/selector.rb +17 -0
- data/lib/mutant/selector/expression.rb +28 -0
- data/lib/mutant/subject.rb +1 -19
- data/lib/mutant/version.rb +1 -1
- data/mutant.gemspec +3 -3
- data/spec/spec_helper.rb +1 -1
- data/spec/support/corpus.rb +5 -5
- data/spec/support/shared_context.rb +1 -6
- data/spec/unit/mutant/cli_spec.rb +2 -2
- data/spec/unit/mutant/env/boostrap_spec.rb +130 -0
- data/spec/unit/mutant/env_spec.rb +61 -49
- data/spec/unit/mutant/expression_spec.rb +13 -0
- data/spec/unit/mutant/matcher/filter_spec.rb +1 -1
- data/spec/unit/mutant/matcher/method/instance_spec.rb +1 -1
- data/spec/unit/mutant/matcher/method/singleton_spec.rb +1 -1
- data/spec/unit/mutant/mutation_spec.rb +0 -36
- data/spec/unit/mutant/reporter/trace_spec.rb +1 -1
- data/spec/unit/mutant/require_highjack_spec.rb +2 -4
- data/spec/unit/mutant/result/subject_spec.rb +2 -1
- data/spec/unit/mutant/runner/{sink_spec.rb → sink/mutation_spec.rb} +1 -3
- data/spec/unit/mutant/runner_spec.rb +4 -3
- data/spec/unit/mutant/selector/expression_spec.rb +60 -0
- data/spec/unit/mutant/subject/method/instance_spec.rb +3 -5
- data/spec/unit/mutant/subject/method/singleton_spec.rb +2 -3
- data/spec/unit/mutant/subject_spec.rb +1 -53
- data/spec/unit/mutant/warning_filter_spec.rb +2 -4
- metadata +18 -15
- data/lib/mutant/line_trace.rb +0 -34
- data/spec/unit/mutant/line_trace_spec.rb +0 -38
- data/spec/unit/mutant/subject/context_spec.rb +0 -16
data/lib/mutant/version.rb
CHANGED
data/mutant.gemspec
CHANGED
@@ -21,16 +21,16 @@ Gem::Specification.new do |gem|
|
|
21
21
|
gem.extra_rdoc_files = %w[TODO LICENSE]
|
22
22
|
gem.executables = %w[mutant]
|
23
23
|
|
24
|
-
gem.required_ruby_version = '>=
|
24
|
+
gem.required_ruby_version = '>= 1.9.3'
|
25
25
|
|
26
|
-
gem.add_runtime_dependency('parser', '~> 2.2.
|
26
|
+
gem.add_runtime_dependency('parser', '~> 2.2.0.2')
|
27
27
|
gem.add_runtime_dependency('ast', '~> 2.0')
|
28
28
|
gem.add_runtime_dependency('diff-lcs', '~> 1.2')
|
29
29
|
gem.add_runtime_dependency('parallel', '~> 1.3')
|
30
30
|
gem.add_runtime_dependency('morpher', '~> 0.2.3')
|
31
31
|
gem.add_runtime_dependency('procto', '~> 0.0.2')
|
32
32
|
gem.add_runtime_dependency('abstract_type', '~> 0.0.7')
|
33
|
-
gem.add_runtime_dependency('unparser', '~> 0.
|
33
|
+
gem.add_runtime_dependency('unparser', '~> 0.2.2')
|
34
34
|
gem.add_runtime_dependency('ice_nine', '~> 0.11.1')
|
35
35
|
gem.add_runtime_dependency('adamantium', '~> 0.2.0')
|
36
36
|
gem.add_runtime_dependency('memoizable', '~> 0.4.2')
|
data/spec/spec_helper.rb
CHANGED
@@ -34,7 +34,7 @@ require 'test_app'
|
|
34
34
|
module Fixtures
|
35
35
|
TEST_CONFIG = Mutant::Config::DEFAULT.update(reporter: Mutant::Reporter::Trace.new)
|
36
36
|
TEST_CACHE = Mutant::Cache.new
|
37
|
-
TEST_ENV = Mutant::Env.
|
37
|
+
TEST_ENV = Mutant::Env::Bootstrap.call(TEST_CONFIG, TEST_CACHE)
|
38
38
|
end # Fixtures
|
39
39
|
|
40
40
|
module ParserHelper
|
data/spec/support/corpus.rb
CHANGED
@@ -94,7 +94,7 @@ module Corpus
|
|
94
94
|
TMP.mkdir unless TMP.directory?
|
95
95
|
if repo_path.exist?
|
96
96
|
Dir.chdir(repo_path) do
|
97
|
-
system(%w[git fetch])
|
97
|
+
system(%w[git fetch origin])
|
98
98
|
system(%w[git reset --hard])
|
99
99
|
system(%w[git clean -f -d -x])
|
100
100
|
system(%w[git checkout origin/master])
|
@@ -119,10 +119,10 @@ module Corpus
|
|
119
119
|
def install_mutant
|
120
120
|
return if noinstall?
|
121
121
|
relative = ROOT.relative_path_from(repo_path)
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
122
|
+
repo_path.join('Gemfile').open('a') do |file|
|
123
|
+
file << "gem 'mutant', path: '#{relative}'\n"
|
124
|
+
file << "gem 'mutant-rspec', path: '#{relative}'\n"
|
125
|
+
end
|
126
126
|
lockfile = repo_path.join('Gemfile.lock')
|
127
127
|
lockfile.delete if lockfile.exist?
|
128
128
|
system('bundle install')
|
@@ -25,7 +25,6 @@ module SharedContext
|
|
25
25
|
let(:matchable_scopes) { double('matchable scopes', length: 10) }
|
26
26
|
let(:test_a) { double('test a', identification: 'test-a') }
|
27
27
|
let(:test_b) { double('test b', identification: 'test-b') }
|
28
|
-
let(:actor_names) { [] }
|
29
28
|
let(:message_sequence) { FakeActor::MessageSequence.new }
|
30
29
|
|
31
30
|
let(:status) do
|
@@ -38,16 +37,11 @@ module SharedContext
|
|
38
37
|
|
39
38
|
let(:config) do
|
40
39
|
Mutant::Config::DEFAULT.update(
|
41
|
-
actor_env: actor_env,
|
42
40
|
jobs: 1,
|
43
41
|
reporter: Mutant::Reporter::Trace.new
|
44
42
|
)
|
45
43
|
end
|
46
44
|
|
47
|
-
let(:actor_env) do
|
48
|
-
FakeActor::Env.new(message_sequence, actor_names)
|
49
|
-
end
|
50
|
-
|
51
45
|
let(:subject_a) do
|
52
46
|
double(
|
53
47
|
'subject a',
|
@@ -111,6 +105,7 @@ module SharedContext
|
|
111
105
|
let(:subject_a_result) do
|
112
106
|
Mutant::Result::Subject.new(
|
113
107
|
subject: subject_a,
|
108
|
+
tests: [test_a],
|
114
109
|
mutation_results: [mutation_a_result, mutation_b_result]
|
115
110
|
)
|
116
111
|
end
|
@@ -9,7 +9,7 @@ end
|
|
9
9
|
RSpec.shared_examples_for 'a cli parser' do
|
10
10
|
it { expect(subject.config.integration).to eql(expected_integration) }
|
11
11
|
it { expect(subject.config.reporter).to eql(expected_reporter) }
|
12
|
-
it { expect(subject.config.
|
12
|
+
it { expect(subject.config.matcher).to eql(expected_matcher_config) }
|
13
13
|
end
|
14
14
|
|
15
15
|
RSpec.describe Mutant::CLI do
|
@@ -25,7 +25,7 @@ RSpec.describe Mutant::CLI do
|
|
25
25
|
|
26
26
|
before do
|
27
27
|
expect(Mutant::CLI).to receive(:call).with(arguments).and_return(config)
|
28
|
-
expect(Mutant::Env).to receive(:
|
28
|
+
expect(Mutant::Env::Bootstrap).to receive(:call).with(config).and_return(env)
|
29
29
|
expect(Mutant::Runner).to receive(:call).with(env).and_return(report)
|
30
30
|
end
|
31
31
|
|
@@ -0,0 +1,130 @@
|
|
1
|
+
RSpec.describe Mutant::Env::Bootstrap do
|
2
|
+
let(:config) do
|
3
|
+
Mutant::Config::DEFAULT.update(
|
4
|
+
jobs: 1,
|
5
|
+
reporter: Mutant::Reporter::Trace.new,
|
6
|
+
includes: [],
|
7
|
+
requires: [],
|
8
|
+
matcher: Mutant::Matcher::Config::DEFAULT
|
9
|
+
)
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:expected_env) do
|
13
|
+
Mutant::Env.new(
|
14
|
+
cache: Mutant::Cache.new,
|
15
|
+
subjects: [],
|
16
|
+
matchable_scopes: [],
|
17
|
+
mutations: [],
|
18
|
+
config: config,
|
19
|
+
selector: Mutant::Selector::Expression.new(config.integration),
|
20
|
+
actor_env: Mutant::Actor::Env.new(Thread)
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
shared_examples_for 'bootstrap call' do
|
25
|
+
it { should eql(expected_env) }
|
26
|
+
end
|
27
|
+
|
28
|
+
let(:object_space_modules) { [] }
|
29
|
+
|
30
|
+
before do
|
31
|
+
allow(ObjectSpace).to receive(:each_object).with(Module).and_return(object_space_modules.each)
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '.call' do
|
35
|
+
subject { described_class.call(config) }
|
36
|
+
|
37
|
+
context 'when Module#name calls result in exceptions' do
|
38
|
+
let(:invalid_class) do
|
39
|
+
Class.new do
|
40
|
+
def self.name
|
41
|
+
fail
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
let(:object_space_modules) { [invalid_class] }
|
47
|
+
|
48
|
+
after do
|
49
|
+
# Fix Class#name so other specs do not see this one
|
50
|
+
class << invalid_class
|
51
|
+
undef :name
|
52
|
+
def name
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'warns via reporter' do
|
58
|
+
expected_warnings = [
|
59
|
+
"Class#name from: #{invalid_class} raised an error: RuntimeError. #{Mutant::Env::SEMANTICS_MESSAGE}"
|
60
|
+
]
|
61
|
+
|
62
|
+
expect { subject }.to change { config.reporter.warn_calls }.from([]).to(expected_warnings)
|
63
|
+
end
|
64
|
+
|
65
|
+
include_examples 'bootstrap call'
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'when includes are present' do
|
69
|
+
let(:config) { super().update(includes: %w[foo bar]) }
|
70
|
+
|
71
|
+
before do
|
72
|
+
%w[foo bar].each do |component|
|
73
|
+
expect($LOAD_PATH).to receive(:<<).with(component).and_return($LOAD_PATH)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
include_examples 'bootstrap call'
|
78
|
+
end
|
79
|
+
|
80
|
+
context 'when Module#name does not return a String or nil' do
|
81
|
+
let(:invalid_class) do
|
82
|
+
Class.new do
|
83
|
+
def self.name
|
84
|
+
Object
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
let(:object_space_modules) { [invalid_class] }
|
90
|
+
|
91
|
+
after do
|
92
|
+
# Fix Class#name so other specs do not see this one
|
93
|
+
class << invalid_class
|
94
|
+
undef :name
|
95
|
+
def name
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'warns via reporter' do
|
101
|
+
|
102
|
+
expected_warnings = [
|
103
|
+
"Class#name from: #{invalid_class.inspect} returned Object. #{Mutant::Env::SEMANTICS_MESSAGE}"
|
104
|
+
]
|
105
|
+
|
106
|
+
expect { subject }.to change { config.reporter.warn_calls }.from([]).to(expected_warnings)
|
107
|
+
end
|
108
|
+
|
109
|
+
include_examples 'bootstrap call'
|
110
|
+
end
|
111
|
+
|
112
|
+
context 'when scope matches expression' do
|
113
|
+
let(:mutations) { [double('Mutation')] }
|
114
|
+
let(:subjects) { [double('Subject', mutations: mutations)] }
|
115
|
+
|
116
|
+
before do
|
117
|
+
expect(Mutant::Matcher::Compiler).to receive(:call).and_return(subjects)
|
118
|
+
end
|
119
|
+
|
120
|
+
let(:expected_env) do
|
121
|
+
super().update(
|
122
|
+
subjects: subjects,
|
123
|
+
mutations: mutations
|
124
|
+
)
|
125
|
+
end
|
126
|
+
|
127
|
+
include_examples 'bootstrap call'
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -1,67 +1,79 @@
|
|
1
1
|
RSpec.describe Mutant::Env do
|
2
|
-
|
2
|
+
context '#kill' do
|
3
|
+
let(:object) do
|
4
|
+
described_class.new(
|
5
|
+
config: config,
|
6
|
+
actor_env: Mutant::Actor::Env.new(Thread),
|
7
|
+
cache: Mutant::Cache.new,
|
8
|
+
selector: selector,
|
9
|
+
subjects: [],
|
10
|
+
mutations: [],
|
11
|
+
matchable_scopes: []
|
12
|
+
)
|
13
|
+
end
|
3
14
|
|
4
|
-
|
5
|
-
|
15
|
+
let(:config) do
|
16
|
+
Mutant::Config::DEFAULT.update(
|
17
|
+
isolation: isolation,
|
18
|
+
integration: integration
|
19
|
+
)
|
20
|
+
end
|
6
21
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
22
|
+
let(:isolation) { Mutant::Isolation::None }
|
23
|
+
let(:integration) { double('Integration') }
|
24
|
+
let(:isolation) { double('Isolation') }
|
25
|
+
let(:mutation) { Mutant::Mutation::Evil.new(mutation_subject, Mutant::AST::Nodes::N_NIL) }
|
26
|
+
let(:wrapped_node) { double('Wrapped Node') }
|
27
|
+
let(:context) { double('Context') }
|
28
|
+
let(:test_a) { double('Test A') }
|
29
|
+
let(:test_b) { double('Test B') }
|
30
|
+
let(:tests) { [test_a, test_b] }
|
31
|
+
let(:selector) { double('Selector') }
|
14
32
|
|
15
|
-
|
16
|
-
|
17
|
-
|
33
|
+
let(:mutation_subject) do
|
34
|
+
double(
|
35
|
+
'Subject',
|
36
|
+
identification: 'subject',
|
37
|
+
context: context,
|
38
|
+
source: 'original',
|
39
|
+
tests: tests
|
40
|
+
)
|
41
|
+
end
|
18
42
|
|
19
|
-
|
43
|
+
subject { object.kill(mutation) }
|
20
44
|
|
21
|
-
|
22
|
-
|
23
|
-
undef :name
|
24
|
-
def name
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
45
|
+
shared_examples_for 'mutation kill' do
|
46
|
+
it { should eql(Mutant::Result::Mutation.new(mutation: mutation, test_result: test_result)) }
|
28
47
|
end
|
29
48
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
Object
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
expected_warnings = ["Class#name from: #{klass.inspect} returned Object. #{Mutant::Env::SEMANTICS_MESSAGE}"]
|
49
|
+
before do
|
50
|
+
expect(selector).to receive(:call).with(mutation_subject).and_return(tests)
|
51
|
+
allow(Time).to receive(:now).and_return(Time.at(0))
|
52
|
+
end
|
39
53
|
|
40
|
-
|
54
|
+
context 'when isolation does not raise error' do
|
55
|
+
let(:test_result) { double('Test Result A', passed: false) }
|
41
56
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
57
|
+
before do
|
58
|
+
expect(isolation).to receive(:call).and_yield.and_return(test_result)
|
59
|
+
expect(mutation_subject).to receive(:public?).and_return(true).ordered
|
60
|
+
expect(mutation_subject).to receive(:prepare).and_return(mutation_subject).ordered
|
61
|
+
expect(context).to receive(:root).with(s(:nil)).and_return(wrapped_node).ordered
|
62
|
+
expect(Mutant::Loader::Eval).to receive(:call).with(wrapped_node, mutation_subject).and_return(nil).ordered
|
63
|
+
expect(integration).to receive(:call).with(tests).and_return(test_result).ordered
|
48
64
|
end
|
49
|
-
end
|
50
|
-
end
|
51
65
|
|
52
|
-
|
53
|
-
|
54
|
-
let(:result) { double('Result') }
|
55
|
-
let(:mutation) { double('Mutation') }
|
66
|
+
include_examples 'mutation kill'
|
67
|
+
end
|
56
68
|
|
57
|
-
|
69
|
+
context 'when isolation does raise error' do
|
70
|
+
before do
|
71
|
+
expect(isolation).to receive(:call).and_raise(Mutant::Isolation::Error, 'test-error')
|
72
|
+
end
|
58
73
|
|
59
|
-
|
60
|
-
expect(mutation).to receive(:kill).with(config.isolation, config.integration).and_return(result)
|
61
|
-
end
|
74
|
+
let(:test_result) { Mutant::Result::Test.new(tests: tests, output: 'test-error', passed: false, runtime: 0.0) }
|
62
75
|
|
63
|
-
|
64
|
-
should eql(Mutant::Result::Mutation.new(mutation: mutation, test_result: result))
|
76
|
+
include_examples 'mutation kill'
|
65
77
|
end
|
66
78
|
end
|
67
79
|
end
|
@@ -44,6 +44,13 @@ RSpec.describe Mutant::Expression do
|
|
44
44
|
it_should_behave_like 'an idempotent method'
|
45
45
|
end
|
46
46
|
|
47
|
+
describe '#_dump' do
|
48
|
+
let(:object) { described_class.parse('Foo') }
|
49
|
+
subject { object._dump(double('Level')) }
|
50
|
+
|
51
|
+
it { should eql('Foo') }
|
52
|
+
end
|
53
|
+
|
47
54
|
describe '.parse' do
|
48
55
|
subject { object.parse(input) }
|
49
56
|
|
@@ -64,4 +71,10 @@ RSpec.describe Mutant::Expression do
|
|
64
71
|
it { should eql(Mutant::Expression::Namespace::Exact.new('Foo')) }
|
65
72
|
end
|
66
73
|
end
|
74
|
+
|
75
|
+
describe '._load' do
|
76
|
+
subject { described_class._load('Foo') }
|
77
|
+
|
78
|
+
it { should eql(described_class.parse('Foo')) }
|
79
|
+
end
|
67
80
|
end
|
@@ -6,7 +6,7 @@ RSpec.describe Mutant::Matcher::Filter do
|
|
6
6
|
subject { object.each { |entry| yields << entry } }
|
7
7
|
|
8
8
|
let(:matcher) { [subject_a, subject_b] }
|
9
|
-
let(:predicate) { ->(
|
9
|
+
let(:predicate) { ->(node) { node.eql?(subject_a) } }
|
10
10
|
|
11
11
|
let(:subject_a) { double('Subject A') }
|
12
12
|
let(:subject_b) { double('Subject B') }
|
@@ -5,7 +5,7 @@ RSpec.describe Mutant::Matcher::Method::Instance do
|
|
5
5
|
let(:reporter) { Fixtures::TEST_CONFIG.reporter }
|
6
6
|
|
7
7
|
describe '#each' do
|
8
|
-
subject { object.each
|
8
|
+
subject { object.each(&yields.method(:<<)) }
|
9
9
|
|
10
10
|
let(:object) { described_class.build(env, scope, method) }
|
11
11
|
let(:method) { scope.instance_method(method_name) }
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# rubocop:disable ClassAndModuleChildren
|
2
2
|
RSpec.describe Mutant::Matcher::Method::Singleton, '#each' do
|
3
|
-
subject { object.each
|
3
|
+
subject { object.each(&yields.method(:<<)) }
|
4
4
|
|
5
5
|
let(:object) { described_class.new(env, scope, method) }
|
6
6
|
let(:method) { scope.method(method_name) }
|
@@ -21,42 +21,6 @@ RSpec.describe Mutant::Mutation do
|
|
21
21
|
let(:test_b) { double('Test B') }
|
22
22
|
let(:tests) { [test_a, test_b] }
|
23
23
|
|
24
|
-
describe '#kill' do
|
25
|
-
let(:isolation) { Mutant::Isolation::None }
|
26
|
-
let(:integration) { double('Integration') }
|
27
|
-
let(:object) { Mutant::Mutation::Evil.new(mutation_subject, Mutant::AST::Nodes::N_NIL) }
|
28
|
-
let(:wrapped_node) { double('Wrapped Node') }
|
29
|
-
|
30
|
-
subject { object.kill(isolation, integration) }
|
31
|
-
|
32
|
-
before do
|
33
|
-
allow(Time).to receive(:now).and_return(Time.at(0))
|
34
|
-
end
|
35
|
-
|
36
|
-
context 'when isolation does not raise error' do
|
37
|
-
let(:test_result) { double('Test Result A', passed: false) }
|
38
|
-
|
39
|
-
before do
|
40
|
-
expect(mutation_subject).to receive(:public?).and_return(true).ordered
|
41
|
-
expect(mutation_subject).to receive(:prepare).and_return(mutation_subject).ordered
|
42
|
-
expect(context).to receive(:root).with(s(:nil)).and_return(wrapped_node).ordered
|
43
|
-
expect(Mutant::Loader::Eval).to receive(:call).with(wrapped_node, mutation_subject).and_return(nil).ordered
|
44
|
-
expect(integration).to receive(:call).with(tests).and_return(test_result).ordered
|
45
|
-
expect(test_result).to receive(:update).with(tests: tests).and_return(test_result).ordered
|
46
|
-
end
|
47
|
-
|
48
|
-
it { should eql(test_result) }
|
49
|
-
end
|
50
|
-
|
51
|
-
context 'when isolation does raise error' do
|
52
|
-
before do
|
53
|
-
expect(isolation).to receive(:call).and_raise(Mutant::Isolation::Error, 'test-error')
|
54
|
-
end
|
55
|
-
|
56
|
-
it { should eql(Mutant::Result::Test.new(tests: tests, output: 'test-error', passed: false, runtime: 0.0)) }
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
24
|
describe '#code' do
|
61
25
|
subject { object.code }
|
62
26
|
|