Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Convert to Lib tests #373

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 11 additions & 29 deletions exercises/triangle/example.sh
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
#!/usr/bin/env bash

set -o errexit # Stop script on command error
set -o nounset # Error out if accessing undefined variable name
#set -o nounset # Error out if accessing undefined variable name
set -o pipefail # Error out if any step in a pipe errors out

if [[ $# -ne 4 ]]; then
usage () {
echo "Usage: $0 [equilateral | isosceles | scalene] <s1> <s2> <s3>"
exit 2 # Improper inputs
fi

triangle_type=$1
s1=$2
s2=$3
s3=$4
}

assert() {
# Takes a numerical inequality in a string.
Expand All @@ -22,14 +17,7 @@ assert() {
}

output() {
exit_code=$1

if [ $exit_code -eq 0 ]; then
echo "true"
else
echo "false"
fi

[[ $1 -eq 0 ]] && echo 'true' || echo 'false'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(( $1 == 0 )) && ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are very right about that.

At least I upgraded it from [] 😂

exit 0
}

Expand All @@ -43,34 +31,28 @@ valid_triangle() {
return 0
}

if ! valid_triangle $s1 $s2 $s3; then
# Sides do not meet triangle inequality requirement
# Given a <= b <= c and a, b, c != 0, a + b >= c
output 1
fi
validate_args () {
[[ $# -eq 3 ]] || usage
valid_triangle "$@"
}

equilateral() {
validate_args "$@" || output 1
assert "$1 == $2" && assert "$1 == $3"

output $?
}

isosceles() {
validate_args "$@" || output 1
assert "$1 == $2" || assert "$1 == $3" || assert "$2 == $3"

output $?
}

scalene() {
validate_args "$@" || output 1
assert "$1 != $2" && assert "$1 != $3" && assert "$2 != $3"

output $?
}

# Bash Ternary Operator:
# (boolean value/calculation) && action if true || action if false
# Works because of boolean shortcutting.
# If boolean clause is false, it doesn't evaluate the other side of &&
# If first thing is false, it evaluates the item after ||,
# but if first two are true, doesn't bother evaluating last part.
$triangle_type $s1 $s2 $s3 && exit 0 || exit 1
33 changes: 11 additions & 22 deletions exercises/triangle/triangle.sh
Original file line number Diff line number Diff line change
@@ -1,24 +1,13 @@
#!/usr/bin/env bash

# The following comments should help you get started:
# - Bash is flexible. You may use functions or write a "raw" script.
#
# - Complex code can be made easier to read by breaking it up
# into functions, however this is sometimes overkill in bash.
#
# - You can find links about good style and other resources
# for Bash in './README.md'. It came with this exercise.
#
# Example:
# # other functions here
# # ...
# # ...
#
# main () {
# # your main function code here
# }
#
# # call main with all of the positional arguments
# main "$@"
#
# *** PLEASE REMOVE THESE COMMENTS BEFORE SUBMITTING YOUR SOLUTION ***
equilateral() {
echo 'unimplemented!'
}

isosceles() {
echo 'unimplemented!'
}

scalene() {
echo 'unimplemented!'
}
41 changes: 22 additions & 19 deletions exercises/triangle/triangle_test.sh
Original file line number Diff line number Diff line change
@@ -1,31 +1,34 @@
#!/usr/bin/env bash

# get user code
source triangle.sh

# Test returns true if the triangle is equilateral

@test "all sides are equal, equilateral" {
#[[ $BATS_RUN_SKIPPED == true ]] || skip
run bash triangle.sh equilateral 2 2 2
run equilateral 2 2 2
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a fan of exporting a single main or triangle function that takes a triangle-type as a first arg.
Thoughts?

Copy link
Contributor Author

@guygastineau guygastineau Aug 6, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that it is a library I think main here is not right, but I could be convinced that a managing function has its place for namespacing.

This also depends on how our other endeavors into namespacing fare.

If you have some ideas for this exercise please feel free to make a PR to this branch on my fork.

If you would prefer I am interested in making a new branch on the base repo for getting through this ordeal.

I want to know other contributor opinions first though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or you can make your own PR for triangle.

Copy link
Contributor

@glennj glennj Aug 6, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this comment thread is getting to the heart of how we want to manage name-spacing in our bash libs.

  1. do we want any? i.e. let source just dump library functionality into the "global" scope
  2. use a top-level function with library functions accessed as sub-commands, as suggested here
  3. use a function prefix, like I did in the forth example

Does anyone know of any common idioms or best practice guidelines?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the world of bash library modules is all cowboys. We should see if Gnu org has any specification.

I learned that functions may have . in their names too.

The only problem I have is that my syntax highlighters get confused.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I did some more tests.
Double colon (::) looks nice, but I have also read that it is not guaranteed to work with older versions of bash. I am of the opinion that there is absolutely no reason to be using bash older than v4.x, but historically we have not wanted to make Mac users upgrade bash against their will.

I am not against setting v4.2 as a requirement for the track. Hell, I am on v5.0.7, but I would not make this requirement all on my own.

If we decide against enforcing a minimum Bash version, then I see no other way to namespace the functions than by using an module access function.

In order for this not to confuse students it should be present in the stub. We would need to decide if we write the whole thing, so they don't have to worry about it or not.

Then we are left with the decision about how to name the parts it calls. Good naming convention would then make the former API functions internal and they should be preceded by a _. I think this will get ugly very quickly, and it is not really exposing students to a common pattern in the shell world. Furthermore, it doesn't do much to help with possible collisions in the world.

While I think that :: is ideal (even if a bit non-standard) I am feeling less comfortable with the interface function pattern. I honestly think that normally lib sourcing is the way to go here.

If it makes us feel any better. Golang's tests are in the same packagespace as the code, so all of the functions are called without namespacing.

Also, our namespacing ideas were never going to solve issue of set options colliisions with bats' internal requirements. To do that we would need to execute the run commands in a sub-shell. This might work, but it needs to be tested.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@issacg I guess we were typing at the same time 🤣

It seems we both came to the same conclusion (although I am very verbose as usual).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking around at other languages like Python and Ruby it is very evident tha no one else is worrying about this (but in their languages those functions will be namespaced once they are imported somewhere). That just isn't the way of life here 😄

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note bash 3 (or, at least 3.2.57) supports space::func just fine. While we may not want to enforce bash 4+, I think it's fair to expect bash 3 or newer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for checking.

[[ $status -eq 0 ]]
[[ $output == "true" ]]
}

@test "any side is unequal" {
[[ $BATS_RUN_SKIPPED == true ]] || skip
run bash triangle.sh equilateral 2 3 2
run equilateral 2 3 2
[[ $status -eq 0 ]]
[[ $output == "false" ]]
}

@test "no sides are equal, equilateral" {
[[ $BATS_RUN_SKIPPED == true ]] || skip
run bash triangle.sh equilateral 5 4 6
run equilateral 5 4 6
[[ $status -eq 0 ]]
[[ $output == "false" ]]
}

@test "all zero sides is not a triangle" {
[[ $BATS_RUN_SKIPPED == true ]] || skip
run bash triangle.sh equilateral 0 0 0
run equilateral 0 0 0
[[ $status -eq 0 ]]
[[ $output == "false" ]]
}
Expand All @@ -34,7 +37,7 @@

@test "sides may be floats, equilateral" {
[[ $BATS_RUN_SKIPPED == true ]] || skip
run bash triangle.sh equilateral 0.5 0.5 0.5
run equilateral 0.5 0.5 0.5
[[ $status -eq 0 ]]
[[ $output == "true" ]]
}
Expand All @@ -43,56 +46,56 @@

@test "last two sides are equal" {
[[ $BATS_RUN_SKIPPED == true ]] || skip
run bash triangle.sh isosceles 3 4 4
run isosceles 3 4 4
[[ $status -eq 0 ]]
[[ $output == "true" ]]
}

@test "first two sides are equal" {
[[ $BATS_RUN_SKIPPED == true ]] || skip
run bash triangle.sh isosceles 4 4 3
run isosceles 4 4 3
[[ $status -eq 0 ]]
[[ $output == "true" ]]
}

@test "first and last sides are equal" {
[[ $BATS_RUN_SKIPPED == true ]] || skip
run bash triangle.sh isosceles 4 3 4
run isosceles 4 3 4
[[ $status -eq 0 ]]
[[ $output == "true" ]]
}

@test "equilateral triangles are also isosceles" {
[[ $BATS_RUN_SKIPPED == true ]] || skip
run bash triangle.sh isosceles 4 4 4
run isosceles 4 4 4
[[ $status -eq 0 ]]
[[ $output == "true" ]]
}

@test "no sides are equal, isosceles" {
[[ $BATS_RUN_SKIPPED == true ]] || skip
run bash triangle.sh isosceles 2 3 4
run isosceles 2 3 4
[[ $status -eq 0 ]]
[[ $output == "false" ]]
}

@test "first triangle inequality violation" {
[[ $BATS_RUN_SKIPPED == true ]] || skip
run bash triangle.sh isosceles 1 1 3
run isosceles 1 1 3
[[ $status -eq 0 ]]
[[ $output == "false" ]]
}

@test "second triangle inequality violation" {
[[ $BATS_RUN_SKIPPED == true ]] || skip
run bash triangle.sh isosceles 1 1 3
run isosceles 1 1 3
[[ $status -eq 0 ]]
[[ $output == "false" ]]
}

@test "third triangle inequality violation" {
[[ $BATS_RUN_SKIPPED == true ]] || skip
run bash triangle.sh isosceles 1 1 3
run isosceles 1 1 3
[[ $status -eq 0 ]]
[[ $output == "false" ]]
}
Expand All @@ -101,7 +104,7 @@

@test "sides may be floats, isosceles" {
[[ $BATS_RUN_SKIPPED == true ]] || skip
run bash triangle.sh isosceles 0.5 0.4 0.5
run isosceles 0.5 0.4 0.5
[[ $status -eq 0 ]]
[[ $output == "true" ]]
}
Expand All @@ -110,28 +113,28 @@

@test "no sides are equal, scalene" {
[[ $BATS_RUN_SKIPPED == true ]] || skip
run bash triangle.sh scalene 5 4 6
run scalene 5 4 6
[[ $status -eq 0 ]]
[[ $output == "true" ]]
}

@test "all sides are equal, scalene" {
[[ $BATS_RUN_SKIPPED == true ]] || skip
run bash triangle.sh scalene 4 4 4
run scalene 4 4 4
[[ $status -eq 0 ]]
[[ $output == "false" ]]
}

@test "two sides are equal" {
[[ $BATS_RUN_SKIPPED == true ]] || skip
run bash triangle.sh scalene 4 4 3
run scalene 4 4 3
[[ $status -eq 0 ]]
[[ $output == "false" ]]
}

@test "may not violate triangle inequality" {
[[ $BATS_RUN_SKIPPED == true ]] || skip
run bash triangle.sh scalene 7 3 2
run scalene 7 3 2
[[ $status -eq 0 ]]
[[ $output == "false" ]]
}
Expand All @@ -140,7 +143,7 @@

@test "sides may be floats, scalene" {
[[ $BATS_RUN_SKIPPED == true ]] || skip
run bash triangle.sh scalene 0.5 0.4 0.6
run scalene 0.5 0.4 0.6
[[ $status -eq 0 ]]
[[ $output == "true" ]]
}
Expand Down
10 changes: 3 additions & 7 deletions exercises/two-fer/example.sh
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
#!/usr/bin/env bash

if [ "$#" -eq 0 ]; then
person="you"
else
person="$1"
fi

echo "One for $person, one for me."
two_fer () {
echo "One for ${1:-you}, one for me."
}
25 changes: 3 additions & 22 deletions exercises/two-fer/two_fer.sh
Original file line number Diff line number Diff line change
@@ -1,24 +1,5 @@
#!/usr/bin/env bash

# The following comments should help you get started:
# - Bash is flexible. You may use functions or write a "raw" script.
#
# - Complex code can be made easier to read by breaking it up
# into functions, however this is sometimes overkill in bash.
#
# - You can find links about good style and other resources
# for Bash in './README.md'. It came with this exercise.
#
# Example:
# # other functions here
# # ...
# # ...
#
# main () {
# # your main function code here
# }
#
# # call main with all of the positional arguments
# main "$@"
#
# *** PLEASE REMOVE THESE COMMENTS BEFORE SUBMITTING YOUR SOLUTION ***
two_fer () {
echo 'unimplemented!'
}
11 changes: 7 additions & 4 deletions exercises/two-fer/two_fer_test.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#!/usr/bin/env bash

# access user code
source two_fer.sh

@test "no name given" {
#[[ $BATS_RUN_SKIPPED == true ]] || skip

Expand All @@ -16,28 +19,28 @@
#
# $ BATS_RUN_SKIPPED=true bats two_fer_test.sh

run bash two_fer.sh
run two_fer
[[ $status -eq 0 ]]
[[ $output == "One for you, one for me." ]]
}

@test "a name given" {
[[ $BATS_RUN_SKIPPED == true ]] || skip
run bash two_fer.sh Alice
run two_fer Alice
[[ $status -eq 0 ]]
[[ $output == "One for Alice, one for me." ]]
}

@test "another name given" {
[[ $BATS_RUN_SKIPPED == true ]] || skip
run bash two_fer.sh Bob
run two_fer Bob
[[ $status -eq 0 ]]
[[ $output == "One for Bob, one for me." ]]
}

@test "handle arg1 properly" {
[[ $BATS_RUN_SKIPPED == true ]] || skip
run bash two_fer.sh "John Smith" "Mary Ann"
run two_fer "John Smith" "Mary Ann"
[[ $status -eq 0 ]]
[[ $output == "One for John Smith, one for me." ]]
}
Expand Down