shdotenv
The atago project wrote these specs on its own initiative and runs them in its own CI, to exercise atago against a real program. They are not shdotenv’s official test suite, and the shdotenv project is not affiliated with atago.
Summary #
2 suites · 17 scenarios
Contents #
- shdotenv (dialects and output formats) — 7 scenarios
- one file, six formats, each exactly as its consumer expects
- the output can be filtered and reduced to names, and sorted only partly
- a backslash escape means different things in different dialects
- a space after the key is a syntax error in some dialects and not in others
- a line that is not a definition is quoted back with what is wrong
- the environment can be dumped as a .env file and read back
- a file can come from standard input
- shdotenv (parsing a .env file safely) — 10 scenarios
- the version, the help, and an option that does not exist
- a file is read as data and printed as shell that can be evaluated
- a file that tries to run a command runs nothing
- shell metacharacters inside a value stay text, even when a command runs
- a reference to something unset is an error unless it is allowed
- the environment already in place is visible, and can be hidden
- the file is a default until overload is asked for
- two files that define the same key are refused rather than merged
- a file that is not there is not an error
- quiet mode validates a file without printing it
shdotenv (dialects and output formats) #
The two matrices shdotenv exists for: the .env syntax dialect it reads, and the format it writes.
A dialect is not a preference — it changes what a file means, so the scenarios here feed one file to several dialects and assert that the answers differ in the documented way: a backslash-n that stays two characters under the POSIX rules and becomes a newline under Ruby’s, a key with a space after it that POSIX and Docker refuse and Ruby accepts. The format half is asserted exactly, because these outputs are consumed by other programs: a shell, csh, fish, JSON, JSONL, YAML.
Source: test/e2e/thirdparty/shdotenv/dialects.atago.yaml
Scenario: one file, six formats, each exactly as its consumer expects #
only when command -v shdotenv succeeds · skipped on Windows
Given #
- Fixture file
.envis created. - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected). - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected). - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected). - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected). - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected). - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected).
Inputs #
Fixture .env:
NAME=world
GREETING="hello, ${NAME}"
When #
shdotenv --format sh
shdotenv --format csh
shdotenv --format fish
shdotenv --format json
shdotenv --format jsonl
shdotenv --format yaml
Then #
- after
shdotenv --format sh:- exit code is
0 - stdout equals an exact value
- exit code is
- after
shdotenv --format csh:- exit code is
0 - stdout equals an exact value
- exit code is
- after
shdotenv --format fish:- exit code is
0 - stdout equals an exact value
- exit code is
- after
shdotenv --format json:- exit code is
0 - stdout at
$.NAMEequalsworld; at$.GREETINGequalshello, world
- exit code is
- after
shdotenv --format jsonl:- exit code is
0 - stdout equals an exact value
- exit code is
- after
shdotenv --format yaml:- exit code is
0 - stdout equals an exact value
- exit code is
Expected output #
expected stdout:
export NAME='world'
export GREETING='hello, world'
expected stdout:
setenv NAME 'world';
setenv GREETING 'hello, world';
expected stdout:
set --export NAME 'world';
set --export GREETING 'hello, world';
expected stdout:
{ "NAME": "world", "GREETING": "hello, world" }
expected stdout:
NAME: "world"
GREETING: "hello, world"
Scenario: the output can be filtered and reduced to names, and sorted only partly #
only when command -v shdotenv succeeds · skipped on Windows
Given #
- Fixture file
.envis created. - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected). - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected). - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected). - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected). - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected).
Inputs #
Fixture .env:
DELTA=4
BRAVO=2
ECHO=5
ALFA=1
CHARLIE=3
When #
shdotenv
shdotenv --sort
shdotenv --grep '^[BC]'
shdotenv --name-only
shdotenv --name-only --sort
Then #
- after
shdotenv:- exit code is
0 - stdout equals an exact value
- exit code is
- after
shdotenv --sort:- exit code is
0 - stdout equals an exact value
- exit code is
- after
shdotenv --grep '^[BC]':- exit code is
0 - stdout equals an exact value
- exit code is
- after
shdotenv --name-only:- exit code is
0 - stdout equals an exact value
- exit code is
- after
shdotenv --name-only --sort:- exit code is
0 - stdout equals an exact value
- exit code is
Expected output #
expected stdout:
export DELTA='4'
export BRAVO='2'
export ECHO='5'
export ALFA='1'
export CHARLIE='3'
expected stdout:
export ALFA='1'
export BRAVO='2'
export DELTA='4'
export ECHO='5'
export CHARLIE='3'
expected stdout:
export BRAVO='2'
export CHARLIE='3'
expected stdout:
DELTA
BRAVO
ECHO
ALFA
CHARLIE
expected stdout:
DELTA
BRAVO
ECHO
ALFA
CHARLIE
Scenario: a backslash escape means different things in different dialects #
only when command -v shdotenv succeeds · skipped on Windows
Given #
- Fixture file
.envis created. - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected). - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected). - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected). - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected).
Inputs #
Fixture .env:
MESSAGE="first\nsecond"
When #
shdotenv --dialect posix --format json
shdotenv --dialect ruby --format json
shdotenv --dialect node --format json
shdotenv --dialect go --format json
Then #
- after
shdotenv --dialect posix --format json:- exit code is
0 - stdout at
$.MESSAGEequalsfirst\nsecond
- exit code is
- after
shdotenv --dialect ruby --format json:- exit code is
0 - stdout at
$.MESSAGEequals"first\nsecond"
- exit code is
- after
shdotenv --dialect node --format json:- exit code is
0 - stdout at
$.MESSAGEequals"first\nsecond"
- exit code is
- after
shdotenv --dialect go --format json:- exit code is
0 - stdout at
$.MESSAGEequals"first\nsecond"
- exit code is
Scenario: a space after the key is a syntax error in some dialects and not in others #
only when command -v shdotenv succeeds · skipped on Windows
Given #
- Fixture file
.envis created. - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected). - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected). - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected).
Inputs #
Fixture .env:
KEY = value
When #
shdotenv --dialect posix
shdotenv --dialect docker
shdotenv --dialect ruby
Then #
- after
shdotenv --dialect posix:- exit code is
1 - stdout is empty
- stderr equals an exact value
- exit code is
- after
shdotenv --dialect docker:- exit code is
1 - stderr equals an exact value
- exit code is
- after
shdotenv --dialect ruby:- exit code is
0 - stdout equals an exact value
- exit code is
Expected output #
expected stderr:
shdotenv: `KEY ': no space allowed after the key
expected stderr:
shdotenv: `KEY ': no space allowed after the key
expected stdout:
export KEY='value'
Scenario: a line that is not a definition is quoted back with what is wrong #
only when command -v shdotenv succeeds · skipped on Windows
Given #
- Fixture file
.envis created. - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected). - Fixture file
.envis created. - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected).
Inputs #
Fixture .env:
GOOD=value
THIS IS NOT A DEFINITION
Fixture .env:
A-B=value
When #
shdotenv
shdotenv
Then #
- after
shdotenv:- exit code is
1 - stdout is empty
- stderr equals an exact value
- exit code is
- after
shdotenv:- exit code is
1 - stderr equals an exact value
- exit code is
Expected output #
expected stderr:
shdotenv: `THIS IS NOT A DEFINITION': not a variable definition
expected stderr:
shdotenv: `A-B': the key is not a valid identifier
Scenario: the environment can be dumped as a .env file and read back #
only when command -v shdotenv succeeds · skipped on Windows
Given #
- Environment variables are set: LC_ALL, OTHER_VALUE, ROUND_TRIP.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected). - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected). - Environment variables are set: LC_ALL, ROUND_TRIP.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected).
When #
shdotenv export ROUND_TRIP OTHER_VALUE
shdotenv -e dumped.env --format json
shdotenv export -n ROUND_TRIP
Then #
- after
shdotenv export ROUND_TRIP OTHER_VALUE:- exit code is
0 - the step changed exactly created
dumped.env, modified nothing, deleted nothing
- exit code is
- after
shdotenv -e dumped.env --format json:- exit code is
0 - stdout at
$.ROUND_TRIPequalsa value with spaces; at$.OTHER_VALUEequalsx'y
- exit code is
- after
shdotenv export -n ROUND_TRIP:- exit code is
0 - stdout equals an exact value
- exit code is
Expected output #
expected stdout:
ROUND_TRIP
Generated artifacts #
dumped.env
Scenario: a file can come from standard input #
only when command -v shdotenv succeeds · skipped on Windows
Given #
- Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected).
Inputs #
stdin for shdotenv:
FROM_STDIN=yes
SECOND=2
When #
shdotenv -e -
Then #
- exit code is
0 - stdout equals an exact value
- the step changed exactly created nothing, modified nothing, deleted nothing
Expected output #
expected stdout:
export FROM_STDIN='yes'
export SECOND='2'
shdotenv (parsing a .env file safely) #
shdotenv loads .env files into the environment without letting the file run anything. Its own test suite is written in ShellSpec; what those tests check is pinned here from outside.
The safety promise is the center of this file, and it is asserted the only way a security claim should be: a .env file that tries to run a command is given every chance to, and the proof is that the file it would have created does not exist. Around it are the parsing rules, the strict defaults that make the tool usable in CI (an unset reference is an error, two files defining the same key is an error), and the precedence between the file and the environment already in place.
Source: test/e2e/thirdparty/shdotenv/shdotenv.atago.yaml
Scenario: the version, the help, and an option that does not exist #
only when command -v shdotenv succeeds · skipped on Windows
Given #
- Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected). - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected). - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected).
When #
shdotenv --version
shdotenv --help
shdotenv --nope
Then #
- after
shdotenv --version:- exit code is
0 - stdout matches
/^[0-9]+\.[0-9]+\.[0-9]+\n$/
- exit code is
- after
shdotenv --help:- exit code is
0 - stdout contains
Usage: shdotenv [OPTION]... [--] [[COMMAND | export] [ARG]...],-d, --dialect DIALECT,-f, --format FORMAT
- exit code is
- after
shdotenv --nope:- exit code is
1 - stdout is empty
- stderr contains
--nope
- exit code is
Scenario: a file is read as data and printed as shell that can be evaluated #
only when command -v shdotenv succeeds · skipped on Windows
Given #
- Fixture file
.envis created. - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected). - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected).
Inputs #
Fixture .env:
# a comment line
NAME=world
GREETING="hello, ${NAME}"
EMPTY=
QUOTED='no ${expansion} here'
When #
shdotenv
shdotenv -- sh -c "echo [$GREETING]"
Then #
- after
shdotenv:- exit code is
0 - stdout equals an exact value
- exit code is
- after
shdotenv -- sh -c "echo [$GREETING]":- exit code is
0 - stdout equals an exact value
- exit code is
Expected output #
expected stdout:
export NAME='world'
export GREETING='hello, world'
export EMPTY=''
export QUOTED='no ${expansion} here'
expected stdout:
[hello, world]
Scenario: a file that tries to run a command runs nothing #
only when command -v shdotenv succeeds · skipped on Windows
Given #
- Fixture file
.envis created. - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected). - Fixture file
.envis created. - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected). - Fixture file
.envis created. - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected).
Inputs #
Fixture .env:
EVIL=$(touch pwned-unquoted)
Fixture .env:
EVIL="$(touch pwned-quoted)"
Fixture .env:
EVIL="`touch pwned-backtick`"
When #
shdotenv
shdotenv
shdotenv
Then #
- after
shdotenv:- exit code is
1 - stdout is empty
- stderr contains
spaces are not allowed without quoting
- exit code is
- after
shdotenv:- exit code is
1 - stdout is empty
- stderr contains
the following metacharacters must be escaped: $`"\
- exit code is
- after
shdotenv:- exit code is
1 - stderr contains
the following metacharacters must be escaped: $`"\ - the step changed exactly created nothing, modified nothing, deleted nothing
- file
pwned-unquoteddoes not exist - file
pwned-quoteddoes not exist - file
pwned-backtickdoes not exist
- exit code is
Scenario: shell metacharacters inside a value stay text, even when a command runs #
only when command -v shdotenv succeeds · skipped on Windows
Given #
- Fixture file
.envis created. - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected). - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected).
Inputs #
Fixture .env:
PAYLOAD="x; touch pwned-semicolon"
When #
shdotenv
shdotenv -- sh -c "echo [$PAYLOAD]"
Then #
- after
shdotenv:- exit code is
0 - stdout equals an exact value
- exit code is
- after
shdotenv -- sh -c "echo [$PAYLOAD]":- exit code is
0 - stdout equals an exact value
- file
pwned-semicolondoes not exist
- exit code is
Expected output #
expected stdout:
export PAYLOAD='x; touch pwned-semicolon'
expected stdout:
[x; touch pwned-semicolon]
Scenario: a reference to something unset is an error unless it is allowed #
only when command -v shdotenv succeeds · skipped on Windows
Given #
- Fixture file
.envis created. - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected). - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected).
Inputs #
Fixture .env:
VALUE="${NOT_SET_ANYWHERE}"
When #
shdotenv
shdotenv --no-nounset
Then #
- after
shdotenv:- exit code is
1 - stdout is empty
- stderr equals an exact value
- exit code is
- after
shdotenv --no-nounset:- exit code is
0 - stdout equals an exact value
- exit code is
Expected output #
expected stderr:
shdotenv: NOT_SET_ANYWHERE: the key is not set
expected stdout:
export VALUE=''
Scenario: the environment already in place is visible, and can be hidden #
only when command -v shdotenv succeeds · skipped on Windows
Given #
- Fixture file
.envis created. - Environment variables are set: LC_ALL, OUTSIDE.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected). - Environment variables are set: LC_ALL, OUTSIDE.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected).
Inputs #
Fixture .env:
DERIVED="from ${OUTSIDE}"
When #
shdotenv
shdotenv --ignore-environment
Then #
- after
shdotenv:- exit code is
0 - stdout equals an exact value
- exit code is
- after
shdotenv --ignore-environment:- exit code is
1 - stderr equals an exact value
- exit code is
Expected output #
expected stdout:
export DERIVED='from the-host'
expected stderr:
shdotenv: OUTSIDE: the key is not set
Scenario: the file is a default until overload is asked for #
only when command -v shdotenv succeeds · skipped on Windows
Given #
- Fixture file
.envis created. - Environment variables are set: LC_ALL, PRESET.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected). - Environment variables are set: LC_ALL, PRESET.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected). - Environment variables are set: LC_ALL, PRESET.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected). - Environment variables are set: LC_ALL, PRESET.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected).
Inputs #
Fixture .env:
PRESET=from-the-file
When #
shdotenv -- sh -c "echo [$PRESET]"
shdotenv --overload -- sh -c "echo [$PRESET]"
shdotenv
shdotenv --overload
Then #
- after
shdotenv -- sh -c "echo [$PRESET]":- exit code is
0 - stdout equals an exact value
- exit code is
- after
shdotenv --overload -- sh -c "echo [$PRESET]":- exit code is
0 - stdout equals an exact value
- exit code is
- after
shdotenv:- exit code is
0 - stdout is empty
- exit code is
- after
shdotenv --overload:- exit code is
0 - stdout equals an exact value
- exit code is
Expected output #
expected stdout:
[from-the-environment]
expected stdout:
[from-the-file]
expected stdout:
export PRESET='from-the-file'
Scenario: two files that define the same key are refused rather than merged #
only when command -v shdotenv succeeds · skipped on Windows
Given #
- Fixture file
first.envis created. - Fixture file
second.envis created. - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected). - Fixture file
second.envis created. - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected).
Inputs #
Fixture first.env:
SHARED=one
ONLY_FIRST=1
Fixture second.env:
SHARED=two
ONLY_SECOND=2
Fixture second.env:
ONLY_SECOND=2
When #
shdotenv -e first.env -e second.env
shdotenv -e first.env -e second.env
Then #
- after
shdotenv -e first.env -e second.env:- exit code is
1 - stdout is empty
- stderr equals an exact value
- exit code is
- after
shdotenv -e first.env -e second.env:- exit code is
0 - stdout equals an exact value
- exit code is
Expected output #
expected stderr:
shdotenv: second.env: `SHARED' is already defined in the first.env
expected stdout:
export SHARED='one'
export ONLY_FIRST='1'
export ONLY_SECOND='2'
Scenario: a file that is not there is not an error #
only when command -v shdotenv succeeds · skipped on Windows
Given #
- Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected).
When #
shdotenv -e missing.env
Then #
- exit code is
0 - stdout is empty
- stderr is empty
Scenario: quiet mode validates a file without printing it #
only when command -v shdotenv succeeds · skipped on Windows
Given #
- Fixture file
.envis created. - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected). - Fixture file
.envis created. - Environment variables are set: LC_ALL.
- The command runs with an isolated home under
${workdir}/.atago-home(HOME/XDG or APPDATA redirected).
Inputs #
Fixture .env:
GOOD=value
Fixture .env:
THIS IS NOT A DEFINITION
When #
shdotenv --quiet
shdotenv --quiet
Then #
- after
shdotenv --quiet:- exit code is
0 - stdout is empty
- exit code is
- after
shdotenv --quiet:- exit code is
1 - stdout is empty
- stderr equals an exact value
- exit code is
Expected output #
expected stderr:
shdotenv: `THIS IS NOT A DEFINITION': not a variable definition