diff options
author | Todd Gamblin <tgamblin@llnl.gov> | 2020-10-26 11:06:03 -0700 |
---|---|---|
committer | Todd Gamblin <tgamblin@llnl.gov> | 2020-10-27 13:59:46 -0700 |
commit | ec9456feb8f14c173a8786c136a1bc496579231b (patch) | |
tree | 46cc8ec6ae39f174fd662fdf0a1a9361062f6ee1 /lib | |
parent | 9f89a7e9f7ce279e4b47ec06a9ffb90a453b2f7a (diff) | |
download | spack-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.py | 2 | ||||
-rw-r--r-- | lib/spack/spack/test/sbang.py | 80 |
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() |