Skip to content
This repository was archived by the owner on Nov 30, 2024. It is now read-only.

Commit 7579efa

Browse files
committed
Add support for $XDG_CONFIG_HOME/rspec/options
This is a XDG Base Directory Specification compatible alternative to ~/.rspec. ~/.rspec has higher precedence in order to reduce risk in cases where users have a file like this dormant. It is unlikely, but this way the risk is lower. If $XDG_CONFIG_HOME is not set, it will fall back to ~/.config, per the specification. Other changes: The "isolated home" example tag has been extended with a `create_fixture_file` helper that also takes care to create the directory where the file resides in, if it does not already exist. Other file creations have been migrated to this method for consistency. The method is only available when an example is tagged with `:isolated_home => true`, for safety. In case the developer running these tests have a custom $XDG_CONFIG_HOME set, it is cleared out when using an isolated home so their real files are not touched.
1 parent 400b1e0 commit 7579efa

File tree

7 files changed

+113
-42
lines changed

7 files changed

+113
-42
lines changed

Filtering.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,10 @@ focus them.
114114

115115
## Options files and command line overrides
116116

117-
Command line option declarations can be stored in `.rspec`, `~/.rspec`, or a custom
118-
options file. This is useful for storing defaults. For example, let's
119-
say you've got some slow specs that you want to suppress most of the
120-
time. You can tag them like this:
117+
Command line option declarations can be stored in `.rspec`, `~/.rspec`,
118+
`$XDG_CONFIG_HOME/rspec/options` or a custom options file. This is useful for
119+
storing defaults. For example, let's say you've got some slow specs that you
120+
want to suppress most of the time. You can tag them like this:
121121

122122
``` ruby
123123
RSpec.describe Something, :slow => true do

features/configuration/default_path.feature

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ Feature: Setting the default spec path
55
This is supported by a `--default-path` option, which is set to `spec` by
66
default. If you prefer to keep your specs in a different directory, or assign
77
an individual file to `--default-path`, you can do so on the command line or
8-
in a configuration file (`.rspec`, `~/.rspec`, or a custom file).
8+
in a configuration file (`.rspec`, `~/.rspec`,
9+
`$XDG_CONFIG_HOME/rspec/options`, or a custom file).
910

1011
**NOTE:** this option is not supported on `RSpec.configuration`, as it needs to be
1112
set before spec files are loaded.

features/configuration/read_options_from_file.feature

+11-5
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,18 @@ Feature: read command line configuration options from files
99
* Project: `./.rspec` (i.e. in the project's root directory, usually
1010
checked into the project)
1111

12-
* Global: `~/.rspec` (i.e. in the user's home directory)
12+
* Global (HOME): `~/.rspec` (i.e. in the user's home directory)
1313

14-
Configuration options are loaded from `~/.rspec`, `.rspec`, `.rspec-local`,
15-
command line switches, and the `SPEC_OPTS` environment variable (listed in
16-
lowest to highest precedence; for example, an option in `~/.rspec` can be
17-
overridden by an option in `.rspec-local`).
14+
* Global (XDG): `$XDG_CONFIG_HOME/rspec/options` (i.e. in the user's
15+
[the XDG Base Directory
16+
Specification](https://specifications.freedesktop.org/basedir-spec/latest/)
17+
config directory)
18+
19+
Configuration options are loaded from `$XDG_CONFIG_HOME/rspec/options`,
20+
`~/.rspec`, `.rspec`, `.rspec-local`, command line switches, and the
21+
`SPEC_OPTS` environment variable (listed in lowest to highest precedence; for
22+
example, an option in `~/.rspec` can be overridden by an option in
23+
`.rspec-local`).
1824

1925
Scenario: Color set in `.rspec`
2026
Given a file named ".rspec" with:

lib/rspec/core/configuration.rb

+5-4
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ module Core
99

1010
# Stores runtime configuration information.
1111
#
12-
# Configuration options are loaded from `~/.rspec`, `.rspec`,
13-
# `.rspec-local`, command line switches, and the `SPEC_OPTS` environment
14-
# variable (listed in lowest to highest precedence; for example, an option
15-
# in `~/.rspec` can be overridden by an option in `.rspec-local`).
12+
# Configuration options are loaded from `$XDG_CONFIG_HOME/rspec/options`,
13+
# `~/.rspec`, `.rspec`, `.rspec-local`, command line switches, and the
14+
# `SPEC_OPTS` environment variable (listed in lowest to highest precedence;
15+
# for example, an option in `~/.rspec` can be overridden by an option in
16+
# `.rspec-local`).
1617
#
1718
# @example Standard settings
1819
# RSpec.configure do |c|

lib/rspec/core/configuration_options.rb

+22-3
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
module RSpec
55
module Core
66
# Responsible for utilizing externally provided configuration options,
7-
# whether via the command line, `.rspec`, `~/.rspec`, `.rspec-local`
8-
# or a custom options file.
7+
# whether via the command line, `.rspec`, `~/.rspec`,
8+
# `$XDG_CONFIG_HOME/rspec/options`, `.rspec-local` or a custom options
9+
# file.
910
class ConfigurationOptions
1011
# @param args [Array<String>] command line arguments
1112
def initialize(args)
@@ -118,7 +119,11 @@ def load_formatters_into(config)
118119
end
119120

120121
def file_options
121-
custom_options_file ? [custom_options] : [global_options, project_options, local_options]
122+
if custom_options_file
123+
[custom_options]
124+
else
125+
[global_xdg_options, global_options, project_options, local_options]
126+
end
122127
end
123128

124129
def env_options
@@ -150,6 +155,10 @@ def global_options
150155
@global_options ||= options_from(global_options_file)
151156
end
152157

158+
def global_xdg_options
159+
@global_xdg_options ||= options_from(global_xdg_options_file)
160+
end
161+
153162
def options_from(path)
154163
args = args_from_options_file(path)
155164
parse_args_ignoring_files_or_dirs_to_run(args, path)
@@ -195,6 +204,16 @@ def global_options_file
195204
nil
196205
# :nocov:
197206
end
207+
208+
def global_xdg_options_file
209+
xdg_config_home = ENV.fetch("XDG_CONFIG_HOME", "~/.config")
210+
File.join(File.expand_path(xdg_config_home), "rspec", "options")
211+
rescue ArgumentError
212+
# :nocov:
213+
RSpec.warning "Unable to find $XDG_CONFIG_HOME/rspec/options because the HOME environment variable is not set"
214+
nil
215+
# :nocov:
216+
end
198217
end
199218
end
200219
end

spec/rspec/core/configuration_options_spec.rb

+48-19
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,14 @@ def expect_parsing_to_fail_mentioning_source(source, options=[])
412412
end
413413
end
414414

415+
context "defined in $XDG_CONFIG_HOME/rspec/options" do
416+
it "mentions the file name in the error so users know where to look for it" do
417+
file_name = File.expand_path("~/.config/rspec/options")
418+
create_fixture_file(file_name, "--foo_bar")
419+
expect_parsing_to_fail_mentioning_source(file_name)
420+
end
421+
end
422+
415423
context "defined in SPEC_OPTS" do
416424
it "mentions ENV['SPEC_OPTS'] as the source in the error so users know where to look for it" do
417425
with_env_vars 'SPEC_OPTS' => "--foo_bar" do
@@ -440,25 +448,43 @@ def expect_parsing_to_fail_mentioning_source(source, options=[])
440448
end
441449
end
442450

443-
describe "sources: ~/.rspec, ./.rspec, ./.rspec-local, custom, CLI, and SPEC_OPTS" do
444-
it "merges global, local, SPEC_OPTS, and CLI" do
445-
File.open("./.rspec", "w") {|f| f << "--require some_file"}
446-
File.open("./.rspec-local", "w") {|f| f << "--format global"}
447-
File.open(File.expand_path("~/.rspec"), "w") {|f| f << "--force-color"}
451+
describe "sources: $XDG_CONFIG_HOME/rspec/options, ~/.rspec, ./.rspec, ./.rspec-local, custom, CLI, and SPEC_OPTS" do
452+
it "merges both global, local, SPEC_OPTS, and CLI" do
453+
create_fixture_file("./.rspec", "--require some_file")
454+
create_fixture_file("./.rspec-local", "--format global")
455+
create_fixture_file("~/.rspec", "--force-color")
456+
create_fixture_file("~/.config/rspec/options", "--order defined")
448457
with_env_vars 'SPEC_OPTS' => "--example 'foo bar'" do
449458
options = parse_options("--drb")
450459
expect(options[:color_mode]).to eq(:on)
451460
expect(options[:requires]).to eq(["some_file"])
452461
expect(options[:full_description]).to eq([/foo\ bar/])
453462
expect(options[:drb]).to be_truthy
454463
expect(options[:formatters]).to eq([['global']])
464+
expect(options[:order]).to eq("defined")
465+
end
466+
end
467+
468+
it "uses $XDG_CONFIG_HOME environment variable when set to find XDG global options" do
469+
create_fixture_file("~/.config/rspec/options", "--format default_xdg")
470+
create_fixture_file("~/.custom-config/rspec/options", "--format overridden_xdg")
471+
472+
with_env_vars 'XDG_CONFIG_HOME' => "~/.custom-config" do
473+
options = parse_options()
474+
expect(options[:formatters]).to eq([['overridden_xdg']])
475+
end
476+
477+
without_env_vars 'XDG_CONFIG_HOME' do
478+
options = parse_options()
479+
expect(options[:formatters]).to eq([['default_xdg']])
455480
end
456481
end
457482

458483
it 'ignores file or dir names put in one of the option files or in SPEC_OPTS, since those are for persistent options' do
459-
File.open("./.rspec", "w") { |f| f << "path/to/spec_1.rb" }
460-
File.open("./.rspec-local", "w") { |f| f << "path/to/spec_2.rb" }
461-
File.open(File.expand_path("~/.rspec"), "w") {|f| f << "path/to/spec_3.rb"}
484+
create_fixture_file("./.rspec", "path/to/spec_1.rb" )
485+
create_fixture_file("./.rspec-local", "path/to/spec_2.rb" )
486+
create_fixture_file("~/.rspec", "path/to/spec_3.rb")
487+
create_fixture_file("~/.config/rspec/options", "path/to/spec_4.rb")
462488
with_env_vars 'SPEC_OPTS' => "path/to/spec_4.rb" do
463489
options = parse_options()
464490
expect(options[:files_or_directories_to_run]).to eq([])
@@ -472,13 +498,14 @@ def expect_parsing_to_fail_mentioning_source(source, options=[])
472498
end
473499

474500
it "prefers CLI over file options" do
475-
File.open("./.rspec", "w") {|f| f << "--format project"}
476-
File.open(File.expand_path("~/.rspec"), "w") {|f| f << "--format global"}
501+
create_fixture_file("./.rspec", "--format project")
502+
create_fixture_file("~/.rspec", "--format global")
503+
create_fixture_file("~/.config/rspec/options", "--format xdg")
477504
expect(parse_options("--format", "cli")[:formatters]).to eq([['cli']])
478505
end
479506

480507
it "prefers CLI over file options for filter inclusion" do
481-
File.open("./.rspec", "w") {|f| f << "--tag ~slow"}
508+
create_fixture_file("./.rspec", "--tag ~slow")
482509
opts = config_options_object("--tag", "slow")
483510
config = RSpec::Core::Configuration.new
484511
opts.configure(config)
@@ -487,14 +514,15 @@ def expect_parsing_to_fail_mentioning_source(source, options=[])
487514
end
488515

489516
it "prefers project file options over global file options" do
490-
File.open("./.rspec", "w") {|f| f << "--format project"}
491-
File.open(File.expand_path("~/.rspec"), "w") {|f| f << "--format global"}
517+
create_fixture_file("./.rspec", "--format project")
518+
create_fixture_file("~/.rspec", "--format global")
519+
create_fixture_file("~/.config/rspec/options", "--format xdg")
492520
expect(parse_options[:formatters]).to eq([['project']])
493521
end
494522

495523
it "prefers local file options over project file options" do
496-
File.open("./.rspec-local", "w") {|f| f << "--format local"}
497-
File.open("./.rspec", "w") {|f| f << "--format global"}
524+
create_fixture_file("./.rspec-local", "--format local")
525+
create_fixture_file("./.rspec", "--format global")
498526
expect(parse_options[:formatters]).to eq([['local']])
499527
end
500528

@@ -510,16 +538,17 @@ def expect_parsing_to_fail_mentioning_source(source, options=[])
510538

511539
context "with custom options file" do
512540
it "ignores project and global options files" do
513-
File.open("./.rspec", "w") {|f| f << "--format project"}
514-
File.open(File.expand_path("~/.rspec"), "w") {|f| f << "--format global"}
515-
File.open("./custom.opts", "w") {|f| f << "--force-color"}
541+
create_fixture_file("./.rspec", "--format project")
542+
create_fixture_file("~/.rspec", "--format global")
543+
create_fixture_file("~/.config/rspec/options", "--format xdg")
544+
create_fixture_file("./custom.opts", "--force-color")
516545
options = parse_options("-O", "./custom.opts")
517546
expect(options[:format]).to be_nil
518547
expect(options[:color_mode]).to eq(:on)
519548
end
520549

521550
it "parses -e 'full spec description'" do
522-
File.open("./custom.opts", "w") {|f| f << "-e 'The quick brown fox jumps over the lazy dog'"}
551+
create_fixture_file("./custom.opts", "-e 'The quick brown fox jumps over the lazy dog'")
523552
options = parse_options("-O", "./custom.opts")
524553
expect(options[:full_description]).to eq([/The\ quick\ brown\ fox\ jumps\ over\ the\ lazy\ dog/])
525554
end
+21-6
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,35 @@
11
require 'tmpdir'
22
require 'fileutils'
3+
require 'pathname'
34

45
RSpec.shared_context "isolated home directory" do
56
around do |ex|
67
Dir.mktmpdir do |tmp_dir|
7-
original_home = ENV['HOME']
8-
begin
9-
ENV['HOME'] = tmp_dir
10-
ex.call
11-
ensure
12-
ENV['HOME'] = original_home
8+
# If user has a custom $XDG_CONFIG_HOME, also clear that out when
9+
# changing $HOME so tests don't touch the user's real config files.
10+
without_env_vars "XDG_CONFIG_HOME" do
11+
with_env_vars "HOME" => tmp_dir do
12+
ex.call
13+
end
1314
end
1415
end
1516
end
1617
end
1718

19+
module HomeFixtureHelpers
20+
def create_fixture_file(file_name, contents)
21+
path = Pathname.new(file_name).expand_path
22+
if !path.exist?
23+
path.dirname.mkpath
24+
path.write(contents)
25+
else
26+
# Abort just in case we're about to destroy something important.
27+
raise "File at #{path} already exists!"
28+
end
29+
end
30+
end
31+
1832
RSpec.configure do |c|
1933
c.include_context "isolated home directory", :isolated_home => true
34+
c.include HomeFixtureHelpers, :isolated_home => true
2035
end

0 commit comments

Comments
 (0)