summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorTodd Gamblin <tgamblin@llnl.gov>2020-10-26 11:06:03 -0700
committerTodd Gamblin <tgamblin@llnl.gov>2020-10-27 13:59:46 -0700
commitec9456feb8f14c173a8786c136a1bc496579231b (patch)
tree46cc8ec6ae39f174fd662fdf0a1a9361062f6ee1 /lib
parent9f89a7e9f7ce279e4b47ec06a9ffb90a453b2f7a (diff)
downloadspack-ec9456feb8f14c173a8786c136a1bc496579231b.tar.gz
spack-ec9456feb8f14c173a8786c136a1bc496579231b.tar.bz2
spack-ec9456feb8f14c173a8786c136a1bc496579231b.tar.xz
spack-ec9456feb8f14c173a8786c136a1bc496579231b.zip
sbang: convert sbang script to POSIX shell
`sbang` was previously a bash script but did not need to be. This converts it to a plain old POSIX shell script and adds some options. This also allows us to simplify sbang shebangs to `#!/bin/sh /path/to/sbang` instead of `#!/bin/bash /path/to/sbang`. The new script passes shellcheck (with a few exceptions noted in the file) - [x] `SBANG_DEBUG` env var enables printing what *would* be executed - [x] `sbang` checks whether it has been passed an option and fails gracefully - [x] `sbang` will now fail if it can't find a second shebang line, or if the second line happens to be sbang (avoid infinite loops) - [x] add more rigorous tests for `sbang` behavior using `SBANG_DEBUG`
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/spack/hooks/sbang.py2
-rw-r--r--lib/spack/spack/test/sbang.py80
2 files changed, 75 insertions, 7 deletions
diff --git a/lib/spack/spack/hooks/sbang.py b/lib/spack/spack/hooks/sbang.py
index 24d84db8af..76b571f3d2 100644
--- a/lib/spack/spack/hooks/sbang.py
+++ b/lib/spack/spack/hooks/sbang.py
@@ -51,7 +51,7 @@ def filter_shebang(path):
original = original.decode('UTF-8')
# This line will be prepended to file
- new_sbang_line = '#!/bin/bash %s\n' % sbang_install_path()
+ new_sbang_line = '#!/bin/sh %s\n' % sbang_install_path()
# Skip files that are already using sbang.
if original.startswith(new_sbang_line):
diff --git a/lib/spack/spack/test/sbang.py b/lib/spack/spack/test/sbang.py
index c2d0adf4d2..bdab7a8cfc 100644
--- a/lib/spack/spack/test/sbang.py
+++ b/lib/spack/spack/test/sbang.py
@@ -23,18 +23,21 @@ from spack.util.executable import which
short_line = "#!/this/is/short/bin/bash\n"
long_line = "#!/this/" + ('x' * 200) + "/is/long\n"
+
lua_line = "#!/this/" + ('x' * 200) + "/is/lua\n"
lua_in_text = ("line\n") * 100 + "lua\n" + ("line\n" * 100)
lua_line_patched = "--!/this/" + ('x' * 200) + "/is/lua\n"
+
node_line = "#!/this/" + ('x' * 200) + "/is/node\n"
node_in_text = ("line\n") * 100 + "lua\n" + ("line\n" * 100)
node_line_patched = "//!/this/" + ('x' * 200) + "/is/node\n"
-sbang_line = '#!/bin/bash %s/bin/sbang\n' % spack.store.layout.root
+
php_line = "#!/this/" + ('x' * 200) + "/is/php\n"
php_in_text = ("line\n") * 100 + "php\n" + ("line\n" * 100)
php_line_patched = "<?php #!/this/" + ('x' * 200) + "/is/php\n"
php_line_patched2 = "?>\n"
-sbang_line = '#!/bin/bash %s/bin/sbang\n' % spack.store.layout.root
+
+sbang_line = '#!/bin/sh %s/bin/sbang\n' % spack.store.layout.root
last_line = "last!\n"
@@ -178,7 +181,7 @@ def test_shebang_handles_non_writable_files(script_dir):
assert oct(not_writable_mode) == oct(st.st_mode)
-def check_sbang():
+def check_sbang_installation():
sbang_path = sbang.sbang_install_path()
sbang_bin_dir = os.path.dirname(sbang_path)
assert sbang_path.startswith(spack.store.layout.root)
@@ -201,7 +204,7 @@ def test_install_sbang(install_mockery):
assert not os.path.exists(sbang_bin_dir)
sbang.install_sbang()
- check_sbang()
+ check_sbang_installation()
# put an invalid file in for sbang
fs.mkdirp(sbang_bin_dir)
@@ -209,8 +212,73 @@ def test_install_sbang(install_mockery):
f.write("foo")
sbang.install_sbang()
- check_sbang()
+ check_sbang_installation()
# install again and make sure sbang is still fine
sbang.install_sbang()
- check_sbang()
+ check_sbang_installation()
+
+
+def test_sbang_fails_without_argument():
+ sbang = which(spack.paths.sbang_script)
+ sbang(fail_on_error=False)
+ assert sbang.returncode == 1
+
+
+@pytest.mark.parametrize("shebang,returncode,expected", [
+ # perl, with and without /usr/bin/env
+ ("#!/path/to/perl", 0, "/path/to/perl -x"),
+ ("#!/usr/bin/env perl", 0, "/usr/bin/env perl -x"),
+
+ # perl -w, with and without /usr/bin/env
+ ("#!/path/to/perl -w", 0, "/path/to/perl -w -x"),
+ ("#!/usr/bin/env perl -w", 0, "/usr/bin/env perl -w -x"),
+
+ # ruby, with and without /usr/bin/env
+ ("#!/path/to/ruby", 0, "/path/to/ruby -x"),
+ ("#!/usr/bin/env ruby", 0, "/usr/bin/env ruby -x"),
+
+ # python, with and without /usr/bin/env
+ ("#!/path/to/python", 0, "/path/to/python"),
+ ("#!/usr/bin/env python", 0, "/usr/bin/env python"),
+
+ # php with one-line php comment
+ ("<?php #!/usr/bin/php ?>", 0, "/usr/bin/php"),
+
+ # simple shell scripts
+ ("#!/bin/sh", 0, "/bin/sh"),
+ ("#!/bin/bash", 0, "/bin/bash"),
+
+ # error case: sbang as infinite loop
+ ("#!/path/to/sbang", 1, None),
+ ("#!/usr/bin/env sbang", 1, None),
+
+ # lua
+ ("--!/path/to/lua", 0, "/path/to/lua"),
+
+ # node
+ ("//!/path/to/node", 0, "/path/to/node"),
+])
+def test_sbang_with_specific_shebang(
+ tmpdir, shebang, returncode, expected):
+
+ script = str(tmpdir.join("script"))
+
+ # write a script out with <shebang> on second line
+ with open(script, "w") as f:
+ f.write("#!/bin/sh {sbang}\n{shebang}\n".format(
+ sbang=spack.paths.sbang_script,
+ shebang=shebang
+ ))
+ fs.set_executable(script)
+
+ # test running the script in debug, which prints what would be executed
+ exe = which(script)
+ out = exe(output=str, fail_on_error=False, env={"SBANG_DEBUG": "1"})
+
+ # check error status and output vs. expected
+ assert exe.returncode == returncode
+
+ if expected is not None:
+ expected += " " + script
+ assert expected == out.strip()