Alex028502
Alex028502

Reputation: 3824

bashcov - does not work if subprogram is called by python

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:

enter image description here

enter image description here

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:

enter image description here


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

and got this: enter image description here


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

Answers (1)

Alex028502
Alex028502

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

Related Questions