bd is a script to conveniently jump multiple directory levels up from the current working directory instead of a tedious and possibly inaccurate cd ../../../..

Say, for example, that you are in the directory /home/user/project/src/org/main and you want to jump to project. Instead of counting the necessary levels to jump and typing cd ../../.., you can do bd -s pro (where -s is to match the start of the directory name, in this example "pro" for "project").

I came across this script in another question, and looking at the github repository I decided to jazz it up a little. But before going ahead and refactoring it, I wanted to make sure I'm not breaking anything, so I needed some sort of unit tests as a sane baseline first.

This is the main function in the bd script under testing, and this is not for code review, I'm including it here only for your reference in case it helps in some way:

# NOT for code review: the reference implementation under test
newpwd() {
  if [ "$2" = "-s" ]
    NEWPWD=`echo $OLDPWD | sed 's|\(.*/'$3'[^/]*/\).*|\1|'`
    index=`echo $NEWPWD | awk '{ print index($0,"/'$3'"); }'`
  elif [ "$2" = "-si" ]
    NEWPWD=`echo $OLDPWD | sed 's|\(.*/'$3'[^/]*/\).*|\1|I'`
    index=`echo $NEWPWD | awk '{ print index(tolower($0),tolower("/'$3'")); }'`
    NEWPWD=`echo $OLDPWD | sed 's|\(.*/'$2'/\).*|\1|'`
    index=`echo $NEWPWD | awk '{ print index($1,"/'$2'/"); }'`

What I'd like reviewed is my implementation of "unit tests" to verify the behavior of the newpwd function inside the bd script:


# loading the "newpwd" function from the bd script, a necessary ugliness for now
. bd nonexistent > /dev/null


assertEquals() {
    if [[ $expected = $actual ]]; then
        echo "Assertion failed: $expected != $actual" >&2
        caller 0


# test run with no args
newpwd $sample
assertEquals $sample $NEWPWD
assertEquals 0 $index

# test run with exact match
newpwd $sample src
assertEquals /home/user/project/src/ $NEWPWD
assertEquals 19 $index

# test run with prefix but no -s so not found
newpwd $sample sr
assertEquals $sample $NEWPWD
assertEquals 0 $index

# test run with prefix found
newpwd $sample -s sr
assertEquals /home/user/project/src/ $NEWPWD
assertEquals 19 $index

# test run with prefix not found because case sensitive
newpwd $sample -s Sr
assertEquals $sample $NEWPWD
assertEquals 0 $index

# test run with prefix found thanks to -si
newpwd $sample -si Sr
assertEquals /home/user/project/src/ $NEWPWD
assertEquals 19 $index

sample='/home/user/my project/src'

# test run with space in dirname
newpwd "$sample" -s my
assertEquals '/home/user/my project/' "$NEWPWD"
assertEquals 11 $index


[[ $failure = 0 ]] && printf $green || printf $red
echo "Tests run: $total ($success success, $failure failed)"
printf $nocolor

My questions:

  • Is this easy to read? If not, how to improve it?
  • Is this a good way to test stuff in Bash? Is there a better way? I'd rather not add external dependencies to the project though, I'm looking for something ultra-light, or a technique
  • Are there any corner cases I missed?
  • Any other improvement ideas?
  • \$\begingroup\$ You might want to take a look at bash_unit. Using a test framework would avoid to have to read and maintain test plumbery code. github.com/pgrange/bash_unit Regards, \$\endgroup\$
    – user101971
    Apr 4 '16 at 9:43
  • \$\begingroup\$ @pgrange thanks for the tip, I'll check it out! \$\endgroup\$
    – janos
    Apr 4 '16 at 9:48

Just 2 cents:

  1. I'd quote the right hand side of the [[ ... = ... ]] to avoid pattern interpretation. Cf.

    [[ $x =  $y  ]] && echo Equal without quotes
    [[ $x = "$y" ]] || echo Not equal with quotes
  2. I like naming the tests. I'd probably add a third parameter to assertEquals and include it in the report. It also improves readability. Inspired by Test::More for Perl.

  • \$\begingroup\$ IIRC, you can avoid pattern interpretation by using double equals. However, I would still have quoted both sides of this test so that whitespace inside either expected or actual value doesn't break my tests. That said, I would have also used printf everywhere for consistency. \$\endgroup\$
    – mkalkov
    Oct 14 '14 at 21:16
  • \$\begingroup\$ @mkalkov: I tried with double equals, the results are the same. Setting x='1 2 3' withough quoting left hand side doesn't change the results either. What were you trying to say? \$\endgroup\$
    – choroba
    Oct 14 '14 at 21:20
  • \$\begingroup\$ I cannot test this until tomorrow, but I'm really surprised it worked with whitespaces, could be a [[ vs [ issue... \$\endgroup\$
    – mkalkov
    Oct 14 '14 at 21:25
  • \$\begingroup\$ @mkalkov: Highly possible. That's why I used [[. \$\endgroup\$
    – choroba
    Oct 14 '14 at 22:07
  • 1
    \$\begingroup\$ @malkov From the man page: "When the == and != operators are used, the string to the right of the operator is considered a pattern" It explicitly states that == does pattern matching. (If anything, it leaves open the possibility that = is strictly for equality testing, but = and == behave identically inside [[ ... ]].) \$\endgroup\$
    – chepner
    Oct 17 '14 at 17:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.