├── .gitignore ├── 2.7 └── Dockerfile ├── 3.4 └── Dockerfile ├── 3.5 └── Dockerfile ├── Makefile ├── README.md ├── circle.yml └── tests ├── app ├── main.py ├── requirements.txt └── test.sh ├── buildfiles ├── expected ├── requirements.txt └── test.sh ├── onbuild ├── .onbuild ├── requirements.txt └── test.sh ├── runtests └── shunit2 /.gitignore: -------------------------------------------------------------------------------- 1 | tests/app/Dockerfile 2 | tests/buildfiles/Dockerfile 3 | tests/onbuild/Dockerfile -------------------------------------------------------------------------------- /2.7/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gliderlabs/alpine:3.2 2 | 3 | RUN apk-install \ 4 | python \ 5 | python-dev \ 6 | py-pip \ 7 | build-base \ 8 | && pip install virtualenv \ 9 | && echo "Dockerfile" >> /etc/buildfiles \ 10 | && echo ".onbuild" >> /etc/buildfiles \ 11 | && echo "requirements.txt" >> /etc/buildfiles 12 | 13 | WORKDIR /app 14 | 15 | ONBUILD COPY . /app 16 | ONBUILD RUN /app/.onbuild || true 17 | ONBUILD RUN virtualenv /env && /env/bin/pip install -r /app/requirements.txt 18 | 19 | EXPOSE 8080 20 | CMD ["/env/bin/python", "main.py"] 21 | -------------------------------------------------------------------------------- /3.4/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gliderlabs/alpine:3.2 2 | 3 | RUN apk-install \ 4 | python3 \ 5 | python3-dev \ 6 | build-base \ 7 | && pip3 install virtualenv \ 8 | && echo "Dockerfile" >> /etc/buildfiles \ 9 | && echo ".onbuild" >> /etc/buildfiles \ 10 | && echo "requirements.txt" >> /etc/buildfiles 11 | 12 | WORKDIR /app 13 | 14 | ONBUILD COPY . /app 15 | ONBUILD RUN /app/.onbuild || true 16 | ONBUILD RUN virtualenv /env && /env/bin/pip install -r /app/requirements.txt 17 | 18 | EXPOSE 8080 19 | CMD ["/env/bin/python", "main.py"] 20 | -------------------------------------------------------------------------------- /3.5/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gliderlabs/alpine:3.3 2 | 3 | RUN apk-install \ 4 | python3 \ 5 | python3-dev \ 6 | build-base \ 7 | && python3 -m ensurepip \ 8 | && pip3 install virtualenv \ 9 | && echo "Dockerfile" >> /etc/buildfiles \ 10 | && echo ".onbuild" >> /etc/buildfiles \ 11 | && echo "requirements.txt" >> /etc/buildfiles 12 | 13 | WORKDIR /app 14 | 15 | ONBUILD COPY . /app 16 | ONBUILD RUN /app/.onbuild || true 17 | ONBUILD RUN virtualenv /env && /env/bin/pip install -r /app/requirements.txt 18 | 19 | EXPOSE 8080 20 | CMD ["/env/bin/python", "main.py"] 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | build: 3 | docker build -t python-runtime:2.7 ./2.7 4 | docker build -t python-runtime:3.4 ./3.4 5 | docker build -t python-runtime:3.5 ./3.5 6 | 7 | test: build 8 | tests/runtests tests/**/test.sh 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # python-runtime 2 | 3 | Simple, optimized, and extensible Python application runtime containers for Docker. 4 | 5 | [![Circle CI](https://circleci.com/gh/gliderlabs/python-runtime.png?style=shield)](https://circleci.com/gh/gliderlabs/python-runtime) -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | services: 3 | - docker 4 | 5 | general: 6 | branches: 7 | ignore: 8 | - release 9 | 10 | dependencies: 11 | override: 12 | - make build 13 | 14 | test: 15 | override: 16 | - tests/runtests: 17 | parallel: true 18 | files: 19 | - tests/**/test.sh 20 | 21 | deployment: 22 | dockerhub: 23 | branch: master 24 | commands: 25 | - git push https://github.com/gliderlabs/python-runtime.git $CIRCLE_SHA1:release -------------------------------------------------------------------------------- /tests/app/main.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | app = Flask(__name__) 3 | 4 | @app.route("/") 5 | def hello(): 6 | return "Hello World!" 7 | 8 | if __name__ == "__main__": 9 | app.run(host='0.0.0.0', port=8080, debug=True) 10 | -------------------------------------------------------------------------------- /tests/app/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==0.10 -------------------------------------------------------------------------------- /tests/app/test.sh: -------------------------------------------------------------------------------- 1 | 2 | use-ip() { 3 | if which boot2docker > /dev/null; then 4 | boot2docker ip 2> /dev/null 5 | else 6 | echo "127.0.0.1" 7 | fi 8 | } 9 | 10 | testApp() { 11 | local testdir="$(dirname $BASH_SOURCE)" 12 | local image="test-$(basename $testdir)" 13 | for version in $VERSIONS; do 14 | echo $version 15 | echo "FROM python-runtime:$version" > "$testdir/Dockerfile" 16 | docker build -q -t "$image" "$testdir" > /dev/null 17 | 18 | local id="$(docker run -d -P $image)" 19 | sleep 1 20 | local addr="$(docker port $id 8080)" 21 | local output="$(curl -s ${addr/0.0.0.0/$(use-ip)})" 22 | docker rm -f "$id" > /dev/null 2>&1 23 | assertEquals "curl response not expected: $output" \ 24 | "Hello World!" "$output" 25 | 26 | docker rmi "$image" > /dev/null 2>&1 27 | done 28 | } 29 | -------------------------------------------------------------------------------- /tests/buildfiles/expected: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | .onbuild 3 | requirements.txt -------------------------------------------------------------------------------- /tests/buildfiles/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gliderlabs/python-runtime/cc22c6f7d7f926f4f345df5d7e365dd6f77792c2/tests/buildfiles/requirements.txt -------------------------------------------------------------------------------- /tests/buildfiles/test.sh: -------------------------------------------------------------------------------- 1 | 2 | testBuildfiles() { 3 | local testdir="$(dirname $BASH_SOURCE)" 4 | local image="test-$(basename $testdir)" 5 | for version in $VERSIONS; do 6 | echo $version 7 | echo "FROM python-runtime:$version" > "$testdir/Dockerfile" 8 | docker build -q -t "$image" "$testdir" > /dev/null 9 | 10 | local output="$(docker run --rm $image cat /etc/buildfiles 2> /dev/null)" 11 | local expected="$(cat $testdir/expected)" 12 | assertEquals "/etc/buildfiles content not what was expected" \ 13 | "$expected" "$output" 14 | 15 | docker rmi "$image" > /dev/null 2>&1 16 | done 17 | } 18 | -------------------------------------------------------------------------------- /tests/onbuild/.onbuild: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | apk-install curl 4 | -------------------------------------------------------------------------------- /tests/onbuild/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gliderlabs/python-runtime/cc22c6f7d7f926f4f345df5d7e365dd6f77792c2/tests/onbuild/requirements.txt -------------------------------------------------------------------------------- /tests/onbuild/test.sh: -------------------------------------------------------------------------------- 1 | 2 | testOnbuild() { 3 | local testdir="$(dirname $BASH_SOURCE)" 4 | local image="test-$(basename $testdir)" 5 | for version in $VERSIONS; do 6 | echo $version 7 | echo "FROM python-runtime:$version" > "$testdir/Dockerfile" 8 | docker build -q -t "$image" "$testdir" > /dev/null 9 | 10 | local output="$(docker run --rm $image which curl 2> /dev/null)" 11 | assertEquals "curl is not installed" \ 12 | "/usr/bin/curl" "$output" 13 | 14 | docker rmi "$image" > /dev/null 2>&1 15 | done 16 | } 17 | -------------------------------------------------------------------------------- /tests/runtests: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | readonly VERSIONS="2.7 3.4 3.5" 4 | 5 | main() { 6 | . "$(dirname $BASH_SOURCE)/shunit2" 7 | } 8 | 9 | main "$@" 10 | -------------------------------------------------------------------------------- /tests/shunit2: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # $Id$ 3 | # vim:et:ft=sh:sts=2:sw=2 4 | # 5 | # Patched by Jeff Lindsay 6 | # Now takes N arguments of files to source in standalone mode, 7 | # and loads test functions from entire environment in both modes. 8 | # 9 | # Copyright 2008 Kate Ward. All Rights Reserved. 10 | # Released under the LGPL (GNU Lesser General Public License) 11 | # 12 | # shUnit2 -- Unit testing framework for Unix shell scripts. 13 | # http://code.google.com/p/shunit2/ 14 | # 15 | # Author: kate.ward@forestent.com (Kate Ward) 16 | # 17 | # shUnit2 is a xUnit based unit test framework for Bourne shell scripts. It is 18 | # based on the popular JUnit unit testing framework for Java. 19 | 20 | # return if shunit already loaded 21 | [ -n "${SHUNIT_VERSION:-}" ] && exit 0 22 | SHUNIT_VERSION='2.1.7pre' 23 | 24 | # return values that scripts can use 25 | SHUNIT_TRUE=0 26 | SHUNIT_FALSE=1 27 | SHUNIT_ERROR=2 28 | 29 | # logging functions 30 | _shunit_warn() { echo "shunit2:WARN $@" >&2; } 31 | _shunit_error() { echo "shunit2:ERROR $@" >&2; } 32 | _shunit_fatal() { echo "shunit2:FATAL $@" >&2; exit ${SHUNIT_ERROR}; } 33 | 34 | # determine some reasonable command defaults 35 | __SHUNIT_UNAME_S=`uname -s` 36 | case "${__SHUNIT_UNAME_S}" in 37 | BSD) __SHUNIT_EXPR_CMD='gexpr' ;; 38 | *) __SHUNIT_EXPR_CMD='expr' ;; 39 | esac 40 | 41 | # commands a user can override if needed 42 | SHUNIT_EXPR_CMD=${SHUNIT_EXPR_CMD:-${__SHUNIT_EXPR_CMD}} 43 | 44 | # enable strict mode by default 45 | SHUNIT_STRICT=${SHUNIT_STRICT:-${SHUNIT_TRUE}} 46 | 47 | # specific shell checks 48 | if [ -n "${ZSH_VERSION:-}" ]; then 49 | setopt |grep "^shwordsplit$" >/dev/null 50 | if [ $? -ne ${SHUNIT_TRUE} ]; then 51 | _shunit_fatal 'zsh shwordsplit option is required for proper operation' 52 | fi 53 | if [ -z "${SHUNIT_PARENT:-}" ]; then 54 | _shunit_fatal "zsh does not pass \$0 through properly. please declare \ 55 | \"SHUNIT_PARENT=\$0\" before calling shUnit2" 56 | fi 57 | fi 58 | 59 | # 60 | # constants 61 | # 62 | 63 | __SHUNIT_ASSERT_MSG_PREFIX='ASSERT:' 64 | __SHUNIT_MODE_SOURCED='sourced' 65 | __SHUNIT_MODE_STANDALONE='standalone' 66 | __SHUNIT_PARENT=${SHUNIT_PARENT:-$0} 67 | 68 | # set the constants readonly 69 | __shunit_constants=`set |grep '^__SHUNIT_' |cut -d= -f1` 70 | echo "${__shunit_constants}" |grep '^Binary file' >/dev/null && \ 71 | __shunit_constants=`set |grep -a '^__SHUNIT_' |cut -d= -f1` 72 | for __shunit_const in ${__shunit_constants}; do 73 | if [ -z "${ZSH_VERSION:-}" ]; then 74 | readonly ${__shunit_const} 75 | else 76 | case ${ZSH_VERSION} in 77 | [123].*) readonly ${__shunit_const} ;; 78 | *) readonly -g ${__shunit_const} # declare readonly constants globally 79 | esac 80 | fi 81 | done 82 | unset __shunit_const __shunit_constants 83 | 84 | # 85 | # internal variables 86 | # 87 | 88 | # variables 89 | __shunit_lineno='' # line number of executed test 90 | __shunit_mode=${__SHUNIT_MODE_SOURCED} # operating mode 91 | __shunit_reportGenerated=${SHUNIT_FALSE} # is report generated 92 | __shunit_script='' # filename of unittest script (standalone mode) 93 | __shunit_skip=${SHUNIT_FALSE} # is skipping enabled 94 | __shunit_suite='' # suite of tests to execute 95 | 96 | # counts of tests 97 | __shunit_testSuccess=${SHUNIT_TRUE} 98 | __shunit_testsTotal=0 99 | __shunit_testsPassed=0 100 | __shunit_testsFailed=0 101 | 102 | # counts of asserts 103 | __shunit_assertsTotal=0 104 | __shunit_assertsPassed=0 105 | __shunit_assertsFailed=0 106 | __shunit_assertsSkipped=0 107 | 108 | # macros 109 | _SHUNIT_LINENO_='eval __shunit_lineno=""; if [ "${1:-}" = "--lineno" ]; then [ -n "$2" ] && __shunit_lineno="[$2] "; shift 2; fi' 110 | 111 | #----------------------------------------------------------------------------- 112 | # private functions 113 | 114 | #----------------------------------------------------------------------------- 115 | # assert functions 116 | # 117 | 118 | # Assert that two values are equal to one another. 119 | # 120 | # Args: 121 | # message: string: failure message [optional] 122 | # expected: string: expected value 123 | # actual: string: actual value 124 | # Returns: 125 | # integer: success (TRUE/FALSE/ERROR constant) 126 | assertEquals() 127 | { 128 | ${_SHUNIT_LINENO_} 129 | if [ $# -lt 2 -o $# -gt 3 ]; then 130 | _shunit_error "assertEquals() requires two or three arguments; $# given" 131 | _shunit_error "1: ${1:+$1} 2: ${2:+$2} 3: ${3:+$3}${4:+ 4: $4}" 132 | return ${SHUNIT_ERROR} 133 | fi 134 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 135 | 136 | shunit_message_=${__shunit_lineno} 137 | if [ $# -eq 3 ]; then 138 | shunit_message_="${shunit_message_}$1" 139 | shift 140 | fi 141 | shunit_expected_=$1 142 | shunit_actual_=$2 143 | 144 | shunit_return=${SHUNIT_TRUE} 145 | if [ "${shunit_expected_}" = "${shunit_actual_}" ]; then 146 | _shunit_assertPass 147 | else 148 | failNotEquals "${shunit_message_}" "${shunit_expected_}" "${shunit_actual_}" 149 | shunit_return=${SHUNIT_FALSE} 150 | fi 151 | 152 | unset shunit_message_ shunit_expected_ shunit_actual_ 153 | return ${shunit_return} 154 | } 155 | _ASSERT_EQUALS_='eval assertEquals --lineno "${LINENO:-}"' 156 | 157 | # Assert that two values are not equal to one another. 158 | # 159 | # Args: 160 | # message: string: failure message [optional] 161 | # expected: string: expected value 162 | # actual: string: actual value 163 | # Returns: 164 | # integer: success (TRUE/FALSE/ERROR constant) 165 | assertNotEquals() 166 | { 167 | ${_SHUNIT_LINENO_} 168 | if [ $# -lt 2 -o $# -gt 3 ]; then 169 | _shunit_error "assertNotEquals() requires two or three arguments; $# given" 170 | return ${SHUNIT_ERROR} 171 | fi 172 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 173 | 174 | shunit_message_=${__shunit_lineno} 175 | if [ $# -eq 3 ]; then 176 | shunit_message_="${shunit_message_}$1" 177 | shift 178 | fi 179 | shunit_expected_=$1 180 | shunit_actual_=$2 181 | 182 | shunit_return=${SHUNIT_TRUE} 183 | if [ "${shunit_expected_}" != "${shunit_actual_}" ]; then 184 | _shunit_assertPass 185 | else 186 | failSame "${shunit_message_}" "$@" 187 | shunit_return=${SHUNIT_FALSE} 188 | fi 189 | 190 | unset shunit_message_ shunit_expected_ shunit_actual_ 191 | return ${shunit_return} 192 | } 193 | _ASSERT_NOT_EQUALS_='eval assertNotEquals --lineno "${LINENO:-}"' 194 | 195 | # Assert that a value is null (i.e. an empty string) 196 | # 197 | # Args: 198 | # message: string: failure message [optional] 199 | # actual: string: actual value 200 | # Returns: 201 | # integer: success (TRUE/FALSE/ERROR constant) 202 | assertNull() 203 | { 204 | ${_SHUNIT_LINENO_} 205 | if [ $# -lt 1 -o $# -gt 2 ]; then 206 | _shunit_error "assertNull() requires one or two arguments; $# given" 207 | return ${SHUNIT_ERROR} 208 | fi 209 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 210 | 211 | shunit_message_=${__shunit_lineno} 212 | if [ $# -eq 2 ]; then 213 | shunit_message_="${shunit_message_}$1" 214 | shift 215 | fi 216 | assertTrue "${shunit_message_}" "[ -z '$1' ]" 217 | shunit_return=$? 218 | 219 | unset shunit_message_ 220 | return ${shunit_return} 221 | } 222 | _ASSERT_NULL_='eval assertNull --lineno "${LINENO:-}"' 223 | 224 | # Assert that a value is not null (i.e. a non-empty string) 225 | # 226 | # Args: 227 | # message: string: failure message [optional] 228 | # actual: string: actual value 229 | # Returns: 230 | # integer: success (TRUE/FALSE/ERROR constant) 231 | assertNotNull() 232 | { 233 | ${_SHUNIT_LINENO_} 234 | if [ $# -gt 2 ]; then # allowing 0 arguments as $1 might actually be null 235 | _shunit_error "assertNotNull() requires one or two arguments; $# given" 236 | return ${SHUNIT_ERROR} 237 | fi 238 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 239 | 240 | shunit_message_=${__shunit_lineno} 241 | if [ $# -eq 2 ]; then 242 | shunit_message_="${shunit_message_}$1" 243 | shift 244 | fi 245 | shunit_actual_=`_shunit_escapeCharactersInString "${1:-}"` 246 | test -n "${shunit_actual_}" 247 | assertTrue "${shunit_message_}" $? 248 | shunit_return=$? 249 | 250 | unset shunit_actual_ shunit_message_ 251 | return ${shunit_return} 252 | } 253 | _ASSERT_NOT_NULL_='eval assertNotNull --lineno "${LINENO:-}"' 254 | 255 | # Assert that two values are the same (i.e. equal to one another). 256 | # 257 | # Args: 258 | # message: string: failure message [optional] 259 | # expected: string: expected value 260 | # actual: string: actual value 261 | # Returns: 262 | # integer: success (TRUE/FALSE/ERROR constant) 263 | assertSame() 264 | { 265 | ${_SHUNIT_LINENO_} 266 | if [ $# -lt 2 -o $# -gt 3 ]; then 267 | _shunit_error "assertSame() requires two or three arguments; $# given" 268 | return ${SHUNIT_ERROR} 269 | fi 270 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 271 | 272 | shunit_message_=${__shunit_lineno} 273 | if [ $# -eq 3 ]; then 274 | shunit_message_="${shunit_message_}$1" 275 | shift 276 | fi 277 | assertEquals "${shunit_message_}" "$1" "$2" 278 | shunit_return=$? 279 | 280 | unset shunit_message_ 281 | return ${shunit_return} 282 | } 283 | _ASSERT_SAME_='eval assertSame --lineno "${LINENO:-}"' 284 | 285 | # Assert that two values are not the same (i.e. not equal to one another). 286 | # 287 | # Args: 288 | # message: string: failure message [optional] 289 | # expected: string: expected value 290 | # actual: string: actual value 291 | # Returns: 292 | # integer: success (TRUE/FALSE/ERROR constant) 293 | assertNotSame() 294 | { 295 | ${_SHUNIT_LINENO_} 296 | if [ $# -lt 2 -o $# -gt 3 ]; then 297 | _shunit_error "assertNotSame() requires two or three arguments; $# given" 298 | return ${SHUNIT_ERROR} 299 | fi 300 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 301 | 302 | shunit_message_=${__shunit_lineno} 303 | if [ $# -eq 3 ]; then 304 | shunit_message_="${shunit_message_:-}$1" 305 | shift 306 | fi 307 | assertNotEquals "${shunit_message_}" "$1" "$2" 308 | shunit_return=$? 309 | 310 | unset shunit_message_ 311 | return ${shunit_return} 312 | } 313 | _ASSERT_NOT_SAME_='eval assertNotSame --lineno "${LINENO:-}"' 314 | 315 | # Assert that a value or shell test condition is true. 316 | # 317 | # In shell, a value of 0 is true and a non-zero value is false. Any integer 318 | # value passed can thereby be tested. 319 | # 320 | # Shell supports much more complicated tests though, and a means to support 321 | # them was needed. As such, this function tests that conditions are true or 322 | # false through evaluation rather than just looking for a true or false. 323 | # 324 | # The following test will succeed: 325 | # assertTrue 0 326 | # assertTrue "[ 34 -gt 23 ]" 327 | # The folloing test will fail with a message: 328 | # assertTrue 123 329 | # assertTrue "test failed" "[ -r '/non/existant/file' ]" 330 | # 331 | # Args: 332 | # message: string: failure message [optional] 333 | # condition: string: integer value or shell conditional statement 334 | # Returns: 335 | # integer: success (TRUE/FALSE/ERROR constant) 336 | assertTrue() 337 | { 338 | ${_SHUNIT_LINENO_} 339 | if [ $# -lt 1 -o $# -gt 2 ]; then 340 | _shunit_error "assertTrue() takes one or two arguments; $# given" 341 | return ${SHUNIT_ERROR} 342 | fi 343 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 344 | 345 | shunit_message_=${__shunit_lineno} 346 | if [ $# -eq 2 ]; then 347 | shunit_message_="${shunit_message_}$1" 348 | shift 349 | fi 350 | shunit_condition_=$1 351 | 352 | # see if condition is an integer, i.e. a return value 353 | shunit_match_=`expr "${shunit_condition_}" : '\([0-9]*\)'` 354 | shunit_return=${SHUNIT_TRUE} 355 | if [ -z "${shunit_condition_}" ]; then 356 | # null condition 357 | shunit_return=${SHUNIT_FALSE} 358 | elif [ -n "${shunit_match_}" -a "${shunit_condition_}" = "${shunit_match_}" ] 359 | then 360 | # possible return value. treating 0 as true, and non-zero as false. 361 | [ ${shunit_condition_} -ne 0 ] && shunit_return=${SHUNIT_FALSE} 362 | else 363 | # (hopefully) a condition 364 | ( eval ${shunit_condition_} ) >/dev/null 2>&1 365 | [ $? -ne 0 ] && shunit_return=${SHUNIT_FALSE} 366 | fi 367 | 368 | # record the test 369 | if [ ${shunit_return} -eq ${SHUNIT_TRUE} ]; then 370 | _shunit_assertPass 371 | else 372 | _shunit_assertFail "${shunit_message_}" 373 | fi 374 | 375 | unset shunit_message_ shunit_condition_ shunit_match_ 376 | return ${shunit_return} 377 | } 378 | _ASSERT_TRUE_='eval assertTrue --lineno "${LINENO:-}"' 379 | 380 | # Assert that a value or shell test condition is false. 381 | # 382 | # In shell, a value of 0 is true and a non-zero value is false. Any integer 383 | # value passed can thereby be tested. 384 | # 385 | # Shell supports much more complicated tests though, and a means to support 386 | # them was needed. As such, this function tests that conditions are true or 387 | # false through evaluation rather than just looking for a true or false. 388 | # 389 | # The following test will succeed: 390 | # assertFalse 1 391 | # assertFalse "[ 'apples' = 'oranges' ]" 392 | # The folloing test will fail with a message: 393 | # assertFalse 0 394 | # assertFalse "test failed" "[ 1 -eq 1 -a 2 -eq 2 ]" 395 | # 396 | # Args: 397 | # message: string: failure message [optional] 398 | # condition: string: integer value or shell conditional statement 399 | # Returns: 400 | # integer: success (TRUE/FALSE/ERROR constant) 401 | assertFalse() 402 | { 403 | ${_SHUNIT_LINENO_} 404 | if [ $# -lt 1 -o $# -gt 2 ]; then 405 | _shunit_error "assertFalse() quires one or two arguments; $# given" 406 | return ${SHUNIT_ERROR} 407 | fi 408 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 409 | 410 | shunit_message_=${__shunit_lineno} 411 | if [ $# -eq 2 ]; then 412 | shunit_message_="${shunit_message_}$1" 413 | shift 414 | fi 415 | shunit_condition_=$1 416 | 417 | # see if condition is an integer, i.e. a return value 418 | shunit_match_=`expr "${shunit_condition_}" : '\([0-9]*\)'` 419 | shunit_return=${SHUNIT_TRUE} 420 | if [ -z "${shunit_condition_}" ]; then 421 | # null condition 422 | shunit_return=${SHUNIT_FALSE} 423 | elif [ -n "${shunit_match_}" -a "${shunit_condition_}" = "${shunit_match_}" ] 424 | then 425 | # possible return value. treating 0 as true, and non-zero as false. 426 | [ ${shunit_condition_} -eq 0 ] && shunit_return=${SHUNIT_FALSE} 427 | else 428 | # (hopefully) a condition 429 | ( eval ${shunit_condition_} ) >/dev/null 2>&1 430 | [ $? -eq 0 ] && shunit_return=${SHUNIT_FALSE} 431 | fi 432 | 433 | # record the test 434 | if [ ${shunit_return} -eq ${SHUNIT_TRUE} ]; then 435 | _shunit_assertPass 436 | else 437 | _shunit_assertFail "${shunit_message_}" 438 | fi 439 | 440 | unset shunit_message_ shunit_condition_ shunit_match_ 441 | return ${shunit_return} 442 | } 443 | _ASSERT_FALSE_='eval assertFalse --lineno "${LINENO:-}"' 444 | 445 | #----------------------------------------------------------------------------- 446 | # failure functions 447 | # 448 | 449 | # Records a test failure. 450 | # 451 | # Args: 452 | # message: string: failure message [optional] 453 | # Returns: 454 | # integer: success (TRUE/FALSE/ERROR constant) 455 | fail() 456 | { 457 | ${_SHUNIT_LINENO_} 458 | if [ $# -gt 1 ]; then 459 | _shunit_error "fail() requires zero or one arguments; $# given" 460 | return ${SHUNIT_ERROR} 461 | fi 462 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 463 | 464 | shunit_message_=${__shunit_lineno} 465 | if [ $# -eq 1 ]; then 466 | shunit_message_="${shunit_message_}$1" 467 | shift 468 | fi 469 | 470 | _shunit_assertFail "${shunit_message_}" 471 | 472 | unset shunit_message_ 473 | return ${SHUNIT_FALSE} 474 | } 475 | _FAIL_='eval fail --lineno "${LINENO:-}"' 476 | 477 | # Records a test failure, stating two values were not equal. 478 | # 479 | # Args: 480 | # message: string: failure message [optional] 481 | # expected: string: expected value 482 | # actual: string: actual value 483 | # Returns: 484 | # integer: success (TRUE/FALSE/ERROR constant) 485 | failNotEquals() 486 | { 487 | ${_SHUNIT_LINENO_} 488 | if [ $# -lt 2 -o $# -gt 3 ]; then 489 | _shunit_error "failNotEquals() requires one or two arguments; $# given" 490 | return ${SHUNIT_ERROR} 491 | fi 492 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 493 | 494 | shunit_message_=${__shunit_lineno} 495 | if [ $# -eq 3 ]; then 496 | shunit_message_="${shunit_message_}$1" 497 | shift 498 | fi 499 | shunit_expected_=$1 500 | shunit_actual_=$2 501 | 502 | _shunit_assertFail "${shunit_message_:+${shunit_message_} }expected:<${shunit_expected_}> but was:<${shunit_actual_}>" 503 | 504 | unset shunit_message_ shunit_expected_ shunit_actual_ 505 | return ${SHUNIT_FALSE} 506 | } 507 | _FAIL_NOT_EQUALS_='eval failNotEquals --lineno "${LINENO:-}"' 508 | 509 | # Records a test failure, stating two values should have been the same. 510 | # 511 | # Args: 512 | # message: string: failure message [optional] 513 | # expected: string: expected value 514 | # actual: string: actual value 515 | # Returns: 516 | # integer: success (TRUE/FALSE/ERROR constant) 517 | failSame() 518 | { 519 | ${_SHUNIT_LINENO_} 520 | if [ $# -lt 2 -o $# -gt 3 ]; then 521 | _shunit_error "failSame() requires two or three arguments; $# given" 522 | return ${SHUNIT_ERROR} 523 | fi 524 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 525 | 526 | shunit_message_=${__shunit_lineno} 527 | if [ $# -eq 3 ]; then 528 | shunit_message_="${shunit_message_}$1" 529 | shift 530 | fi 531 | 532 | _shunit_assertFail "${shunit_message_:+${shunit_message_} }expected not same" 533 | 534 | unset shunit_message_ 535 | return ${SHUNIT_FALSE} 536 | } 537 | _FAIL_SAME_='eval failSame --lineno "${LINENO:-}"' 538 | 539 | # Records a test failure, stating two values were not equal. 540 | # 541 | # This is functionally equivalent to calling failNotEquals(). 542 | # 543 | # Args: 544 | # message: string: failure message [optional] 545 | # expected: string: expected value 546 | # actual: string: actual value 547 | # Returns: 548 | # integer: success (TRUE/FALSE/ERROR constant) 549 | failNotSame() 550 | { 551 | ${_SHUNIT_LINENO_} 552 | if [ $# -lt 2 -o $# -gt 3 ]; then 553 | _shunit_error "failNotEquals() requires one or two arguments; $# given" 554 | return ${SHUNIT_ERROR} 555 | fi 556 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 557 | 558 | shunit_message_=${__shunit_lineno} 559 | if [ $# -eq 3 ]; then 560 | shunit_message_="${shunit_message_}$1" 561 | shift 562 | fi 563 | failNotEquals "${shunit_message_}" "$1" "$2" 564 | shunit_return=$? 565 | 566 | unset shunit_message_ 567 | return ${shunit_return} 568 | } 569 | _FAIL_NOT_SAME_='eval failNotSame --lineno "${LINENO:-}"' 570 | 571 | #----------------------------------------------------------------------------- 572 | # skipping functions 573 | # 574 | 575 | # Force remaining assert and fail functions to be "skipped". 576 | # 577 | # This function forces the remaining assert and fail functions to be "skipped", 578 | # i.e. they will have no effect. Each function skipped will be recorded so that 579 | # the total of asserts and fails will not be altered. 580 | # 581 | # Args: 582 | # None 583 | startSkipping() 584 | { 585 | __shunit_skip=${SHUNIT_TRUE} 586 | } 587 | 588 | # Resume the normal recording behavior of assert and fail calls. 589 | # 590 | # Args: 591 | # None 592 | endSkipping() 593 | { 594 | __shunit_skip=${SHUNIT_FALSE} 595 | } 596 | 597 | # Returns the state of assert and fail call skipping. 598 | # 599 | # Args: 600 | # None 601 | # Returns: 602 | # boolean: (TRUE/FALSE constant) 603 | isSkipping() 604 | { 605 | return ${__shunit_skip} 606 | } 607 | 608 | #----------------------------------------------------------------------------- 609 | # suite functions 610 | # 611 | 612 | # Stub. This function should contains all unit test calls to be made. 613 | # 614 | # DEPRECATED (as of 2.1.0) 615 | # 616 | # This function can be optionally overridden by the user in their test suite. 617 | # 618 | # If this function exists, it will be called when shunit2 is sourced. If it 619 | # does not exist, shunit2 will search the parent script for all functions 620 | # beginning with the word 'test', and they will be added dynamically to the 621 | # test suite. 622 | # 623 | # This function should be overridden by the user in their unit test suite. 624 | # Note: see _shunit_mktempFunc() for actual implementation 625 | # 626 | # Args: 627 | # None 628 | #suite() { :; } # DO NOT UNCOMMENT THIS FUNCTION 629 | 630 | # Adds a function name to the list of tests schedule for execution. 631 | # 632 | # This function should only be called from within the suite() function. 633 | # 634 | # Args: 635 | # function: string: name of a function to add to current unit test suite 636 | suite_addTest() 637 | { 638 | shunit_func_=${1:-} 639 | 640 | __shunit_suite="${__shunit_suite:+${__shunit_suite} }${shunit_func_}" 641 | __shunit_testsTotal=`expr ${__shunit_testsTotal} + 1` 642 | 643 | unset shunit_func_ 644 | } 645 | 646 | # Stub. This function will be called once before any tests are run. 647 | # 648 | # Common one-time environment preparation tasks shared by all tests can be 649 | # defined here. 650 | # 651 | # This function should be overridden by the user in their unit test suite. 652 | # Note: see _shunit_mktempFunc() for actual implementation 653 | # 654 | # Args: 655 | # None 656 | #oneTimeSetUp() { :; } # DO NOT UNCOMMENT THIS FUNCTION 657 | 658 | # Stub. This function will be called once after all tests are finished. 659 | # 660 | # Common one-time environment cleanup tasks shared by all tests can be defined 661 | # here. 662 | # 663 | # This function should be overridden by the user in their unit test suite. 664 | # Note: see _shunit_mktempFunc() for actual implementation 665 | # 666 | # Args: 667 | # None 668 | #oneTimeTearDown() { :; } # DO NOT UNCOMMENT THIS FUNCTION 669 | 670 | # Stub. This function will be called before each test is run. 671 | # 672 | # Common environment preparation tasks shared by all tests can be defined here. 673 | # 674 | # This function should be overridden by the user in their unit test suite. 675 | # Note: see _shunit_mktempFunc() for actual implementation 676 | # 677 | # Args: 678 | # None 679 | #setUp() { :; } 680 | 681 | # Note: see _shunit_mktempFunc() for actual implementation 682 | # Stub. This function will be called after each test is run. 683 | # 684 | # Common environment cleanup tasks shared by all tests can be defined here. 685 | # 686 | # This function should be overridden by the user in their unit test suite. 687 | # Note: see _shunit_mktempFunc() for actual implementation 688 | # 689 | # Args: 690 | # None 691 | #tearDown() { :; } # DO NOT UNCOMMENT THIS FUNCTION 692 | 693 | #------------------------------------------------------------------------------ 694 | # internal shUnit2 functions 695 | # 696 | 697 | # Create a temporary directory to store various run-time files in. 698 | # 699 | # This function is a cross-platform temporary directory creation tool. Not all 700 | # OSes have the mktemp function, so one is included here. 701 | # 702 | # Args: 703 | # None 704 | # Outputs: 705 | # string: the temporary directory that was created 706 | _shunit_mktempDir() 707 | { 708 | # try the standard mktemp function 709 | ( exec mktemp -dqt shunit.XXXXXX 2>/dev/null ) && return 710 | 711 | # the standard mktemp didn't work. doing our own. 712 | if [ -r '/dev/urandom' -a -x '/usr/bin/od' ]; then 713 | _shunit_random_=`/usr/bin/od -vAn -N4 -tx4 "${_shunit_file_}" 742 | #! /bin/sh 743 | exit ${SHUNIT_TRUE} 744 | EOF 745 | chmod +x "${_shunit_file_}" 746 | done 747 | 748 | unset _shunit_file_ 749 | } 750 | 751 | # Final cleanup function to leave things as we found them. 752 | # 753 | # Besides removing the temporary directory, this function is in charge of the 754 | # final exit code of the unit test. The exit code is based on how the script 755 | # was ended (e.g. normal exit, or via Ctrl-C). 756 | # 757 | # Args: 758 | # name: string: name of the trap called (specified when trap defined) 759 | _shunit_cleanup() 760 | { 761 | _shunit_name_=$1 762 | 763 | case ${_shunit_name_} in 764 | EXIT) _shunit_signal_=0 ;; 765 | INT) _shunit_signal_=2 ;; 766 | TERM) _shunit_signal_=15 ;; 767 | *) 768 | _shunit_warn "unrecognized trap value (${_shunit_name_})" 769 | _shunit_signal_=0 770 | ;; 771 | esac 772 | 773 | # do our work 774 | rm -fr "${__shunit_tmpDir}" 775 | 776 | # exit for all non-EXIT signals 777 | if [ ${_shunit_name_} != 'EXIT' ]; then 778 | _shunit_warn "trapped and now handling the (${_shunit_name_}) signal" 779 | # disable EXIT trap 780 | trap 0 781 | # add 128 to signal and exit 782 | exit `expr ${_shunit_signal_} + 128` 783 | elif [ ${__shunit_reportGenerated} -eq ${SHUNIT_FALSE} ] ; then 784 | _shunit_assertFail 'Unknown failure encountered running a test' 785 | _shunit_generateReport 786 | exit ${SHUNIT_ERROR} 787 | fi 788 | 789 | unset _shunit_name_ _shunit_signal_ 790 | } 791 | 792 | # The actual running of the tests happens here. 793 | # 794 | # Args: 795 | # None 796 | _shunit_execSuite() 797 | { 798 | for _shunit_test_ in ${__shunit_suite}; do 799 | __shunit_testSuccess=${SHUNIT_TRUE} 800 | 801 | # disable skipping 802 | endSkipping 803 | 804 | # execute the per-test setup function 805 | setUp 806 | 807 | # execute the test 808 | echo "${_shunit_test_}" 809 | eval ${_shunit_test_} 810 | 811 | # execute the per-test tear-down function 812 | tearDown 813 | 814 | # update stats 815 | if [ ${__shunit_testSuccess} -eq ${SHUNIT_TRUE} ]; then 816 | __shunit_testsPassed=`expr ${__shunit_testsPassed} + 1` 817 | else 818 | __shunit_testsFailed=`expr ${__shunit_testsFailed} + 1` 819 | fi 820 | done 821 | 822 | unset _shunit_test_ 823 | } 824 | 825 | # Generates the user friendly report with appropriate OK/FAILED message. 826 | # 827 | # Args: 828 | # None 829 | # Output: 830 | # string: the report of successful and failed tests, as well as totals. 831 | _shunit_generateReport() 832 | { 833 | _shunit_ok_=${SHUNIT_TRUE} 834 | 835 | # if no exit code was provided one, determine an appropriate one 836 | [ ${__shunit_testsFailed} -gt 0 \ 837 | -o ${__shunit_testSuccess} -eq ${SHUNIT_FALSE} ] \ 838 | && _shunit_ok_=${SHUNIT_FALSE} 839 | 840 | echo 841 | if [ ${__shunit_testsTotal} -eq 1 ]; then 842 | echo "Ran ${__shunit_testsTotal} test." 843 | else 844 | echo "Ran ${__shunit_testsTotal} tests." 845 | fi 846 | 847 | _shunit_failures_='' 848 | _shunit_skipped_='' 849 | [ ${__shunit_assertsFailed} -gt 0 ] \ 850 | && _shunit_failures_="failures=${__shunit_assertsFailed}" 851 | [ ${__shunit_assertsSkipped} -gt 0 ] \ 852 | && _shunit_skipped_="skipped=${__shunit_assertsSkipped}" 853 | 854 | if [ ${_shunit_ok_} -eq ${SHUNIT_TRUE} ]; then 855 | _shunit_msg_='OK' 856 | [ -n "${_shunit_skipped_}" ] \ 857 | && _shunit_msg_="${_shunit_msg_} (${_shunit_skipped_})" 858 | else 859 | _shunit_msg_="FAILED (${_shunit_failures_}" 860 | [ -n "${_shunit_skipped_}" ] \ 861 | && _shunit_msg_="${_shunit_msg_},${_shunit_skipped_}" 862 | _shunit_msg_="${_shunit_msg_})" 863 | fi 864 | 865 | echo 866 | echo ${_shunit_msg_} 867 | __shunit_reportGenerated=${SHUNIT_TRUE} 868 | 869 | unset _shunit_failures_ _shunit_msg_ _shunit_ok_ _shunit_skipped_ 870 | } 871 | 872 | # Test for whether a function should be skipped. 873 | # 874 | # Args: 875 | # None 876 | # Returns: 877 | # boolean: whether the test should be skipped (TRUE/FALSE constant) 878 | _shunit_shouldSkip() 879 | { 880 | [ ${__shunit_skip} -eq ${SHUNIT_FALSE} ] && return ${SHUNIT_FALSE} 881 | _shunit_assertSkip 882 | } 883 | 884 | # Records a successful test. 885 | # 886 | # Args: 887 | # None 888 | _shunit_assertPass() 889 | { 890 | __shunit_assertsPassed=`expr ${__shunit_assertsPassed} + 1` 891 | __shunit_assertsTotal=`expr ${__shunit_assertsTotal} + 1` 892 | } 893 | 894 | # Records a test failure. 895 | # 896 | # Args: 897 | # message: string: failure message to provide user 898 | _shunit_assertFail() 899 | { 900 | _shunit_msg_=$1 901 | 902 | __shunit_testSuccess=${SHUNIT_FALSE} 903 | __shunit_assertsFailed=`expr ${__shunit_assertsFailed} + 1` 904 | __shunit_assertsTotal=`expr ${__shunit_assertsTotal} + 1` 905 | echo "${__SHUNIT_ASSERT_MSG_PREFIX}${_shunit_msg_}" 906 | 907 | unset _shunit_msg_ 908 | } 909 | 910 | # Records a skipped test. 911 | # 912 | # Args: 913 | # None 914 | _shunit_assertSkip() 915 | { 916 | __shunit_assertsSkipped=`expr ${__shunit_assertsSkipped} + 1` 917 | __shunit_assertsTotal=`expr ${__shunit_assertsTotal} + 1` 918 | } 919 | 920 | # Prepare a script filename for sourcing. 921 | # 922 | # Args: 923 | # script: string: path to a script to source 924 | # Returns: 925 | # string: filename prefixed with ./ (if necessary) 926 | _shunit_prepForSourcing() 927 | { 928 | _shunit_script_=$1 929 | case "${_shunit_script_}" in 930 | /*|./*) echo "${_shunit_script_}" ;; 931 | *) echo "./${_shunit_script_}" ;; 932 | esac 933 | unset _shunit_script_ 934 | } 935 | 936 | # Escape a character in a string. 937 | # 938 | # Args: 939 | # c: string: unescaped character 940 | # s: string: to escape character in 941 | # Returns: 942 | # string: with escaped character(s) 943 | _shunit_escapeCharInStr() 944 | { 945 | [ -n "$2" ] || return # no point in doing work on an empty string 946 | 947 | # Note: using shorter variable names to prevent conflicts with 948 | # _shunit_escapeCharactersInString(). 949 | _shunit_c_=$1 950 | _shunit_s_=$2 951 | 952 | 953 | # escape the character 954 | echo ''${_shunit_s_}'' |sed 's/\'${_shunit_c_}'/\\\'${_shunit_c_}'/g' 955 | 956 | unset _shunit_c_ _shunit_s_ 957 | } 958 | 959 | # Escape a character in a string. 960 | # 961 | # Args: 962 | # str: string: to escape characters in 963 | # Returns: 964 | # string: with escaped character(s) 965 | _shunit_escapeCharactersInString() 966 | { 967 | [ -n "$1" ] || return # no point in doing work on an empty string 968 | 969 | _shunit_str_=$1 970 | 971 | # Note: using longer variable names to prevent conflicts with 972 | # _shunit_escapeCharInStr(). 973 | for _shunit_char_ in '"' '$' "'" '`'; do 974 | _shunit_str_=`_shunit_escapeCharInStr "${_shunit_char_}" "${_shunit_str_}"` 975 | done 976 | 977 | echo "${_shunit_str_}" 978 | unset _shunit_char_ _shunit_str_ 979 | } 980 | 981 | # Extract list of functions to run tests against. 982 | # 983 | # Args: 984 | # script: string: name of script to extract functions from 985 | # Returns: 986 | # string: of function names 987 | _shunit_extractTestFunctions() 988 | { 989 | _shunit_script_=$1 990 | 991 | # extract the lines with test function names, strip of anything besides the 992 | # function name, and output everything on a single line. 993 | _shunit_regex_='^[ ]*(function )*test[A-Za-z0-9_]* *\(\)' 994 | egrep "${_shunit_regex_}" "${_shunit_script_}" \ 995 | |sed 's/^[^A-Za-z0-9_]*//;s/^function //;s/\([A-Za-z0-9_]*\).*/\1/g' \ 996 | |xargs 997 | 998 | unset _shunit_regex_ _shunit_script_ 999 | } 1000 | 1001 | #------------------------------------------------------------------------------ 1002 | # main 1003 | # 1004 | 1005 | # determine the operating mode 1006 | if [ $# -ne 0 ]; then 1007 | for file in "$@"; do 1008 | . "$file" 1009 | done 1010 | fi 1011 | for test in $(declare -F | awk '/ test/{print $3}'); do 1012 | suite_addTest $test 1013 | done 1014 | 1015 | # create a temporary storage location 1016 | __shunit_tmpDir=`_shunit_mktempDir` 1017 | 1018 | # provide a public temporary directory for unit test scripts 1019 | # TODO(kward): document this 1020 | SHUNIT_TMPDIR="${__shunit_tmpDir}/tmp" 1021 | mkdir "${SHUNIT_TMPDIR}" 1022 | 1023 | # setup traps to clean up after ourselves 1024 | trap '_shunit_cleanup EXIT' 0 1025 | trap '_shunit_cleanup INT' 2 1026 | trap '_shunit_cleanup TERM' 15 1027 | 1028 | # create phantom functions to work around issues with Cygwin 1029 | _shunit_mktempFunc 1030 | PATH="${__shunit_tmpDir}:${PATH}" 1031 | 1032 | # make sure phantom functions are executable. this will bite if /tmp (or the 1033 | # current $TMPDIR) points to a path on a partition that was mounted with the 1034 | # 'noexec' option. the noexec command was created with _shunit_mktempFunc(). 1035 | noexec 2>/dev/null || _shunit_fatal \ 1036 | 'please declare TMPDIR with path on partition with exec permission' 1037 | 1038 | # we must manually source the tests in standalone mode 1039 | if [ "${__shunit_mode}" = "${__SHUNIT_MODE_STANDALONE}" ]; then 1040 | . "`_shunit_prepForSourcing \"${__shunit_script}\"`" 1041 | fi 1042 | 1043 | # execute the oneTimeSetUp function (if it exists) 1044 | oneTimeSetUp 1045 | 1046 | # execute the suite function defined in the parent test script 1047 | # deprecated as of 2.1.0 1048 | suite 1049 | 1050 | # if no suite function was defined, dynamically build a list of functions 1051 | if [ -z "${__shunit_suite}" ]; then 1052 | shunit_funcs_=`_shunit_extractTestFunctions "${__shunit_script}"` 1053 | for shunit_func_ in ${shunit_funcs_}; do 1054 | suite_addTest ${shunit_func_} 1055 | done 1056 | fi 1057 | unset shunit_func_ shunit_funcs_ 1058 | 1059 | # execute the tests 1060 | _shunit_execSuite 1061 | 1062 | # execute the oneTimeTearDown function (if it exists) 1063 | oneTimeTearDown 1064 | 1065 | # generate the report 1066 | _shunit_generateReport 1067 | 1068 | # that's it folks 1069 | [ ${__shunit_testsFailed} -eq 0 ] 1070 | exit $? --------------------------------------------------------------------------------