test_data 0.0.1 → 0.2.1
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/.github/workflows/ruby.yml +41 -0
- data/.standard.yml +2 -0
- data/CHANGELOG.md +43 -0
- data/Gemfile.lock +17 -15
- data/LICENSE.txt +1 -6
- data/README.md +1232 -17
- data/example/.gitignore +1 -4
- data/example/Gemfile +3 -0
- data/example/Gemfile.lock +100 -71
- data/example/README.md +2 -22
- data/example/config/application.rb +3 -0
- data/example/config/credentials.yml.enc +1 -1
- data/example/config/database.yml +2 -0
- data/example/spec/rails_helper.rb +64 -0
- data/example/spec/requests/boops_spec.rb +17 -0
- data/example/spec/requests/rails_fixtures_override_spec.rb +106 -0
- data/example/spec/spec_helper.rb +94 -0
- data/example/test/factories.rb +4 -0
- data/example/test/integration/better_mode_switching_demo_test.rb +41 -0
- data/example/test/integration/boops_that_boop_boops_test.rb +17 -0
- data/example/test/integration/dont_dump_tables_test.rb +7 -0
- data/example/test/integration/load_rollback_truncate_test.rb +190 -0
- data/example/test/integration/mode_switching_demo_test.rb +38 -0
- data/example/test/integration/parallel_boops_with_fixtures_test.rb +10 -0
- data/example/test/integration/parallel_boops_without_fixtures_test.rb +9 -0
- data/example/test/integration/rails_fixtures_double_load_test.rb +10 -0
- data/example/test/integration/rails_fixtures_override_test.rb +110 -0
- data/example/test/integration/test_data_hooks_test.rb +89 -0
- data/example/test/integration/transaction_committing_boops_test.rb +27 -0
- data/example/test/test_helper.rb +4 -31
- data/lib/generators/test_data/cable_yaml_generator.rb +18 -0
- data/lib/generators/test_data/database_yaml_generator.rb +3 -4
- data/lib/generators/test_data/environment_file_generator.rb +7 -14
- data/lib/generators/test_data/initializer_generator.rb +51 -0
- data/lib/generators/test_data/secrets_yaml_generator.rb +19 -0
- data/lib/generators/test_data/webpacker_yaml_generator.rb +4 -3
- data/lib/test_data.rb +42 -1
- data/lib/test_data/active_record_ext.rb +11 -0
- data/lib/test_data/config.rb +57 -4
- data/lib/test_data/configurators.rb +3 -0
- data/lib/test_data/configurators/cable_yaml.rb +25 -0
- data/lib/test_data/configurators/environment_file.rb +3 -2
- data/lib/test_data/configurators/initializer.rb +26 -0
- data/lib/test_data/configurators/secrets_yaml.rb +25 -0
- data/lib/test_data/configurators/webpacker_yaml.rb +4 -3
- data/lib/test_data/custom_loaders/abstract_base.rb +25 -0
- data/lib/test_data/custom_loaders/rails_fixtures.rb +42 -0
- data/lib/test_data/dumps_database.rb +55 -5
- data/lib/test_data/generator_support.rb +3 -0
- data/lib/test_data/inserts_test_data.rb +25 -0
- data/lib/test_data/loads_database_dumps.rb +8 -8
- data/lib/test_data/log.rb +76 -0
- data/lib/test_data/manager.rb +187 -0
- data/lib/test_data/rake.rb +20 -9
- data/lib/test_data/save_point.rb +34 -0
- data/lib/test_data/statistics.rb +31 -0
- data/lib/test_data/truncates_test_data.rb +31 -0
- data/lib/test_data/verifies_dumps_are_loadable.rb +4 -4
- data/lib/test_data/version.rb +1 -1
- data/script/reset_example_app +18 -0
- data/script/test +78 -13
- data/test_data.gemspec +1 -1
- metadata +36 -4
- data/lib/test_data/transactional_data_loader.rb +0 -77
@@ -0,0 +1,31 @@
|
|
1
|
+
module TestData
|
2
|
+
class TruncatesTestData
|
3
|
+
def initialize
|
4
|
+
@config = TestData.config
|
5
|
+
@statistics = TestData.statistics
|
6
|
+
end
|
7
|
+
|
8
|
+
def call
|
9
|
+
connection.disable_referential_integrity do
|
10
|
+
connection.execute("TRUNCATE TABLE #{tables_to_truncate.map { |t| connection.quote_table_name(t) }.join(", ")} #{"CASCADE" unless @config.truncate_these_test_data_tables.present?}")
|
11
|
+
end
|
12
|
+
@statistics.count_truncate!
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def tables_to_truncate
|
18
|
+
if @config.truncate_these_test_data_tables.present?
|
19
|
+
@config.truncate_these_test_data_tables
|
20
|
+
else
|
21
|
+
@tables_to_truncate ||= IO.foreach(@config.data_dump_path).grep(/^INSERT INTO/) { |line|
|
22
|
+
line.match(/^INSERT INTO ([^\s]+)/)&.captures&.first
|
23
|
+
}.compact.uniq
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def connection
|
28
|
+
ActiveRecord::Base.connection
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -8,22 +8,22 @@ module TestData
|
|
8
8
|
def call(quiet: false)
|
9
9
|
schema_dump_looks_good = Pathname.new(@config.schema_dump_full_path).readable?
|
10
10
|
if !quiet && !schema_dump_looks_good
|
11
|
-
warn "Warning: Database schema dump '#{@config.schema_dump_path}' not readable"
|
11
|
+
log.warn "Warning: Database schema dump '#{@config.schema_dump_path}' not readable"
|
12
12
|
end
|
13
13
|
|
14
14
|
data_dump_looks_good = Pathname.new(@config.data_dump_full_path).readable?
|
15
15
|
if !quiet && !data_dump_looks_good
|
16
|
-
warn "Warning: Database data dump '#{@config.data_dump_path}' not readable"
|
16
|
+
log.warn "Warning: Database data dump '#{@config.data_dump_path}' not readable"
|
17
17
|
end
|
18
18
|
|
19
19
|
non_test_data_dump_looks_good = Pathname.new(@config.non_test_data_dump_full_path).readable?
|
20
20
|
if !quiet && !non_test_data_dump_looks_good
|
21
|
-
warn "Warning: Database non-test data dump '#{@config.non_test_data_dump_path}' not readable"
|
21
|
+
log.warn "Warning: Database non-test data dump '#{@config.non_test_data_dump_path}' not readable"
|
22
22
|
end
|
23
23
|
|
24
24
|
database_empty = @detects_database_emptiness.empty?
|
25
25
|
unless quiet || database_empty
|
26
|
-
warn "Warning: Database '#{@config.database_name}' is not empty"
|
26
|
+
log.warn "Warning: Database '#{@config.database_name}' is not empty"
|
27
27
|
end
|
28
28
|
|
29
29
|
[
|
data/lib/test_data/version.rb
CHANGED
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
PS4='[script/test:${LINENO}] $ '
|
4
|
+
set -euo pipefail
|
5
|
+
set -x
|
6
|
+
|
7
|
+
cd example
|
8
|
+
|
9
|
+
# Reset database:
|
10
|
+
bin/rake db:drop
|
11
|
+
dropdb example_test_data 2>/dev/null || true
|
12
|
+
|
13
|
+
# Reset files:
|
14
|
+
git checkout app/models/boop.rb
|
15
|
+
git checkout config/application.rb
|
16
|
+
git checkout config/database.yml
|
17
|
+
git checkout db/schema.rb
|
18
|
+
git clean -xdf .
|
data/script/test
CHANGED
@@ -1,25 +1,30 @@
|
|
1
1
|
#!/usr/bin/env bash
|
2
2
|
|
3
3
|
PS4='[script/test:${LINENO}] $ '
|
4
|
-
set -
|
4
|
+
set -euo pipefail
|
5
5
|
set -x
|
6
6
|
|
7
|
-
#
|
7
|
+
# Install deps and make sure gem passes its own rake
|
8
8
|
bundle
|
9
9
|
bundle exec rake
|
10
|
-
|
11
|
-
# Exercise the example app
|
12
10
|
cd example
|
13
11
|
bundle
|
14
12
|
|
15
13
|
# Avoid test pollution by clearing out any initial state that might be lingering
|
16
|
-
|
17
|
-
|
14
|
+
cd ..
|
15
|
+
./script/reset_example_app
|
16
|
+
|
17
|
+
# Exercise the example app
|
18
|
+
cd example
|
19
|
+
bin/rake db:setup
|
18
20
|
|
19
21
|
# Test basic initial usage
|
20
22
|
bin/rake test_data:install
|
21
23
|
bin/rake test_data:dump
|
22
24
|
bin/rails test test/integration/basic_boops_test.rb
|
25
|
+
bundle exec rspec spec/requests/boops_spec.rb
|
26
|
+
bin/rails test test/integration/mode_switching_demo_test.rb
|
27
|
+
bin/rails test test/integration/better_mode_switching_demo_test.rb
|
23
28
|
bin/rails test test/integration/parallel_boops_with_fixtures_test.rb
|
24
29
|
bin/rails test test/integration/parallel_boops_without_fixtures_test.rb
|
25
30
|
|
@@ -30,6 +35,9 @@ bin/rails test test/integration/updated_boops_test.rb
|
|
30
35
|
|
31
36
|
# Test a migration being added and run and an out-of-date dump being loaded
|
32
37
|
cp ../test/fixtures/20210418220133_add_beep_to_boops.rb db/migrate
|
38
|
+
cp ../test/fixtures/20210624180810_create_pants.rb db/migrate
|
39
|
+
cp ../test/fixtures/pant.rb app/models
|
40
|
+
cp ../test/fixtures/pants.yml test/fixtures
|
33
41
|
bin/rake db:migrate
|
34
42
|
bin/rake db:test:prepare
|
35
43
|
bin/rake test_data:drop_database
|
@@ -38,12 +46,69 @@ RAILS_ENV=test_data bin/rake db:migrate
|
|
38
46
|
bin/rake test_data:dump
|
39
47
|
bin/rails test test/integration/migrated_boops_test.rb
|
40
48
|
|
41
|
-
#
|
49
|
+
# Run a test that commits test data thru to the database
|
50
|
+
bin/rails test test/integration/transaction_committing_boops_test.rb
|
42
51
|
|
43
|
-
#
|
52
|
+
# Run a test that prevents Rails fixtures for preloading and then loads them in a transaction
|
53
|
+
bin/rails test test/integration/rails_fixtures_override_test.rb
|
54
|
+
bundle exec rspec spec/requests/rails_fixtures_override_spec.rb
|
55
|
+
|
56
|
+
# Run a test that forgets to prevent Rails fixtures but then tries to load them in a transaction
|
57
|
+
bin/rails test test/integration/rails_fixtures_double_load_test.rb
|
58
|
+
|
59
|
+
# Add a second migration, this time without wiping the test_data db and with a table we want to ignore
|
60
|
+
cp ../test/fixtures/20210423114916_add_table_we_want_to_ignore.rb db/migrate
|
61
|
+
cp ../test/fixtures/chatty_audit_log.rb app/models
|
62
|
+
bin/rake db:migrate
|
63
|
+
RAILS_ENV=test_data bin/rake db:migrate
|
64
|
+
RAILS_ENV=test_data rails runner "50.times { ChattyAuditLog.create!(message: 'none of this matters') }"
|
65
|
+
# Gsub config file and uncomment + add table to excluded table list
|
66
|
+
ruby -e '
|
67
|
+
path = "config/initializers/test_data.rb"
|
68
|
+
IO.write(path, File.open(path) { |f|
|
69
|
+
f.read.gsub("# config.dont_dump_these_tables = []", "config.dont_dump_these_tables = [\"chatty_audit_logs\"]")
|
70
|
+
})
|
71
|
+
'
|
72
|
+
bin/rake test_data:dump
|
73
|
+
if grep -q "INSERT INTO public.chatty_audit_logs" "test/support/test_data/data.sql"; then
|
74
|
+
echo "Dump contained excluded table 'chatty_audit_logs'"
|
75
|
+
exit 1
|
76
|
+
fi
|
77
|
+
bin/rake db:test:prepare
|
78
|
+
bin/rails test test/integration/dont_dump_tables_test.rb
|
79
|
+
bin/rails test test/integration/load_rollback_truncate_test.rb
|
80
|
+
|
81
|
+
# Test circular FK constraints
|
82
|
+
cp ../test/fixtures/20210423190737_add_foreign_keys.rb db/migrate/
|
83
|
+
cp ../test/fixtures/boop_with_other_boops.rb app/models/boop.rb
|
84
|
+
RAILS_ENV=test_data bin/rake db:migrate
|
85
|
+
bin/rake test_data:dump
|
86
|
+
bin/rake db:migrate db:test:prepare
|
87
|
+
bin/rails test test/integration/boops_that_boop_boops_test.rb
|
88
|
+
|
89
|
+
# Make sure it loads cleanly again
|
44
90
|
bin/rake test_data:drop_database
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
#
|
49
|
-
|
91
|
+
bin/rake test_data:load
|
92
|
+
bin/rails test test/integration/boops_that_boop_boops_test.rb
|
93
|
+
|
94
|
+
# Test all the after hooks!
|
95
|
+
cp ../test/fixtures/20210729130542_add_materialized_meta_boop_view.rb db/migrate/
|
96
|
+
cp ../test/fixtures/meta_boop.rb app/models/meta_boop.rb
|
97
|
+
# Gsub config file to switch to structure.sql b/c materialized view
|
98
|
+
ruby -e '
|
99
|
+
path = "config/application.rb"
|
100
|
+
IO.write(path, File.open(path) { |f|
|
101
|
+
f.read.gsub("# config.active_record.schema_format = :sql", "config.active_record.schema_format = :sql")
|
102
|
+
})
|
103
|
+
'
|
104
|
+
rm db/schema.rb
|
105
|
+
bin/rake db:migrate db:test:prepare
|
106
|
+
RAILS_ENV=test_data bin/rake db:migrate
|
107
|
+
bin/rake test_data:dump
|
108
|
+
bin/rails test test/integration/test_data_hooks_test.rb
|
109
|
+
|
110
|
+
# Cleanup
|
111
|
+
cd ..
|
112
|
+
./script/reset_example_app
|
113
|
+
|
114
|
+
echo "You win!"
|
data/test_data.gemspec
CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
|
|
12
12
|
|
13
13
|
spec.metadata["homepage_uri"] = spec.homepage
|
14
14
|
spec.metadata["source_code_uri"] = spec.homepage
|
15
|
-
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/
|
15
|
+
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
|
16
16
|
|
17
17
|
# Specify which files should be added to the gem when it is released.
|
18
18
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: test_data
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Searls
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-07-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: railties
|
@@ -31,7 +31,9 @@ executables: []
|
|
31
31
|
extensions: []
|
32
32
|
extra_rdoc_files: []
|
33
33
|
files:
|
34
|
+
- ".github/workflows/ruby.yml"
|
34
35
|
- ".gitignore"
|
36
|
+
- ".standard.yml"
|
35
37
|
- CHANGELOG.md
|
36
38
|
- Gemfile
|
37
39
|
- Gemfile.lock
|
@@ -90,36 +92,66 @@ files:
|
|
90
92
|
- example/public/apple-touch-icon.png
|
91
93
|
- example/public/favicon.ico
|
92
94
|
- example/public/robots.txt
|
95
|
+
- example/spec/rails_helper.rb
|
96
|
+
- example/spec/requests/boops_spec.rb
|
97
|
+
- example/spec/requests/rails_fixtures_override_spec.rb
|
98
|
+
- example/spec/spec_helper.rb
|
93
99
|
- example/test/application_system_test_case.rb
|
100
|
+
- example/test/factories.rb
|
94
101
|
- example/test/fixtures/boops.yml
|
95
102
|
- example/test/integration/basic_boops_test.rb
|
103
|
+
- example/test/integration/better_mode_switching_demo_test.rb
|
104
|
+
- example/test/integration/boops_that_boop_boops_test.rb
|
105
|
+
- example/test/integration/dont_dump_tables_test.rb
|
106
|
+
- example/test/integration/load_rollback_truncate_test.rb
|
96
107
|
- example/test/integration/migrated_boops_test.rb
|
108
|
+
- example/test/integration/mode_switching_demo_test.rb
|
97
109
|
- example/test/integration/parallel_boops_with_fixtures_test.rb
|
98
110
|
- example/test/integration/parallel_boops_without_fixtures_test.rb
|
111
|
+
- example/test/integration/rails_fixtures_double_load_test.rb
|
112
|
+
- example/test/integration/rails_fixtures_override_test.rb
|
113
|
+
- example/test/integration/test_data_hooks_test.rb
|
114
|
+
- example/test/integration/transaction_committing_boops_test.rb
|
99
115
|
- example/test/integration/updated_boops_test.rb
|
100
116
|
- example/test/test_helper.rb
|
117
|
+
- lib/generators/test_data/cable_yaml_generator.rb
|
101
118
|
- lib/generators/test_data/database_yaml_generator.rb
|
102
119
|
- lib/generators/test_data/environment_file_generator.rb
|
120
|
+
- lib/generators/test_data/initializer_generator.rb
|
121
|
+
- lib/generators/test_data/secrets_yaml_generator.rb
|
103
122
|
- lib/generators/test_data/webpacker_yaml_generator.rb
|
104
123
|
- lib/test_data.rb
|
124
|
+
- lib/test_data/active_record_ext.rb
|
105
125
|
- lib/test_data/active_support_ext.rb
|
106
126
|
- lib/test_data/config.rb
|
107
127
|
- lib/test_data/configuration_verification.rb
|
108
128
|
- lib/test_data/configurators.rb
|
129
|
+
- lib/test_data/configurators/cable_yaml.rb
|
109
130
|
- lib/test_data/configurators/database_yaml.rb
|
110
131
|
- lib/test_data/configurators/environment_file.rb
|
132
|
+
- lib/test_data/configurators/initializer.rb
|
133
|
+
- lib/test_data/configurators/secrets_yaml.rb
|
111
134
|
- lib/test_data/configurators/webpacker_yaml.rb
|
135
|
+
- lib/test_data/custom_loaders/abstract_base.rb
|
136
|
+
- lib/test_data/custom_loaders/rails_fixtures.rb
|
112
137
|
- lib/test_data/detects_database_emptiness.rb
|
113
138
|
- lib/test_data/dumps_database.rb
|
114
139
|
- lib/test_data/error.rb
|
140
|
+
- lib/test_data/generator_support.rb
|
141
|
+
- lib/test_data/inserts_test_data.rb
|
115
142
|
- lib/test_data/installs_configuration.rb
|
116
143
|
- lib/test_data/loads_database_dumps.rb
|
144
|
+
- lib/test_data/log.rb
|
145
|
+
- lib/test_data/manager.rb
|
117
146
|
- lib/test_data/railtie.rb
|
118
147
|
- lib/test_data/rake.rb
|
119
|
-
- lib/test_data/
|
148
|
+
- lib/test_data/save_point.rb
|
149
|
+
- lib/test_data/statistics.rb
|
150
|
+
- lib/test_data/truncates_test_data.rb
|
120
151
|
- lib/test_data/verifies_configuration.rb
|
121
152
|
- lib/test_data/verifies_dumps_are_loadable.rb
|
122
153
|
- lib/test_data/version.rb
|
154
|
+
- script/reset_example_app
|
123
155
|
- script/test
|
124
156
|
- test_data.gemspec
|
125
157
|
homepage: https://github.com/testdouble/test_data
|
@@ -127,7 +159,7 @@ licenses: []
|
|
127
159
|
metadata:
|
128
160
|
homepage_uri: https://github.com/testdouble/test_data
|
129
161
|
source_code_uri: https://github.com/testdouble/test_data
|
130
|
-
changelog_uri: https://github.com/testdouble/test_data/blob/
|
162
|
+
changelog_uri: https://github.com/testdouble/test_data/blob/main/CHANGELOG.md
|
131
163
|
post_install_message:
|
132
164
|
rdoc_options: []
|
133
165
|
require_paths:
|
@@ -1,77 +0,0 @@
|
|
1
|
-
require "fileutils"
|
2
|
-
|
3
|
-
module TestData
|
4
|
-
def self.load_data_dump
|
5
|
-
@transactional_data_loader ||= TransactionalDataLoader.new
|
6
|
-
@transactional_data_loader.load_data_dump
|
7
|
-
end
|
8
|
-
|
9
|
-
def self.rollback(to: :after_data_load)
|
10
|
-
raise Error.new("rollback called before load_data_dump") unless @transactional_data_loader.present?
|
11
|
-
@transactional_data_loader.rollback(to: to)
|
12
|
-
end
|
13
|
-
|
14
|
-
class TransactionalDataLoader
|
15
|
-
SavePoint = Struct.new(:name, :transaction, keyword_init: true)
|
16
|
-
|
17
|
-
def initialize
|
18
|
-
@config = TestData.config
|
19
|
-
@save_points = []
|
20
|
-
@dump_count = 0
|
21
|
-
end
|
22
|
-
|
23
|
-
def load_data_dump
|
24
|
-
create_save_point(:before_data_load) unless save_point?(:before_data_load)
|
25
|
-
unless save_point?(:after_data_load)
|
26
|
-
execute_data_dump
|
27
|
-
@dump_count += 1
|
28
|
-
create_save_point(:after_data_load)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def rollback(to:)
|
33
|
-
return unless save_point?(to)
|
34
|
-
rollback_save_point(to)
|
35
|
-
end
|
36
|
-
|
37
|
-
private
|
38
|
-
|
39
|
-
def execute_data_dump
|
40
|
-
search_path = execute("show search_path").first["search_path"]
|
41
|
-
execute(File.read(@config.data_dump_full_path))
|
42
|
-
execute <<~SQL
|
43
|
-
select pg_catalog.set_config('search_path', '#{search_path}', false)
|
44
|
-
SQL
|
45
|
-
end
|
46
|
-
|
47
|
-
def save_point?(name)
|
48
|
-
purge_closed_save_points!
|
49
|
-
@save_points.any? { |sp| sp.name == name }
|
50
|
-
end
|
51
|
-
|
52
|
-
def create_save_point(name)
|
53
|
-
save_point = SavePoint.new(
|
54
|
-
name: name,
|
55
|
-
transaction: ActiveRecord::Base.connection.begin_transaction(joinable: false, _lazy: false)
|
56
|
-
)
|
57
|
-
@save_points << save_point
|
58
|
-
end
|
59
|
-
|
60
|
-
def rollback_save_point(name)
|
61
|
-
if (save_point = @save_points.find { |sp| sp.name == name }) && save_point.transaction.open?
|
62
|
-
save_point.transaction.rollback
|
63
|
-
end
|
64
|
-
purge_closed_save_points!
|
65
|
-
end
|
66
|
-
|
67
|
-
def purge_closed_save_points!
|
68
|
-
@save_points = @save_points.select { |save_point|
|
69
|
-
save_point.transaction.open?
|
70
|
-
}
|
71
|
-
end
|
72
|
-
|
73
|
-
def execute(sql)
|
74
|
-
ActiveRecord::Base.connection.execute(sql)
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|