Gauze¶
Gauze framework provides unified C++/CMake interface for testing on
iOS/Android/Linux/OSX/Windows platforms. CMake function gauze_add_tests
wraps standard
add_test
and in case of testing on host (Linux on Linux, OSX on OSX, etc.) just
forwards NAME
and COMMAND
arguments. Main functionality of the framework
is uploading/starting binaries for iOS/Android testing. CMake style generator
expressions $<GAUZE_RESOURCE_FILE:...>
and $<GAUZE_RESOURCE_DIR:...>
can be used for managing resources.
Hunter
iOS¶
ios-deploy
tool will be used for uploading and running executables.
Building from source:
> git clone https://github.com/phonegap/ios-deploy
> cd ios-deploy
[ios-deploy]> xcodebuild
[ios-deploy]> ls build/Release/ios-deploy
build/Release/ios-deploy
[ios-deploy]> export PATH=`pwd`/build/Release:$PATH
[ios-deploy]> which ios-deploy
/.../ios-deploy/build/Release/ios-deploy
Warning
Currently only testing on device is supported (no simulator testing)
Android¶
By default Gauze will run tests on real device connected to host. To check
that device is connected successfully run adb devices
:
> adb devices
List of devices attached
<device-name> device
To use emulator add GAUZE_ANDROID_USE_EMULATOR=ON
. In this case AVD
will be created automatically and emulator will be launched:
Creating AVD with name 'gauze_android-19_armeabi-v7a'
Starting emulator
Waiting for emulator
Emulator is ready!
Created AVD can be found in list of available AVDs:
> /.../android-sdk/tools/android list avd
Name: gauze_android-19_armeabi-v7a
Path: /.../.android/avd/gauze_android-19_armeabi-v7a.avd
Target: Android 4.4.2 (API level 19)
Tag/ABI: default/armeabi-v7a
Skin: WVGA800
Emulator is visible by the same adb device
command:
> /.../android-sdk/platform-tools/adb devices
List of devices attached
emulator-5678 device
<device-name> device
Deleting AVD:
> /.../android-sdk/tools/android delete avd --name gauze_android-19_armeabi-v7a
Deleting file /.../.android/avd/gauze_android-19_armeabi-v7a.ini
Deleting folder /.../.android/avd/gauze_android-19_armeabi-v7a.avd
AVD 'gauze_android-19_armeabi-v7a' deleted.
Emulator can be stopped by kill -9
command:
> ps aux | grep gauze_android
<username> 9160 ... /.../android-sdk/tools/emulator64-arm -avd gauze_android-19_armeabi-v7a -no-window -port 5678 -gpu host
> kill -9 9160
Warning
Pushed resource file and executable are not cleaned up automatically. You
have to remove files explicitly from GAUZE_ANDROID_DEVICE_TESTING_ROOT
directory (defaults to /data/local/tmp
):
> adb shell
> cd /data/local/tmp
> ls -la
> rm -rf somefile somedir
Other options¶
GAUZE_ANDROID_EMULATOR_GPU¶
You can set GAUZE_ANDROID_EMULATOR_GPU
to control what GPU type will be
using while creating Android emulator:
Hint
For Travis CI:
- use
host
on macOS machines - use
off
on Linux machines
GAUZE_ANDROID_EMULATOR_PARTITION_SIZE¶
You can set GAUZE_ANDROID_EMULATOR_PARTITION_SIZE
to specify the system
data partition size in MBs.
GAUZE_ANDROID_PUSH_QUIET¶
Set GAUZE_ANDROID_PUSH_QUIET
to ON
to suppress output from adb push
commands.
GAUZE_ANDROID_PUSH_RESOURCES_ONLY¶
Do not run tests but only push resources.
GAUZE_ANDROID_START_EMULATOR¶
Gauze will create suitable Android emulator image and start emulator
automatically. In case if you want to reuse existing emulator instead, e.g. one
created manually in Android Studio, you can set GAUZE_ANDROID_START_EMULATOR
option to OFF
. GAUZE_ANDROID_START_EMULATOR
is set to ON
by default.
Real device | Emulator (automatically) | Emulator (external) | |
---|---|---|---|
adb shell [1] | adb -d shell |
adb -e shell |
adb -e shell |
GAUZE_ANDROID_USE_EMULATOR | OFF [2] |
ON |
ON |
GAUZE_ANDROID_START_EMULATOR | - |
ON [3] |
OFF |
[1] | https://developer.android.com/studio/command-line/adb#issuingcommands |
[2] | By default real device used |
[3] | By default Gauze will start emulator automatically |
Simple¶
Simple example, without arguments on launch:
add_executable(gauze_simple main.cpp)
gauze_add_test(NAME gauze_simple COMMAND gauze_simple)
User should define int gauze_main(int argc, char** argv)
function instead
of main
:
#include <iostream> // std::cout
#include <cstdlib> // EXIT_SUCCESS
int gauze_main(int argc, char** argv) {
std::cout << "Hello, Gauze!" << std::endl;
return EXIT_SUCCESS;
}
Warning
While migrating to Gauze framework note that gauze_main
unlike main
doesn’t have default return value - you should return explicit int
from function.
With arguments¶
Launch with arguments:
add_executable(gauze_args main.cpp)
gauze_add_test(NAME gauze_args COMMAND gauze_args arg1 arg2 arg3)
#include <iostream> // std::cout
#include <cstdlib> // EXIT_SUCCESS
int gauze_main(int argc, char** argv) {
std::cout << "argc = " << argc << std::endl;
for (int i=0; i<argc; ++i) {
std::cout << "argv[" << i << "] = " << argv[i] << std::endl;
}
return EXIT_SUCCESS;
}
With resources¶
If we need some resource file while testing it can be uploaded by adding
$<GAUZE_RESOURCE_FILE:...>
directive:
add_executable(gauze_resource main.cpp)
set(data_dir "${CMAKE_CURRENT_LIST_DIR}/data")
gauze_add_test(
NAME gauze_resource
COMMAND
gauze_resource
arg1
arg2
arg3
$<GAUZE_RESOURCE_FILE:${data_dir}/input.txt>
${data_dir}/just_a_string.txt
--input=$<GAUZE_RESOURCE_FILE:${data_dir}/input.txt>
)
Files specified with GAUZE_RESOURCE_FILE
will be uploaded to device
and path will be substituted with real path on device.
Note that similar string without GAUZE_RESOURCE_FILE
will be used as is:
add_executable(gauze_resource main.cpp)
set(data_dir "${CMAKE_CURRENT_LIST_DIR}/data")
gauze_add_test(
NAME gauze_resource
COMMAND
gauze_resource
arg1
arg2
arg3
$<GAUZE_RESOURCE_FILE:${data_dir}/input.txt>
${data_dir}/just_a_string.txt
--input=$<GAUZE_RESOURCE_FILE:${data_dir}/input.txt>
)
Read resource file:
#include <cstdlib> // EXIT_SUCCESS
#include <fstream> // std::ifstream
#include <iostream> // std::cout
#include <string> // std::getline
int gauze_main(int argc, char** argv) {
std::cout << "argc = " << argc << std::endl;
for (int i=0; i<argc; ++i) {
std::cout << "argv[" << i << "] = " << argv[i] << std::endl;
}
if(argc < 7) {
std::cerr << "Unexpected number of arguments: " << argc << std::endl;
return EXIT_FAILURE;
}
const char* filename = argv[4];
std::ifstream file(filename);
std::string content;
std::getline(file, content);
std::cout << "Content: '" << content << "'" << std::endl;
return EXIT_SUCCESS;
}
Running this test on Android device:
> ctest -VV -R gauze_resource
3: Command output (with exit code):
3: *** BEGIN ***
3: argc = 6
3: argv[0] = /data/local/tmp/gauze/android-ndk-r10e-api-19-armeabi-v7a-neon/bin/gauze_resource
3: argv[1] = arg1
3: argv[2] = arg2
3: argv[3] = arg3
3: argv[4] = /data/local/tmp/gauze/android-ndk-r10e-api-19-armeabi-v7a-neon/data/input.txt
3: argv[5] = /.../gauze/test/gauze/resource/data/just_a_string.txt
3: Content: 'Gauze resource file'
3: 0
3: *** END ***
3: Done
1/1 Test #3: gauze_resource ................... Passed 1.15 sec
Directory¶
To copy directory with resources use $<GAUZE_RESOURCE_DIR:...>
:
hunter_add_package(Boost COMPONENTS filesystem system)
find_package(Boost REQUIRED COMPONENTS filesystem system)
add_executable(gauze_directory main.cpp)
target_link_libraries(gauze_directory PRIVATE Boost::filesystem Boost::system)
set_target_properties(
gauze_directory PROPERTIES BUILD_RPATH "${BOOST_ROOT}/lib"
)
set(data_dir "${CMAKE_CURRENT_LIST_DIR}/resdir")
gauze_add_test(
NAME gauze_directory
COMMAND
gauze_directory
arg1
arg2
arg3
$<GAUZE_RESOURCE_DIR:${data_dir}>
${data_dir}/just_a_string.txt
--directory=$<GAUZE_RESOURCE_DIR:${data_dir}>
)
if(WIN32 OR CYGWIN)
set(new_path "${BOOST_ROOT}/lib")
list(APPEND new_path $ENV{PATH})
if(WIN32)
string(REPLACE ";" "\;" new_path "${new_path}")
elseif(CYGWIN)
string(REPLACE ";" ":" new_path "${new_path}")
else()
message(FATAL_ERROR "Unreachable")
endif()
set_tests_properties(
gauze_directory PROPERTIES ENVIRONMENT "PATH=${new_path}"
)
endif()
Read files in directory:
#include <cstdlib> // EXIT_SUCCESS
#include <fstream> // std::ifstream
#include <iostream> // std::cout
#include <string> // std::getline
#include <boost/filesystem.hpp>
#include <sstream> // std::ostringstream
int gauze_main(int argc, char** argv) {
std::cout << "argc = " << argc << std::endl;
for (int i=0; i<argc; ++i) {
std::cout << "argv[" << i << "] = " << argv[i] << std::endl;
}
if(argc < 7) {
std::cerr << "Unexpected number of arguments: " << argc << std::endl;
return EXIT_FAILURE;
}
boost::filesystem::path resdir(argv[4]);
for (int i=0; i<3; ++i) {
std::ostringstream file_name;
file_name << "file." << i;
boost::filesystem::path file_path(resdir);
file_path /= file_name.str();
std::cout << "Processing file: " << file_path << std::endl;
std::ifstream file(file_path.string());
std::string content;
std::getline(file, content);
if(!file) {
std::cerr << "Can't read file: " << file_path << std::endl;
return EXIT_FAILURE;
}
std::cout << "Content: '" << content << "'" << std::endl;
}
return EXIT_SUCCESS;
}
Running this test on Android device:
> ctest -VV -R gauze_directory
4: Command output (with exit code):
4: *** BEGIN ***
4: argc = 6
4: argv[0] = /data/local/tmp/gauze/android-ndk-r10e-api-19-armeabi-v7a-neon/bin/gauze_directory
4: argv[1] = arg1
4: argv[2] = arg2
4: argv[3] = arg3
4: argv[4] = /data/local/tmp/gauze/android-ndk-r10e-api-19-armeabi-v7a-neon/data/resdir
4: argv[5] = /.../gauze/test/gauze/directory/resdir/just_a_string.txt
4: Processing file: "/data/local/tmp/gauze/android-ndk-r10e-api-19-armeabi-v7a-neon/data/resdir/file.0"
4: Content: 'Content 0'
4: Processing file: "/data/local/tmp/gauze/android-ndk-r10e-api-19-armeabi-v7a-neon/data/resdir/file.1"
4: Content: 'Content 1'
4: Processing file: "/data/local/tmp/gauze/android-ndk-r10e-api-19-armeabi-v7a-neon/data/resdir/file.2"
4: Content: 'Content 2'
4: 0
4: *** END ***
4: Done
1/1 Test #4: gauze_directory .................. Passed 0.54 sec
Dependent libraries¶
Dependent shared libraries on Android will be uploaded automatically and
LD_LIBRARY_PATH
will be updated before running executable:
add_executable(gauze_deps main.cpp)
target_link_libraries(gauze_deps PUBLIC gauze_deplib)
gauze_add_test(NAME gauze_deps COMMAND gauze_deps)
if(WIN32 OR CYGWIN)
set(new_path "")
foreach(target gauze_deplib)
list(APPEND new_path $<TARGET_FILE_DIR:${target}>)
endforeach()
list(APPEND new_path $ENV{PATH})
if(WIN32)
string(REPLACE ";" "\;" new_path "${new_path}")
elseif(CYGWIN)
string(REPLACE ";" ":" new_path "${new_path}")
else()
message(FATAL_ERROR "Unreachable")
endif()
set_tests_properties(
gauze_deps PROPERTIES ENVIRONMENT "PATH=${new_path}"
)
endif()
Calling C++ function from library:
#include <cstdlib> // EXIT_SUCCESS
#include <iostream> // std::cout
#include <gauze/deplib/Deplib.hpp>
int gauze_main(int argc, char** argv) {
gauze::deplib::Deplib deplib;
std::cout << "Result: " << deplib.result() << std::endl;
return EXIT_SUCCESS;
}
Run test (you should build with BUILD_SHARED_LIBS=ON
):
4: Creating directory on Android device: '/data/local/tmp/gauze/android-ndk-r10e-api-19-armeabi-v7a-neon/lib'
4: Uploading dependent libraries to Android device
4: '/.../test/gauze/deplib/libgauze_deplib.so'
4: -> '/data/local/tmp/gauze/android-ndk-r10e-api-19-armeabi-v7a-neon/lib/libgauze_deplib.so'
4: Set LD_LIBRARY_PATH to '/data/local/tmp/gauze/android-ndk-r10e-api-19-armeabi-v7a-neon/lib'
4: Run command on Android device:
4: [/data/local/tmp/gauze/android-ndk-r10e-api-19-armeabi-v7a-neon]> "/data/local/tmp/gauze/android-ndk-r10e-api-19-armeabi-v7a-neon/bin/gauze_deps"
4: Command output (with exit code):
4: *** BEGIN ***
4: Result: 42
4: 0
4: *** END ***
4: Done
1/1 Test #4: gauze_deps ....................... Passed 2.13 sec
With GTest¶
Working with GTest:
hunter_add_package(GTest)
find_package(GTest CONFIG REQUIRED)
hunter_add_package(cxxopts)
find_package(cxxopts CONFIG REQUIRED)
add_executable(gauze_gtest main.cpp)
target_link_libraries(gauze_gtest PUBLIC GTest::gtest cxxopts::cxxopts)
set(data_dir "${CMAKE_CURRENT_LIST_DIR}/data")
gauze_add_test(NAME gauze_gtest
COMMAND gauze_gtest
"-a" # test short bool
"--aint=314159"
"--afloat=3.14159265359"
"--astring=3.14159265359"
"--afile=$<GAUZE_RESOURCE_FILE:${data_dir}/input.txt>"
"--adir=$<GAUZE_RESOURCE_DIR:${data_dir}>"
)
if(WIN32 OR CYGWIN)
set(new_path "${GTEST_ROOT}/bin")
list(APPEND new_path $ENV{PATH})
if(WIN32)
string(REPLACE ";" "\;" new_path "${new_path}")
elseif(CYGWIN)
string(REPLACE ";" ":" new_path "${new_path}")
else()
message(FATAL_ERROR "Unreachable")
endif()
set_tests_properties(
gauze_gtest PROPERTIES ENVIRONMENT "PATH=${new_path}"
)
endif()
#include <gtest/gtest.h> // TEST
#include <cxxopts.hpp>
#include <fstream>
int argc_;
char** argv_;
int gauze_main(int argc, char** argv) {
std::cout << "argc = " << argc << std::endl;
for (int i=0; i<argc; ++i) {
std::cout << "argv[" << i << "] = " << argv[i] << std::endl;
}
argc_ = argc;
argv_ = argv;
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
TEST(gauze_gtest, arith) {
ASSERT_EQ(2 + 2, 4);
}
TEST(gauze_gtest, boolean) {
const bool a = true;
ASSERT_TRUE(a);
}
static void check_file(const std::string &filename, const std::string &message) {
std::ifstream ifs(filename);
ASSERT_TRUE(ifs);
std::string line((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
ASSERT_EQ(line, message);
}
TEST(gauze_gtest, cli) {
cxxopts::Options options("gauze-gtest", "Test command line parsing");
bool a = false;
int aint = 0;
float afloat = 0.f;
std::string astring;
std::string afile;
std::string adir;
bool b = false;
int bint = 0;
float bfloat = 0.f;
std::string bstring;
std::string bfile;
std::string bdir;
// clang-format off
options.add_options()
("a,aval", "equals boolean", cxxopts::value<bool>(a))
("aint", "equals integer", cxxopts::value<int>(aint))
("afloat", "equals float", cxxopts::value<float>(afloat))
("astring", "equals string", cxxopts::value<std::string>(astring))
("afile", "equals filename", cxxopts::value<std::string>(afile))
("adir", "equals directory", cxxopts::value<std::string>(adir))
;
// clang-format on
options.parse(argc_, argv_);
static const std::string message = "Gauze resource file\n";
ASSERT_EQ(a, true);
ASSERT_EQ(aint, 314159);
ASSERT_EQ(afloat, 3.14159265359f);
ASSERT_EQ(astring, "3.14159265359");
check_file(afile, message);
check_file(adir + "/input.txt", message);
}
Run test:
> ctest -VV -R gauze_gtest
7: [==========] Running 3 tests from 1 test case.
7: [----------] Global test environment set-up.
7: [----------] 3 tests from gauze_gtest
7: [ RUN ] gauze_gtest.arith
7: [ OK ] gauze_gtest.arith (0 ms)
7: [ RUN ] gauze_gtest.boolean
7: [ OK ] gauze_gtest.boolean (0 ms)
7: [ RUN ] gauze_gtest.cli
7: [ OK ] gauze_gtest.cli (8 ms)
7: [----------] 3 tests from gauze_gtest (8 ms total)
7:
7: [----------] Global test environment tear-down
7: [==========] 3 tests from 1 test case ran. (8 ms total)
7: [ PASSED ] 3 tests.
1/1 Test #7: gauze_gtest ...................... Passed 0.03 sec
Environment variables¶
FORWARD_ENV
option can be used to forward environment variable from host
to target. Feature is useful while running tests on iOS and Android devices
since environment for such tests created from scratch and is not the same as
local user environment. For other platforms there are no extra functionality
introduced.
As an example let assume test is reading environment variables
MY_GAUZE_VARIABLE_1
and MY_GAUZE_VARIABLE_2
:
#include <iostream> // std::cout
#include <cstdlib> // EXIT_SUCCESS
int gauze_main(int argc, char** argv)
{
if (argc != 1)
{
std::cerr << "No arguments expected" << std::endl;
return EXIT_FAILURE;
}
const char* var_1_name = "MY_GAUZE_VARIABLE_1";
const char* var_1 = std::getenv(var_1_name);
if (var_1 == nullptr)
{
std::cerr << "Variable " << var_1_name << " not found" << std::endl;
return EXIT_FAILURE;
}
if (std::string(var_1) != "42")
{
std::cerr << "Variable " << var_1_name << " unexpected value" << std::endl;
return EXIT_FAILURE;
}
std::cout << "Variable " << var_1_name << " found!" << std::endl;
const char* var_2_name = "MY_GAUZE_VARIABLE_2";
const char* var_2 = std::getenv(var_2_name);
if (var_2 != nullptr)
{
std::cout << var_2_name << " value: " << var_2 << std::endl;
}
return EXIT_SUCCESS;
}
Environment variable MY_GAUZE_VARIABLE_1
will be set by CTest:
add_executable(gauze_forward_env main.cpp)
gauze_add_test(
NAME gauze_forward_env
COMMAND gauze_forward_env
FORWARD_ENV MY_GAUZE_VARIABLE_1 MY_GAUZE_VARIABLE_2
)
set_tests_properties(
gauze_forward_env
PROPERTIES
ENVIRONMENT
MY_GAUZE_VARIABLE_1=42
)
Run test (Android build):
> ctest -VV -R gauze_forward_env
...
5: Forwarding user's variable 'MY_GAUZE_VARIABLE_1' with value '42'
5: Forwarding user's variable 'MY_GAUZE_VARIABLE_2' with value ''
5: Command output (with exit code):
5: *** BEGIN ***
5: Variable MY_GAUZE_VARIABLE_1 found!
5: MY_GAUZE_VARIABLE_2 value:
5: 0
5: *** END ***
5: Done
1/1 Test #5: gauze_forward_env ................ Passed 0.53 sec
If environment variable MY_GAUZE_VARIABLE_2
will be set on host then
Gauze will forward it to the Android test environment:
> export MY_GAUZE_VARIABLE_2=hello
> ctest -VV -R gauze_forward_env
...
5: Forwarding user's variable 'MY_GAUZE_VARIABLE_1' with value '42'
5: Forwarding user's variable 'MY_GAUZE_VARIABLE_2' with value 'hello'
5: Command output (with exit code):
5: *** BEGIN ***
5: Variable MY_GAUZE_VARIABLE_1 found!
5: MY_GAUZE_VARIABLE_2 value: hello
5: 0
5: *** END ***
5: Done
1/1 Test #5: gauze_forward_env ................ Passed 0.48 sec
There is no need to rebuild test or reconfigure CMake project.