From fd30291ebc9ab3459e1396ca2ec204820e798a65 Mon Sep 17 00:00:00 2001 From: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com> Date: Thu, 18 Aug 2022 12:07:46 -0300 Subject: [PATCH] tests: add dummy plugin for daemon unit tests The TestSuite plugin contains audio, video and chat handlers to support toggling/untoogling tests. GitLab: https://git.jami.net/savoirfairelinux/jami-daemon/-/issues/745 Change-Id: I45f55127443a5c904b89066b6fe1ca102d37e361 --- AutoAnswer/BotPeerChatSubscriber.h | 2 - TestSuite/AudioHandlerTester.cpp | 89 ++++++++ TestSuite/AudioHandlerTester.h | 50 +++++ TestSuite/AudioSubscriberTester.cpp | 65 ++++++ TestSuite/AudioSubscriberTester.h | 49 +++++ TestSuite/CMakeLists.txt | 91 ++++++++ TestSuite/ChatHandlerTester.cpp | 76 +++++++ TestSuite/ChatHandlerTester.h | 56 +++++ TestSuite/ChatSubscriberTester.cpp | 60 ++++++ TestSuite/ChatSubscriberTester.h | 48 +++++ TestSuite/VideoHandlerTester.cpp | 88 ++++++++ TestSuite/VideoHandlerTester.h | 50 +++++ TestSuite/VideoSubscriberTester.cpp | 66 ++++++ TestSuite/VideoSubscriberTester.h | 46 ++++ TestSuite/build.sh | 267 ++++++++++++++++++++++++ TestSuite/data/accountpreferences.json | 9 + TestSuite/data/icon.png | Bin 0 -> 31362 bytes TestSuite/data/icon.svg | 30 +++ TestSuite/data/locale/TestSuite_en.json | 8 + TestSuite/data/locale/TestSuite_fr.json | 8 + TestSuite/data/preferences.json | 9 + TestSuite/main.cpp | 86 ++++++++ TestSuite/manifest.json | 6 + TestSuite/package.json | 19 ++ 24 files changed, 1276 insertions(+), 2 deletions(-) create mode 100644 TestSuite/AudioHandlerTester.cpp create mode 100644 TestSuite/AudioHandlerTester.h create mode 100644 TestSuite/AudioSubscriberTester.cpp create mode 100644 TestSuite/AudioSubscriberTester.h create mode 100644 TestSuite/CMakeLists.txt create mode 100644 TestSuite/ChatHandlerTester.cpp create mode 100644 TestSuite/ChatHandlerTester.h create mode 100644 TestSuite/ChatSubscriberTester.cpp create mode 100644 TestSuite/ChatSubscriberTester.h create mode 100644 TestSuite/VideoHandlerTester.cpp create mode 100644 TestSuite/VideoHandlerTester.h create mode 100644 TestSuite/VideoSubscriberTester.cpp create mode 100644 TestSuite/VideoSubscriberTester.h create mode 100755 TestSuite/build.sh create mode 100644 TestSuite/data/accountpreferences.json create mode 100644 TestSuite/data/icon.png create mode 100644 TestSuite/data/icon.svg create mode 100644 TestSuite/data/locale/TestSuite_en.json create mode 100644 TestSuite/data/locale/TestSuite_fr.json create mode 100644 TestSuite/data/preferences.json create mode 100644 TestSuite/main.cpp create mode 100644 TestSuite/manifest.json create mode 100644 TestSuite/package.json diff --git a/AutoAnswer/BotPeerChatSubscriber.h b/AutoAnswer/BotPeerChatSubscriber.h index cac463b..70e1964 100644 --- a/AutoAnswer/BotPeerChatSubscriber.h +++ b/AutoAnswer/BotPeerChatSubscriber.h @@ -46,8 +46,6 @@ public: std::string& peerId, std::map<std::string, std::string>& sendMsg, bool swarm); - void setAnswer(const std::string& textAnswer); - void setInText(const std::string& inText); protected: // Observer pattern diff --git a/TestSuite/AudioHandlerTester.cpp b/TestSuite/AudioHandlerTester.cpp new file mode 100644 index 0000000..4b6b73a --- /dev/null +++ b/TestSuite/AudioHandlerTester.cpp @@ -0,0 +1,89 @@ +/** + * Copyright (C) 2020-2021 Savoir-faire Linux Inc. + * + * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "AudioHandlerTester.h" + +#include "pluglog.h" +#include <string_view> + +const char sep = separator(); +const std::string TAG = "AudioHandlerTester"; + +#define NAME "AudioHandlerTester" + +namespace jami { + +AudioHandlerTester::AudioHandlerTester(std::string&& datapath) + : datapath_ {datapath} +{ + setId(datapath_); + mAS = std::make_shared<AudioSubscriberTester>(); +} + +void +AudioHandlerTester::notifyAVFrameSubject(const StreamData& data, jami::avSubjectPtr subject) +{ + if (!mAS) + return; + std::ostringstream oss; + std::string_view direction = data.direction ? "Receive" : "Preview"; + oss << "NEW SUBJECT: [" << data.id << "," << direction << "]" << std::endl; + bool preferredStreamDirection = false; // false for output; true for input + oss << "preferredStreamDirection " << preferredStreamDirection << std::endl; + if (data.type == StreamType::audio && !data.direction + && data.direction == preferredStreamDirection) { + subject->attach(mAS.get()); // your image + oss << "got my sent audio attached" << std::endl; + attached_ = '1'; + } else if (data.type == StreamType::audio && data.direction + && data.direction == preferredStreamDirection) { + subject->attach(mAS.get()); // the image you receive from others on the call + oss << "got received audio attached" << std::endl; + attached_ = '1'; + } + + Plog::log(Plog::LogPriority::INFO, TAG, oss.str()); +} + +std::map<std::string, std::string> +AudioHandlerTester::getCallMediaHandlerDetails() +{ + return {{"name", NAME}, + {"iconPath", datapath_ + sep + "icon.svg"}, + {"pluginId", id()}, + {"attached", attached_}, + {"dataType", "0"}}; +} + +void +AudioHandlerTester::detach() +{ + attached_ = '0'; + mAS->detach(); +} + +AudioHandlerTester::~AudioHandlerTester() +{ + std::ostringstream oss; + oss << " ~AudioHandlerTester from TestSuite Plugin" << std::endl; + Plog::log(Plog::LogPriority::INFO, TAG, oss.str()); + detach(); +} +} // namespace jami diff --git a/TestSuite/AudioHandlerTester.h b/TestSuite/AudioHandlerTester.h new file mode 100644 index 0000000..c31a430 --- /dev/null +++ b/TestSuite/AudioHandlerTester.h @@ -0,0 +1,50 @@ +/** + * Copyright (C) 2020-2021 Savoir-faire Linux Inc. + * + * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#pragma once + +#include "AudioSubscriberTester.h" + +#include "plugin/jamiplugin.h" +#include "plugin/mediahandler.h" + +using avSubjectPtr = std::weak_ptr<jami::Observable<AVFrame*>>; + +namespace jami { + +class AudioHandlerTester : public CallMediaHandler +{ +public: + AudioHandlerTester(std::string&& dataPath); + ~AudioHandlerTester(); + + virtual void notifyAVFrameSubject(const StreamData& data, avSubjectPtr subject) override; + virtual std::map<std::string, std::string> getCallMediaHandlerDetails() override; + virtual void detach() override; + virtual void setPreferenceAttribute(const std::string& key, const std::string& value) override {} + virtual bool preferenceMapHasKey(const std::string& key) override { return false; } + + std::shared_ptr<AudioSubscriberTester> mAS{}; + +private: + const std::string datapath_; + std::string attached_ {'0'}; +}; +} // namespace jami diff --git a/TestSuite/AudioSubscriberTester.cpp b/TestSuite/AudioSubscriberTester.cpp new file mode 100644 index 0000000..52f4afb --- /dev/null +++ b/TestSuite/AudioSubscriberTester.cpp @@ -0,0 +1,65 @@ +/** + * Copyright (C) 2020-2021 Savoir-faire Linux Inc. + * + * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "AudioSubscriberTester.h" + +#include <pluglog.h> + +const std::string TAG = "AudioHandlerTester"; +const char sep = separator(); + +namespace jami { + +AudioSubscriberTester::~AudioSubscriberTester() +{ + std::ostringstream oss; + oss << "~AudioSubscriberTester" << std::endl; + Plog::log(Plog::LogPriority::INFO, TAG, oss.str()); +} + +void +AudioSubscriberTester::attached(Observable<AVFrame*>* observable) +{ + std::ostringstream oss; + oss << "::Attached ! " << std::endl; + Plog::log(Plog::LogPriority::INFO, TAG, oss.str()); + observable_ = observable; +} + +void +AudioSubscriberTester::detached(Observable<AVFrame*>*) +{ + observable_ = nullptr; + std::ostringstream oss; + oss << "::Detached()" << std::endl; + Plog::log(Plog::LogPriority::INFO, TAG, oss.str()); +} + +void +AudioSubscriberTester::detach() +{ + if (observable_) { + std::ostringstream oss; + oss << "::Calling detach()" << std::endl; + Plog::log(Plog::LogPriority::INFO, TAG, oss.str()); + observable_->detach(this); + } +} +} // namespace jami diff --git a/TestSuite/AudioSubscriberTester.h b/TestSuite/AudioSubscriberTester.h new file mode 100644 index 0000000..83e1bfa --- /dev/null +++ b/TestSuite/AudioSubscriberTester.h @@ -0,0 +1,49 @@ +/** + * Copyright (C) 2020-2021 Savoir-faire Linux Inc. + * + * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#pragma once + +extern "C" { +#include <libavutil/frame.h> +} +#include <observer.h> + +namespace jami { + +class AudioSubscriberTester : public Observer<AVFrame*> +{ +public: + AudioSubscriberTester() {} + ~AudioSubscriberTester(); + + virtual void update(Observable<AVFrame*>*, AVFrame* const&) override {} + virtual void attached(Observable<AVFrame*>*) override; + virtual void detached(Observable<AVFrame*>*) override; + + void detach(); + +private: + // Observer pattern + Observable<AVFrame*>* observable_{}; + + // Data + std::string path_; +}; +} // namespace jami diff --git a/TestSuite/CMakeLists.txt b/TestSuite/CMakeLists.txt new file mode 100644 index 0000000..51f516f --- /dev/null +++ b/TestSuite/CMakeLists.txt @@ -0,0 +1,91 @@ +cmake_minimum_required(VERSION 3.10) + +# set the project name +set (ProjectName TestSuite) +set (Version 1.0.0) + +project(${ProjectName} VERSION ${Version}) + +set (DAEMON ${PROJECT_SOURCE_DIR}/../../daemon) +set (JPL_FILE_NAME ${ProjectName}.jpl) +set (DAEMON_SRC ${DAEMON}/src) +set (CONTRIB_PATH ${DAEMON}/contrib) +set (PLUGINS_LIB ${PROJECT_SOURCE_DIR}/../lib) +set (JPL_DIRECTORY ${PROJECT_BINARY_DIR}/jpl) +set (LIBS_DIR ${PROJECT_SOURCE_DIR}/../contrib/Libs) + +if(WIN32) + message(OS:\ WINDOWS\ ${CMAKE_SYSTEM_PROCESSOR}) + if (NOT ${CMAKE_CL_64}) + message( FATAL_ERROR "\nUse CMake only for x64 Windows" ) + endif() + set (CONTRIB_PLATFORM_CURT x64) + set (CONTRIB_PLATFORM ${CONTRIB_PLATFORM_CURT}-windows) + set (LIBRARY_FILE_NAME ${ProjectName}.dll) + set (FFMPEG ${CONTRIB_PATH}/build/ffmpeg/Build/win32/x64) +else() + message( FATAL_ERROR "\nUse CMake only for Windows! For linux or Android (linux host), use our bash scripts." ) +endif() + +message(Building:\ ${ProjectName}\ ${Version}) +message(Build\ path:\ ${PROJECT_BINARY_DIR}) +message(JPL\ assembling\ path:\ ${JPL_DIRECTORY}) +message(JPL\ path:\ ${JPL_DIRECTORY}/../../../build/${ProjectName}/${JPL_FILE_NAME}) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED True) +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT") +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd") + +set(plugin_SRC AudioHandlerTester.cpp + AudioSubscriberTester.cpp + VideoHandlerTester.cpp + VideoSubscriberTester.cpp + ChatHandlerTester.cpp + ChatSubscriberTester.cpp + main.cpp + ) + +set(plugin_HDR AudioHandlerTester.h + AudioSubscriberTester.h + VideoHandlerTester.h + VideoSubscriberTester.h + ChatHandlerTester.h + ChatSubscriberTester.h + ./../lib/pluglog.h + ) + +add_library(${ProjectName} SHARED ${plugin_SRC} + ${plugin_HDR} + ) + +target_include_directories(${ProjectName} PUBLIC ${PROJECT_BINARY_DIR} + ${PROJECT_SOURCE_DIR} + ${PLUGINS_LIB} + ${DAEMON_SRC} + ${CONTRIB_PATH} + ${CONTRIB_PATH}/build/fmt/include + ${FFMPEG}/include + ) +target_link_directories(${ProjectName} PUBLIC ${CONTRIB_PATH} + ${FFMPEG}/bin + ${CONTRIB_PATH}/build/fmt/msvc/Release + ) + +target_link_libraries(${ProjectName} PUBLIC avutil) + +add_custom_command( + TARGET ${ProjectName} + PRE_BUILD + COMMAND python ${PROJECT_SOURCE_DIR}/../SDK/jplManipulation.py --preassemble --plugin=${ProjectName} + COMMENT "Assembling Plugin files" +) + +add_custom_command( + TARGET ${ProjectName} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_BINARY_DIR}/Release/${ProjectName}.lib ${JPL_DIRECTORY}/lib/${CONTRIB_PLATFORM} + COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_BINARY_DIR}/Release/${LIBRARY_FILE_NAME} ${JPL_DIRECTORY}/lib/${CONTRIB_PLATFORM} + COMMAND python ${PROJECT_SOURCE_DIR}/../SDK/jplManipulation.py --assemble --plugin=${ProjectName} + COMMENT "Generating JPL archive" +) diff --git a/TestSuite/ChatHandlerTester.cpp b/TestSuite/ChatHandlerTester.cpp new file mode 100644 index 0000000..f1b1907 --- /dev/null +++ b/TestSuite/ChatHandlerTester.cpp @@ -0,0 +1,76 @@ +/** + * Copyright (C) 2020-2021 Savoir-faire Linux Inc. + * + * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "ChatHandlerTester.h" + +#include "pluglog.h" + +const char sep = separator(); +const std::string TAG = "ChatHandlerTester"; + +#define NAME "ChatHandlerTester" + +namespace jami { + +ChatHandlerTester::ChatHandlerTester(std::string&& dataPath) + : datapath_ {dataPath} +{ + setId(datapath_); + mCS_ = std::make_shared<ChatSubscriberTester>(); +} + +void +ChatHandlerTester::notifyChatSubject(std::pair<std::string, std::string>& subjectConnection, + chatSubjectPtr subject) +{ + if (mCS_ && subjects.find(subject) == subjects.end()) { + std::ostringstream oss; + oss << "NEW SUBJECT: account = " << subjectConnection.first + << " peer = " << subjectConnection.second << std::endl; + Plog::log(Plog::LogPriority::INFO, TAG, oss.str()); + subject->attach(mCS_.get()); + subjects.insert(subject); + } +} + +std::map<std::string, std::string> +ChatHandlerTester::getChatHandlerDetails() +{ + return {{"name", NAME}, {"iconPath", datapath_ + sep + "icon.svg"}, {"pluginId", id()}}; +} + +void +ChatHandlerTester::detach(chatSubjectPtr subject) +{ + auto it = subjects.find(subject); + if (it != subjects.end()) { + subject->detach(mCS_.get()); + subjects.erase(it); + } +} + +ChatHandlerTester::~ChatHandlerTester() +{ + const auto copy = subjects; + for (const auto& subject : copy) { + detach(subject); + } +} +} // namespace jami diff --git a/TestSuite/ChatHandlerTester.h b/TestSuite/ChatHandlerTester.h new file mode 100644 index 0000000..8357ef7 --- /dev/null +++ b/TestSuite/ChatHandlerTester.h @@ -0,0 +1,56 @@ +/** + * Copyright (C) 2020-2021 Savoir-faire Linux Inc. + * + * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#pragma once + +#include "ChatSubscriberTester.h" + +#include "plugin/jamiplugin.h" +#include "plugin/chathandler.h" + +#include <string> +#include <map> +#include <memory> +#include <set> + +using chatSubjectPtr = std::shared_ptr<jami::PublishObservable<jami::pluginMessagePtr>>; + +namespace jami { + +class ChatHandlerTester : public jami::ChatHandler +{ +public: + ChatHandlerTester(std::string&& dataPath); + ~ChatHandlerTester(); + + void notifyChatSubject(std::pair<std::string, std::string>& subjectConnection, + chatSubjectPtr subject) override; + std::map<std::string, std::string> getChatHandlerDetails() override; + void detach(chatSubjectPtr subject) override; + void setPreferenceAttribute(const std::string& key, const std::string& value) override {} + bool preferenceMapHasKey(const std::string& key) override { return false; } + + std::shared_ptr<ChatSubscriberTester> mCS_ {}; + +private: + const std::string datapath_; + std::set<chatSubjectPtr> subjects; +}; +} // namespace jami diff --git a/TestSuite/ChatSubscriberTester.cpp b/TestSuite/ChatSubscriberTester.cpp new file mode 100644 index 0000000..7ee5ab5 --- /dev/null +++ b/TestSuite/ChatSubscriberTester.cpp @@ -0,0 +1,60 @@ +/** + * Copyright (C) 2020-2021 Savoir-faire Linux Inc. + * + * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "ChatSubscriberTester.h" +#include "pluglog.h" + +const std::string TAG = "ChatHandlerTester"; + +namespace jami { + +ChatSubscriberTester::~ChatSubscriberTester() +{ + std::ostringstream oss; + oss << "~ChatSubscriberTester" << std::endl; + Plog::log(Plog::LogPriority::INFO, TAG, oss.str()); +} + +void +ChatSubscriberTester::attached(Observable<pluginMessagePtr>* observable) +{ + if (observables_.find(observable) == observables_.end()) { + std::ostringstream oss; + oss << "::Attached ! " << std::endl; + Plog::log(Plog::LogPriority::INFO, TAG, oss.str()); + observables_.insert(observable); + isAttached = true; + } +} + +void +ChatSubscriberTester::detached(Observable<pluginMessagePtr>* observable) +{ + auto it = observables_.find(observable); + if (it != observables_.end()) { + observables_.erase(it); + std::ostringstream oss; + oss << "::Detached()" << std::endl; + Plog::log(Plog::LogPriority::INFO, TAG, oss.str()); + if (observables_.empty()) + isAttached = false; + } +} +} // namespace jami diff --git a/TestSuite/ChatSubscriberTester.h b/TestSuite/ChatSubscriberTester.h new file mode 100644 index 0000000..f93e38d --- /dev/null +++ b/TestSuite/ChatSubscriberTester.h @@ -0,0 +1,48 @@ +/** + * Copyright (C) 2020-2021 Savoir-faire Linux Inc. + * + * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#pragma once + +#include "observer.h" + +#include "plugin/streamdata.h" +#include "plugin/jamiplugin.h" +#include "plugin/chathandler.h" + +#include <map> +#include <set> + +namespace jami { + +class ChatSubscriberTester : public Observer<pluginMessagePtr> +{ +public: + ChatSubscriberTester() {} + ~ChatSubscriberTester(); + virtual void update(Observable<pluginMessagePtr>*, pluginMessagePtr const&) override {} + virtual void attached(Observable<pluginMessagePtr>*) override; + virtual void detached(Observable<pluginMessagePtr>*) override; + +protected: + // Observer pattern + std::set<Observable<pluginMessagePtr>*> observables_; + bool isAttached {false}; +}; +} // namespace jami diff --git a/TestSuite/VideoHandlerTester.cpp b/TestSuite/VideoHandlerTester.cpp new file mode 100644 index 0000000..1029161 --- /dev/null +++ b/TestSuite/VideoHandlerTester.cpp @@ -0,0 +1,88 @@ +/** + * Copyright (C) 2020-2021 Savoir-faire Linux Inc. + * + * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "VideoHandlerTester.h" + +#include "pluglog.h" +#include <string_view> + +const char sep = separator(); +const std::string TAG = "VideoHandlerTester"; + +#define NAME "VideoHandlerTester" + +namespace jami { + +VideoHandlerTester::VideoHandlerTester(std::string&& datapath) + : datapath_ {datapath} +{ + setId(datapath_); + mVS = std::make_shared<VideoSubscriberTester>(); +} + +void +VideoHandlerTester::notifyAVFrameSubject(const StreamData& data, jami::avSubjectPtr subject) +{ + std::ostringstream oss; + std::string_view direction = data.direction ? "Receive" : "Preview"; + oss << "NEW SUBJECT: [" << data.id << "," << direction << "]" << std::endl; + + bool preferredStreamDirection = false; // false for output; true for input + oss << "preferredStreamDirection " << preferredStreamDirection << std::endl; + if (data.type == StreamType::video && !data.direction + && data.direction == preferredStreamDirection) { + subject->attach(mVS.get()); // your image + oss << "got my sent image attached" << std::endl; + attached_ = "1"; + } else if (data.type == StreamType::video && data.direction + && data.direction == preferredStreamDirection) { + subject->attach(mVS.get()); // the image you receive from others on the call + oss << "got received image attached" << std::endl; + attached_ = "1"; + } + + Plog::log(Plog::LogPriority::INFO, TAG, oss.str()); +} + +std::map<std::string, std::string> +VideoHandlerTester::getCallMediaHandlerDetails() +{ + return {{"name", NAME}, + {"iconPath", datapath_ + sep + "icon.svg"}, + {"pluginId", id()}, + {"attached", attached_}, + {"dataType", "1"}}; +} + +void +VideoHandlerTester::detach() +{ + attached_ = "0"; + mVS->detach(); +} + +VideoHandlerTester::~VideoHandlerTester() +{ + std::ostringstream oss; + oss << " ~VideoHandlerTester from TestSuite Plugin" << std::endl; + Plog::log(Plog::LogPriority::INFO, TAG, oss.str()); + detach(); +} +} // namespace jami diff --git a/TestSuite/VideoHandlerTester.h b/TestSuite/VideoHandlerTester.h new file mode 100644 index 0000000..b5a57e7 --- /dev/null +++ b/TestSuite/VideoHandlerTester.h @@ -0,0 +1,50 @@ +/** + * Copyright (C) 2020-2021 Savoir-faire Linux Inc. + * + * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#pragma once + +#include "VideoSubscriberTester.h" +#include "plugin/jamiplugin.h" +#include "plugin/mediahandler.h" + +using avSubjectPtr = std::weak_ptr<jami::Observable<AVFrame*>>; + +namespace jami { + +class VideoHandlerTester : public jami::CallMediaHandler +{ +public: + VideoHandlerTester(std::string&& dataPath); + ~VideoHandlerTester(); + + virtual void notifyAVFrameSubject(const StreamData& data, avSubjectPtr subject) override; + virtual std::map<std::string, std::string> getCallMediaHandlerDetails() override; + + virtual void detach() override; + virtual void setPreferenceAttribute(const std::string& key, const std::string& value) override {} + virtual bool preferenceMapHasKey(const std::string& key) override { return false; } + + std::shared_ptr<VideoSubscriberTester> mVS{}; + +private: + const std::string datapath_; + std::string attached_ {"0"}; +}; +} // namespace jami diff --git a/TestSuite/VideoSubscriberTester.cpp b/TestSuite/VideoSubscriberTester.cpp new file mode 100644 index 0000000..d428a84 --- /dev/null +++ b/TestSuite/VideoSubscriberTester.cpp @@ -0,0 +1,66 @@ +/** + * Copyright (C) 2020-2021 Savoir-faire Linux Inc. + * + * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "VideoSubscriberTester.h" + +#include <pluglog.h> +#include <stdio.h> + +const std::string TAG = "VideoHandlerTester"; +const char sep = separator(); + +namespace jami { + +VideoSubscriberTester::~VideoSubscriberTester() +{ + std::ostringstream oss; + oss << "~VideoSubscriberTester" << std::endl; + Plog::log(Plog::LogPriority::INFO, TAG, oss.str()); +} + +void +VideoSubscriberTester::attached(jami::Observable<AVFrame*>* observable) +{ + std::ostringstream oss; + oss << "::Attached ! " << std::endl; + Plog::log(Plog::LogPriority::INFO, TAG, oss.str()); + observable_ = observable; +} + +void +VideoSubscriberTester::detached(jami::Observable<AVFrame*>*) +{ + observable_ = nullptr; + std::ostringstream oss; + oss << "::Detached()" << std::endl; + Plog::log(Plog::LogPriority::INFO, TAG, oss.str()); +} + +void +VideoSubscriberTester::detach() +{ + if (observable_) { + std::ostringstream oss; + oss << "::Calling detach()" << std::endl; + Plog::log(Plog::LogPriority::INFO, TAG, oss.str()); + observable_->detach(this); + } +} +} // namespace jami diff --git a/TestSuite/VideoSubscriberTester.h b/TestSuite/VideoSubscriberTester.h new file mode 100644 index 0000000..49d5ca7 --- /dev/null +++ b/TestSuite/VideoSubscriberTester.h @@ -0,0 +1,46 @@ +/** + * Copyright (C) 2020-2021 Savoir-faire Linux Inc. + * + * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#pragma once + +extern "C" { +#include <libavutil/frame.h> +} +#include <observer.h> + +namespace jami { + +class VideoSubscriberTester : public jami::Observer<AVFrame*> +{ +public: + VideoSubscriberTester() {} + ~VideoSubscriberTester(); + + virtual void update(jami::Observable<AVFrame*>*, AVFrame* const&) override {} + virtual void attached(jami::Observable<AVFrame*>*) override; + virtual void detached(jami::Observable<AVFrame*>*) override; + + void detach(); + +private: + // Observer pattern + Observable<AVFrame*>* observable_{}; +}; +} // namespace jami diff --git a/TestSuite/build.sh b/TestSuite/build.sh new file mode 100755 index 0000000..881221a --- /dev/null +++ b/TestSuite/build.sh @@ -0,0 +1,267 @@ +#! /bin/bash +# Build the plugin for the project +set -e +export OSTYPE +ARCH=$(uname -m) +EXTRAPATH='' +# Flags: + +# -p: number of processors to use +# -c: Runtime plugin cpu/gpu setting. +# -t: target platform. + + +if [ -z "${DAEMON}" ]; then + DAEMON="./../../daemon" + echo "DAEMON not provided, building with ${DAEMON}" +fi + +PLUGIN_NAME="TestSuite" +JPL_FILE_NAME="${PLUGIN_NAME}.jpl" +SO_FILE_NAME="lib${PLUGIN_NAME}.so" +DAEMON_SRC="${DAEMON}/src" +CONTRIB_PATH="${DAEMON}/contrib" +PLUGINS_LIB="../lib" +LIBS_DIR="./../contrib/Libs" +PLATFORM=$(uname) + +if [ "${PLATFORM}" = "Linux" ]; then + PLATFORM="linux-gnu" + CONTRIB_PLATFORM_CURT=${ARCH} + echo "Building with ${PLATFORM}" +elif [ "${PLATFORM}" = "Darwin" ]; then + PLATFORM="darwin" + SO_FILE_NAME="lib${PLUGIN_NAME}.dylib" + alias nproc='sysctl -n hw.logicalcpu' + CONTRIB_PLATFORM_CURT=${ARCH}-apple + CONTRIB_PLATFORM_EXTRA=$(uname -r) + echo "Building with ${PLATFORM}" +fi + +while getopts t:c:p OPT; do + case "$OPT" in + t) + PLATFORM="${OPTARG}" + ;; + c) + PROCESSOR="${OPTARG}" + ;; + p) + ;; + \?) + exit 1 + ;; + esac +done + +if [ "${PLATFORM}" = "linux-gnu" ] || [ "${PLATFORM}" = "redhat-linux" ] +then + python3 ./../SDK/jplManipulation.py --preassemble --plugin=${PLUGIN_NAME} + + CONTRIB_PLATFORM=${CONTRIB_PLATFORM_CURT}-${PLATFORM} + + # Compile + clang++ -std=c++17 -shared -fPIC \ + -Wl,-Bsymbolic,-rpath,"\${ORIGIN}" \ + -Wall -Wextra \ + -Wno-unused-variable \ + -Wno-unused-function \ + -Wno-unused-parameter \ + -I"." \ + -I"${DAEMON_SRC}" \ + -I"${CONTRIB_PATH}/${CONTRIB_PLATFORM}/include" \ + -I"${PLUGINS_LIB}" \ + AudioHandlerTester.cpp \ + AudioSubscriberTester.cpp \ + VideoHandlerTester.cpp \ + VideoSubscriberTester.cpp \ + ChatHandlerTester.cpp \ + ChatSubscriberTester.cpp \ + main.cpp \ + -L"${CONTRIB_PATH}/${CONTRIB_PLATFORM}/lib/" \ + -l:libavutil.a \ + -lva \ + -o "build-local/jpl/lib/${CONTRIB_PLATFORM}/${SO_FILE_NAME}" + +elif [ "${PLATFORM}" = "darwin" ] +then + python3 ./../SDK/jplManipulation.py --preassemble --plugin=${PLUGIN_NAME} + + CONTRIB_PLATFORM=${CONTRIB_PLATFORM_CURT}-${PLATFORM} + + # Compile + clang++ -std=c++17 -shared -fPIC \ + -Wl,-no_compact_unwind -Wl,-framework,CoreFoundation \ + -Wl,-framework,Security -Wl,-framework,VideoToolbox \ + -Wl,-framework,CoreMedia -Wl,-framework,CoreVideo \ + -Wl,-framework,OpenCl -Wl,-framework,Accelerate \ + -Wl,-rpath,"\${ORIGIN}" \ + -Wall -Wextra \ + -Wno-unused-variable \ + -Wno-unused-function \ + -Wno-unused-parameter \ + -I"." \ + -I"${DAEMON_SRC}" \ + -I"${CONTRIB_PATH}/${CONTRIB_PLATFORM}${CONTRIB_PLATFORM_EXTRA}/include" \ + -I"${CONTRIB_PATH}/${CONTRIB_PLATFORM}${CONTRIB_PLATFORM_EXTRA}/include/opencv4" \ + -I"${PLUGINS_LIB}" \ + AudioHandlerTester.cpp \ + AudioSubscriberTester.cpp \ + VideoHandlerTester.cpp \ + VideoSubscriberTester.cpp \ + ChatHandlerTester.cpp \ + ChatSubscriberTester.cpp \ + main.cpp \ + -L"${CONTRIB_PATH}/${CONTRIB_PLATFORM}${CONTRIB_PLATFORM_EXTRA}/lib/" \ + -lavutil \ + -lvpx -lx264 -lbz2 -liconv -lz -lspeex -lopus \ + -o "build-local/jpl/lib/${CONTRIB_PLATFORM}/${SO_FILE_NAME}" + + install_name_tool -id "@loader_path/${SO_FILE_NAME}" "build-local/jpl/lib/${CONTRIB_PLATFORM}/${SO_FILE_NAME}" + + if [ -n "${APPLE_SIGN_CERTIFICATE}" ]; then + codesign --force --verify --timestamp -o runtime --sign "${APPLE_SIGN_CERTIFICATE}" "build-local/jpl/lib/${CONTRIB_PLATFORM}/${SO_FILE_NAME}" + ditto -c -k --rsrc "build-local/jpl/lib/${CONTRIB_PLATFORM}/${SO_FILE_NAME}" "build-local/${SO_FILE_NAME}.zip" + LIBRARYNAME=${PLUGIN_NAME} sh ./../notarize.sh + cd .. + ditto -x -k "build-local/${SO_FILE_NAME}.zip" "build-local/notarized" + cp "build-local/notarized/${SO_FILE_NAME}" "build-local/jpl/lib/${CONTRIB_PLATFORM}/${SO_FILE_NAME}" + fi + +elif [ "${PLATFORM}" = "android" ] +then + python3 ./../SDK/jplManipulation.py --preassemble --plugin=${PLUGIN_NAME} --distribution=${PLATFORM} + + if [ -z "$ANDROID_NDK" ]; then + ANDROID_NDK="/home/${USER}/Android/Sdk/ndk/21.1.6352462" + echo "ANDROID_NDK not provided, building with ${ANDROID_NDK}" + fi + + #========================================================= + # Check if the ANDROID_ABI was provided + # if not, set default + #========================================================= + if [ -z "$ANDROID_ABI" ]; then + ANDROID_ABI="armeabi-v7a arm64-v8a x86_64" + echo "ANDROID_ABI not provided, building for ${ANDROID_ABI}" + fi + + buildlib() { + echo "$CURRENT_ABI" + + #========================================================= + # ANDROID TOOLS + #========================================================= + export HOST_TAG=linux-x86_64 + export TOOLCHAIN=$ANDROID_NDK/toolchains/llvm/prebuilt/$HOST_TAG + + if [ "$CURRENT_ABI" = armeabi-v7a ] + then + export AR=$TOOLCHAIN/bin/arm-linux-androideabi-ar + export AS=$TOOLCHAIN/bin/arm-linux-androideabi-as + export CC=$TOOLCHAIN/bin/armv7a-linux-androideabi21-clang + export CXX=$TOOLCHAIN/bin/armv7a-linux-androideabi21-clang++ + export LD=$TOOLCHAIN/bin/arm-linux-androideabi-ld + export RANLIB=$TOOLCHAIN/bin/arm-linux-androideabi-ranlib + export STRIP=$TOOLCHAIN/bin/arm-linux-androideabi-strip + export ANDROID_SYSROOT=${DAEMON}/../client-android/android-toolchain-21-arm/sysroot + + elif [ "$CURRENT_ABI" = arm64-v8a ] + then + export AR=$TOOLCHAIN/bin/aarch64-linux-android-ar + export AS=$TOOLCHAIN/bin/aarch64-linux-android-as + export CC=$TOOLCHAIN/bin/aarch64-linux-android21-clang + export CXX=$TOOLCHAIN/bin/aarch64-linux-android21-clang++ + export LD=$TOOLCHAIN/bin/aarch64-linux-android-ld + export RANLIB=$TOOLCHAIN/bin/aarch64-linux-android-ranlib + export STRIP=$TOOLCHAIN/bin/aarch64-linux-android-strip + export ANDROID_SYSROOT=${DAEMON}/../client-android/android-toolchain-21-arm64/sysroot + + elif [ "$CURRENT_ABI" = x86_64 ] + then + export AR=$TOOLCHAIN/bin/x86_64-linux-android-ar + export AS=$TOOLCHAIN/bin/x86_64-linux-android-as + export CC=$TOOLCHAIN/bin/x86_64-linux-android21-clang + export CXX=$TOOLCHAIN/bin/x86_64-linux-android21-clang++ + export LD=$TOOLCHAIN/bin/x86_64-linux-android-ld + export RANLIB=$TOOLCHAIN/bin/x86_64-linux-android-ranlib + export STRIP=$TOOLCHAIN/bin/x86_64-linux-android-strip + export ANDROID_SYSROOT=${DAEMON}/../client-android/android-toolchain-21-x86_64/sysroot + + else + echo "ABI NOT OK" >&2 + exit 1 + fi + + #========================================================= + # CONTRIBS + #========================================================= + if [ "$CURRENT_ABI" = armeabi-v7a ] + then + CONTRIB_PLATFORM=arm-linux-androideabi + + elif [ "$CURRENT_ABI" = arm64-v8a ] + then + CONTRIB_PLATFORM=aarch64-linux-android + + elif [ "$CURRENT_ABI" = x86_64 ] + then + CONTRIB_PLATFORM=x86_64-linux-android + fi + + #NDK SOURCES FOR cpufeatures + NDK_SOURCES=${ANDROID_NDK}/sources/android + + #========================================================= + # LD_FLAGS + #========================================================= + if [ "$CURRENT_ABI" = armeabi-v7a ] + then + export EXTRA_LDFLAGS="${EXTRA_LDFLAGS} -L${ANDROID_SYSROOT}/usr/lib/arm-linux-androideabi -L${ANDROID_SYSROOT}/usr/lib/arm-linux-androideabi/21" + elif [ "$CURRENT_ABI" = arm64-v8a ] + then + export EXTRA_LDFLAGS="${EXTRA_LDFLAGS} -L${ANDROID_SYSROOT}/usr/lib/aarch64-linux-android -L${ANDROID_SYSROOT}/usr/lib/aarch64-linux-android/21" + elif [ "$CURRENT_ABI" = x86_64 ] + then + export EXTRA_LDFLAGS="${EXTRA_LDFLAGS} -L${ANDROID_SYSROOT}/usr/lib/x86_64-linux-android -L${ANDROID_SYSROOT}/usr/lib/x86_64-linux-android/21" + fi + + #========================================================= + # Compile the plugin + #========================================================= + + # Create so destination folder + $CXX --std=c++17 -O3 -g -fPIC \ + -Wl,-Bsymbolic,-rpath,"\${ORIGIN}" \ + -shared \ + -Wall -Wextra \ + -Wno-unused-variable \ + -Wno-unused-function \ + -Wno-unused-parameter \ + -I"." \ + -I"${DAEMON_SRC}" \ + -I"${CONTRIB_PATH}/${CONTRIB_PLATFORM}/include" \ + -I"${CONTRIB_PATH}/${CONTRIB_PLATFORM}/include/opencv4" \ + -I"${PLUGINS_LIB}" \ + AudioHandlerTester.cpp \ + AudioSubscriberTester.cpp \ + VideoHandlerTester.cpp \ + VideoSubscriberTester.cpp \ + ChatHandlerTester.cpp \ + ChatSubscriberTester.cpp \ + main.cpp \ + -L"${CONTRIB_PATH}/${CONTRIB_PLATFORM}/lib/" \ + -lavutil \ + -llog -lz \ + --sysroot=$ANDROID_SYSROOT \ + -o "build-local/jpl/lib/$CURRENT_ABI/${SO_FILE_NAME}" + } + + # Build the so + for i in ${ANDROID_ABI}; do + CURRENT_ABI=$i + buildlib + done +fi + +python3 ./../SDK/jplManipulation.py --assemble --plugin=${PLUGIN_NAME} --distribution=${PLATFORM} --extraPath=${EXTRAPATH} diff --git a/TestSuite/data/accountpreferences.json b/TestSuite/data/accountpreferences.json new file mode 100644 index 0000000..e1d4b8e --- /dev/null +++ b/TestSuite/data/accountpreferences.json @@ -0,0 +1,9 @@ +[ + { + "type": "EditText", + "key": "textAccountPreference", + "title": "{{text_translatable2}}", + "summary": "{{summary_translatable2}}", + "defaultValue": "{{default_translatable2}}" + } +] \ No newline at end of file diff --git a/TestSuite/data/icon.png b/TestSuite/data/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..437e40834746471d23081d0643d6f6f59b648c1c GIT binary patch literal 31362 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 literal 0 HcmV?d00001 diff --git a/TestSuite/data/icon.svg b/TestSuite/data/icon.svg new file mode 100644 index 0000000..4ced6a7 --- /dev/null +++ b/TestSuite/data/icon.svg @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="300px" height="300px" viewBox="0 0 300 300" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <title>O</title> + <defs> + <linearGradient x1="50.001267%" y1="120.94311%" x2="50.001267%" y2="-257.222291%" id="linearGradient-1"> + <stop stop-color="#EFB000" offset="0%"></stop> + <stop stop-color="#F1B904" offset="2.798313%"></stop> + <stop stop-color="#F6CF0E" offset="11.34%"></stop> + <stop stop-color="#FAE016" offset="21.42%"></stop> + <stop stop-color="#FDEB1B" offset="33.9%"></stop> + <stop stop-color="#FFF21E" offset="51.4%"></stop> + <stop stop-color="#FFF41F" offset="100%"></stop> + </linearGradient> + <linearGradient x1="49.9829639%" y1="-278.222509%" x2="49.9829639%" y2="99.9773676%" id="linearGradient-2"> + <stop stop-color="#EFB000" offset="80.23%"></stop> + <stop stop-color="#F6CD0D" offset="85.32%"></stop> + <stop stop-color="#FBE317" offset="90.44%"></stop> + <stop stop-color="#FEF01D" offset="95.41%"></stop> + <stop stop-color="#FFF41F" offset="100%"></stop> + </linearGradient> + </defs> + <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> + <g id="O" fill-rule="nonzero"> + <g transform="translate(2.000000, 0.000000)" id="Path"> + <path d="M295.571429,152.285714 C295.571429,152.214286 295.571429,152.142857 295.571429,152.071429 C295.571429,150.428571 295.571429,148.714286 295.5,146.928571 C295.5,146.214286 295.5,145.428571 295.5,144.714286 L295.428571,144.714286 C293.857143,105.928571 280.071429,50.7142857 210.642857,15.1428571 C135.928571,-23.0714286 70.0714286,23.3571429 70.0714286,23.3571429 C70.1428571,23.3571429 70.1428571,23.3571429 70.2142857,23.3571429 C110.857143,16.2857143 172.357143,46.8571429 206.285714,90.9285714 C211.571429,97.7857143 216.214286,104.928571 219.928571,112.357143 C226.214286,124.642857 228.928571,136.214286 229.428571,146.642857 C229.428571,147.071429 229.5,147.571429 229.5,148 C229.571429,151.357143 229.5,154.714286 229.142857,157.857143 L229.142857,157.857143 C227.357143,176.214286 219,194.071429 205,208.142857 C173.428571,239.642857 121.142857,240.142857 89.5714286,208.571429 L89.4285714,209.142857 C123.357143,253.214286 184.857143,283.785714 225.5,276.714286 C235.071429,270.785714 244.071429,263.785714 252.357143,255.5 C279.428571,228.428571 293.714286,193.428571 295.357143,157.928571 L295.5,157.928571 C295.571429,156.428571 295.571429,154.928571 295.571429,153.428571 C295.571429,153 295.571429,152.642857 295.571429,152.285714 Z" fill="url(#linearGradient-1)"></path> + <path d="M225.5,276.642857 C184.857143,283.714286 123.357143,253.142857 89.4285714,209.071429 C84.1428571,202.214286 79.5,195.071429 75.7857143,187.642857 C69.5,175.357143 66.7857143,163.785714 66.2857143,153.357143 C66.2857143,152.928571 66.2142857,152.428571 66.2142857,152 C66.1428571,148.642857 66.2142857,145.285714 66.5714286,142.142857 L66.5714286,142.142857 C68.3571429,123.785714 76.7142857,105.928571 90.7142857,91.8571429 C122.285714,60.3571429 174.571429,59.8571429 206.142857,91.4285714 L206.285714,90.8571429 C172.357143,46.8571429 110.857143,16.2857143 70.2142857,23.3571429 C60.6428571,29.2857143 51.6428571,36.2857143 43.3571429,44.5714286 C16.2857143,71.6428571 2,106.642857 0.357142857,142.142857 L0.214285714,142.142857 C0.142857143,143.642857 0.142857143,145.142857 0.142857143,146.642857 C0.142857143,147 0.142857143,147.357143 0.142857143,147.714286 C0.142857143,147.785714 0.142857143,147.857143 0.142857143,147.928571 C0.142857143,149.571429 0.142857143,151.285714 0.214285714,153.071429 C0.214285714,153.785714 0.214285714,154.571429 0.285714286,155.285714 L0.357142857,155.285714 C1.92857143,194.071429 15.7142857,249.285714 85.1428571,284.857143 C159.857143,323.142857 225.714286,276.714286 225.714286,276.714286 C225.571429,276.642857 225.571429,276.642857 225.5,276.642857 Z" fill="url(#linearGradient-2)"></path> + </g> + </g> + </g> +</svg> \ No newline at end of file diff --git a/TestSuite/data/locale/TestSuite_en.json b/TestSuite/data/locale/TestSuite_en.json new file mode 100644 index 0000000..866f977 --- /dev/null +++ b/TestSuite/data/locale/TestSuite_en.json @@ -0,0 +1,8 @@ +{ + "text_translatable1": "text one in english", + "text_translatable2": "text two in english", + "summary_translatable1": "summary one in english", + "summary_translatable2": "summary two in english", + "default_translatable1": "default one in english", + "default_translatable2": "default two in english" +} diff --git a/TestSuite/data/locale/TestSuite_fr.json b/TestSuite/data/locale/TestSuite_fr.json new file mode 100644 index 0000000..c292bca --- /dev/null +++ b/TestSuite/data/locale/TestSuite_fr.json @@ -0,0 +1,8 @@ +{ + "text_translatable1": "text un en français", + "text_translatable2": "text deux en français", + "summary_translatable1": "summary un en français", + "summary_translatable2": "summary deux en français", + "default_translatable1": "default un en français", + "default_translatable2": "default deux en français" +} diff --git a/TestSuite/data/preferences.json b/TestSuite/data/preferences.json new file mode 100644 index 0000000..d6a496d --- /dev/null +++ b/TestSuite/data/preferences.json @@ -0,0 +1,9 @@ +[ + { + "type": "EditText", + "key": "textGlobalPreference", + "title": "{{text_translatable1}}", + "summary": "{{summary_translatable1}}", + "defaultValue": "{{default_translatable1}}" + } +] \ No newline at end of file diff --git a/TestSuite/main.cpp b/TestSuite/main.cpp new file mode 100644 index 0000000..cca526c --- /dev/null +++ b/TestSuite/main.cpp @@ -0,0 +1,86 @@ +/** + * Copyright (C) 2020-2021 Savoir-faire Linux Inc. + * + * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <iostream> +#include <string.h> +#include <thread> +#include <memory> +#include <plugin/jamiplugin.h> + +#include "AudioHandlerTester.h" +#include "VideoHandlerTester.h" +#include "ChatHandlerTester.h" + +#ifdef WIN32 +#define EXPORT_PLUGIN __declspec(dllexport) +#else +#define EXPORT_PLUGIN +#endif + +#define TestSuite_VERSION_MAJOR 1 +#define TestSuite_VERSION_MINOR 0 +#define TestSuite_VERSION_PATCH 0 + +extern "C" { +void +pluginExit(void) +{} + +EXPORT_PLUGIN JAMI_PluginExitFunc +JAMI_dynPluginInit(const JAMI_PluginAPI* api) +{ + std::cout << "*******************" << std::endl; + std::cout << "** TestSuite **" << std::endl; + std::cout << "*******************" << std::endl << std::endl; + std::cout << "Version " << TestSuite_VERSION_MAJOR << "." << TestSuite_VERSION_MINOR << "." + << TestSuite_VERSION_PATCH << std::endl; + + // If invokeService doesn't return an error + if (api) { + std::string dataPath; + api->invokeService(api, "getPluginDataPath", &dataPath); + + auto fmpAudio + = std::make_unique<jami::AudioHandlerTester>(std::move(dataPath)); + if (api->manageComponent(api, + "CallMediaHandlerManager", + fmpAudio.release())) { + return nullptr; + } + + auto fmpVideo + = std::make_unique<jami::VideoHandlerTester>(std::move(dataPath)); + if (api->manageComponent(api, + "CallMediaHandlerManager", + fmpVideo.release())) { + return nullptr; + } + + auto fmpChat + = std::make_unique<jami::ChatHandlerTester>(std::move(dataPath)); + if (api->manageComponent(api, + "ChatHandlerManager", + fmpChat.release())) { + return nullptr; + } + } + return pluginExit; +} +} diff --git a/TestSuite/manifest.json b/TestSuite/manifest.json new file mode 100644 index 0000000..5ff8a07 --- /dev/null +++ b/TestSuite/manifest.json @@ -0,0 +1,6 @@ +{ + "name": "TestSuite", + "description": "TestSuite is a empty process plugin conceived for use in the daemon unit tests.", + "version": "1.0.0", + "iconPath" : "icon.svg" +} \ No newline at end of file diff --git a/TestSuite/package.json b/TestSuite/package.json new file mode 100644 index 0000000..f4ad235 --- /dev/null +++ b/TestSuite/package.json @@ -0,0 +1,19 @@ +{ + "name": "TestSuite", + "version": "1.0.0", + "extractLibs": false, + "deps": [ + "fmt", + "ffmpeg" + ], + "defines": [], + "custom_scripts": { + "pre_build": [ + "mkdir msvc" + ], + "build": [ + "cmake --build ./msvc --config Release" + ], + "post_build": [] + } +} \ No newline at end of file -- GitLab