--- /dev/null
+#!/bin/bash
+
+. toolbox.sh
+include "sem"
+
+Describe "sem_init()"
+ It "creates a private semaphore"
+ _test_sem_init_create_private() {
+ local name
+ local path
+
+ name="$FUNCNAME.$RANDOM"
+ path=$(_sem_get_path "$name")
+
+ if [ -d "$path" ]; then
+ return 1
+ elif ! sem_init "$name" 0; then
+ return 2
+ elif ! [ -d "$path" ]; then
+ return 3
+ fi
+
+ rm -rf "$path"
+ return 0
+ }
+
+ When call _test_sem_init_create_private
+ The status should equal 0
+ End
+
+ It "creates a public semaphore"
+ _test_sem_init_create_public() {
+ local name
+
+ if ! name=$(mktemp --dry-run); then
+ return 1
+ fi
+
+ if ! sem_init "$name" 0; then
+ return 1
+ elif ! [ -d "$name" ]; then
+ return 2
+ fi
+
+ rm -rf "$name"
+ return 0
+ }
+
+ When call _test_sem_init_create_public
+ The status should equal 0
+ End
+
+ It "locks the waitlock if the counter is 0"
+ _test_sem_init_lock_waitlock() {
+ local name
+ local lock
+ local -i err
+
+ if ! name=$(mktemp --dry-run); then
+ return 1
+ fi
+ lock=$(_sem_get_waitlock "$name")
+ err=0
+
+ if ! sem_init "$name" 0; then
+ return 1
+ elif ! [ -L "$lock" ]; then
+ err=2
+ fi
+
+ rm -rf "$name"
+ return "$err"
+ }
+
+ When call _test_sem_init_lock_waitlock
+ The status should equal 0
+ End
+
+ It "does not lock the waitlock if the counter is larger than 0"
+ _test_sem_init_unlock_waitlock() {
+ local name
+ local lock
+ local -i err
+
+ if ! name=$(mktemp --dry-run); then
+ return 1
+ fi
+ lock=$(_sem_get_waitlock "$name")
+ err=0
+
+ if ! sem_init "$name" 1; then
+ return 1
+ elif [ -L "$lock" ]; then
+ err=2
+ fi
+
+ rm -rf "$name"
+ return "$err"
+ }
+
+ When call _test_sem_init_unlock_waitlock
+ The status should equal 0
+ End
+
+ It "sets the semaphore's counter"
+ _test_sem_init_set_counter() {
+ local path
+ local -i i
+ local -i err
+
+ if ! path=$(mktemp --directory); then
+ return 1
+ fi
+ err=0
+
+ for (( i = 0; i < 8; i++ )); do
+ local name
+ local counter
+ local value
+
+ name="$path/sem$i"
+ counter=$(_sem_get_counter "$name")
+
+ if ! sem_init "$name" "$i"; then
+ (( err++ ))
+ continue
+ fi
+
+ value=$(< "$counter")
+ if (( value != i )); then
+ (( err++ ))
+ fi
+ done
+
+ rm -rf "$path"
+ return "$err"
+ }
+
+ When call _test_sem_init_set_counter
+ The status should equal 0
+ End
+
+ It "sets the ownerlock"
+ _test_sem_init_set_ownerlock() {
+ local sem
+ local lock
+
+ local -i err
+
+ if ! sem=$(mktemp --dry-run); then
+ return 1
+ elif ! sem_init "$sem" 0; then
+ return 2
+ fi
+
+ err=0
+ lock=$(_sem_get_owner "$sem")
+
+ if ! [ -L "$lock" ]; then
+ err=1
+ fi
+
+ rm -rf "$sem"
+ return "$err"
+ }
+
+ When call _test_sem_init_set_ownerlock
+ The status should equal 0
+ End
+
+ It "makes the current process the owner of the semaphore"
+ _test_sem_init_owner_curproc() {
+ local sem
+ local lock
+ local -i owner
+ local -i err
+
+ if ! sem=$(mktemp --dry-run); then
+ return 1
+ elif ! sem_init "$sem" 0; then
+ return 2
+ fi
+
+ err=0
+ lock=$(_sem_get_owner "$sem")
+
+ if ! owner=$(readlink "$lock"); then
+ err=3
+ elif (( owner != $$ )); then
+ err=4
+ fi
+
+ rm -rf "$sem"
+ return "$err"
+ }
+
+ When call _test_sem_init_owner_curproc
+ The status should equal 0
+ End
+
+ It "fails if the counter value was not passed"
+ When call sem_init "/tmp/dontcare.$RANDOM"
+ The status should not equal 0
+ End
+
+ It "fails if the counter value is negative"
+ When call sem_init "/tmp/dontcare.$RANDOM" -1
+ The status should not equal 0
+ End
+
+ It "fails if the counter value is not numeric"
+ When call sem_init "/tmp/dontcare.$RANDOM" "test"
+ The status should not equal 0
+ End
+
+ It "does not allocate a semaphore if the function failed"
+ When call sem_init "/tmp/dontcare"
+ The status should not equal 0
+ The path "/tmp/dontcare" should not exist
+ End
+End
+
+Describe "sem_destroy()"
+ It "removes a semaphore from the file system"
+ _test_sem_destroy_removes_semaphore() {
+ local sem
+ local -i err
+
+ err=0
+
+ if ! sem=$(mktemp --dry-run); then
+ return 1
+ elif ! sem_init "$sem" 0; then
+ return 2
+ elif ! sem_destroy "$sem"; then
+ err=3
+ elif [ -e "$sem" ]; then
+ err=4
+ rm -rf "$sem"
+ fi
+
+ return "$err"
+ }
+ When call _test_sem_destroy_removes_semaphore
+ The status should equal 0
+ End
+
+ It "does not destroy another process's semaphore"
+ _test_sem_destroy_foreign_semaphore() {
+ local sem
+ local -i err
+
+ err=0
+
+ if ! sem=$(mktemp --dry-run); then
+ return 1
+ elif ! bash -c ". toolbox.sh; include sem; sem_init \"$sem\" 0"; then
+ return 2
+ fi
+
+ if sem_destroy "$sem"; then
+ err=3
+ fi
+
+ rm -rf "$sem"
+ return "$err"
+ }
+ When call _test_sem_destroy_foreign_semaphore
+ The status should equal 0
+ End
+End
+
+Describe "sem_post()"
+ It "increases the counter by one"
+ _test_sem_post_increase() {
+ local sem
+ local counter
+ local -i value
+ local -i err
+
+ if ! sem=$(mktemp --dry-run); then
+ return 1
+ elif ! sem_init "$sem" 0; then
+ return 2
+ fi
+
+ err=0
+ counter=$(_sem_get_counter "$sem")
+
+ if ! sem_post "$sem"; then
+ err=3
+ elif ! value=$(< "$counter"); then
+ err=4
+ elif (( value != 1 )); then
+ err=5
+ fi
+
+ rm -rf "$sem"
+ return "$err"
+ }
+
+ When call _test_sem_post_increase
+ The status should equal 0
+ End
+
+ It "wakes up a blocked call to sem_wait()"
+ _test_sem_post_wake_up_sem_wait() {
+ local sem
+ local -i timeout
+ local -i child_pid
+ local -i err
+
+ err=0
+ timeout=10
+
+ if ! sem=$(mktemp --dry-run); then
+ return 1
+ elif ! sem_init "$sem" 0; then
+ return 2
+ fi
+
+ ( sem_wait "$sem" ) &
+ child_pid="$!"
+
+ sem_post "$sem"
+
+ while (( timeout > 0 )) &&
+ kill -0 "$child_pid" &> /dev/null; do
+ sleep 1
+ (( timeout-- ))
+ done
+
+ if (( timeout == 0 )); then
+ err=3
+ kill "$child_pid"
+ fi
+
+ rm -rf "$sem"
+ return "$err"
+ }
+
+ When call _test_sem_post_wake_up_sem_wait
+ The status should equal 0
+ End
+End
+
+Describe "sem_wait()"
+ It "decreases the counter by one"
+ _test_sem_wait_decrease() {
+ local sem
+ local counter
+ local -i value
+ local -i err
+
+ if ! sem=$(mktemp --dry-run); then
+ return 1
+ elif ! sem_init "$sem" 1; then
+ return 2
+ fi
+
+ err=0
+ counter=$(_sem_get_counter "$sem")
+
+ if ! sem_wait "$sem"; then
+ err=3
+ elif ! value=$(< "$counter"); then
+ err=4
+ elif (( value != 0 )); then
+ err=5
+ fi
+
+ rm -rf "$sem"
+ return "$err"
+ }
+
+ When call _test_sem_wait_decrease
+ The status should equal 0
+ End
+
+ It "locks the waitlock if the counter is 1"
+ _test_sem_wait_lock_waitlock() {
+ local sem
+ local lock
+ local -i err
+
+ if ! sem=$(mktemp --dry-run); then
+ return 1
+ elif ! sem_init "$sem" 1; then
+ return 2
+ fi
+
+ err=0
+ lock=$(_sem_get_waitlock "$sem")
+
+ if ! sem_wait "$sem"; then
+ err=3
+ elif ! [ -L "$lock" ]; then
+ err=4
+ fi
+
+ rm -rf "$sem"
+ return "$err"
+ }
+
+ When call _test_sem_wait_lock_waitlock
+ The status should equal 0
+ End
+
+ It "releases the waitlock if the counter is greater than 1"
+ _test_sem_wait_unlock_waitlock() {
+ local sem
+ local lock
+ local -i err
+
+ if ! sem=$(mktemp --dry-run); then
+ return 1
+ elif ! sem_init "$sem" 2; then
+ return 2
+ fi
+
+ err=0
+ lock=$(_sem_get_waitlock "$sem")
+
+ if ! sem_wait "$sem"; then
+ err=3
+ elif [ -L "$lock" ]; then
+ err=4
+ fi
+
+ rm -rf "$sem"
+ return "$err"
+ }
+
+ When call _test_sem_wait_unlock_waitlock
+ The status should equal 0
+ End
+End
+
+Describe "sem_peek()"
+ It "returns the counter of a semaphore"
+ _test_sem_peek_returns_counter() {
+ local sem
+ local -i i
+ local -i err
+
+ err=0
+
+ if ! sem=$(mktemp --dry-run); then
+ return 1
+ elif ! sem_init "$sem" 0; then
+ return 2
+ fi
+
+ for (( i = 0; i < 16; i++ )); do
+ local -i counter
+
+ counter=$(sem_peek "$sem")
+ if (( counter != i )); then
+ err=3
+ fi
+
+ sem_post "$sem"
+ done
+
+ rm -rf "$sem"
+ return "$err"
+ }
+
+ When call _test_sem_peek_returns_counter
+ The status should equal 0
+ End
+End