diff --git a/.dockerignore b/.dockerignore index f78244c..5f6b13d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,8 @@ +.git + +# --- Below Should mirror .gitignore, without leading '/' --- # .DS_Store .*sw[op] *.py[co] *.egg-info +testdata diff --git a/.gitignore b/.gitignore index f78244c..cf8c79a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ .*sw[op] *.py[co] *.egg-info +testdata diff --git a/.travis.yml b/.travis.yml index 6d6eba0..e492614 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,39 +1,29 @@ sudo: false -language: python -python: - - "2.7" +dist: trusty +services: + - docker env: global: - - DCMTK_VERSION="dcmtk-3.6.1_20150924" - - DCMTK_DB_DIR="dcmtk_dicom_db" - - TESTDATA_DIR="testdata" - - ORTHANC_VERSION="Orthanc-1.1.0" + - TESTDATA_DIR="$HOME/.cache/testdata" + - DOCKER_DIR="$HOME/.cache/docker" - secure: tLyIDfETpHCDRyEqF2ROM8ue+D+fl/IeOdFBY/Mov7zhAHpgBdQQJi+HQ1/RK+G7DDsnM/FIjvCcf4etphmBHK3xs8vDM1oPntYs+3iE2AbTcVGglHYoX1++H76xPt3eXyf1S0frgVg6qYCNmuP0chhDUkYXPydcKvq1ANjfTQU= # BUILD_TRIGGER_URL cache: - pip: true directories: - - $DCMTK_VERSION - - $DCMTK_DB_DIR - $TESTDATA_DIR - - $ORTHANC_VERSION + - $DOCKER_DIR -addons: - apt: - packages: - - uuid-dev -before_install: - - ln -s bin $VIRTUAL_ENV/sbin - -install: - - pip install . - - pip install -r test/requirements.txt - - ./test/install_deps.sh +install: true script: - - ./test/lint.sh && ./test/test.sh + - test -f "$DOCKER_DIR/image.tar" && sudo docker load -i "$DOCKER_DIR/image.tar" || true + - docker build -t my_image . + - docker save -o "$DOCKER_DIR/image.tar" $(docker history -q my_image | grep -v '') + - mkdir -p "$TESTDATA_DIR" + - curl -L https://github.com/scitran/testdata/archive/master.tar.gz | tar xz -C "$TESTDATA_DIR" --strip-components 1 + - ./docker/test.sh --testdata "$TESTDATA_DIR" after_success: - if [ "$TRAVIS_TAG" ]; then diff --git a/bin/dicom_sniper b/bin/dicom_sniper index a720028..1049727 100755 --- a/bin/dicom_sniper +++ b/bin/dicom_sniper @@ -44,6 +44,7 @@ auth_group.add_argument('--key', help='user API key') args = arg_parser.parse_args(sys.argv[1:] or ['--help']) args.query = dict(args.query) + args.timezone = reaper.util.validate_timezone(args.timezone) if args.timezone is None: log.error('invalid timezone') @@ -60,7 +61,7 @@ matched_series = {} for study in scu_studies: scu_series = scu_.find(reaper.scu.SeriesQuery(**reaper.scu.SCUQuery(StudyInstanceUID=study.StudyInstanceUID))) for series in scu_series: - if series.NumberOfSeriesRelatedInstances is None: + if not series.NumberOfSeriesRelatedInstances: scu_images = scu_.find(reaper.scu.ImageQuery(**reaper.scu.SCUQuery(SeriesInstanceUID=series.SeriesInstanceUID))) series.NumberOfSeriesRelatedInstances = len(scu_images) matched_series[series.SeriesInstanceUID] = { diff --git a/docker/Dockerfile-orthanc b/docker/Dockerfile-orthanc new file mode 100644 index 0000000..caf26ca --- /dev/null +++ b/docker/Dockerfile-orthanc @@ -0,0 +1,20 @@ +# +# Image used for hosting . +# +# Example usage is in README.md +# + +FROM ubuntu:16.04 + + +# Install pre-requisites +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + ca-certificates curl \ + dcmtk \ + orthanc \ + && rm -rf /var/lib/apt/lists/* + +COPY orthanc-config.json /etc/orthanc/orthanc-reaper-config.json + +ENTRYPOINT ["Orthanc", "/etc/orthanc/orthanc-reaper-config.json"] diff --git a/docker/orthanc-config.json b/docker/orthanc-config.json new file mode 100644 index 0000000..bc2e974 --- /dev/null +++ b/docker/orthanc-config.json @@ -0,0 +1,7 @@ +{ + "UnknownSopClassAccepted" : true, + "RemoteAccessAllowed" : true, + "DicomModalities" : { + "reaper" : [ "REAPER", "reaper-test", 5104] + } +} diff --git a/docker/test.sh b/docker/test.sh new file mode 100755 index 0000000..69390a1 --- /dev/null +++ b/docker/test.sh @@ -0,0 +1,130 @@ +#!/usr/bin/env bash + +set -e + +unset CDPATH +cd "$( dirname "${BASH_SOURCE[0]}" )/.." + +function usage() { +cat >&2 < 0 ]]; do + case "$1" in + -B|--no-build) DOCKER_BUILD=false; ;; + -h|--help) usage; exit 0 ;; + --testdata) + TESTDATA_DIR="$2"; + if [ ! "$(ls -A $TESTDATA_DIR)" ] ; then + >&2 echo "ERROR: --testdata must exist and not be empty." + exit 1 + fi + shift + ;; + --) TEST_ARGS="${@:2}"; break;; + *) echo "Invalid argument: $1" >&2; usage; exit 1 ;; + esac + shift + done + + if $DOCKER_BUILD ; then + # build this repo + docker build -t reaper-test . + + # build dependencies + docker build -t orthanc-test -f docker/Dockerfile-orthanc docker/ + fi + + #Spin up dependencies + docker network create reaper-test + + + # Orthanc + # calledAE = ORTHANC 4242 + # REST port = 8042 + docker run -d --name orthanc-test --network reaper-test orthanc-test + + + # scitran-core + docker run -d --name scitran-core-mongo --network reaper-test mongo + docker run -d --name scitran-core \ + --network reaper-test \ + -e "SCITRAN_PERSISTENT_DB_URI=mongodb://scitran-core-mongo:27017/scitran" \ + -e "SCITRAN_PERSISTENT_DB_LOG_URI=mongodb://scitran-core-mongo:27017/logs" \ + -e "SCITRAN_CORE_DRONE_SECRET=secret" \ + scitran/core + + + # Fetch test data + if [ -z ${TESTDATA_DIR} ]; then + TESTDATA_DIR="./testdata" + mkdir -p $TESTDATA_DIR + if [ ! "$(ls -A $TESTDATA_DIR)" ]; then + curl -L https://github.com/scitran/testdata/archive/master.tar.gz | tar xz -C "$TESTDATA_DIR" --strip-components 1 + fi + fi + + # Make sure testdata path is absolute. Could be either absolute or relative. + TESTDATA_DIR="$(cd "$(dirname "$TESTDATA_DIR")" && pwd)/$(basename "$TESTDATA_DIR")" + + set +e + docker run -it \ + --rm \ + --name reaper-test \ + --network reaper-test \ + -v "$TESTDATA_DIR:/testdata" \ + -v "$(pwd)/bin:/src/reaper/bin" \ + -v "$(pwd)/reaper:/src/reaper/reaper" \ + -v "$(pwd)/tests:/src/reaper/tests" \ + reaper-test \ + /src/reaper/tests/bin/test.sh \ + --testdata /testdata \ + --core-url http://scitran-core:8080 \ + --core-secret secret \ + --dicom-scp-host orthanc-test \ + --dicom-scp-port 4242 \ + --dicom-scp-aet ORTHANC \ + --orthanc http://orthanc-test:8042 \ + $TEST_ARGS + + TEST_RESULT_CODE=$? + >&2 echo + >&2 echo "INFO: Test return code = $TEST_RESULT_CODE" + if [ "${TEST_RESULT_CODE}" != "0" ] ; then + >&2 echo "INFO: Printing container logs..." + docker logs scitran-core-mongo + docker logs scitran-core + docker logs orthanc-test + >&2 echo + >&2 echo "ERROR: Test return code = $TEST_RESULT_CODE. Container logs printed above." + fi +} + +clean_up () {( + set +e + # Spin down dependencies + docker rm -f -v scitran-core-mongo + docker rm -f -v scitran-core + docker rm -f -v orthanc-test + docker network rm reaper-test +)} +trap clean_up EXIT + +main "$@" diff --git a/setup.py b/setup.py index da15f31..b5d592e 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ 'pydicom', 'python-dateutil', 'pytz', - 'requests', + 'requests<2.16', 'requests_toolbelt', 'tzlocal', ] diff --git a/test/install_deps.sh b/test/install_deps.sh deleted file mode 100755 index c53d427..0000000 --- a/test/install_deps.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env bash - -set -ue - -# DCMTK -( -if [ ! -f $DCMTK_VERSION/config/config.status ]; then - curl http://dicom.offis.de/download/dcmtk/snapshot/old/$DCMTK_VERSION.tar.gz | tar xz - cd $DCMTK_VERSION - curl https://raw.githubusercontent.com/scitran/reaper/master/movescu.cc.patch | patch --strip 1 - ./configure --prefix=$VIRTUAL_ENV - make all -else - cd $DCMTK_VERSION -fi - -make install -) - -# Orthanc -( -if [ ! -f "$ORTHANC_VERSION/Orthanc" ]; then - curl -L "http://www.orthanc-server.com/downloads/get.php?path=/orthanc/$ORTHANC_VERSION.tar.gz" | tar xz - cd "$ORTHANC_VERSION" - cmake -DCMAKE_INSTALL_PREFIX=$VIRTUAL_ENV -DSTATIC_BUILD=ON -DCMAKE_BUILD_TYPE=Release - make -else - cd "$ORTHANC_VERSION" -fi - -make install -) diff --git a/test/test.sh b/test/test.sh deleted file mode 100755 index e7455e0..0000000 --- a/test/test.sh +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env bash - -set -eu - -unset CDPATH -cd "$( dirname "${BASH_SOURCE[0]}" )/.." - -DCMTK_DB_DIR=${DCMTK_DB_DIR:-"./dcmtk_dicom_db"} -TESTDATA_DIR=${TESTDATA_DIR:-"./testdata"} - -PORT=${PORT:-"8027"} -HOST=${HOST:-"http://localhost:$PORT"} - - -# Set up exit and error trap to shutdown dependencies -shutdown() { - echo 'Exit signal trapped' - kill $DCMQRSCP_PID || true - kill $RECEIVER_PID || true - kill $ORTHANC_PID || true - wait -} -trap "shutdown" EXIT ERR - - -# Launch dummy upload receiver -uwsgi --http :$PORT --wsgi-file ./test/upload_receiver.wsgi --master --die-on-term & -RECEIVER_PID=$! - - -# Fetch test data -mkdir -p $TESTDATA_DIR -if [ ! "$(ls -A $TESTDATA_DIR)" ]; then - curl -L https://github.com/scitran/testdata/archive/master.tar.gz | tar xz -C $TESTDATA_DIR --strip-components 1 -fi - - -# Populate DICOM test server -if [ ! -f $DCMTK_DB_DIR/index.dat ]; then - mkdir -p $DCMTK_DB_DIR - find $TESTDATA_DIR -type f -exec dcmqridx $DCMTK_DB_DIR {} + -fi - - -# Configure and launch DICOM test server -DCMQRSCP_CONFIG_FILE=$(mktemp) -cat << EOF > $DCMQRSCP_CONFIG_FILE -NetworkTCPPort = 5104 -MaxPDUSize = 16384 -MaxAssociations = 16 - -HostTable BEGIN -reaper = (REAPER, localhost, 3333) -HostTable END - -AETable BEGIN -DCMQRSCP $DCMTK_DB_DIR RW (200, 1024mb) ANY -AETable END -EOF - -dcmqrscp -c $DCMQRSCP_CONFIG_FILE & -DCMQRSCP_PID=$! - - -# Test DICOM Sniper -dicom_sniper -y --secret secret -k StudyID "" localhost 5104 3333 REAPER DCMQRSCP $HOST - - -# Test DICOM Reaper -dicom_reaper -o -s 1 --secret secret $(mktemp) localhost 5104 3333 REAPER DCMQRSCP $HOST - - -# Test Folder Sniper -folder_sniper -y --secret secret $TESTDATA_DIR $HOST - -# Test Orthanc DICOM Reaper -ORTHANC_CONFIG_FILE="orthanc-config.json" -cat << EOF > $ORTHANC_CONFIG_FILE -{ - "DicomModalities" : { - "reaper" : [ "REAPER", "127.0.0.1", 3333 ] - } -} -EOF - -Orthanc "${ORTHANC_CONFIG_FILE}" & -ORTHANC_PID=$! -sleep 5 - -storescu -v --scan-directories -aec ORTHANC localhost 4242 $(find $TESTDATA_DIR -type d -name dicom | tail -n 1) -orthanc_reaper -o -s 1 --secret secret $(mktemp) localhost 4242 3333 REAPER ORTHANC "http://localhost:8042" $HOST diff --git a/test/upload_receiver.wsgi b/test/upload_receiver.wsgi deleted file mode 100644 index 8d6519c..0000000 --- a/test/upload_receiver.wsgi +++ /dev/null @@ -1,6 +0,0 @@ -# vim: filetype=python - -def application(env, start_response): - env['wsgi.input'].read() - start_response('200 OK', [('Content-Type','text/html')]) - return [] diff --git a/test/lint.sh b/tests/bin/lint.sh similarity index 54% rename from test/lint.sh rename to tests/bin/lint.sh index e71742e..8504c1e 100755 --- a/test/lint.sh +++ b/tests/bin/lint.sh @@ -3,12 +3,12 @@ set -eu unset CDPATH -cd "$( dirname "${BASH_SOURCE[0]}" )/.." +cd "$( dirname "${BASH_SOURCE[0]}" )/../.." -echo "Running pylint ..." +>&2 echo +>&2 echo "Running pylint ..." pylint --jobs=2 --reports=no --disable=R1705 reaper -echo - -echo "Running pep8 ..." +>&2 echo +>&2 echo "Running pep8 ..." pep8 --max-line-length=150 --ignore=E402 reaper diff --git a/tests/bin/test.sh b/tests/bin/test.sh new file mode 100755 index 0000000..6ad58ad --- /dev/null +++ b/tests/bin/test.sh @@ -0,0 +1,130 @@ +#!/usr/bin/env bash + +set -e +unset CDPATH +cd "$( dirname "${BASH_SOURCE[0]}" )/../.." + + +function usage() { +cat >&2 < 0 ]]; do + case "$1" in + -L|--no-lint) RUN_LINT=false; ;; + -U|--no-unit) RUN_UNIT=false; ;; + --core-url) + CORE_URL="$2" + shift;; + --core-secret) + CORE_SECRET="$2" + shift;; + --dicom-scp-host) + DICOM_SCP_HOST="$2" + shift;; + --dicom-scp-port) + DICOM_SCP_PORT="$2" + shift;; + --dicom-scp-aet) + DICOM_SCP_AET="$2" + shift;; + --orthanc) + ORTHANC_REST_URL="$2" + shift;; + --testdata) + TESTDATA_DIR="$2" + shift;; + --) PYTEST_ARGS="${@:2}"; echo "$PYTEST_ARGS"; break;; + -h|--help) usage; exit 0;; + *) >&2 echo "Invalid argument: $1"; usage; exit 1;; + esac + shift + done + + # install dependencies + pip install -r tests/requirements.txt + pip freeze + + if ${RUN_LINT} ; then + ./tests/bin/lint.sh + fi + + if ${RUN_UNIT} ; then + >&2 echo + >&2 echo "Running unit tests ..." + #TODO: add unit tests + fi + + # Validate input dependencies + + # Dicom_SCP required TESTDATA_DIR + + + # Orthanc requires DICOM_SCP + if [ ${ORTHANC_REST_URL} ] ; then + if [ -z ${DICOM_SCP_HOST} ] || [ -z ${DICOM_SCP_PORT} ] || [ -z ${DICOM_SCP_AET} ] || [ -z ${CORE_URL} ] || [ -z ${CORE_SECRET} ] ; then + >&2 echo "ERROR: orthanc testing requires ..." + fi + fi + + # TODO: check testdata provided if DICOM stuff provided + + if [ ${DICOM_SCP_HOST} ] && [ ${DICOM_SCP_PORT} ] && [ ${DICOM_SCP_AET} ] && [ ${TESTDATA_DIR} ] ; then + >&2 echo + >&2 echo "INFO: Loading test data into DICOM SCP" + storescu -v --scan-directories -aec "${DICOM_SCP_AET}" "${DICOM_SCP_HOST}" "${DICOM_SCP_PORT}" $(find $TESTDATA_DIR -type d -name dicom | tail -n 1) + + >&2 echo + >&2 echo "INFO: Test DICOM Sniper" + dicom_sniper -y --secret "${CORE_SECRET}" -k StudyID "" "${DICOM_SCP_HOST}" "${DICOM_SCP_PORT}" 5104 REAPER "${DICOM_SCP_AET}" "${CORE_URL}" + + >&2 echo + >&2 echo "INFO: Test DICOM Reaper" + dicom_reaper -o -s 1 --secret "${CORE_SECRET}" $(mktemp) "${DICOM_SCP_HOST}" "${DICOM_SCP_PORT}" 5104 REAPER "${DICOM_SCP_AET}" "${CORE_URL}" + + if [ ${ORTHANC_REST_URL} ] ; then + >&2 echo + >&2 echo "INFO: Test Orthanc DICOM Reaper" + orthanc_reaper -o -s 1 --secret "${CORE_SECRET}" $(mktemp) "${DICOM_SCP_HOST}" "${DICOM_SCP_PORT}" 5104 REAPER "${DICOM_SCP_AET}" "${ORTHANC_REST_URL}" "${CORE_URL}" + fi + fi + + if [ ${TESTDATA_DIR} ] && [ ${CORE_URL} ] && [ ${CORE_SECRET} ] ; then + # Test Folder Sniper + folder_sniper -y --secret "${CORE_SECRET}" "${TESTDATA_DIR}" "${CORE_URL}" + fi + + +} + + +main "$@" diff --git a/test/requirements.txt b/tests/requirements.txt similarity index 66% rename from test/requirements.txt rename to tests/requirements.txt index bb825fa..cfa44af 100644 --- a/test/requirements.txt +++ b/tests/requirements.txt @@ -1,3 +1,2 @@ pep8 pylint -uwsgi