diff --git a/test/Dockerfile b/test/Dockerfile new file mode 100644 index 0000000..c624e89 --- /dev/null +++ b/test/Dockerfile @@ -0,0 +1,43 @@ +FROM ubuntu:15.10 + +MAINTAINER brendan.leglaunec@etixgroup.com + +ENV LD_LIBRARY_PATH="/cctv/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 PATH=$PATH:/go/bin +ENV PATH=$PATH:/usr/local/go/bin +ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib + +# needed for cameradar +RUN apt-get update && apt-get install -y \ + nmap \ + libmysqlclient18 \ + ffmpeg \ + mysql-client \ + libgstreamer1.0-dev \ + gstreamer1.0-plugins-base \ + gstreamer1.0-plugins-good \ + libcurl4-openssl-dev + +RUN apt-get install -y psmisc + +ADD cctv_*_Debug_Linux.tar.gz / +RUN mv cctv_*_Debug_Linux cctv + +# create cameradaratest folder in go src path +RUN mkdir -p /go/src/cameradartest +ADD ./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 +CMD ["/run.sh"] diff --git a/test/Dockerfile-camera b/test/Dockerfile-camera new file mode 100644 index 0000000..4488e0b --- /dev/null +++ b/test/Dockerfile-camera @@ -0,0 +1,14 @@ +FROM ubuntu:15.10 +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 + +ADD ./docker/screen.png /vlc/screen.png +COPY ./docker/run_vlc.sh /start.sh +COPY ./etix_rtsp_server /etix_rtsp_server + +EXPOSE 8554 diff --git a/test/conf/cameradar.conf.json b/test/conf/cameradar.conf.json new file mode 100644 index 0000000..53187c7 --- /dev/null +++ b/test/conf/cameradar.conf.json @@ -0,0 +1,14 @@ +{ + "mysql_db" : { + "host" : "0.0.0.0", + "port" : 3306, + "user": "root", + "password": "root", + "db_name": "cctv" + }, + "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 diff --git a/test/conf/cameratest.conf.json b/test/conf/cameratest.conf.json new file mode 100644 index 0000000..ed08a0d --- /dev/null +++ b/test/conf/cameratest.conf.json @@ -0,0 +1,44 @@ +{ + "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 + }, + "Tests" : [ + { + "address" : "127.0.0.1", + "password" : "", + "port" : "8554", + "route" : "live.sdp", + "username" : "", + "valid" : true + }, + { + "address" : "172.16.100.11", + "password" : "", + "port" : "553", + "route" : "live.sdp", + "username" : "admin", + "valid" : false + }, + { + "address" : "172.16.100.13", + "password" : "", + "port" : "554", + "route" : "live.sdp", + "username" : "admin", + "valid" : true + } + ] +} diff --git a/test/conf/ids.json b/test/conf/ids.json new file mode 100644 index 0000000..046b60c --- /dev/null +++ b/test/conf/ids.json @@ -0,0 +1,31 @@ +{ + "username": [ + "", + "admin", + "Admin", + "root", + "supervisor", + "ubnt" + ], + "password" : [ + "", + "admin", + "9999", + "123456", + "pass", + "camera", + "1234", + "12345", + "fliradmin", + "system", + "jvc", + "meinsm", + "root", + "4321", + "1111111", + "password", + "ikwd", + "supervisor", + "ubnt" + ] +} \ No newline at end of file diff --git a/test/conf/url.json b/test/conf/url.json new file mode 100644 index 0000000..31d250c --- /dev/null +++ b/test/conf/url.json @@ -0,0 +1,77 @@ +{ + "urls" : [ + "/", + "/1.AMP", + "/1/stream1", + "/CAM_ID.password.mp2", + "/GetData.cgi", + "/MediaInput/h264", + "/MediaInput/mpeg4", + "/VideoInput/1/h264/1", + "/access_code", + "/access_name_for_stream_1_to_5", + "/av0_0", + "/av2", + "/avn=2", + "/axis-media/media.amp", + "/cam", + "/cam0_0", + "/cam0_1", + "/cam1/h264", + "/cam1/h264/multicast", + "/cam1/mjpeg", + "/cam1/mpeg4", + "/camera.stm", + "/ch0", + "/ch001.sdp", + "/ch0_unicast_firststream", + "/ch0_unicast_secondstream", + "/channel1", + "/h264", + "/h264/media.amp", + "/image.mpg", + "/img/media.sav", + "/img/video.asf", + "/img/video.sav", + "/ioImage/1", + "/ipcam.sdp", + "/ipcam_h264.sdp", + "/live.sdp", + "/live/h264", + "/live/mpeg4", + "/live_mpeg4.sdp", + "/livestream", + "/livestream/", + "/media/media.amp", + "/media/video1", + "/mjpeg/media.smp", + "/mp4", + "/mpeg4", + "/mpeg4/1/media.amp", + "/mpeg4/media.amp", + "/mpeg4/media.smp", + "/mpeg4unicast", + "/mpg4/rtsp.amp", + "/multicaststream", + "/now.mp4", + "/nph-h264.cgi", + "/nphMpeg4/g726-640x", + "/nphMpeg4/g726-640x480", + "/nphMpeg4/nil-320x240", + "/play1.sdp", + "/play2.sdp", + "/rtpvideo1.sdp", + "/rtsp_tunnel", + "/rtsph264", + "/stream1", + "/user.pin.mp2", + "/user_defined", + "/video", + "/video.3gp", + "/video.mp4", + "/video1", + "/video1+audio1", + "/vis", + "/wfov" + ] +} diff --git a/test/docker/cameratest.conf.tmpl.json b/test/docker/cameratest.conf.tmpl.json new file mode 100644 index 0000000..de8eca7 --- /dev/null +++ b/test/docker/cameratest.conf.tmpl.json @@ -0,0 +1,18 @@ +{ + "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 + }, + "Tests" : __CAMERAS__ +} \ No newline at end of file diff --git a/test/docker/conf/ids.json b/test/docker/conf/ids.json new file mode 100644 index 0000000..046b60c --- /dev/null +++ b/test/docker/conf/ids.json @@ -0,0 +1,31 @@ +{ + "username": [ + "", + "admin", + "Admin", + "root", + "supervisor", + "ubnt" + ], + "password" : [ + "", + "admin", + "9999", + "123456", + "pass", + "camera", + "1234", + "12345", + "fliradmin", + "system", + "jvc", + "meinsm", + "root", + "4321", + "1111111", + "password", + "ikwd", + "supervisor", + "ubnt" + ] +} \ No newline at end of file diff --git a/test/docker/conf/url.json b/test/docker/conf/url.json new file mode 100644 index 0000000..31d250c --- /dev/null +++ b/test/docker/conf/url.json @@ -0,0 +1,77 @@ +{ + "urls" : [ + "/", + "/1.AMP", + "/1/stream1", + "/CAM_ID.password.mp2", + "/GetData.cgi", + "/MediaInput/h264", + "/MediaInput/mpeg4", + "/VideoInput/1/h264/1", + "/access_code", + "/access_name_for_stream_1_to_5", + "/av0_0", + "/av2", + "/avn=2", + "/axis-media/media.amp", + "/cam", + "/cam0_0", + "/cam0_1", + "/cam1/h264", + "/cam1/h264/multicast", + "/cam1/mjpeg", + "/cam1/mpeg4", + "/camera.stm", + "/ch0", + "/ch001.sdp", + "/ch0_unicast_firststream", + "/ch0_unicast_secondstream", + "/channel1", + "/h264", + "/h264/media.amp", + "/image.mpg", + "/img/media.sav", + "/img/video.asf", + "/img/video.sav", + "/ioImage/1", + "/ipcam.sdp", + "/ipcam_h264.sdp", + "/live.sdp", + "/live/h264", + "/live/mpeg4", + "/live_mpeg4.sdp", + "/livestream", + "/livestream/", + "/media/media.amp", + "/media/video1", + "/mjpeg/media.smp", + "/mp4", + "/mpeg4", + "/mpeg4/1/media.amp", + "/mpeg4/media.amp", + "/mpeg4/media.smp", + "/mpeg4unicast", + "/mpg4/rtsp.amp", + "/multicaststream", + "/now.mp4", + "/nph-h264.cgi", + "/nphMpeg4/g726-640x", + "/nphMpeg4/g726-640x480", + "/nphMpeg4/nil-320x240", + "/play1.sdp", + "/play2.sdp", + "/rtpvideo1.sdp", + "/rtsp_tunnel", + "/rtsph264", + "/stream1", + "/user.pin.mp2", + "/user_defined", + "/video", + "/video.3gp", + "/video.mp4", + "/video1", + "/video1+audio1", + "/vis", + "/wfov" + ] +} diff --git a/test/docker/gen_cameras.sh b/test/docker/gen_cameras.sh new file mode 100755 index 0000000..a59a835 --- /dev/null +++ b/test/docker/gen_cameras.sh @@ -0,0 +1,107 @@ +#!/bin/bash + +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') +cams_name_pattern="fake_camera_" + +# json generation variable only +json="[\n" +first=true +# $1 = adress, $2 = port, $3 = path, $4 = usernam $5 = password, $6 = valid +function make_json { + if [ "$first" = true ] ; then first=false + else json="$json,\n"; fi + json="$json{" + json="$json\"address\":\"$1\"," + json="$json\"port\":\"$2\"," + json="$json\"route\":\"$3\"," + json="$json\"username\":\"$4\"," + json="$json\"password\":\"$5\"," + json="$json\"valid\":$6" + json="$json}" +} + +# $1 = configuration template path +function generate_conf { + echo "generate configuration" + sed s#__CAMERAS__#$json#g $1 > cameradartest.conf.json +} + +# $1 = numbers of cameras to generate +function start { + # Seed random generator + RANDOM=$(date +%s) + + # start cameras + for (( i=1; i<=$1; i++ )); do + name="$cams_name_pattern$i" + # random conf + conf_idx=$(($RANDOM % ${#ports[@]})) + + # get conf variables + port=${ports[$conf_idx]} + user=${users[$conf_idx]} + passw=${passwords[$conf_idx]} + route=${routes[$conf_idx]} + is_valid=true + + # 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 + done + + # finalize json + json="$json]" + echo "$json" +} + +function stop { + # if no cameras containers are started just exit + camera_count="`docker ps -a -q --filter="name=$cams_name_pattern" | wc -l`" + if [ "$camera_count" == "0" ]; then + echo "error: no cameras started"; exit 1 + fi + + echo "stopping and removing $camera_count containers" + # docker stop $(docker ps -a -q --filter="name=$cams_name_pattern") + docker rm -f $(docker ps -a -q --filter="name=$cams_name_pattern") > /dev/null +} + +# need first argument at least +if [ "$1" == "" ]; then + echo "error: invalid number of argument" + exit 1 +fi +case $1 in +"start") + # check if the argument is a number. + re='^[0-9]+$' + if ! [[ $2 =~ $re ]] ; then + echo "error: argument is not a number"; exit 1 + fi + if [[ "$3" == "" ]] ; then + echo "error: missing path to the configuration file template"; exit 1 + fi + echo "starting $2 cameras" + start $2 + generate_conf $3 + ;; +"stop") + echo "stopping all cameras tests" + stop + ;; +"help") + echo "./gen_cameras.sh start CAMS_NB - start CAMS_NB cameras" + echo " stop - stop all started cameras" + echo " help - display this help" + exit 0 + ;; +*) + echo "invalid test name" + exit 1 + ;; +esac diff --git a/test/docker/run_cameradartest.sh b/test/docker/run_cameradartest.sh new file mode 100755 index 0000000..59b2823 --- /dev/null +++ b/test/docker/run_cameradartest.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +while ! mysqladmin ping -h"mysql_cameradar" -P3306 --silent; do + sleep 1 +done + +ls -alhR /conf +cat /etc/hosts + +# build +go build +# run test +./cameradartest /tmp/tests/cameradartest.conf.json + +cp cameratest.log.xml /tmp/tests/ \ No newline at end of file diff --git a/test/docker/run_vlc.sh b/test/docker/run_vlc.sh new file mode 100755 index 0000000..1a9f4a7 --- /dev/null +++ b/test/docker/run_vlc.sh @@ -0,0 +1,16 @@ +#!/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/docker/screen.png b/test/docker/screen.png new file mode 100644 index 0000000..a4cc88b Binary files /dev/null and b/test/docker/screen.png differ diff --git a/test/etix_rtsp_server b/test/etix_rtsp_server new file mode 100755 index 0000000..34c8b12 Binary files /dev/null and b/test/etix_rtsp_server differ diff --git a/test/run.sh b/test/run.sh new file mode 100755 index 0000000..bef1e33 --- /dev/null +++ b/test/run.sh @@ -0,0 +1,58 @@ +#!/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 + (echo "no debug package in the current folder"; exit 137) + exit 137 +fi + +cams_name_pattern="fake_camera_" +cmd="" + +function make_docker_command { + cmd="docker run --rm" + + # start cameras + for (( i=1; i<=$1; i++ )); do + name="$cams_name_pattern$i" + cmd="$cmd --link=\"$name\"" + done + + # add mysql libk + cmd="$cmd --link=\"mysql_cameradar\"" + # 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 cameradartest" +} + +function start_test { + make_docker_command $1 + ./docker/gen_cameras.sh start $1 ./docker/cameratest.conf.tmpl.json + eval $cmd + ./docker/gen_cameras.sh stop +} + +# build images +echo "building docker images" +# building fake-camera container +docker build -f Dockerfile-camera -t fake-camera . + +# building cameradartest image +docker build -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 + +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 diff --git a/test/src/configuration.go b/test/src/configuration.go new file mode 100644 index 0000000..4040176 --- /dev/null +++ b/test/src/configuration.go @@ -0,0 +1,32 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" +) + +func (m *manager) parseConfig() bool { + // Get config file path + confPath := "conf/cameratest.conf.json" + av := len(os.Args) + if av == 2 { + confPath = os.Args[1] + } + + // Load config + fmt.Printf("Loading config file: %s ... ", confPath) + configFile, err := os.Open(confPath) + if err != nil { + fmt.Printf("\nCan't open config 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) + return false + } + fmt.Println("Configuration file successfully loaded\n") + + return true +} diff --git a/test/src/dropDB.go b/test/src/dropDB.go new file mode 100644 index 0000000..35f9935 --- /dev/null +++ b/test/src/dropDB.go @@ -0,0 +1,24 @@ +package main + +import ( + "database/sql" + _ "github.com/go-sql-driver/mysql" + "fmt" + "strconv" +) + +func (m *manager) dropDB() bool { + dsn := m.DB.User + ":" + m.DB.Password + "@" + "tcp(" + m.DB.Host + ":" + strconv.Itoa(m.DB.Port) + ")/" + m.DB.Db_name + "?charset=utf8" + db, err := sql.Open("mysql", dsn) + if err != nil { + fmt.Println(err) + } + defer db.Close() + q := "DROP DATABASE cctv;" + _, err = db.Exec(q) + if err != nil { + fmt.Println(err) + } + fmt.Println("------ Dropped CCTV Database -------") + return true +} \ No newline at end of file diff --git a/test/src/getResult.go b/test/src/getResult.go new file mode 100644 index 0000000..7640691 --- /dev/null +++ b/test/src/getResult.go @@ -0,0 +1,24 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" +) + +// 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 +} diff --git a/test/src/log.go b/test/src/log.go new file mode 100644 index 0000000..6124835 --- /dev/null +++ b/test/src/log.go @@ -0,0 +1,28 @@ +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 new file mode 100644 index 0000000..0ab4db5 --- /dev/null +++ b/test/src/main.go @@ -0,0 +1,30 @@ +package main + +import ( + "fmt" +) + +func main() { + manager := new(manager) + defer manager.Stop() + + // Parse conf (streams should already be launched by Jenkins) + fmt.Println("--- Initializing Cameradar Test Tool ... ---") + if !manager.Init() { + fmt.Println("-> Cameradar Test Tool initialization FAILED") + return + } + + // Run tests + if !manager.Run() { + fmt.Println("-> Cameradar Test Tool FAILED") + } + + // Write results + fmt.Println("--- Writing results... ---") + if !manager.WriteResults(*(manager.Result), manager.Config.Output) { + fmt.Println("-> Write results FAILED") + return + } + fmt.Println("--- Writing results done ---") +} diff --git a/test/src/manager.go b/test/src/manager.go new file mode 100644 index 0000000..13d33ff --- /dev/null +++ b/test/src/manager.go @@ -0,0 +1,54 @@ +package main + +import ( + "fmt" + "sync" +) + +type manager struct { + Config + + Tests []Result + Result *TestCase + DB mysql_db +} + +type Config struct { + Cameradar Service `json:"Cameradar"` + + Output string +} + +func (m *manager) Init() bool { + fmt.Println("- Parsing") + if !m.parseConfig() { + return false + } + + fmt.Println("- Cleaning content") + killService(&m.Config.Cameradar) + + return true +} + +func (m *manager) Run() bool { + var wg sync.WaitGroup + + fmt.Println("\n- Launching all tests") + var newTest = new(TestCase) + newTest.expected = m.Tests + if (m.generateConfig(m.Tests, &m.DB)) { + m.dropDB() + wg.Add(1) + go m.invokeTestCase(newTest, &wg) + m.Result = newTest + } + wg.Wait() + fmt.Printf("All tests completed\n") + return true +} + +func (m *manager) Stop() bool { + killService(&m.Config.Cameradar) + return true +} \ No newline at end of file diff --git a/test/src/service.go b/test/src/service.go new file mode 100644 index 0000000..0140ab8 --- /dev/null +++ b/test/src/service.go @@ -0,0 +1,87 @@ +package main + +import ( + "fmt" + "os/exec" + "strings" + "sync" +) + +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"` + + Logs []string + Active bool // Based on io.ReadCloser status + + Mutex sync.Mutex + cmd *exec.Cmd // Go handler of the service +} + +func startService(service *Service) bool { + // Launch service + args := strings.Fields(service.Args) + service.cmd = exec.Command(service.Path, args...) + + handler, err := service.cmd.StdoutPipe() + if err != nil { + fmt.Println(err) + return false + } + errHandler, err := service.cmd.StderrPipe() + if err != nil { + fmt.Println(err) + return false + } + // Launch + err = service.cmd.Start() + if err != nil { + fmt.Println(err) + return false + } + + fmt.Printf("Service: [%s] started\n", service.Path) + service.Active = true + + // Read service logs and update service status + // Set pipes + go readLog(service, handler) + go readLog(service, errHandler) + + return true +} + +// Stop only specified service instance +func stopService(service *Service) { + service.cmd.Process.Kill() +} + +// Kill all instances of specified service +func killService(service *Service) { + // Sending SIGTERM + fmt.Printf("Executing: killall %s\n", service.Path) + cmd := exec.Command("killall", service.Path) + err := cmd.Run() + if err != nil { + 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...) + err = cmd.Run() + if err != nil { + fmt.Println(err) + } +} diff --git a/test/src/testCase.go b/test/src/testCase.go new file mode 100644 index 0000000..6f1e83f --- /dev/null +++ b/test/src/testCase.go @@ -0,0 +1,168 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "sync" + "time" + "net" +) + +type mysql_db struct { + Host string `json:"host"` + Port int `json:"port"` + User string `json:"user"` + Password string `json:"password"` + Db_name string `json:"db_name"` +} + +type CameradarConfig struct { + Mysql_db mysql_db `json:"mysql_db"` + Subnets string `json:"subnets"` + Ports string `json:"ports"` + Rtsp_url_file string `json:"rtsp_url_file"` + Rtsp_ids_file string `json:"rtsp_ids_file"` + Thumbnail_storage_path string `json:"thumbnail_storage_path"` +} + +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"` +} + +type TestCase 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) { + 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()) + } + 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) + } + 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 + 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) + 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.") + } + } + 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 { + if !e.Valid { + found++ + validResults = Extend(validResults, e) + test.expected = append(test.expected[:index], test.expected[index+1:]...) + break + } + 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 +} + +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/writeResult.go b/test/src/writeResult.go new file mode 100644 index 0000000..9f2db3e --- /dev/null +++ b/test/src/writeResult.go @@ -0,0 +1,146 @@ +package main + +import ( + "encoding/xml" + "fmt" + "io" + "os" + "time" +) + +//////////////////////////////////////////////// +// Data declarations + +// JUnitTestSuites is a collection of JUnit test suites. +type JUnitTestSuites struct { + XMLName xml.Name `xml:"testsuites"` + Suites []JUnitTestSuite +} + +// 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 +} + +// JUnitTestCase is a single test case with its result. +type JUnitTestCase struct { + Message string `xml:"message,attr"` + Time string `xml:"time,attr"` + Failure *JUnitFailure `xml:"failure,omitempty"` +} + +// 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"` +} + +func (m *manager) WriteResults(result TestCase, output string) bool { + fmt.Printf("Displaying results...\n") + // Write Console report + m.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) + if err != nil { + fmt.Printf("Error writing XML: %s\n", err) + return false + } + fmt.Printf("-> JUnit XML report written: %s\n", output) + return true +} + +// Write tests results under JUnit format on w +func (m *manager) writeJUnitReportXML(result TestCase, r 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) + } + 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, + } + 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 _, fail := range result.expected { + ts.Failures++ + testCase.Failure.Message += " " + fail.Address + } + ts.TestCases = append(ts.TestCases, testCase) + + suites.Suites = append(suites.Suites, ts) + // Fix indent + bytes, err := xml.MarshalIndent(suites, "", "\t") + if err != nil { + return err + } + // Write in param stream + + w, err := os.OpenFile(output, os.O_WRONLY|os.O_TRUNC, 0644) + 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++ + } + fmt.Println("--- Test summary ---\n") + 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()) + } else { + fmt.Printf("No test in success\n") + } + + return true +}