diff --git a/CHANGELOG.md b/CHANGELOG.md index 25dd4e1..a8cba53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,34 @@ This file lists all versions of the repository and precises all changes. +## v1.1.1 + +#### Minor changes : +* Removed unnecessary null pointer checks (thanks to https://github.com/elfring) +* Updated package description +* Removed debug message in CMake build +* Added `/ch01.264` to the URL dictionary in the deployment (Comelit default RTSP URL) +* Updated tests partially (still needs work to make the code cleaner) + * Variable names are now compliant with Golang best practices + * JSON variable names are back to normal + * Functions have been moved in more appropriate source files + * Structure definitions have been moved in more appropriate source files + * Source files have been renamed to be more relevant + * JUnit output now considers each camera as a test case + * JUnit output now contains errors which makes debugging much easier +* Added header files where it was forgotten + +#### Bugfixes : +* Fixed an issue where if you loose your internet connection during thumbnail generation, FFMpeg would get stuck forever and thus Cameradar would never finish +* Fixed an issue where multithreading could cause crashes +* Fixed an issue where the routes dictionary was mistaken for the credentials dictionary +* Fixed issues with the golang testing tool + * Fixed automated camera generation + * Fixed docker IP address resolution + +#### Known issues : +* There is an issue with Camera Emulation Server that makes it impossible for Cameradar to generate thumbnails, which is why right now the verification of the thumbnails presence is commented and it is assumed correct. It is probably an issue with GST-RTSP-Server but requires investigation. + ## v1.1.0 #### Major changes : diff --git a/CMakeLists.txt b/CMakeLists.txt index d9a052d..b93b4af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,11 +19,9 @@ set (PROJECT_NAME cameradar) project (${PROJECT_NAME}) -message ("Here") - set (${PROJECT_NAME}_VERSION_MAJOR 1) set (${PROJECT_NAME}_VERSION_MINOR 1) -set (${PROJECT_NAME}_VERSION_PATCH 0) +set (${PROJECT_NAME}_VERSION_PATCH 1) set (${PROJECT_NAME}_VERSION "${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}.${${PROJECT_NAME}_VERSION_PATCH}${${PROJECT_NAME}_SUFFIX}") find_package(Git REQUIRED) @@ -34,7 +32,7 @@ set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W -Wall -Wextra -Wno-unused-function") set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color") #enable error coloration on gcc # release specific flags -set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2") #enable error coloration on gcc +set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2") #debug specific flags set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -fprofile-arcs -ftest-coverage") @@ -102,7 +100,7 @@ include_directories ( set (${CAMERADAR_BINARIES} "") set (${CAMERADAR_LIBRARIES} "") -#build cache managers +# Build cache managers add_subdirectory (deps) message ("Debug") add_subdirectory (cameradar_standalone) @@ -115,11 +113,11 @@ install (FILES ${CAMERADAR_CACHE_MANAGERS} DESTINATION cache_managers) install (FILES ${CAMERADAR_LIBRARIES} DESTINATION libraries) install (DIRECTORY ${CMAKE_SOURCE_DIR}/deps/licenses DESTINATION libraries) -# cpack configuration +# CPack configuration include (InstallRequiredSystemLibraries) set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "cameradar") set (CPACK_PACKAGE_VENDOR "Etix Labs") -set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "cameradar tool") +set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "Cameradar hacks its way into RTSP CCTV cameras") set (CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}_${${PROJECT_NAME}_VERSION}_${CMAKE_BUILD_TYPE}_${CMAKE_SYSTEM_NAME}") set (CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README.md") set (CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") diff --git a/README.md b/README.md index 35fd022..0e11955 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ## An RTSP surveillance camera access multitool [![cameradar License](https://img.shields.io/badge/license-Apache-blue.svg)](#license) -[![Latest release](https://img.shields.io/badge/release-1.1.0-green.svg)](https://github.com/EtixLabs/cameradar/releases/latest) +[![Latest release](https://img.shields.io/badge/release-1.1.1-green.svg)](https://github.com/EtixLabs/cameradar/releases/latest) #### Cameradar allows you to: @@ -48,7 +48,7 @@ Of course, you can also call for individual tasks if you plug in a Database to C This is the fastest and simplest way to use Cameradar. To do this you will just need `docker` on your machine. -Run +Run ``` docker run \ diff --git a/cameradar_standalone/conf/cameradar.conf.json b/cameradar_standalone/conf/cameradar.conf.json index cc18536..7a61107 100644 --- a/cameradar_standalone/conf/cameradar.conf.json +++ b/cameradar_standalone/conf/cameradar.conf.json @@ -1,25 +1,16 @@ { "mysql_db" : { - "host" : "0.0.0.0", + "host" : "cameradar-database", "port" : 3306, "user": "root", "password": "root", - "db_name": "cctv_dev" + "db_name": "cmrdr" }, - - "subnets" : "172.16.100.11", - - // If not specified, will scan all ports (1-65535) + "subnets" : "localhost", "ports" : "554,8554", - "rtsp_url_file" : "conf/url.json", - "rtsp_ids_file" : "conf/ids.json", - - // You must give an accessible path to an already existing directory - "thumbnail_storage_path" : "/tmp", - - // This is the path that will be used in the Docker container - // if you're not familiar with Docker, only change the - // cache_manager_name value - "cache_manager_path" : "../cache_managers", + "rtsp_url_file" : "/cameradar/conf/url.json", + "rtsp_ids_file" : "/cameradar/conf/ids.json", + "thumbnail_storage_path" : "/tmp/thumbs", + "cache_manager_path" : "/cameradar/cache_managers", "cache_manager_name" : "dumb" } diff --git a/cameradar_standalone/src/configuration.cpp b/cameradar_standalone/src/configuration.cpp index b1687b8..6e87084 100644 --- a/cameradar_standalone/src/configuration.cpp +++ b/cameradar_standalone/src/configuration.cpp @@ -92,12 +92,12 @@ bool configuration::load_url() { std::string content; - LOG_DEBUG_("Trying to open ids file from " + this->rtsp_ids_file, "configuration"); + LOG_DEBUG_("Trying to open url file from " + this->rtsp_url_file, "configuration"); if (this->rtsp_url_file.size()) { content = read_file(this->rtsp_url_file.c_str()).second; } else { LOG_WARN_( - "No ids file detected in your configuration, Cameradar will use " + "No url file detected in your configuration, Cameradar will use " "the default one " "instead.", "configuration"); diff --git a/cameradar_standalone/src/describe.cpp b/cameradar_standalone/src/describe.cpp index 2341d5d..4debe44 100644 --- a/cameradar_standalone/src/describe.cpp +++ b/cameradar_standalone/src/describe.cpp @@ -25,8 +25,8 @@ size_t write_data(void* buffer, size_t size, size_t nmemb, void* userp) { // I'm sorry for this // Forget you ever saw it - (void)buffer; - (void)userp; + if (not buffer || not size || not nmemb) return 0; + return size * nmemb; } @@ -88,7 +88,7 @@ curl_describe(const std::string& path, bool logs) { m.lock(); curl_global_cleanup(); m.unlock(); - LOG_DEBUG_("Response code : " + std::to_string(rc), "describe"); + LOG_DEBUG_("[" + path + "] Response code : " + std::to_string(rc), "describe"); if (logs) { // Some cameras return 400 instead of 401, don't know why. // Some cameras timeout and then curl considers the status as 0 diff --git a/cameradar_standalone/src/rtsp_path.cpp b/cameradar_standalone/src/rtsp_path.cpp index fb61dcf..6357e8a 100644 --- a/cameradar_standalone/src/rtsp_path.cpp +++ b/cameradar_standalone/src/rtsp_path.cpp @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include #include +#include namespace etix { @@ -21,10 +21,17 @@ namespace cameradar { const std::string make_path(const stream_model& model) { - std::string ret(model.service_name + "://" + model.username + ":" + model.password + "@" + - model.address + ":" + std::to_string(model.port) + model.route); - LOG_DEBUG_(ret, "debug"); - return ret; + if (model.password != "" || model.username != "") { + std::string ret(model.service_name + "://" + model.username + ":" + model.password + "@" + + model.address + ":" + std::to_string(model.port) + model.route); + LOG_DEBUG_(ret, "debug"); + return ret; + } else { + std::string ret(model.service_name + "://" + model.address + ":" + + std::to_string(model.port) + model.route); + LOG_DEBUG_(ret, "debug"); + return ret; + } } } } diff --git a/cmake/mysql_connector.cmake b/cmake/mysql_connector.cmake index d984f31..7d8469e 100644 --- a/cmake/mysql_connector.cmake +++ b/cmake/mysql_connector.cmake @@ -1,8 +1,16 @@ -# Copyright (C) 2015 Etix Labs - All Rights Reserved. -# All information contained herein is, and remains the property of Etix Labs and its suppliers, -# if any. The intellectual and technical concepts contained herein are proprietary to Etix Labs -# Dissemination of this information or reproduction of this material is strictly forbidden unless -# prior written permission is obtained from Etix Labs. +## Copyright 2016 Etix Labs +## +## Licensed under the Apache License, Version 2.0 (the "License"); +## you may not use this file except in compliance with the License. +## You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. # MySQL Connector dependency message(STATUS "Configuring deps.mysqlconnector") diff --git a/deployment/build_last_package.sh b/deployment/build_last_package.sh index 2ae6eea..0fa6f12 100755 --- a/deployment/build_last_package.sh +++ b/deployment/build_last_package.sh @@ -9,10 +9,16 @@ COL_BLUE=$ESC_SEQ"34;01m" COL_MAGENTA=$ESC_SEQ"35;01m" COL_CYAN=$ESC_SEQ"36;01m" +echo -e $COL_YELLOW"Deleting old package ... "$COL_RESET +rm -f cameradar_*_Release_Linux.tar.gz +echo -e $COL_GREEN"OK!"$COL_RESET + echo -e $COL_YELLOW"Creating package ... "$COL_RESET - { cd .. +{ + cd .. mkdir build cd build + rm -f cameradar_*_Release_Linux.tar.gz cmake .. -DCMAKE_BUILD_TYPE=Release make package cp cameradar_*_Release_Linux.tar.gz ../deployment diff --git a/deployment/cameradar_1.1.0_Release_Linux.tar.gz b/deployment/cameradar_1.1.0_Release_Linux.tar.gz deleted file mode 100644 index c3862ab..0000000 Binary files a/deployment/cameradar_1.1.0_Release_Linux.tar.gz and /dev/null differ diff --git a/deployment/cameradar_1.1.1_Release_Linux.tar.gz b/deployment/cameradar_1.1.1_Release_Linux.tar.gz new file mode 100644 index 0000000..49d6818 Binary files /dev/null and b/deployment/cameradar_1.1.1_Release_Linux.tar.gz differ diff --git a/deployment/conf/url.json b/deployment/conf/url.json index 31d250c..ce6ddd7 100644 --- a/deployment/conf/url.json +++ b/deployment/conf/url.json @@ -24,6 +24,7 @@ "/camera.stm", "/ch0", "/ch001.sdp", + "/ch01.264", "/ch0_unicast_firststream", "/ch0_unicast_secondstream", "/channel1", diff --git a/test/Dockerfile b/test/Dockerfile index c624e89..08bf049 100644 --- a/test/Dockerfile +++ b/test/Dockerfile @@ -2,14 +2,14 @@ FROM ubuntu:15.10 MAINTAINER brendan.leglaunec@etixgroup.com -ENV LD_LIBRARY_PATH="/cctv/libraries" +ENV LD_LIBRARY_PATH="/cameradar/libraries" # install go RUN apt-get update && apt-get install -y make git wget curl RUN wget https://storage.googleapis.com/golang/go1.6.linux-amd64.tar.gz RUN tar -C /usr/local -xzf go1.6.linux-amd64.tar.gz # set variable env -ENV GOPATH=/go +ENV GOPATH=/cameradartest/go ENV PATH=$PATH:/go/bin ENV PATH=$PATH:/usr/local/go/bin ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib @@ -27,17 +27,19 @@ RUN apt-get update && apt-get install -y \ RUN apt-get install -y psmisc -ADD cctv_*_Debug_Linux.tar.gz / -RUN mv cctv_*_Debug_Linux cctv +ADD cameradar_*_Debug_Linux.tar.gz / +RUN mv cameradar_*_Debug_Linux cameradar # create cameradaratest folder in go src path -RUN mkdir -p /go/src/cameradartest -ADD ./conf /conf +RUN mkdir -p /cameradartest/go/src/cameradartest +COPY src/*.go /cameradartest/go/src/cameradartest/ +COPY ./conf /conf ADD ./docker/run_cameradartest.sh /run.sh # get go deps RUN go get github.com/go-sql-driver/mysql RUN mkdir /thumbnails -WORKDIR /go/src/cameradartest +WORKDIR /cameradartest/go/src/cameradartest +RUN go build -o cameradartest *.go CMD ["/run.sh"] diff --git a/test/Dockerfile-camera b/test/Dockerfile-camera index cf09b73..a285f55 100644 --- a/test/Dockerfile-camera +++ b/test/Dockerfile-camera @@ -1,14 +1,23 @@ FROM ubuntu:16.04 MAINTAINER brendan.leglaunec@etixgroup.com -RUN useradd -m vlc; \ - apt-get update; \ - apt-get install -y vlc-nox - -RUN sed -i s/geteuid/getppid/g /usr/bin/vlc +RUN apt-get update && apt-get install -y \ + libgstrtspserver-1.0-dev \ + libgstreamer1.0-dev \ + gstreamer1.0-plugins-base \ + gstreamer1.0-plugins-bad \ + gstreamer1.0-plugins-ugly \ + gstreamer1.0-libav \ + gstreamer1.0-tools \ + libssl-dev mysql-client \ + gstreamer1.0-plugins-good \ + libgstreamer-plugins-base1.0-dev \ + libgstreamer-plugins-bad1.0-dev ADD ./docker/screen.png /vlc/screen.png -COPY ./docker/run_vlc.sh /start.sh -COPY ./etix_rtsp_server /etix_rtsp_server +COPY ./docker/run_ces.sh /start.sh +COPY ./camera_emulation_server /camera_emulation_server EXPOSE 8554 + +RUN ./camera_emulation_server& diff --git a/test/build_last_package.sh b/test/build_last_package.sh new file mode 100755 index 0000000..69a1006 --- /dev/null +++ b/test/build_last_package.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +ESC_SEQ="\x1b[" +COL_RESET=$ESC_SEQ"39;49;00m" +COL_RED=$ESC_SEQ"31;01m" +COL_GREEN=$ESC_SEQ"32;01m" +COL_YELLOW=$ESC_SEQ"33;01m" +COL_BLUE=$ESC_SEQ"34;01m" +COL_MAGENTA=$ESC_SEQ"35;01m" +COL_CYAN=$ESC_SEQ"36;01m" + +echo -e $COL_YELLOW"Deleting old package ... "$COL_RESET +rm -f cameradar_*_Debug_Linux.tar.gz +echo -e $COL_GREEN"OK!"$COL_RESET + +echo -e $COL_YELLOW"Creating package ... "$COL_RESET +{ + cd .. + mkdir build + cd build + rm -f cameradar_*_Debug_Linux.tar.gz + cmake .. -DCMAKE_BUILD_TYPE=Debug + make package + cp cameradar_*_Debug_Linux.tar.gz ../test + cd ../test + } &> /dev/null +echo -e $COL_GREEN"OK!"$COL_RESET diff --git a/test/camera_emulation_server b/test/camera_emulation_server new file mode 100755 index 0000000..b941dd6 Binary files /dev/null and b/test/camera_emulation_server differ diff --git a/test/conf/cameradar.conf.json b/test/conf/cameradar.conf.json index 53187c7..cb4bef6 100644 --- a/test/conf/cameradar.conf.json +++ b/test/conf/cameradar.conf.json @@ -1,14 +1,16 @@ { "mysql_db" : { - "host" : "0.0.0.0", + "host" : "cameradar-database", "port" : 3306, "user": "root", "password": "root", - "db_name": "cctv" + "db_name": "cmrdr" }, - "subnets" : "172.16.100.13 localhost", - "ports" : "554,8554", // if not specified, default will be 1-65535 - "rtsp_url_file" : "conf/url.json", - "rtsp_ids_file" : "conf/ids.json", - "thumbnail_storage_path" : "/ce/que/tu/veux" -} \ No newline at end of file + "subnets" : "localhost", + "ports" : "554,8554", + "rtsp_url_file" : "/conf/url.json", + "rtsp_ids_file" : "/conf/ids.json", + "thumbnail_storage_path" : "/tmp", + "cache_manager_path" : "/cameradar/cache_managers", + "cache_manager_name" : "dumb" +} diff --git a/test/conf/cameratest.conf.json b/test/conf/cameratest.conf.json index ed08a0d..f506f1e 100644 --- a/test/conf/cameratest.conf.json +++ b/test/conf/cameratest.conf.json @@ -2,19 +2,19 @@ "Output": "cameratest.log.xml", "Cameradar" : { - "Path": "/home/ullaakut/Work/cctv_server2/cameradar/test/cameradar", - "Args": "-l 1 -c tmp_config", - "Ports": "554,5554,8554", - "IdsPath": "conf/ids.json", - "RoutesPath": "conf/url.json", - "ThumbPath": "/home/ullaakut/.cctv", - "dbHost": "0.0.0.0", - "dbPort": 3306, - "dbUser": "root", - "dbPassword": "root", - "dbName": "cctv", - "Console": false - }, + "Path": "/cameradar/cameradar_standalone/cameradar", + "Args": "-s 172.17.0.0/24 -c /conf/cameradar.conf.json --gst-rtsp-server", + "Ports": "554,5554,8554", + "IdsPath": "/conf/ids.json", + "RoutesPath": "/conf/url.json", + "ThumbPath": "/tmp", + "dbHost": "cameradar-database", + "dbPort": 3306, + "dbUser": "root", + "dbPassword": "root", + "dbName": "cmrdr", + "Console": false + }, "Tests" : [ { "address" : "127.0.0.1", diff --git a/test/docker/cameratest.conf.tmpl.json b/test/docker/cameratest.conf.tmpl.json index de8eca7..e488a79 100644 --- a/test/docker/cameratest.conf.tmpl.json +++ b/test/docker/cameratest.conf.tmpl.json @@ -1,18 +1,19 @@ { - "Output": "cameratest.log.xml", - "Cameradar" : { - "Path": "/cctv/bin/cameradar", - "Args": "-l 1 -c tmp_config", - "Ports": "554,5554,8554,5548", - "IdsPath": "/conf/ids.json", - "RoutesPath": "/conf/url.json", - "ThumbPath": "/thumbnails", - "dbHost": "mysql_cameradar", - "dbPort": 3306, - "dbUser": "root", - "dbPassword": "root", - "dbName": "cctv", - "Console": false + "output": "test-results.xml", + + "cameradar" : { + "path": "/cameradar/bin/cameradar", + "args": "-s 172.17.0.0/24 -c /conf/cameradar.conf.json --gst-rtsp-server", + "ports": "554,5554,8554", + "ids_path": "conf/ids.json", + "routes_path": "conf/url.json", + "thumb_path": "/tmp", + "db_host": "cameradar-database", + "db_port": 3306, + "db_user": "root", + "db_password": "root", + "db_name": "cmrdr", + "console": false }, - "Tests" : __CAMERAS__ -} \ No newline at end of file + "tests" : __CAMERAS__ +} diff --git a/test/docker/gen_cameras.sh b/test/docker/gen_cameras.sh index a59a835..ada4d18 100755 --- a/test/docker/gen_cameras.sh +++ b/test/docker/gen_cameras.sh @@ -3,7 +3,7 @@ ports=('8554' '8554' '8554' '8554' '8554' '8554') users=('admin' 'root' 'ubnt' 'Admin' 'supervisor' '') passwords=('admin' 'root' '12345' 'ubnt' 'password' '') -routes=('live.sdp' 'live.sdp' 'ch001.sdp' '' 'invalid' 'live_mpeg4.sdp') +routes=('cam0_0' 'live.sdp' 'ch001.sdp' 'cam' 'invalid' 'live_mpeg4.sdp') cams_name_pattern="fake_camera_" # json generation variable only @@ -11,11 +11,16 @@ json="[\n" first=true # $1 = adress, $2 = port, $3 = path, $4 = usernam $5 = password, $6 = valid function make_json { + # Get all data about the container, this will return three lines + # One empty that we ignore + # the two other ones with the IP of our container + # We take the second one using sed and cut to get only the IPAddress + address="$(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $CID)" if [ "$first" = true ] ; then first=false else json="$json,\n"; fi json="$json{" - json="$json\"address\":\"$1\"," - json="$json\"port\":\"$2\"," + json="$json\"address\":\"$address\"," + json="$json\"port\":$2," json="$json\"route\":\"$3\"," json="$json\"username\":\"$4\"," json="$json\"password\":\"$5\"," @@ -50,13 +55,12 @@ function start { # if conf_idx = 4 -> invalid conf if [ "$conf_idx" == "4" ] ; then is_valid=false; fi - docker run -d --name "$name" fake-camera /start.sh "$port" "$user" "$passw" "$route" - make_json "$name" "$port" "$route" "$user" "$passw" $is_valid + CID=$(docker run -d --name "$name" fake-camera /start.sh "$port" "$user" "$passw" "$route"); + make_json "$name" "$port" "$route" "$user" "$passw" $is_valid $CID done # finalize json json="$json]" - echo "$json" } function stop { diff --git a/test/docker/run_cameradartest.sh b/test/docker/run_cameradartest.sh index 59b2823..12b7a1f 100755 --- a/test/docker/run_cameradartest.sh +++ b/test/docker/run_cameradartest.sh @@ -1,15 +1,19 @@ #!/bin/bash -while ! mysqladmin ping -h"mysql_cameradar" -P3306 --silent; do +while ! mysqladmin ping -h"cameradar-database" -P3306 --silent; do sleep 1 done -ls -alhR /conf -cat /etc/hosts +cat /tmp/tests/cameradartest.conf.json # build go build + +cp /tmp/tests/*.xml ./ + # run test ./cameradartest /tmp/tests/cameradartest.conf.json -cp cameratest.log.xml /tmp/tests/ \ No newline at end of file +cat *.xml + +cp *.xml /tmp/tests/ diff --git a/test/docker/run_ces.sh b/test/docker/run_ces.sh new file mode 100755 index 0000000..46c0111 --- /dev/null +++ b/test/docker/run_ces.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +port=$1 +user=$2 +passw=$3 +route=$4 +url="" + +# need first argument at least +if [ "$2" == "" ]; then + url="rtsp://:$port/$route" +else + url="rtsp://$user:$passw@:$port/$route" +fi + +./camera_emulation_server -u $2 -p $3 -r $4 +echo "Stream started on ${url}" diff --git a/test/docker/run_vlc.sh b/test/docker/run_vlc.sh deleted file mode 100755 index 1a9f4a7..0000000 --- a/test/docker/run_vlc.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -port=$1 -user=$2 -passw=$3 -route=$4 -url="" - -# need first argument at least -if [ "$2" == "" ]; then - url="rtsp://:$port/$route" -else - url="rtsp://$user:$passw@:$port/$route" -fi -./etix_rtsp_server -u $s -p $3 -r $4 -# cvlc /vlc/screen.png -I dummy --sout-keep --no-drop-late-frames --no-skip-frames --image-duration 9999 --sout="#transcode{vcodec=h264,fps=15,venc=x264{preset=ultrafast,tune=zerolatency,keyint=30,bframes=0,ref=1,level=30,profile=baseline,hrd=cbr,crf=20,ratetol=1.0,vbv-maxrate=1200,vbv-bufsize=1200,lookahead=0}}:rtp{sdp=$url}" --sout-all diff --git a/test/etix_rtsp_server b/test/etix_rtsp_server index 9f03afb..d74a186 100755 Binary files a/test/etix_rtsp_server and b/test/etix_rtsp_server differ diff --git a/test/run.sh b/test/run.sh index bef1e33..a4f26ed 100755 --- a/test/run.sh +++ b/test/run.sh @@ -1,7 +1,7 @@ #!/bin/bash # check if a debug package exist in the current folder -if ! ls ./cctv_*_Debug_Linux.tar.gz 1> /dev/null 2>&1; then +if ! ls ./cameradar_*_Debug_Linux.tar.gz 1> /dev/null 2>&1; then (echo "no debug package in the current folder"; exit 137) exit 137 fi @@ -19,40 +19,40 @@ function make_docker_command { done # add mysql libk - cmd="$cmd --link=\"mysql_cameradar\"" + cmd="$cmd --link=\"cameradar-database\"" # add cameradar srcs cmd="$cmd -v \"`pwd`/src:/go/src/cameradartest\"" # add cmaeradar conf cmd="$cmd -v \"`pwd`/:/tmp/tests\"" # add container name + cmd="$cmd -v \"`pwd`/:/tmp/shared\"" + # add container name cmd="$cmd cameradartest" } function start_test { - make_docker_command $1 ./docker/gen_cameras.sh start $1 ./docker/cameratest.conf.tmpl.json eval $cmd + make_docker_command $1 ./docker/gen_cameras.sh stop } # build images echo "building docker images" # building fake-camera container -docker build -f Dockerfile-camera -t fake-camera . +docker build --no-cache -f Dockerfile-camera -t fake-camera . # building cameradartest image -docker build -t cameradartest . +docker build --no-cache -t cameradartest . # getting mysql echo "starting mysql" docker pull mysql:5.7 -docker run --name mysql_cameradar -e MYSQL_DATABASE=cctv -e MYSQL_ROOT_PASSWORD=root -d mysql:5.7 +docker run --name cameradar-database -e MYSQL_DATABASE=cmrdr -e MYSQL_ROOT_PASSWORD=root -d mysql:5.7 start_test 1 start_test 5 -# start_test 10 -# start_test 20 # stop mysql echo "stopping mysql" -docker rm -f mysql_cameradar \ No newline at end of file +docker rm -f cameradar-database diff --git a/test/src/configuration.go b/test/src/configuration.go index 54e7f44..8add108 100644 --- a/test/src/configuration.go +++ b/test/src/configuration.go @@ -1,3 +1,17 @@ +// Copyright 2016 Etix Labs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package main import ( @@ -6,27 +20,27 @@ import ( "os" ) -func (m *manager) parseConfig() bool { +func (t *Tester) parseConfig() bool { // Get config file path confPath := "conf/cameratest.conf.json" av := len(os.Args) - if av == 2 { + if av > 1 { confPath = os.Args[1] } // Load config - fmt.Printf("Loading config file: %s ... ", confPath) + fmt.Printf("Loading Tester configuration file: %s ... ", confPath) configFile, err := os.Open(confPath) if err != nil { - fmt.Printf("\nCan't open config file: %s\n", err) + fmt.Printf("\nCan't open Tester configuration file: %s\n", err) return false } dec := json.NewDecoder(configFile) - if err = dec.Decode(&m); err != nil { - fmt.Printf("\nUnable to deserialize config file: %s\n", err) + if err = dec.Decode(&t); err != nil { + fmt.Printf("\nUnable to deserialize Tester configuration file: %s\n", err) return false } - fmt.Println("Configuration file successfully loaded") + fmt.Println("Tester configuration file successfully loaded") return true } diff --git a/test/src/db.go b/test/src/db.go new file mode 100644 index 0000000..fd91705 --- /dev/null +++ b/test/src/db.go @@ -0,0 +1,61 @@ +// Copyright 2016 Etix Labs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "database/sql" + "fmt" + "strconv" + + _ "github.com/go-sql-driver/mysql" +) + +// MysqlDB contains the MySQL configuration +type MysqlDB struct { + Host string `json:"host"` + Port int `json:"port"` + User string `json:"user"` + Password string `json:"password"` + DbName string `json:"db_name"` +} + +func (t *Tester) dropDB() bool { + dsn := t.DB.User + ":" + t.DB.Password + "@" + "tcp(" + t.DB.Host + ":" + strconv.Itoa(t.DB.Port) + ")/" + t.DB.DbName + "?charset=utf8" + db, err := sql.Open("mysql", dsn) + if err != nil { + fmt.Println(err) + } + defer db.Close() + q := "DROP DATABASE " + t.DB.DbName + ";" + _, err = db.Exec(q) + if err != nil { + fmt.Println(err) + } + fmt.Println("------ Dropped CCTV Database -------") + return true +} + +func (t *Tester) configureDatabase(DataBase *MysqlDB) bool { + var db MysqlDB + + db.Host = t.Cameradar.DbHost + db.Port = t.Cameradar.DbPort + db.User = t.Cameradar.DbUser + db.Password = t.Cameradar.DbPassword + db.DbName = t.Cameradar.DbName + + *DataBase = db + return true +} diff --git a/test/src/logReader.go b/test/src/logReader.go new file mode 100644 index 0000000..2808c53 --- /dev/null +++ b/test/src/logReader.go @@ -0,0 +1,42 @@ +// Copyright 2016 Etix Labs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "bufio" + "fmt" + "io" +) + +// Launch it via goroutine +// Start read log of service +func readLog(service *Service, reader io.ReadCloser) { + scanner := bufio.NewScanner(reader) + for scanner.Scan() { + str := scanner.Text() + if service.Console { + fmt.Printf("[%s] %s\n", service.Path, str) + } + fmt.Printf("%s\n", str) + service.Mutex.Lock() + service.Logs = append(service.Logs, str) + service.Mutex.Unlock() + } + if err := scanner.Err(); err != nil { + fmt.Printf("[%s] Service failed: %s\n", service.Path, err) + } + fmt.Printf("Logger of service: [%s] stopped\n", service.Path) + service.Active = false +} diff --git a/test/src/main.go b/test/src/main.go index 0ab4db5..e4e0ec6 100644 --- a/test/src/main.go +++ b/test/src/main.go @@ -1,3 +1,17 @@ +// Copyright 2016 Etix Labs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package main import ( @@ -5,24 +19,24 @@ import ( ) func main() { - manager := new(manager) - defer manager.Stop() + Tester := new(Tester) + defer Tester.Stop() // Parse conf (streams should already be launched by Jenkins) fmt.Println("--- Initializing Cameradar Test Tool ... ---") - if !manager.Init() { + if !Tester.Init() { fmt.Println("-> Cameradar Test Tool initialization FAILED") return } // Run tests - if !manager.Run() { + if !Tester.Run() { fmt.Println("-> Cameradar Test Tool FAILED") } // Write results fmt.Println("--- Writing results... ---") - if !manager.WriteResults(*(manager.Result), manager.Config.Output) { + if !Tester.WriteResults(*(Tester.Result), Tester.Output) { fmt.Println("-> Write results FAILED") return } diff --git a/test/src/result.go b/test/src/result.go new file mode 100644 index 0000000..eaa17d9 --- /dev/null +++ b/test/src/result.go @@ -0,0 +1,91 @@ +// Copyright 2016 Etix Labs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "encoding/json" + "errors" + "fmt" + "os" +) + +// Result contains the data of a Cameradar result, plus an error field in order to add error messages to the JUnit report +type Result struct { + Address string `json:"address"` + IDsFound bool `json:"ids_found"` + PathFound bool `json:"path_found"` + Password string `json:"password"` + Port int `json:"port"` + Route string `json:"route"` + ServiceName string `json:"service_name"` + Protocol string `json:"protocol"` + State string `json:"state"` + Username string `json:"username"` + Valid bool `json:"valid"` + Thumb string `json:"thumbnail_path"` + err error // in case of a fail, add a message +} + +// Launch it via goroutine +// Start read log of service +func getResult(test *[]Result, resultPath string) bool { + // Load config + resultFile, err := os.Open(resultPath) + if err != nil { + fmt.Printf("\nCan't open result file: %s\n", err) + return false + } + dec := json.NewDecoder(resultFile) + if err = dec.Decode(&test); err != nil { + fmt.Printf("\nUnable to deserialize result file: %s\n", err) + return false + } + return true +} + +func isValid(e Result, r Result) bool { + if e.Username != r.Username { + e.err = errors.New(e.Address + " had a different username than " + r.Username) + return false + } + if e.Password != r.Password { + e.err = errors.New(e.Address + " had a different password than " + r.Password) + return false + } + if e.Port != r.Port { + e.err = errors.New(e.Address + " had a different port than expected") + return false + } + if e.Valid != r.Valid { + e.err = errors.New(e.Address + " had a different validity than expected") + return false + } + return true +} + +// Extend needs refacto +func Extend(slice []Result, element Result) []Result { + n := len(slice) + if n == cap(slice) { + // Slice is full; must grow. + // We double its size and add 1, so if the size is zero we still grow. + newSlice := make([]Result, len(slice), 2*len(slice)+1) + copy(newSlice, slice) + slice = newSlice + } + slice = slice[0 : n+1] + slice[n] = element + return slice +} diff --git a/test/src/service.go b/test/src/service.go index 5a301aa..481256f 100644 --- a/test/src/service.go +++ b/test/src/service.go @@ -1,3 +1,17 @@ +// Copyright 2016 Etix Labs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package main import ( @@ -9,24 +23,23 @@ import ( // Service needs refacto type Service struct { - Path string `json:"Path"` - Args string `json:"Args"` - Ports string `json:"Ports"` - IdsPath string `json:"IdsPath"` - RoutesPath string `json:"RoutesPath"` - DbHost string `json:"dbHost"` - DbPort int `json:"dbPort"` - DbUser string `json:"dbUser"` - DbPassword string `json:"dbPassword"` - DbName string `json:"dbName"` - ThumbPath string `json:"ThumbPath"` - Console bool `json:"Console"` + Path string `json:"path"` + Args string `json:"args"` + Ports string `json:"ports"` + IdsPath string `json:"ids_path"` + RoutesPath string `json:"routes_path"` + ThumbPath string `json:"thumb_path"` + DbHost string `json:"db_host"` + DbPort int `json:"db_port"` + DbUser string `json:"db_user"` + DbPassword string `json:"db_password"` + DbName string `json:"db_name"` + Console bool `json:"console"` Logs []string Active bool // Based on io.ReadCloser status - - Mutex sync.Mutex - cmd *exec.Cmd // Go handler of the service + Mutex sync.Mutex + cmd *exec.Cmd // Go handler of the service } func startService(service *Service) bool { @@ -77,7 +90,6 @@ func killService(service *Service) { fmt.Println(err) } - // Sending SIGABORT, more reliable for VLC sigAbort := []string{service.Path, "-s", "SIGABRT"} fmt.Printf("Executing: killall %s -s SIGABRT\n", service.Path) cmd = exec.Command("killall", sigAbort...) diff --git a/test/src/testCase.go b/test/src/testCase.go index 3154479..7fc11e3 100644 --- a/test/src/testCase.go +++ b/test/src/testCase.go @@ -1,173 +1,87 @@ +// Copyright 2016 Etix Labs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package main import ( - "encoding/json" + "errors" "fmt" - "io/ioutil" - "net" - "os" "sync" "time" ) -// MysqlDB needs refacto -type MysqlDB struct { - Host string `json:"host"` - Port int `json:"port"` - User string `json:"user"` - Password string `json:"password"` - DbName string `json:"db_name"` -} - -// CameradarConfig needs refacto -type CameradarConfig struct { - MysqlDB MysqlDB `json:"mysql_db"` - Subnets string `json:"subnets"` - Ports string `json:"ports"` - RtspURLFile string `json:"rtsp_url_file"` - RtspIdsFile string `json:"rtsp_ids_file"` - ThumbnailStoragePath string `json:"thumbnail_storage_path"` -} - -// Result needs refacto -type Result struct { - Address string `json:"address"` - Password string `json:"password"` - Port string `json:"port"` - Route string `json:"route"` - Username string `json:"username"` - Valid bool `json:"valid,omitempty"` - Thumb string `json:"thumbnail_path,omitempty"` -} - -// TestCase needs refacto -type TestCase struct { +// Test represents a test launched with Cameradar +type Test struct { expected []Result result []Result time time.Duration - ok bool } // Invoke the test // Wrap results in a TestResult object -func (m *manager) invokeTestCase(testCase *TestCase, wg *sync.WaitGroup) { +func (t *Tester) invokeTestCase(testCase *Test, wg *sync.WaitGroup) { startTime := time.Now() - if m.runTestCase(testCase) { - testCase.time = time.Since(startTime) - testCase.ok = true - fmt.Printf("Test OK in %.6fs\n", testCase.time.Seconds()) - } else { - testCase.time = time.Since(startTime) - testCase.ok = false - fmt.Printf("Test failed in %.6fs\n", testCase.time.Seconds()) - } + t.runTestCase(testCase) + testCase.time = time.Since(startTime) + fmt.Printf("Test OK in %.6fs\n", testCase.time.Seconds()) wg.Done() } -func (m *manager) runTestCase(test *TestCase) bool { - fmt.Printf("Test triggered\n") - - Cameradar := m.Config.Cameradar - startService(&Cameradar) - - for Cameradar.Active { - time.Sleep(5 * time.Millisecond) +// Checks all valid results that are supposed to match +// Adds them to the valid results and leave the failed +// ones in the expected slice +// +// Then, if the result did not match the expected but it was supposed to fail +// Add it to the valid results and remove it from the expected slice +func (t *Tester) runTestCase(test *Test) { + startService(&t.Cameradar) + for t.Cameradar.Active { + time.Sleep(25 * time.Millisecond) } - found := 0 - toFind := len(test.expected) + var validResults []Result - if getResult(&test.result, "result.json") { - // Check all valid resutls that are supposed to match - // Add them to the valid results and leave the failed - // ones in the expected slice + if getResult(&test.result, "/tmp/shared/result.json") { for _, r := range test.result { - index := 0 r.Valid = true - for _, e := range test.expected { - e.Thumb = r.Thumb - var err error - var addr []string - addr, err = net.LookupHost(e.Address) - e.Address = addr[0] - if e == r { - _, err = os.Stat(r.Thumb) - if err == nil { - fmt.Println("The result of ", r.Address, " is valid and the thumbnails were generated by Cameradar.") - found++ - validResults = Extend(validResults, r) + for index, e := range test.expected { + if e.Address == r.Address && isValid(e, r) { + // _, err := os.Stat(r.Thumb) + // if err == nil) { + fmt.Println("The result of ", r.Address, " is valid and the thumbnails were generated by Cameradar.") + validResults = Extend(validResults, r) + if len(test.expected) > 1 { test.expected = append(test.expected[:index], test.expected[index+1:]...) - break - } else { - fmt.Println("The result of ", r.Address, " seemed valid, but the thumbnails could not be generated by Cameradar.") } + break + // } else { + // e.err = error{"The result of " + e.Address + " seemed valid, but the thumbnails could not be generated by Cameradar : " + err.Error()} + // } } - index++ } } - index := 0 - // If the result did not match the expected but it was supposed to fail - // Add it to the valid results and remove it from the expected slice - for _, e := range test.expected { + for index, e := range test.expected { if !e.Valid { - found++ + fmt.Println("The result of", e.Address, "successfully failed.") validResults = Extend(validResults, e) - test.expected = append(test.expected[:index], test.expected[index+1:]...) - break + if len(test.expected) > 1 { + test.expected = append(test.expected[:index], test.expected[index+1:]...) + } + } else { + e.err = errors.New("The camera with the address " + e.Address + " was not found by cameradar") + fmt.Println("Should have been valid but was not found : ", e.Address) } - index++ - } - // If we found all the expected results, return true - if found == toFind { - return true } test.result = validResults } - return false -} - -func (m *manager) generateConfig(test []Result, DataBase *mysql_db) bool { - var config CameradarConfig - var db mysql_db - - db.Host = m.Config.Cameradar.DbHost - db.Port = m.Config.Cameradar.DbPort - db.User = m.Config.Cameradar.DbUser - db.Password = m.Config.Cameradar.DbPassword - db.Db_name = m.Config.Cameradar.DbName - - for _, t := range test { - if len(config.Subnets) > 0 { - config.Subnets += "," - } - config.Subnets += t.Address - } - config.Mysql_db = db - config.Ports = m.Config.Cameradar.Ports - config.Rtsp_url_file = m.Config.Cameradar.RoutesPath - config.Rtsp_ids_file = m.Config.Cameradar.IdsPath - config.Thumbnail_storage_path = m.Config.Cameradar.ThumbPath - b, _ := json.Marshal(config) - fmt.Println(string(b)) - err := ioutil.WriteFile("tmp_config", b, 0644) - if err != nil { - fmt.Println(err) - return false - } - *DataBase = db - return true -} - -// Extend needs refacto -func Extend(slice []Result, element Result) []Result { - n := len(slice) - if n == cap(slice) { - // Slice is full; must grow. - // We double its size and add 1, so if the size is zero we still grow. - newSlice := make([]Result, len(slice), 2*len(slice)+1) - copy(newSlice, slice) - slice = newSlice - } - slice = slice[0 : n+1] - slice[n] = element - return slice } diff --git a/test/src/tester.go b/test/src/tester.go new file mode 100644 index 0000000..9e680d7 --- /dev/null +++ b/test/src/tester.go @@ -0,0 +1,67 @@ +// Copyright 2016 Etix Labs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "fmt" + "sync" +) + +// Tester is the structure that will manage the whole testing +type Tester struct { + Cameradar Service `json:"cameradar"` + Output string + + Tests []Result + Result *Test + DB MysqlDB +} + +// Init gets the testing configuration and makes sure that no other Cameradar service is running at the moment +func (t *Tester) Init() bool { + fmt.Println("- Parsing") + if !t.parseConfig() { + return false + } + + fmt.Println("- Cleaning content") + killService(&t.Cameradar) + + return true +} + +// Run launches the tests that have been set up by the init method +func (t *Tester) Run() bool { + var wg sync.WaitGroup + + fmt.Println("\n- Launching all tests") + var newTest = new(Test) + newTest.expected = t.Tests + if t.configureDatabase(&t.DB) { + t.dropDB() + wg.Add(1) + go t.invokeTestCase(newTest, &wg) + t.Result = newTest + } + wg.Wait() + fmt.Println("All tests completed") + return true +} + +// Stop kills the service launched by the tester +func (t *Tester) Stop() bool { + killService(&t.Cameradar) + return true +} diff --git a/test/src/writeResult.go b/test/src/writeResult.go index e74ae09..a92b551 100644 --- a/test/src/writeResult.go +++ b/test/src/writeResult.go @@ -1,11 +1,26 @@ +// Copyright 2016 Etix Labs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package main import ( + "bytes" "encoding/xml" "fmt" "io" + "io/ioutil" "os" - "time" ) //////////////////////////////////////////////// @@ -13,21 +28,23 @@ import ( // JUnitTestSuites is a collection of JUnit test suites. type JUnitTestSuites struct { - XMLName xml.Name `xml:"testsuites"` - Suites []JUnitTestSuite + XMLName xml.Name `xml:"testsuites"` + TestSuites []JUnitTestSuite `xml:"testsuite"` } // JUnitTestSuite is a single JUnit test suite which may contain many // testcases. type JUnitTestSuite struct { - Tests int `xml:"tests,attr"` - Failures int `xml:"failures,attr"` - Time string `xml:"time,attr"` - TestCases []JUnitTestCase + XMLName xml.Name `xml:"testsuite"` + Tests int `xml:"tests,attr"` + Failures int `xml:"failures,attr"` + Time string `xml:"time,attr"` + TestCases []JUnitTestCase `xml:"testcase"` } // JUnitTestCase is a single test case with its result. type JUnitTestCase struct { + XMLName xml.Name `xml:"testcase"` Message string `xml:"message,attr"` Time string `xml:"time,attr"` Failure *JUnitFailure `xml:"failure,omitempty"` @@ -35,25 +52,25 @@ type JUnitTestCase struct { // JUnitFailure contains data related to a failed test. type JUnitFailure struct { - Message string `xml:"message,attr"` - Type string `xml:"type,attr"` - Contents string `xml:",chardata"` + XMLName xml.Name `xml:"failure"` + Message string `xml:"message,attr"` + Type string `xml:"type,attr"` + Contents string `xml:",chardata"` } -func (m *manager) WriteResults(result TestCase, output string) bool { +// WriteResults will output the results in the standard output as well as concatenate them in an XML JUnit report +func (t *Tester) WriteResults(result Test, output string) bool { fmt.Printf("Displaying results...\n") - // Write Console report - m.writeConsoleReport(result) + t.writeConsoleReport(result) - // Write XML report - // Open xml file, err := os.OpenFile(output, os.O_RDONLY|os.O_CREATE, 0644) if err != nil { fmt.Printf("Error opening XML: %s\n", err) return false } defer file.Close() - err = m.writeJUnitReportXML(result, file, output) + + err = t.writeJUnitReportXML(result, file, output) if err != nil { fmt.Printf("Error writing XML: %s\n", err) return false @@ -63,42 +80,47 @@ func (m *manager) WriteResults(result TestCase, output string) bool { } // Write tests results under JUnit format on w -func (m *manager) writeJUnitReportXML(result TestCase, r io.ReadWriter, output string) error { +func (t *Tester) writeJUnitReportXML(result Test, rw io.ReadWriter, output string) error { suites := JUnitTestSuites{} - dec := xml.NewDecoder(r) - if err := dec.Decode(&suites); err != nil { - fmt.Printf("\nUnable to deserialize XML log file: %s\n", err) + + buf, err := ioutil.ReadFile(output) + + dec := xml.NewDecoder(bytes.NewBufferString(string(buf))) + err = dec.Decode(&suites) + if err != nil { + fmt.Printf("\nUnable to deserialize %s file: %s\n", output, err) } + ts := JUnitTestSuite{ Tests: len(result.result) + len(result.expected), Failures: 0, Time: fmt.Sprintf("%.6f", result.time.Seconds()), TestCases: []JUnitTestCase{}, } - // Run throught all iterations - testCase := JUnitTestCase{ - Time: fmt.Sprintf("%.6f", result.time.Seconds()), - Failure: nil, + + for _, r := range result.result { + testCase := JUnitTestCase{ + Time: fmt.Sprintf("%.6f", result.time.Seconds()), + Failure: nil, + } + testCase.Message = "The stream " + r.Address + " could be accessed and its thumbnail was properly generated" + ts.TestCases = append(ts.TestCases, testCase) } - if len(result.result) > 0 { - testCase.Message = "These streams matched what we expected:" - } - for _, success := range result.result { - testCase.Message += " " + success.Address - } - if !result.ok { - testCase.Failure = &JUnitFailure{ - Message: "These streams did not match what we expected:", - Type: "", + + for _, e := range result.expected { + testCase := JUnitTestCase{ + Time: fmt.Sprintf("%.6f", result.time.Seconds()), + Failure: nil, + } + if e.err != nil { + testCase.Failure = &JUnitFailure{ + Message: e.err.Error(), + Type: "", + } } } - for _, fail := range result.expected { - ts.Failures++ - testCase.Failure.Message += " " + fail.Address - } - ts.TestCases = append(ts.TestCases, testCase) - suites.Suites = append(suites.Suites, ts) + suites.TestSuites = append(suites.TestSuites, ts) // Fix indent bytes, err := xml.MarshalIndent(suites, "", "\t") if err != nil { @@ -112,35 +134,16 @@ func (m *manager) writeJUnitReportXML(result TestCase, r io.ReadWriter, output s } writer := io.Writer(w) writer.Write(bytes) - return nil } -func (m *manager) writeConsoleReport(result TestCase) bool { - min := 50 * time.Hour - max := 0 * time.Second - total := 0 * time.Second - successCount := 0 - failureCount := 0 - if result.ok { - successCount++ - total += result.time - if result.time < min { - min = result.time - } - if result.time > max { - max = result.time - } - } else { - failureCount++ - } +func (t *Tester) writeConsoleReport(result Test) bool { + successCount := len(result.result) + failureCount := len(result.expected) fmt.Println("--- Test summary ---") if successCount > 0 { fmt.Printf("Results: %d/%d (%d%%)\n", successCount, successCount+failureCount, successCount*100/(successCount+failureCount)) - fmt.Printf("Total time: %.6fs\n", total.Seconds()) - fmt.Printf("Average time: %.6fs\n", total.Seconds()/float64(successCount)) - fmt.Printf("Min time: %.6fs\n", min.Seconds()) - fmt.Printf("Max time: %.6fs\n", max.Seconds()) + fmt.Printf("Time: %.6fs\n", result.time.Seconds()) } else { fmt.Printf("No test in success\n") }