Customize Shell
Shell Type
buildtest will default to bash
shell when running test, but we can configure shell
option using the shell
field. The shell field is defined in schema as follows:
"shell": {
"type": "string",
"description": "Specify a shell launcher to use when running jobs. This sets the shebang line in your test script. The ``shell`` key can be used with ``run`` section to describe content of script and how its executed",
"pattern": "^(/bin/bash|/bin/sh|/bin/csh|/bin/tcsh|/bin/zsh|bash|sh|csh|tcsh|zsh|python).*"
},
The shell pattern is a regular expression where one can specify a shell name along with shell options. The shell will configure the shebang in the test-script. In this example, we illustrate a few tests using different shell field.
buildspecs:
_bin_sh_shell:
executor: generic.local.sh
type: script
description: "/bin/sh shell example"
shell: /bin/sh
tags: [tutorials]
run: "bzip2 --help"
_bin_bash_shell:
executor: generic.local.bash
type: script
description: "/bin/bash shell example"
shell: /bin/bash
tags: [tutorials]
run: "bzip2 -h"
bash_shell:
executor: generic.local.bash
type: script
description: "bash shell example"
shell: bash
tags: [tutorials]
run: "echo $SHELL"
sh_shell:
executor: generic.local.sh
type: script
description: "sh shell example"
shell: sh
tags: [tutorials]
run: "echo $SHELL"
shell_options:
executor: generic.local.sh
type: script
description: "shell options"
shell: "sh -x"
tags: [tutorials]
run: |
echo $SHELL
hostname
The generated test-script for buildspec _bin_sh_shell will specify shebang
/bin/sh because we specified shell: /bin/sh
:
#!/bin/sh
# Content of run section
bzip2 --help
If you don’t specify a shell path such as shell: sh
, then buildtest will resolve
path by looking in $PATH and build the shebang line.
In test shell_options we specify shell: "sh -x"
, buildtest will tack on the
shell options into the called script as follows:
#!/bin/bash
############# START VARIABLE DECLARATION ########################
export BUILDTEST_TEST_NAME=shell_options
export BUILDTEST_TEST_ROOT=/Users/siddiq90/Documents/GitHubDesktop/buildtest/var/tests/generic.local.sh/shell_examples/shell_options/0
export BUILDTEST_BUILDSPEC_DIR=/Users/siddiq90/Documents/GitHubDesktop/buildtest/tutorials
export BUILDTEST_STAGE_DIR=/Users/siddiq90/Documents/GitHubDesktop/buildtest/var/tests/generic.local.sh/shell_examples/shell_options/0/stage
export BUILDTEST_TEST_ID=95c11f54-bbb1-4154-849d-44313e4417c2
############# END VARIABLE DECLARATION ########################
# source executor startup script
source /Users/siddiq90/Documents/GitHubDesktop/buildtest/var/executor/generic.local.sh/before_script.sh
# Run generated script
sh -x /Users/siddiq90/Documents/GitHubDesktop/buildtest/var/tests/generic.local.sh/shell_examples/shell_options/0/stage/shell_options.sh
# Get return code
returncode=$?
# Exit with return code
exit $returncode
If you prefer csh or tcsh for writing scripts just set shell: csh
or
shell: tcsh
, note you will need to match this with appropriate executor. For now
use executor: generic.local.csh
to run your csh/tcsh scripts. In this example below
we define a script using csh, take note of run
section we can write csh style.
buildspecs:
csh_shell:
executor: generic.local.csh
type: script
description: "csh shell example"
shell: csh
tags: [tutorials]
vars:
file: "/etc/csh.cshrc"
run: |
if (! -e $file) then
echo "$file file not found"
exit 1
else
echo "$file file found"
endif
Customize Shebang
You may customize the shebang line in testscript using shebang
field. This
takes precedence over the shell
property which automatically detects the shebang
based on shell path.
In next example we have two tests bash_login_shebang and bash_nonlogin_shebang
which tests if shell is Login or Non-Login. The #!/bin/bash -l
indicates we
want to run in login shell and expects an output of Login Shell
while
test bash_nonlogin_shebang should run in default behavior which is non-login
shell and expects output Not Login Shell
. We match this with regular expression
with stdout stream.
buildspecs:
bash_login_shebang:
type: script
executor: generic.local.bash
shebang: "#!/bin/bash -l"
description: customize shebang line with bash login shell
tags: tutorials
run: shopt -q login_shell && echo 'Login Shell' || echo 'Not Login Shell'
status:
regex:
exp: "^Login Shell$"
stream: stdout
bash_nonlogin_shebang:
type: script
executor: generic.local.bash
shebang: "#!/bin/bash"
description: customize shebang line with default bash (nonlogin) shell
tags: tutorials
run: shopt -q login_shell && echo 'Login Shell' || echo 'Not Login Shell'
status:
regex:
exp: "^Not Login Shell$"
stream: stdout
Now let’s run this test as we see the following.
buildtest build -b tutorials/shebang.yml
$ buildtest build -b tutorials/shebang.yml
╭───────────────────────────── buildtest summary ──────────────────────────────╮
│ │
│ User: docs │
│ Hostname: build-25548776-project-280831-buildtest │
│ Platform: Linux │
│ Current Time: 2024/09/06 19:06:44 │
│ buildtest path: /home/docs/checkouts/readthedocs.org/user_builds/buildte │
│ buildtest version: 2.1 │
│ python path: /home/docs/checkouts/readthedocs.org/user_builds/buildte │
│ python version: 3.9.19 │
│ Configuration File: /tmp/tmpt43y32aw/config.yml │
│ Test Directory: /tmp/tmpt43y32aw/var/tests │
│ Report File: /tmp/tmpt43y32aw/var/report.json │
│ Command: /home/docs/checkouts/readthedocs.org/user_builds/buildte │
│ │
╰──────────────────────────────────────────────────────────────────────────────╯
─────────────────────────── Discovering Buildspecs ────────────────────────────
Discovered buildspecs
╔══════════════════════════════════════════════════════════════════════════════╗
║ buildspec ║
╟──────────────────────────────────────────────────────────────────────────────╢
║ /home/docs/checkouts/readthedocs.org/user_builds/buildtest/checkouts/devel/t ║
║ utorials/shebang.yml ║
╟──────────────────────────────────────────────────────────────────────────────╢
║ Total: 1 ║
╚══════════════════════════════════════════════════════════════════════════════╝
Total Discovered Buildspecs: 1
Total Excluded Buildspecs: 0
Detected Buildspecs after exclusion: 1
────────────────────────────── Parsing Buildspecs ──────────────────────────────
Valid Buildspecs: 1
Invalid Buildspecs: 0
/home/docs/checkouts/readthedocs.org/user_builds/buildtest/checkouts/devel/tutorials/shebang.yml: VALID
Total builder objects created: 2
Builders by type=script
┏━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━┳━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━┓
┃ ┃ ┃ ┃ ┃ ┃ ┃ descript ┃ buildsp ┃
┃ builder ┃ type ┃ executor ┃ compiler ┃ nodes ┃ procs ┃ ion ┃ ecs ┃
┡━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━╇━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━┩
│ bash_log │ script │ generic. │ None │ None │ None │ customiz │ /home/d │
│ in_sheba │ │ local.ba │ │ │ │ e │ ocs/che │
│ ng/2fb35 │ │ sh │ │ │ │ shebang │ ckouts/ │
│ d51 │ │ │ │ │ │ line │ readthe │
│ │ │ │ │ │ │ with │ docs.or │
│ │ │ │ │ │ │ bash │ g/user_ │
│ │ │ │ │ │ │ login │ builds/ │
│ │ │ │ │ │ │ shell │ buildte │
│ │ │ │ │ │ │ │ st/chec │
│ │ │ │ │ │ │ │ kouts/d │
│ │ │ │ │ │ │ │ evel/tu │
│ │ │ │ │ │ │ │ torials │
│ │ │ │ │ │ │ │ /sheban │
│ │ │ │ │ │ │ │ g.yml │
├──────────┼────────┼──────────┼──────────┼───────┼───────┼──────────┼─────────┤
│ bash_non │ script │ generic. │ None │ None │ None │ customiz │ /home/d │
│ login_sh │ │ local.ba │ │ │ │ e │ ocs/che │
│ ebang/ce │ │ sh │ │ │ │ shebang │ ckouts/ │
│ 434663 │ │ │ │ │ │ line │ readthe │
│ │ │ │ │ │ │ with │ docs.or │
│ │ │ │ │ │ │ default │ g/user_ │
│ │ │ │ │ │ │ bash │ builds/ │
│ │ │ │ │ │ │ (nonlogi │ buildte │
│ │ │ │ │ │ │ n) shell │ st/chec │
│ │ │ │ │ │ │ │ kouts/d │
│ │ │ │ │ │ │ │ evel/tu │
│ │ │ │ │ │ │ │ torials │
│ │ │ │ │ │ │ │ /sheban │
│ │ │ │ │ │ │ │ g.yml │
└──────────┴────────┴──────────┴──────────┴───────┴───────┴──────────┴─────────┘
──────────────────────────────── Building Test ─────────────────────────────────
bash_login_shebang/2fb35d51: Creating Test Directory: /tmp/tmpt43y32aw/var/tests/generic.local.bash/shebang/bash_login_shebang/2fb35d51
bash_nonlogin_shebang/ce434663: Creating Test Directory: /tmp/tmpt43y32aw/var/tests/generic.local.bash/shebang/bash_nonlogin_shebang/ce434663
──────────────────────────────── Running Tests ─────────────────────────────────
Spawning 1 processes for processing builders
───────────────────────────────── Iteration 1 ──────────────────────────────────
bash_login_shebang/2fb35d51 does not have any dependencies adding test to queue
bash_nonlogin_shebang/ce434663 does not have any dependencies adding test to queue
Builders Eligible to Run
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Builder ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ bash_login_shebang/2fb35d51 │
│ bash_nonlogin_shebang/ce434663 │
└────────────────────────────────┘
bash_login_shebang/2fb35d51: Current Working Directory : /tmp/tmpt43y32aw/var/tests/generic.local.bash/shebang/bash_login_shebang/2fb35d51/stage
bash_login_shebang/2fb35d51: Running Test via command: bash bash_login_shebang_build.sh
bash_login_shebang/2fb35d51: Test completed in 0.005928 seconds with returncode: 0
bash_login_shebang/2fb35d51: Writing output file - /tmp/tmpt43y32aw/var/tests/generic.local.bash/shebang/bash_login_shebang/2fb35d51/bash_login_shebang.out
bash_login_shebang/2fb35d51: Writing error file - /tmp/tmpt43y32aw/var/tests/generic.local.bash/shebang/bash_login_shebang/2fb35d51/bash_login_shebang.err
bash_login_shebang/2fb35d51: performing regular expression - '^Login Shell$' on file: /tmp/tmpt43y32aw/var/tests/generic.local.bash/shebang/bash_login_shebang/2fb35d51/bash_login_shebang.out
bash_login_shebang/2fb35d51: Regular Expression Match - Failed!
bash_nonlogin_shebang/ce434663: Current Working Directory : /tmp/tmpt43y32aw/var/tests/generic.local.bash/shebang/bash_nonlogin_shebang/ce434663/stage
bash_nonlogin_shebang/ce434663: Running Test via command: bash bash_nonlogin_shebang_build.sh
bash_nonlogin_shebang/ce434663: Test completed in 0.005639 seconds with returncode: 0
bash_nonlogin_shebang/ce434663: Writing output file - /tmp/tmpt43y32aw/var/tests/generic.local.bash/shebang/bash_nonlogin_shebang/ce434663/bash_nonlogin_shebang.out
bash_nonlogin_shebang/ce434663: Writing error file - /tmp/tmpt43y32aw/var/tests/generic.local.bash/shebang/bash_nonlogin_shebang/ce434663/bash_nonlogin_shebang.err
bash_nonlogin_shebang/ce434663: performing regular expression - '^Not Login Shell$' on file: /tmp/tmpt43y32aw/var/tests/generic.local.bash/shebang/bash_nonlogin_shebang/ce434663/bash_nonlogin_shebang.out
bash_nonlogin_shebang/ce434663: Regular Expression Match - Success!
Test Summary
┏━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━┓
┃ builder ┃ executor ┃ status ┃ returncode ┃ runtime ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━┩
│ bash_nonlogin_shebang/c │ generic.local.bash │ PASS │ 0 │ 0.006 │
│ e434663 │ │ │ │ │
├─────────────────────────┼────────────────────┼────────┼────────────┼─────────┤
│ bash_login_shebang/2fb3 │ generic.local.bash │ FAIL │ 0 │ 0.006 │
│ 5d51 │ │ │ │ │
└─────────────────────────┴────────────────────┴────────┴────────────┴─────────┘
Passed Tests: 1/2 Percentage: 50.000%
Failed Tests: 1/2 Percentage: 50.000%
Adding 2 test results to report file: /tmp/tmpt43y32aw/var/report.json
Writing Logfile to /tmp/tmpt43y32aw/var/logs/buildtest_c5cfzj78.log
If we look at the generated test for bash_login_shebang we see the shebang line is passed into the script:
$ cat $(buildtest path -t bash_login_shebang)
#!/bin/bash -l
# Content of run section
shopt -q login_shell && echo 'Login Shell' || echo 'Not Login Shell'
Python Shell
You can use script schema to write python scripts using the run
property. In order to write python code you
must set shell
property to python interpreter such as shell: python
or full path to python wrapper such
as shell: /usr/bin/python
.
Here is a python example calculating area of circle
buildspecs:
circle_area:
executor: generic.local.bash
type: script
shell: python
description: "Calculate circle of area given a radius"
tags: [tutorials, python]
run: |
import math
radius = 2
area = math.pi * radius * radius
print("Circle Radius ", radius)
print("Area of circle ", area)
Note
Python scripts are very picky when it comes to formatting, in the run
section
if you are defining multiline python script you must remember to use 2 space indent
to register multiline string. buildtest will extract the content from run section
and inject in your test script. To ensure proper formatting for a more complex python
script you may be better off writing a python script in separate file and invoke the
python script in the run
section.
Let’s try building this example and analyze the generated test script.
buildtest build -b tutorials/python-shell.yml
$ buildtest build -b tutorials/python-shell.yml
╭───────────────────────────── buildtest summary ──────────────────────────────╮
│ │
│ User: docs │
│ Hostname: build-25548776-project-280831-buildtest │
│ Platform: Linux │
│ Current Time: 2024/09/06 19:06:45 │
│ buildtest path: /home/docs/checkouts/readthedocs.org/user_builds/buildte │
│ buildtest version: 2.1 │
│ python path: /home/docs/checkouts/readthedocs.org/user_builds/buildte │
│ python version: 3.9.19 │
│ Configuration File: /tmp/tmpt43y32aw/config.yml │
│ Test Directory: /tmp/tmpt43y32aw/var/tests │
│ Report File: /tmp/tmpt43y32aw/var/report.json │
│ Command: /home/docs/checkouts/readthedocs.org/user_builds/buildte │
│ │
╰──────────────────────────────────────────────────────────────────────────────╯
─────────────────────────── Discovering Buildspecs ────────────────────────────
Discovered buildspecs
╔══════════════════════════════════════════════════════════════════════════════╗
║ buildspec ║
╟──────────────────────────────────────────────────────────────────────────────╢
║ /home/docs/checkouts/readthedocs.org/user_builds/buildtest/checkouts/devel/t ║
║ utorials/python-shell.yml ║
╟──────────────────────────────────────────────────────────────────────────────╢
║ Total: 1 ║
╚══════════════════════════════════════════════════════════════════════════════╝
Total Discovered Buildspecs: 1
Total Excluded Buildspecs: 0
Detected Buildspecs after exclusion: 1
────────────────────────────── Parsing Buildspecs ──────────────────────────────
Valid Buildspecs: 1
Invalid Buildspecs: 0
/home/docs/checkouts/readthedocs.org/user_builds/buildtest/checkouts/devel/tutorials/python-shell.yml: VALID
Total builder objects created: 1
Builders by type=script
┏━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━┳━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━┓
┃ ┃ ┃ ┃ ┃ ┃ ┃ descript ┃ buildsp ┃
┃ builder ┃ type ┃ executor ┃ compiler ┃ nodes ┃ procs ┃ ion ┃ ecs ┃
┡━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━╇━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━┩
│ circle_a │ script │ generic. │ None │ None │ None │ Calculat │ /home/d │
│ rea/a440 │ │ local.ba │ │ │ │ e circle │ ocs/che │
│ 9e5a │ │ sh │ │ │ │ of area │ ckouts/ │
│ │ │ │ │ │ │ given a │ readthe │
│ │ │ │ │ │ │ radius │ docs.or │
│ │ │ │ │ │ │ │ g/user_ │
│ │ │ │ │ │ │ │ builds/ │
│ │ │ │ │ │ │ │ buildte │
│ │ │ │ │ │ │ │ st/chec │
│ │ │ │ │ │ │ │ kouts/d │
│ │ │ │ │ │ │ │ evel/tu │
│ │ │ │ │ │ │ │ torials │
│ │ │ │ │ │ │ │ /python │
│ │ │ │ │ │ │ │ -shell. │
│ │ │ │ │ │ │ │ yml │
└──────────┴────────┴──────────┴──────────┴───────┴───────┴──────────┴─────────┘
──────────────────────────────── Building Test ─────────────────────────────────
circle_area/a4409e5a: Creating Test Directory: /tmp/tmpt43y32aw/var/tests/generic.local.bash/python-shell/circle_area/a4409e5a
──────────────────────────────── Running Tests ─────────────────────────────────
Spawning 1 processes for processing builders
───────────────────────────────── Iteration 1 ──────────────────────────────────
circle_area/a4409e5a does not have any dependencies adding test to queue
Builders Eligible to Run
┏━━━━━━━━━━━━━━━━━━━━━━┓
┃ Builder ┃
┡━━━━━━━━━━━━━━━━━━━━━━┩
│ circle_area/a4409e5a │
└──────────────────────┘
circle_area/a4409e5a: Current Working Directory : /tmp/tmpt43y32aw/var/tests/generic.local.bash/python-shell/circle_area/a4409e5a/stage
circle_area/a4409e5a: Running Test via command: bash circle_area_build.sh
circle_area/a4409e5a: Test completed in 0.035178 seconds with returncode: 0
circle_area/a4409e5a: Writing output file - /tmp/tmpt43y32aw/var/tests/generic.local.bash/python-shell/circle_area/a4409e5a/circle_area.out
circle_area/a4409e5a: Writing error file - /tmp/tmpt43y32aw/var/tests/generic.local.bash/python-shell/circle_area/a4409e5a/circle_area.err
Test Summary
┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━┓
┃ builder ┃ executor ┃ status ┃ returncode ┃ runtime ┃
┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━┩
│ circle_area/a4409e5a │ generic.local.bash │ PASS │ 0 │ 0.035 │
└──────────────────────┴────────────────────┴────────┴────────────┴─────────┘
Passed Tests: 1/1 Percentage: 100.000%
Failed Tests: 0/1 Percentage: 0.000%
Adding 1 test results to report file: /tmp/tmpt43y32aw/var/report.json
Writing Logfile to /tmp/tmpt43y32aw/var/logs/buildtest_z120h544.log
Take note in the generated test script, we simply call a python script
$ cat $(buildtest path -t circle_area)
#!/bin/bash
python /tmp/tmpt43y32aw/var/tests/generic.local.bash/python-shell/circle_area/a4409e5a/stage/circle_area.py
The python script will contain content specified in the run
section. This script
is located in the stage directory, we can retrieve the path to this file using the following
expression $(buildtest path -s circle_area)/circle_area.py
and then view the content via cat
.
$ ls -l $(buildtest path -s circle_area)/circle_area.py
-rw-r--r-- 1 docs docs 119 Sep 6 19:06 /tmp/tmpt43y32aw/var/tests/generic.local.bash/python-shell/circle_area/a4409e5a/stage/circle_area.py
$ cat $(buildtest path -s circle_area)/circle_area.py
import math
radius = 2
area = math.pi * radius * radius
print("Circle Radius ", radius)
print("Area of circle ", area)