682 lines
21 KiB
Ruby
682 lines
21 KiB
Ruby
require "tmpdir"
|
|
require_relative "../base"
|
|
|
|
require "vagrant/bundler"
|
|
|
|
describe Vagrant::Bundler::SolutionFile do
|
|
let(:plugin_path) { Pathname.new(tmpdir) + "plugin_file" }
|
|
let(:solution_path) { Pathname.new(tmpdir) + "solution_file" }
|
|
let(:tmpdir) { @tmpdir ||= Dir.mktmpdir("vagrant-bundler-test") }
|
|
let(:subject) {
|
|
described_class.new(
|
|
plugin_file: plugin_path,
|
|
solution_file: solution_path
|
|
)
|
|
}
|
|
|
|
after do
|
|
if @tmpdir
|
|
FileUtils.rm_rf(@tmpdir)
|
|
@tmpdir = nil
|
|
end
|
|
end
|
|
|
|
describe "#initialize" do
|
|
context "file paths" do
|
|
context "with solution_file not provided" do
|
|
let(:subject) { described_class.new(plugin_file: plugin_path) }
|
|
|
|
it "should set the plugin_file" do
|
|
expect(subject.plugin_file.to_s).to eq(plugin_path.to_s)
|
|
end
|
|
|
|
it "should set solution path to same directory" do
|
|
expect(subject.solution_file.to_s).to eq(plugin_path.to_s + ".sol")
|
|
end
|
|
end
|
|
|
|
context "with custom solution_file provided" do
|
|
let(:subject) { described_class.
|
|
new(plugin_file: plugin_path, solution_file: solution_path) }
|
|
|
|
it "should set the plugin file path" do
|
|
expect(subject.plugin_file.to_s).to eq(plugin_path.to_s)
|
|
end
|
|
|
|
it "should set the solution file path to given value" do
|
|
expect(subject.solution_file.to_s).to eq(solution_path.to_s)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "initialization behavior" do
|
|
context "on creation" do
|
|
before { expect_any_instance_of(described_class).to receive(:load) }
|
|
|
|
it "should load solution file during initialization" do
|
|
subject
|
|
end
|
|
end
|
|
|
|
it "should be invalid by default" do
|
|
expect(subject.valid?).to be_falsey
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#dependency_list=" do
|
|
it "should accept a list of Gem::Dependency instances" do
|
|
list = ["dep1", "dep2"].map{ |x| Gem::Dependency.new(x) }
|
|
expect(subject.dependency_list = list).to eq(list)
|
|
end
|
|
|
|
it "should error if list includes instance not Gem::Dependency" do
|
|
list = ["dep1", "dep2"].map{ |x| Gem::Dependency.new(x) } << :invalid
|
|
expect{ subject.dependency_list = list }.to raise_error(TypeError)
|
|
end
|
|
end
|
|
|
|
describe "#delete!" do
|
|
context "when file does not exist" do
|
|
before { subject.solution_file.delete if subject.solution_file.exist? }
|
|
|
|
it "should return false" do
|
|
expect(subject.delete!).to be_falsey
|
|
end
|
|
|
|
it "should not exist" do
|
|
subject.delete!
|
|
expect(subject.solution_file.exist?).to be_falsey
|
|
end
|
|
end
|
|
|
|
context "when file does exist" do
|
|
before { subject.solution_file.write('x') }
|
|
|
|
it "should return true" do
|
|
expect(subject.delete!).to be_truthy
|
|
end
|
|
|
|
it "should not exist" do
|
|
expect(subject.solution_file.exist?).to be_truthy
|
|
subject.delete!
|
|
expect(subject.solution_file.exist?).to be_falsey
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "store!" do
|
|
context "when plugin file does not exist" do
|
|
before { subject.plugin_file.delete if subject.plugin_file.exist? }
|
|
|
|
it "should return false" do
|
|
expect(subject.store!).to be_falsey
|
|
end
|
|
|
|
it "should not create a solution file" do
|
|
subject.store!
|
|
expect(subject.solution_file.exist?).to be_falsey
|
|
end
|
|
end
|
|
|
|
|
|
context "when plugin file does exist" do
|
|
before { subject.plugin_file.write("x") }
|
|
|
|
it "should return true" do
|
|
expect(subject.store!).to be_truthy
|
|
end
|
|
|
|
it "should create a solution file" do
|
|
expect(subject.solution_file.exist?).to be_falsey
|
|
subject.store!
|
|
expect(subject.solution_file.exist?).to be_truthy
|
|
end
|
|
|
|
context "stored file" do
|
|
let(:content) {
|
|
@content ||= JSON.load(subject.solution_file.read)
|
|
}
|
|
before { subject.store! }
|
|
after { @content = nil }
|
|
|
|
it "should store JSON hash" do
|
|
expect(content).to be_a(Hash)
|
|
end
|
|
|
|
it "should include dependencies key as array value" do
|
|
expect(content["dependencies"]).to be_a(Array)
|
|
end
|
|
|
|
it "should include checksum key as string value" do
|
|
expect(content["checksum"]).to be_a(String)
|
|
end
|
|
|
|
it "should include vagrant_version key as string value" do
|
|
expect(content["vagrant_version"]).to be_a(String)
|
|
end
|
|
|
|
it "should include vagrant_version key that matches current version" do
|
|
expect(content["vagrant_version"]).to eq(Vagrant::VERSION)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "behavior" do
|
|
context "when storing new solution set" do
|
|
let(:deps) { ["dep1", "dep2"].map{ |n| Gem::Dependency.new(n) } }
|
|
|
|
context "when plugin file does not exist" do
|
|
before { subject.solution_file.delete if subject.solution_file.exist? }
|
|
|
|
it "should not create a solution file" do
|
|
subject.dependency_list = deps
|
|
subject.store!
|
|
expect(subject.solution_file.exist?).to be_falsey
|
|
end
|
|
end
|
|
|
|
context "when plugin file does exist" do
|
|
before { subject.plugin_file.write("x") }
|
|
|
|
it "should create a solution file" do
|
|
subject.dependency_list = deps
|
|
subject.store!
|
|
expect(subject.solution_file.exist?).to be_truthy
|
|
end
|
|
|
|
it "should update solution file instance to valid" do
|
|
expect(subject.valid?).to be_falsey
|
|
subject.dependency_list = deps
|
|
subject.store!
|
|
expect(subject.valid?).to be_truthy
|
|
end
|
|
|
|
context "when solution file does exist" do
|
|
before do
|
|
subject.dependency_list = deps
|
|
subject.store!
|
|
end
|
|
|
|
it "should be a valid solution" do
|
|
subject = described_class.new(
|
|
plugin_file: plugin_path,
|
|
solution_file: solution_path
|
|
)
|
|
expect(subject.valid?).to be_truthy
|
|
end
|
|
|
|
it "should have expected dependency list" do
|
|
subject = described_class.new(
|
|
plugin_file: plugin_path,
|
|
solution_file: solution_path
|
|
)
|
|
expect(subject.dependency_list).to eq(deps)
|
|
end
|
|
|
|
context "when plugin file has been changed" do
|
|
before { subject.plugin_file.write("xy") }
|
|
|
|
it "should not be a valid solution" do
|
|
subject = described_class.new(
|
|
plugin_file: plugin_path,
|
|
solution_file: solution_path
|
|
)
|
|
expect(subject.valid?).to be_falsey
|
|
end
|
|
|
|
it "should have empty dependency list" do
|
|
subject = described_class.new(
|
|
plugin_file: plugin_path,
|
|
solution_file: solution_path
|
|
)
|
|
expect(subject.dependency_list).to be_empty
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe Vagrant::Bundler do
|
|
include_context "unit"
|
|
|
|
let(:iso_env) { isolated_environment }
|
|
let(:env) { iso_env.create_vagrant_env }
|
|
let(:tmpdir) { @v_tmpdir ||= Pathname.new(Dir.mktmpdir("vagrant-bundler-test")) }
|
|
|
|
before do
|
|
@tmpdir = Dir.mktmpdir("vagrant-bundler-test")
|
|
@vh = ENV["VAGRANT_HOME"]
|
|
ENV["VAGRANT_HOME"] = @tmpdir
|
|
end
|
|
|
|
after do
|
|
ENV["VAGRANT_HOME"] = @vh
|
|
FileUtils.rm_rf(@tmpdir)
|
|
FileUtils.rm_rf(@v_tmpdir) if @v_tmpdir
|
|
end
|
|
|
|
it "should isolate gem path based on Ruby version" do
|
|
expect(subject.plugin_gem_path.to_s).to end_with(RUBY_VERSION)
|
|
end
|
|
|
|
it "should not have an env_plugin_gem_path by default" do
|
|
expect(subject.env_plugin_gem_path).to be_nil
|
|
end
|
|
|
|
describe "#initialize" do
|
|
it "should automatically set the plugin gem path" do
|
|
expect(subject.plugin_gem_path).not_to be_nil
|
|
end
|
|
|
|
it "should add current ruby version to plugin gem path suffix" do
|
|
expect(subject.plugin_gem_path.to_s).to end_with(RUBY_VERSION)
|
|
end
|
|
|
|
it "should freeze the plugin gem path" do
|
|
expect(subject.plugin_gem_path).to be_frozen
|
|
end
|
|
end
|
|
|
|
describe "#environment_path=" do
|
|
it "should error if not given Pathname" do
|
|
expect { subject.environment_path = :value }.
|
|
to raise_error(TypeError)
|
|
end
|
|
|
|
context "when set with Pathname" do
|
|
let(:env_path) { Pathname.new("/dev/null") }
|
|
before { subject.environment_path = env_path }
|
|
|
|
it "should set the environment_data_path" do
|
|
expect(subject.environment_data_path).to eq(env_path)
|
|
end
|
|
|
|
it "should set the env_plugin_gem_path" do
|
|
expect(subject.env_plugin_gem_path).not_to be_nil
|
|
end
|
|
|
|
it "should suffix current ruby version to env_plugin_gem_path" do
|
|
expect(subject.env_plugin_gem_path.to_s).to end_with(RUBY_VERSION)
|
|
end
|
|
|
|
it "should base env_plugin_gem_path on environment_path value" do
|
|
expect(subject.env_plugin_gem_path.to_s).to start_with(env_path.to_s)
|
|
end
|
|
|
|
it "should freeze the env_plugin_gem_path" do
|
|
expect(subject.env_plugin_gem_path).to be_frozen
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#load_solution_file" do
|
|
let(:local_opt) { nil }
|
|
let(:global_opt) { nil }
|
|
let(:options) { {local: local_opt, global: global_opt} }
|
|
|
|
it "should return nil when local and global options are blank" do
|
|
expect(subject.load_solution_file(options)).to be_nil
|
|
end
|
|
|
|
context "when environment data path is set" do
|
|
let(:env_path) { "/dev/null" }
|
|
before { subject.environment_path = Pathname.new(env_path) }
|
|
|
|
context "when local option is set" do
|
|
let(:local_opt) { tmpdir + "local" }
|
|
|
|
it "should return a SolutionFile instance" do
|
|
expect(subject.load_solution_file(options)).to be_a(Vagrant::Bundler::SolutionFile)
|
|
end
|
|
|
|
it "should be located in the environment data path" do
|
|
file = subject.load_solution_file(options)
|
|
expect(file.solution_file.to_s).to start_with(env_path)
|
|
end
|
|
|
|
it "should have a local.sol solution file" do
|
|
file = subject.load_solution_file(options)
|
|
expect(file.solution_file.to_s).to end_with("local.sol")
|
|
end
|
|
|
|
it "should have plugin file set to local value" do
|
|
file = subject.load_solution_file(options)
|
|
expect(file.plugin_file.to_s).to eq(local_opt.to_s)
|
|
end
|
|
end
|
|
|
|
context "when global option is set" do
|
|
let(:global_opt) { tmpdir + "global" }
|
|
|
|
it "should return a SolutionFile instance" do
|
|
expect(subject.load_solution_file(options)).to be_a(Vagrant::Bundler::SolutionFile)
|
|
end
|
|
|
|
it "should be located in the environment data path" do
|
|
file = subject.load_solution_file(options)
|
|
expect(file.solution_file.to_s).to start_with(env_path)
|
|
end
|
|
|
|
it "should have a global.sol solution file" do
|
|
file = subject.load_solution_file(options)
|
|
expect(file.solution_file.to_s).to end_with("global.sol")
|
|
end
|
|
|
|
it "should have plugin file set to global value" do
|
|
file = subject.load_solution_file(options)
|
|
expect(file.plugin_file.to_s).to eq(global_opt.to_s)
|
|
end
|
|
end
|
|
|
|
context "when local and global option is set" do
|
|
let(:global_opt) { tmpdir + "global" }
|
|
let(:local_opt) { tmpdir + "local" }
|
|
|
|
it "should return nil" do
|
|
expect(subject.load_solution_file(options)).to be_nil
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when environment data path is unset" do
|
|
context "when local option is set" do
|
|
let(:local_opt) { tmpdir + "local" }
|
|
|
|
it "should return nil" do
|
|
expect(subject.load_solution_file(options)).to be_nil
|
|
end
|
|
end
|
|
|
|
context "when global option is set" do
|
|
let(:global_opt) { tmpdir + "global" }
|
|
|
|
it "should return a SolutionFile instance" do
|
|
expect(subject.load_solution_file(options)).to be_a(Vagrant::Bundler::SolutionFile)
|
|
end
|
|
|
|
it "should be located in the vagrant user data path" do
|
|
file = subject.load_solution_file(options)
|
|
expect(file.solution_file.to_s).to start_with(Vagrant.user_data_path.to_s)
|
|
end
|
|
|
|
it "should have a global.sol solution file" do
|
|
file = subject.load_solution_file(options)
|
|
expect(file.solution_file.to_s).to end_with("global.sol")
|
|
end
|
|
|
|
it "should have plugin file set to global value" do
|
|
file = subject.load_solution_file(options)
|
|
expect(file.plugin_file.to_s).to eq(global_opt.to_s)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#deinit" do
|
|
it "should provide method for backwards compatibility" do
|
|
subject.deinit
|
|
end
|
|
end
|
|
|
|
describe "DEFAULT_GEM_SOURCES" do
|
|
it "should list hashicorp gemstore first" do
|
|
expect(described_class.const_get(:DEFAULT_GEM_SOURCES).first).to eq(
|
|
described_class.const_get(:HASHICORP_GEMSTORE))
|
|
end
|
|
end
|
|
|
|
describe "#init!" do
|
|
context "Gem.sources" do
|
|
before {
|
|
Gem.sources.clear
|
|
Gem.sources << "https://rubygems.org/" }
|
|
|
|
it "should add hashicorp gem store" do
|
|
subject.init!([])
|
|
expect(Gem.sources).to include(described_class.const_get(:HASHICORP_GEMSTORE))
|
|
end
|
|
|
|
it "should add hashicorp gem store to start of sources list" do
|
|
subject.init!([])
|
|
expect(Gem.sources.sources.first.uri.to_s).to eq(described_class.const_get(:HASHICORP_GEMSTORE))
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#install" do
|
|
let(:plugins){ {"my-plugin" => {"gem_version" => "> 0"}} }
|
|
|
|
it "should pass plugin information hash to internal install" do
|
|
expect(subject).to receive(:internal_install).with(plugins, any_args)
|
|
subject.install(plugins)
|
|
end
|
|
|
|
it "should not include any update plugins" do
|
|
expect(subject).to receive(:internal_install).with(anything, nil, any_args)
|
|
subject.install(plugins)
|
|
end
|
|
|
|
it "should flag local when local is true" do
|
|
expect(subject).to receive(:internal_install).with(any_args, env_local: true)
|
|
subject.install(plugins, true)
|
|
end
|
|
|
|
it "should not flag local when local is not set" do
|
|
expect(subject).to receive(:internal_install).with(any_args, env_local: false)
|
|
subject.install(plugins)
|
|
end
|
|
end
|
|
|
|
describe "#install_local" do
|
|
let(:plugin_source){ double("plugin_source", spec: plugin_spec) }
|
|
let(:plugin_spec){ double("plugin_spec", name: plugin_name, version: plugin_version) }
|
|
let(:plugin_name){ "PLUGIN_NAME" }
|
|
let(:plugin_version){ "1.0.0" }
|
|
let(:plugin_path){ "PLUGIN_PATH" }
|
|
let(:sources){ "SOURCES" }
|
|
|
|
before do
|
|
allow(Gem::Source::SpecificFile).to receive(:new).and_return(plugin_source)
|
|
allow(subject).to receive(:internal_install)
|
|
end
|
|
|
|
it "should return plugin gem specification" do
|
|
expect(subject.install_local(plugin_path)).to eq(plugin_spec)
|
|
end
|
|
|
|
it "should set custom sources" do
|
|
expect(subject).to receive(:internal_install) do |info, update, opts|
|
|
expect(info[plugin_name]["sources"]).to eq(sources)
|
|
end
|
|
subject.install_local(plugin_path, sources: sources)
|
|
end
|
|
|
|
it "should not set the update parameter" do
|
|
expect(subject).to receive(:internal_install) do |info, update, opts|
|
|
expect(update).to be_nil
|
|
end
|
|
subject.install_local(plugin_path)
|
|
end
|
|
|
|
it "should not set plugin as environment local by default" do
|
|
expect(subject).to receive(:internal_install) do |info, update, opts|
|
|
expect(opts[:env_local]).to be_falsey
|
|
end
|
|
subject.install_local(plugin_path)
|
|
end
|
|
|
|
it "should set if plugin is environment local" do
|
|
expect(subject).to receive(:internal_install) do |info, update, opts|
|
|
expect(opts[:env_local]).to be_truthy
|
|
end
|
|
subject.install_local(plugin_path, env_local: true)
|
|
end
|
|
end
|
|
|
|
describe "#update" do
|
|
let(:plugins){ :plugins }
|
|
let(:specific){ [] }
|
|
|
|
after{ subject.update(plugins, specific) }
|
|
|
|
it "should mark update as true" do
|
|
expect(subject).to receive(:internal_install) do |info, update, opts|
|
|
expect(update).to be_truthy
|
|
end
|
|
end
|
|
|
|
context "with specific plugins named" do
|
|
let(:specific){ ["PLUGIN_NAME"] }
|
|
|
|
it "should set update to specific names" do
|
|
expect(subject).to receive(:internal_install) do |info, update, opts|
|
|
expect(update[:gems]).to eq(specific)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#vagrant_internal_specs" do
|
|
let(:vagrant_spec) { double("vagrant_spec", name: "vagrant", version: Gem::Version.new(Vagrant::VERSION),
|
|
activated?: vagrant_spec_activated, activate: nil, runtime_dependencies: vagrant_dep_specs) }
|
|
let(:spec_list) { [] }
|
|
let(:spec_dirs) { [] }
|
|
let(:spec_default_dir) { "/dev/null" }
|
|
let(:dir_spec_list) { [] }
|
|
let(:vagrant_spec_activated) { true }
|
|
let(:vagrant_dep_specs) { [] }
|
|
|
|
before do
|
|
allow(Gem::Specification).to receive(:find) { |&b| vagrant_spec if b.call(vagrant_spec) }
|
|
allow(Gem::Specification).to receive(:find_all).and_return(spec_list)
|
|
allow(Gem::Specification).to receive(:dirs).and_return(spec_dirs)
|
|
allow(Gem::Specification).to receive(:default_specifications_dir).and_return(spec_default_dir)
|
|
allow(Gem::Specification).to receive(:each_spec).and_return(dir_spec_list)
|
|
end
|
|
|
|
it "should return an empty list" do
|
|
expect(subject.send(:vagrant_internal_specs)).to eq([])
|
|
end
|
|
|
|
context "when vagrant specification is not activated" do
|
|
let(:vagrant_spec_activated) { false }
|
|
|
|
it "should activate the specification" do
|
|
expect(vagrant_spec).to receive(:activate)
|
|
subject.send(:vagrant_internal_specs)
|
|
end
|
|
end
|
|
|
|
context "when vagrant specification is not found" do
|
|
before { allow(Gem::Specification).to receive(:find).and_return(nil) }
|
|
|
|
it "should raise not found error" do
|
|
expect { subject.send(:vagrant_internal_specs) }.to raise_error(Vagrant::Errors::SourceSpecNotFound)
|
|
end
|
|
end
|
|
|
|
context "when run time dependencies are defined" do
|
|
let(:vagrant_dep_specs) { [double("spec", name: "vagrant-dep", requirement: double("spec-req", as_list: []))] }
|
|
|
|
it "should call #gem to activate the dependencies" do
|
|
expect(subject).to receive(:gem).with("vagrant-dep", any_args)
|
|
subject.send(:vagrant_internal_specs)
|
|
end
|
|
end
|
|
|
|
context "when bundler is not defined" do
|
|
before { expect(Object).to receive(:const_defined?).with(:Bundler).and_return(false) }
|
|
|
|
it "should load gem specification directories" do
|
|
expect(Gem::Specification).to receive(:dirs).and_return(spec_dirs)
|
|
subject.send(:vagrant_internal_specs)
|
|
end
|
|
|
|
context "when checking paths" do
|
|
let(:spec_dirs) { [double("spec-dir", start_with?: in_user_dir)] }
|
|
let(:in_user_dir) { true }
|
|
let(:user_dir) { double("user-dir") }
|
|
|
|
before { allow(Gem).to receive(:user_dir).and_return(user_dir) }
|
|
|
|
it "should check if path is within local user directory" do
|
|
expect(spec_dirs.first).to receive(:start_with?).with(user_dir).and_return(false)
|
|
subject.send(:vagrant_internal_specs)
|
|
end
|
|
|
|
context "when path is not within user directory" do
|
|
let(:in_user_dir) { false }
|
|
|
|
it "should use path when loading specs" do
|
|
expect(Gem::Specification).to receive(:each_spec) { |arg| expect(arg).to include(spec_dirs.first) }
|
|
subject.send(:vagrant_internal_specs)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe Vagrant::Bundler::PluginSet do
|
|
let(:name) { "test-gem" }
|
|
let(:version) { "1.0.0" }
|
|
let(:directory) { @directory ||= Dir.mktmpdir("vagrant-bundler-test") }
|
|
|
|
after do
|
|
FileUtils.rm_rf(@directory) if @directory
|
|
@directory = nil
|
|
end
|
|
|
|
describe "#add_vendor_gem" do
|
|
context "when spec file does not exist" do
|
|
it "should raise a not found error" do
|
|
expect { subject.add_vendor_gem(name, directory) }.to raise_error(Gem::GemNotFoundException)
|
|
end
|
|
end
|
|
|
|
context "when spec file exists" do
|
|
before do
|
|
spec = Gem::Specification.new(name, version)
|
|
File.write(File.join(directory, "#{name}.gemspec"), spec.to_ruby)
|
|
end
|
|
|
|
it "should load the specification" do
|
|
expect(subject.add_vendor_gem(name, directory)).to be_a(Gem::Specification)
|
|
end
|
|
|
|
it "should set the full path in specification" do
|
|
spec = subject.add_vendor_gem(name, directory)
|
|
expect(spec.full_gem_path).to eq(directory)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#find_all" do
|
|
let(:request) { Gem::Resolver::DependencyRequest.new(dependency, nil) }
|
|
let(:dependency) { Gem::Dependency.new("test-gem", requirement) }
|
|
let(:requirement) { Gem::Requirement.new(version) }
|
|
|
|
context "when specification is not included in set" do
|
|
it "should return empty array" do
|
|
expect(subject.find_all(request)).to eq([])
|
|
end
|
|
end
|
|
|
|
context "when specification is included in set" do
|
|
before do
|
|
spec = Gem::Specification.new(name, version)
|
|
File.write(File.join(directory, "#{name}.gemspec"), spec.to_ruby)
|
|
subject.add_vendor_gem(name, directory)
|
|
end
|
|
|
|
it "should return a vendor specification instance" do
|
|
expect(subject.find_all(request).first).to be_a(Gem::Resolver::VendorSpecification)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|