TypedDevs / bashunit

A simple testing library for bash scripts. Test your bash scripts in the fastest and simplest way, discover the most modern bash testing library.
https://bashunit.typeddevs.com
MIT License
270 stars 21 forks source link

Tests failing when sourcing files with execution outside of functions #272

Closed lanyou0 closed 1 week ago

lanyou0 commented 2 weeks ago

Hello, I am trying out your framework with some files I possess, but for some reason, every time I run my tests after sourcing my script.sh file, my tests fail. For context, my script.sh file is sourcing another file (outside of any function) and also exports some variables (outside of any function). I am wondering if this is a bug in the framework or if the framework does not support sourcing files that execute commands outside of functions for testing. Thank you.

Chemaclass commented 2 weeks ago

Hi, @lanyou0. I am not sure what you mean. Sourcing other scripts should work as usual. Can you provide a code snippet example that can help us reproduce the bug?

lanyou0 commented 2 weeks ago

Thank you for answering here's a snippet:

external.sh `

!/bin/bash `

export MY_VARIABLE="Hello, World!" echo "Sourced external.sh"

script.sh: #!/bin/bash source ./external.sh help() { echo "Usage: script.sh [options]" exit 1 }

test.sh function set_up() { source 'script.sh' }

function test_help() { result="$(help)" assert_exit_code "1" }

The test.sh is run through ./bashunit test.sh as mentioned in the documentation on the website

When I do that the assertion is passing but the test itself is failing because of having source, echo, and export being outside of functions. The tests pass normally if I remove all three but my problem is I have more lengthy files with a lot more sourcing and export commands so I wanted to make sure the framework is compatible before moving forward

Chemaclass commented 2 weeks ago

Can you try renaming in your script.sh your function help to other name (eg: my_help)?

snippets

```bash #!/bin/bash # test.sh function set_up() { source './script.sh' } function test_help() { local result result="$(my_help)" assert_exit_code 1 } ##################### #!/bin/bash # script.sh source ./external.sh my_help() { echo "Usage: script.sh [options]" exit 1 } ##################### #!/bin/bash # external.sh export MY_VARIABLE="Hello, World!" echo "Sourced external.sh" ```

Screenshot 2024-06-21 at 17 03 08

For me this works. Can you let me know if you can reproduce the same or you got any issues, @lanyou0?

lanyou0 commented 2 weeks ago
Screenshot 2024-06-21 at 11 15 58 AM

Hello, for some reason it is still failing, and I have tried the exact code you provided with the changes to help

Chemaclass commented 1 week ago

@lanyou0 from where are you running ./bashunit? I am testing it with all these files inside a common directory named local and ./bashunit is at the root of the project. When being inside the same directory of those files, then it's working. But if I am outside, then it's not working. Do you have the same behavior? I am trying to understand your case to reproduce the same problem, otherwise it is pretty hard for me to fix it. Suggestions that can help me reproduce it:

  1. download the latest beta version to make sure you have the latest code from main.
  2. create a short video showing how do you reproduce it (with these examples), so I can make sure I do repeat the same as you are doing.

Screenshot 2024-06-21 at 21 07 02

Chemaclass commented 1 week ago

@lanyou0 can you change in your test.sh the set_up() to make sure the script.sh is loaded with a relative path?

_ROOT_DIR="$(dirname "${BASH_SOURCE[0]}")"

function set_up() {
  source "$_ROOT_DIR/script.sh"
}

UPDATE: Check this example: https://github.com/TypedDevs/bashunit/blob/main/example/custom_functions_test.sh#L5

lanyou0 commented 1 week ago

Hello, thank you for your help: Basically, I downloaded the bashunit framework using the following command: curl -s https://bashunit.typeddevs.com/install.sh | bash, which gives me the newest version, and added it onto a folder called bashunit_tests where I have my test.sh file. I added the external.sh and script.sh files in a different directory and the test seems to pass if:

However this framework is a bit confusing since if I try to do this in my script.sh

Screenshot 2024-06-21 at 3 45 37 PM

with external.sh having:

Screenshot 2024-06-21 at 3 46 52 PM

my test.sh passes:

Screenshot 2024-06-21 at 3 46 00 PM Screenshot 2024-06-21 at 3 47 47 PM

but if I remove # echo "$(dirname)" from script.sh, my test fails

Screenshot 2024-06-21 at 3 48 23 PM

Sorry if this seems confusing I am just trying to debug as much as I can

Chemaclass commented 1 week ago

I can reproduce the error 🤔 weird weird... with the echo "$(dirname)" Screenshot 2024-06-21 at 22 10 22

but removing it (commenting it out) then it fails, wtf 🤣 Screenshot 2024-06-21 at 22 11 11

CC: @khru @antonio-gg-dev @cmayo

lanyou0 commented 1 week ago

Yes it's so weird it only passes if I do echo "$(dirname)" or echo "$(realpath)" I am not really sure why 😅

Chemaclass commented 1 week ago

@lanyou0 I gave the entire context of the three files to ChatGPT and asked why is that error occurring when removing that line, and the answer is outstanding.

Question: The issue is that when removing the line echo "$(dirname)" from script.sh then I get an error, why?

Ghat-GPT answer

The issue likely arises because the line `echo "$(dirname)"` is forcing a call to `dirname` when `script.sh` is sourced. This call causes the script to fail and may mask other issues that become apparent when this line is removed. Here's a step-by-step analysis of what might be happening: 1. **With the `echo "$(dirname)"` line present**: - When `script.sh` is sourced by `test.sh`, the command `dirname` is executed. - Since `dirname` expects an argument (a path), the call without an argument likely produces an error or empty output. - This error might cause the script to terminate prematurely or behave unexpectedly, but it does not cause the `source` command to fail. - As a result, the rest of `script.sh` is executed, and `external.sh` is sourced successfully. 2. **When the `echo "$(dirname)"` line is removed**: - The problematic `dirname` call is no longer executed, so no immediate error occurs from that line. - The script proceeds to the next line: `source "$(dirname "${BASH_SOURCE[0]}")"/external.sh`. - Here, `dirname "${BASH_SOURCE[0]}"` should give the directory containing the current script, but if `BASH_SOURCE[0]` is not set or not set correctly, it might lead to an invalid path being constructed. - If `dirname "${BASH_SOURCE[0]}"` returns an empty string or an incorrect directory, the `source` command will fail because it cannot find `external.sh`. ### Troubleshooting Steps 1. **Check the Output of `dirname "${BASH_SOURCE[0]}"`**: Add a debug statement before the `source` line to check what path is being constructed: ```bash # script.sh echo "Sourcing external.sh from: $(dirname "${BASH_SOURCE[0]}")/external.sh" source "$(dirname "${BASH_SOURCE[0]}")"/external.sh ``` 2. **Ensure `BASH_SOURCE` is Set Correctly**: Verify that `BASH_SOURCE` is being set correctly when `script.sh` is sourced by `test.sh`. It should contain the path to `script.sh`. 3. **Check for Other Errors**: Ensure there are no other hidden issues in `script.sh` or `external.sh` that might be causing the failure when the problematic line is removed. ### Example Debugging Update `script.sh` to include debug information: ```bash #!/bin/bash # script.sh # Add debug information echo "Current script directory: $(dirname "${BASH_SOURCE[0]}")" source "$(dirname "${BASH_SOURCE[0]}")"/external.sh function my_help() { echo "Usage: script.sh [options]" smth "BASH_SOURCE: ${BASH_SOURCE[0]}" exit 1 } ``` Run `test.sh` again and observe the output. This should give you more information about why the `source` command might be failing. If the issue persists, share the debug output, and we can further investigate the problem.

Chemaclass commented 1 week ago

@lanyou0 the error seems to be on the redirection to the error output inside the smth() function -> >&2. If you remove that redirection then it will work 🤔

function smth(){
#  echo "$*" >&2
  echo "$*"
}
The reason is coming from src/runner.sh:169

```bash # (FD = File Descriptor) # Duplicate the current std-output (FD 1) and assigns it to FD 3. # This means that FD 3 now points to wherever the std-output was pointing. exec 3>&1 local test_execution_result test_execution_result=$( state::initialize_assertions_count runner::run_set_up # 2>&1: Redirects the std-error (FD 2) to the std-output (FD 1). # 1>&3: Redirects the std-output (FD 1) to FD 3, which, as set up earlier, # points to the original std-output. "$function_name" "$@" 2>&1 1>&3 # <---- HERE runner::run_tear_down runner::clear_mocks state::export_assertions_count ) # Closes FD 3, which was used temporarily to hold the original std-output. exec 3>&- # ... ```

The question now is, if (and how?) we want/can allow this feature of redirecting the output of a command to the standard error stream instead of the standard output with >&2 🤔 CC: @cmayo @antonio-gg-dev @khru

Chemaclass commented 1 week ago

@lanyou0 can you try adding exec 2>&3 to your test_help()? check this out: https://github.com/TypedDevs/bashunit/pull/274

does it help?

```bash function test_help() { exec 2>&3 local result result="$(my_help)" assert_exit_code 1 } ```

staabm commented 1 week ago

Btw: I also experienced the problem that output on stderr makes test fail.

The biggest DX issue with this IMO is, that we don't get a clear error message which shows why the test is failling (e.g. because of output to stderr on line xy)

lanyou0 commented 1 week ago

Hey! Thank you very much for your help and replies! I tried these solutions to see if it was the error but echo "$*" still failed if I did not have the echo "$(realpath)" or echo "$(dirname)" in script.sh. I also see that you have created a pull request to redirect the file descriptor error, could you explain to me a bit more how it would help with the stderr?

Chemaclass commented 1 week ago

@lanyou0 Here I wrote the reason: https://github.com/TypedDevs/bashunit/issues/272#issuecomment-2183967605 Click on The reason is coming from src/runner.sh:169

TL:DR: by redirecting the stderr to another FD (with exec 2>&3 2>/dev/null) so the stderr from your logic does not interfere with bashunit stderr FD

Chemaclass commented 1 week ago

@lanyou0 good news! @cmayo found the fix for this issue: https://github.com/TypedDevs/bashunit/pull/275 Install the beta version (which contains main branch) and try it out again 🙏