Fine Tuned Shell Scripting and Bash Automated Testing System Using Bats

A brief presentation at MashhadBUG

Introduction

Last Saturday, I gave a brief presentation on shell scripting and bats, a bash based automated testing system at MashhadBUG. Please Don't forget to check the references at the end of the post.

Table of Contents

PART I: Shell Tables by Isaac Levy

I/O Redirection

$ prog > file       # direct stdout to file
$ prog >> file      # append stdout to file
$ prog < file       # take standard input from file
$ < file prog
$ prog1 | prog2 # connect standard output of p1 to standard input of p2
$ prog <<HEREDOC    # HereDoc

Commands

$ prog1; prog2      # command terminator
$ prog1 & prog2     # like ; but doesn't wait for p1 to finish
$ `prog1`       # run command, output replaces
$ (prog1)       # run command in sub-shell
$ {prog1}       # run command in current-shell

Shell Scripts

$0...$9         # positional arguments
$#          # total number of arguments
$*          # all the arguments as a single string (expanded/double-quoted)
$@          # all the arguments as separate strings (expanded/double-quoted)
$$          # pid of current shell process
$!          # pid of last background command
$?          # exit status of previous command

Evaluation and Substitution of Shell Variables

$var            # Value of 'var'
${var}          # useful if alphanumerics follows
${var-thing}        # 'var' if defined, O.W thing
${var:-word}        # 'var' if exists and isn't null, O.W 'word'
${var:=word}        # like above but changes var
${var:?message}     # abort if var doesn't exist or is null
${var:+word}        # returns word if var exists and isn't null

PART II: The 3 finger claw technique by Isaac Levy

The Original 3 short functions

shout() { echo "$0: $*" >&2; } 
barf() { shout "$*"; exit 100; } 
safe() { "$@" || barf "cannot $*"; }

The 3 short functions

yell() { echo "$0: $*" >&2; }
die() { yell "$*"; exit 111; }
try() { "$@" || die "cannot $*"; }

Using The 3 finger claw technique

#!/bin/sh

yell() { echo "$0: $*" >&2; }
die() { yell "$*"; exit 111; }
try() { "$@" || die "cannot $*"; }

# using it
try cd /some/place
tar tar xzvfp /another/place/stuff.tbz

exit 0

PART III: Bats: Bash Automated Testing System

Bats is a TAP-compliant testing framework for Bash. It provides a simple way to verify that the UNIX programs you write behave as expected.

TAP: Test Anything Protocol

TAP, the Test Anything Protocol, is a simple text-based interface between testing modules in a test harness. TAP started life as part of the test harness for Perl but now has implementations in C, C++, Python, PHP, Perl, Java, JavaScript, and others.

1..4
ok 1 - Input file opened
not ok 2 - First line of the input valid
ok 3 - Read the rest of the file
not ok 4 - Summarized correctly # TODO Not written yet

TAP: Resources

Bats: Bash Automated Testing System

Bats is a TAP-compliant testing framework for Bash. It provides a simple way to verify that the UNIX programs you write behave as expected.

Bats: Installation

$ git clone https://github.com/sstephenson/bats.git
$ cd bats
$ doas ./install.sh /usr/local

Bats: Writing Tests

$ cat test.bats
#!/usr/bin/env bats

@test "addition using bc" {
  result="$(echo 2+2 | bc)"
  [ "$result" -eq 4 ]
}

@test "addition using dc" {
  result="$(echo 2 2+p | dc)"
  [ "$result" -eq 4 ]
}

Bats: Running Tests

$ bats test.bats 
 ✓ addition using bc
 ✓ addition using dc

WAIT, THAT'S NOT TAP COMPLIANT

Bats: Running Tests (Cont.)

$ bats --tap test.bats
1..2
ok 1 addition using bc
ok 2 addition using dc

Bats: Writing Tests; run command

@test "invoking foo with a nonexistent file prints an error" {
  run foo nonexistent_filename
  [ "$status" -eq 1 ]
  [ "$output" = "foo: no such file 'nonexistent_filename'" ]
}

Bats: Writing Tests; load command

load test_helper

Bats: Writing Tests; skip command

$ cat test.bats
@test "A test I don`t want to execute for now" {
  skip "This command will return zero soon, but not now"
  run foo
  [ "$status" -eq 0 ]
}

Running Skiped Tests

$ bats test.bats
 - A test I don`t want to execute for now (skipped: This command will return zero soon, but not now)

1 test, 0 failures, 1 skipped

Bats: Writing Tests; skip command (Cont.)

@test "A test which should run only when bar is foo" {
  [ foo != bar ] && skip "foo isn't bar"

  run foo
  [ "$status" -eq 0 ]
}

Bats: Writing Tests; setup and teardown functions

setup()
{
  /* Initialization call per test calls */
}

teardown()
{
  /* Finalization call per test calls */
}

Bats: Continues Integration Support

$ cat .travis.yml
before_install:
  - sudo add-apt-repository ppa:duggan/bats --yes
  - sudo apt-get update -qq
  - sudo apt-get install -qq bats
script:
  - bats test/bats

Bats: Helpers

Bats: Helpers (Cont)

config() {
  command=""
  while read line; do
    command+="-c '$line' "
  done
  bash -c "prog $command"
}
remote() {
 host="$1"
 command="${@:2}"
 ssh $host "$(typeset -f); $command"
}

Resources