Reputation: 3824
I have installed bashcov.. in order to measure code coverage in a bunch of bash scripts:
$ bash --version GNU bash, version 5.0.17(1)-release (x86_64-pc-linux-gnu)
...
$ bundle exec bashcov --version
bashcov 1.8.2
and this totally works as expected:
$ cat main.sh
#! /usr/bin/env bash
set -e
if [[ "$1" == "" ]]
then
echo no args
else
echo args
fi
./sub.sh
$ cat sub.sh
#! /usr/bin/env bash
set -e
if [[ "$1" == "" ]]
then
echo no args in subprogram
else
echo args in subprogram
fi
$ bundle exec bashcov ./main.sh
no args
no args in subprogram
Run completed using bashcov 1.8.2 with Bash 5.0, Ruby 2.7.0, and SimpleCov 0.15.1.
Coverage report generated for /bin/bash ./main.sh to ~/bashcov/coverage. 7 / 9 LOC (77.78%) covered.
I get these html reports:
So it can totally call another bash program and measure the coverage for it too.
HOWEVER
When I try to put a python program in the middle, things don't work quite as I would like:
$ cat main.sh
#! /usr/bin/env bash
set -e
if [[ "$1" == "" ]]
then
echo no args
else
echo args
fi
python3 -u sub.py
$ cat sub.py
import subprocess
print("running subprograms")
subprocess.run(["./sub.sh"])
$ # and leave sub.sh the same as above
$ bundle exec bashcov ./main.sh
no args
running subprograms
bash: BASH_XTRACEFD: 8: invalid value for trace file descriptor
+BASHCOV>f4697250-c3da-4086-.....set -e
+BASHCOV>f4697250-c3da-4086-.....[[ '' == '' ]]
+BASHCOV>f4697250-c3da-4086-.....echo no args in subprogram
no args in subprogram
Run completed using bashcov 1.8.2 with Bash 5.0, Ruby 2.7.0, and SimpleCov 0.15.1.
Coverage report generated for /bin/bash ./main.sh to ~/bashcov/coverage. 4 / 9 LOC (44.44%) covered.
and we have no coverage info for the subprogram:
SO...
It looks like that the file descriptor '8' that it puts in the environment is no longer valid in the subprogram if called from python, but somehow is valid when one bash program calls another directly. I have also tried calling the subprocess with shell=True
and the result is no error and no coverage info for the subprogram.
Does anybody know how it fits together? Why is the file descriptor valid in a bash program that is called directly from main, but not in one that is called from python?
I know it is using bashcov in the subprogram called by python because I hacked bashcov to uses stderr instead of the custom file descriptor... which it does for bash versions older than 4.1
# Older versions of Bash (< 4.1) don't have the BASH_XTRACEFD variable
+ if Bashcov.bash_xtracefd? and false
- if Bashcov.bash_xtracefd?
options[fd] = fd # bind FDs to the child process
env["BASH_XTRACEFD"] = fd.to_s
else
# Send subprocess standard error to @xtrace.file_descriptor
options[:err] = fd
# Don't bother issuing warning if we're silencing output anyway
unless Bashcov.mute
write_warning <<-WARNING
you are using a version of Bash that does not support
BASH_XTRACEFD. All xtrace output will print to standard error, and
your script's output on standard error will not be printed to the
console.
WARNING
end
I can pretty much show what is going on without using bashcov
at all.
This is really just a python and linux question. Maybe I should have create a new question:
I have created a new set of sample programs that look like this:
==> m.sh <==
#! /usr/bin/env bash
set -e
exec 3<>messages
ls -l /proc/$$/fd/
echo bash program called directly
./s.sh
==> r.py <==
import subprocess
import sys
import os
print("python program:")
subprocess.run(["ls", "-l", "/proc/%s/fd/" % os.getpid()])
print("bash program called by python:")
subprocess.run(sys.argv[1:], check=True)
==> s.sh <==
#! /usr/bin/env bash
set -e
ls -l /proc/$$/fd/
and this is the result
$ ./m.sh
total 0
lrwx------ 1 me me 64 Apr 15 22:43 0 -> /dev/pts/2
l-wx------ 1 me me 64 Apr 15 22:43 1 -> pipe:[188295]
lrwx------ 1 me me 64 Apr 15 22:43 2 -> /dev/pts/2
lr-x------ 1 me me 64 Apr 15 22:43 255 -> /home/me/bashcov/m.sh
lrwx------ 1 me me 64 Apr 15 22:43 3 -> /home/me/bashcov/messages
bash program called directly
total 0
lrwx------ 1 me me 64 Apr 15 22:43 0 -> /dev/pts/2
l-wx------ 1 me me 64 Apr 15 22:43 1 -> pipe:[188295]
lrwx------ 1 me me 64 Apr 15 22:43 2 -> /dev/pts/2
lr-x------ 1 me me 64 Apr 15 22:43 255 -> /home/me/bashcov/s.sh
lrwx------ 1 me me 64 Apr 15 22:43 3 -> /home/me/bashcov/messages
python program:
total 0
lrwx------ 1 me me 64 Apr 15 22:43 0 -> /dev/pts/2
l-wx------ 1 me me 64 Apr 15 22:43 1 -> pipe:[188295]
lrwx------ 1 me me 64 Apr 15 22:43 2 -> /dev/pts/2
lrwx------ 1 me me 64 Apr 15 22:43 3 -> /home/me/bashcov/messages
bash program called by python:
total 0
lrwx------ 1 me me 64 Apr 15 22:43 0 -> /dev/pts/2
l-wx------ 1 me me 64 Apr 15 22:43 1 -> pipe:[188295]
lrwx------ 1 me me 64 Apr 15 22:43 2 -> /dev/pts/2
lr-x------ 1 me me 64 Apr 15 22:43 255 -> /home/me/bashcov/s.sh
bash passes all file descriptors down to subprocesses, but python doesn't. Does anybody know if there is a way to change that?
Upvotes: 1
Views: 222
Reputation: 3824
Thanks to a comment, and this answer: https://stackoverflow.com/a/67108740/147356
This works:
replace:
subprocess.run(["./sub.sh"])
with
subprocess.Popen(["./sub.sh"], close_fds=False).communicate()
that feature can be turned off for Popen
but not run
with close_fds
Upvotes: 2